001// ***** This file is automatically generated from LinearBinaryCore.java.jpp
002
003package daikon.inv.binary.twoScalar;
004
005import org.checkerframework.checker.lock.qual.GuardSatisfied;
006import org.checkerframework.checker.nullness.qual.Nullable;
007import org.checkerframework.dataflow.qual.Pure;
008import org.checkerframework.dataflow.qual.SideEffectFree;
009import daikon.*;
010import daikon.inv.*;
011import java.io.Serializable;
012import java.util.*;
013import java.util.logging.Level;
014import java.util.logging.Logger;
015import org.plumelib.util.MathPlume;
016
017public final class LinearBinaryCoreFloat implements Serializable, Cloneable {
018  static final long serialVersionUID = 20030822L;
019
020  /** Debug tracer. */
021  public static final Logger debug =
022    Logger.getLogger("daikon.inv.binary.twoScalar.LinearBinaryCoreFloat");
023
024  /** This invariant represents ax + by + c = 0; the first argument is x, second is y. */
025  public double a = 0, b = 0, c = 0;
026
027  public double min_x, min_y, max_x, max_y;
028  public double min_a, max_a, min_b, max_b, min_c, max_c;
029
030  public Invariant wrapper;
031
032  // The number of distinct values (not samples) observed.
033  public int values_seen = 0;
034
035  // We delay computation of a, b and c until we have seen several pairs so
036  // that we can compute a, b and c based on a far-separated pair.  We reduce
037  // the likelihood of roundoff error by getting 4 points, then choosing
038  // the two that are furthest apart in order to compute a, b and c.
039  static final int MINPAIRS = 4;
040
041  public double[] x_cache = new double[MINPAIRS];
042  public double[] y_cache = new double[MINPAIRS];
043
044  @SuppressWarnings("fields.uninitialized") // delayed initialization
045  public LinearBinaryCoreFloat(Invariant wrapper) {
046    this.wrapper = wrapper;
047  }
048
049  @SideEffectFree
050  @Override
051  public LinearBinaryCoreFloat clone(@GuardSatisfied LinearBinaryCoreFloat this) {
052    try {
053      LinearBinaryCoreFloat result = (LinearBinaryCoreFloat) super.clone();
054      if (x_cache != null) {
055        result.x_cache = x_cache.clone();
056      }
057      if (y_cache != null) {
058        result.y_cache = y_cache.clone();
059      }
060      return result;
061    } catch (CloneNotSupportedException e) {
062      throw new Error(); // can't happen
063    }
064  }
065
066// There is no more need for swap because the equation is in standard form.
067  public void swap() {
068    // was a swap
069    if (values_seen < MINPAIRS) {
070    } else {
071      if (a == 0) {
072        // can't swap horizontal line into vertical, but if a was 0,
073        // then we might as well falsify ourselves because this is just
074        // a constant
075        values_seen = Integer.MAX_VALUE;
076        a = 0;
077        b = 0;
078        c = 0;
079      } else {
080    // since the equation is already in standard form, just guarantee that the x coefficient is positive
081
082        if (b < 0) {
083          a = -b;
084          b = -a;
085          c = -c;
086        }
087      }
088    }
089
090    // swap the value caches
091    double[] tmp = x_cache;
092    x_cache = y_cache;
093    y_cache = tmp;
094
095    // swap min_x/min_y and max_x/max_y
096    double tmp_val = max_x;
097    max_x = max_y;
098    max_y = tmp_val;
099    tmp_val = min_x;
100    min_x = min_y;
101    min_y = tmp_val;
102 }
103
104  /**
105   * Returns whether or not the invariant is currently active. We become active after MINPAIRS
106   * values have been seen and a line calculated. Before that, a and b are uninitialized.
107   */
108  @Pure
109  public boolean isActive() {
110    return values_seen >= MINPAIRS;
111  }
112
113  /**
114   * LinearBinary can't be flowed because it keeps samples to build the line. These sample can't be
115   * flowed from ppt to ppt (since they probably didn't occur at the lower ppt).
116   */
117  @Pure
118  public boolean isFlowable() {
119    return false;
120  }
121
122  // Note that this method is relatively inconsistent with respect to
123  // differing ordering of the points.  It will always give exactly the
124  // same answer with points that are delivered in the same order, but
125  // it can calculate different a/b and change whether or not it even
126  // finds a line with a different order.  Current code always guarantees
127  // that the points arrive in the same order (I think).
128  public InvariantStatus add_modified(double x, double y, int count) {
129    if (values_seen < MINPAIRS) {
130      if (debug.isLoggable(Level.FINE)) {
131        debug.fine("min point: " + wrapper.ppt.name() + ": " + x + ", " + y);
132      }
133      // We delay computation of a and b until we have seen several pairs
134      // so that we can compute a and b based on a far-separated pair.  If
135      // the points in a pair are nearby, then roundoff errors in the
136      // computation of the slope can be non-negligible.
137
138      for (int i = 0; i < values_seen; i++) {
139        if ((x_cache[i] == x) && (y_cache[i] == y)) {
140          return InvariantStatus.NO_CHANGE;
141        }
142      }
143      x_cache[values_seen] = x;
144      y_cache[values_seen] = y;
145      values_seen++;
146      if (values_seen == MINPAIRS) {
147        // Find the most separated pair.
148        int[] sep_indices = maxsep_indices();
149        int max_i = sep_indices[0];
150        int max_j = sep_indices[1];
151
152        // Set a, b and c based on that pair
153        if (max_i == -1) {
154          values_seen--;
155          wrapper.log("falsified, max_i = -1");
156          return InvariantStatus.FALSIFIED;
157        }
158        // remember the min and max pairs
159        if (x_cache[max_i] < x_cache[max_j]) {
160          // swap max_i and max_j
161          int tmp = max_i;
162          max_i = max_j;
163          max_j = tmp;
164        }
165        min_x = x_cache[max_i];
166        min_y = y_cache[max_i];
167        max_x = x_cache[max_j];
168        max_y = y_cache[max_j];
169
170        // calculate a, b and c for this pair and remember them as min/max a,b,c
171        if (set_bi_linear(min_x, max_x, min_y, max_y) == InvariantStatus.FALSIFIED) {
172          values_seen--;
173          wrapper.log("falsified, no set_bi_linear");
174          return InvariantStatus.FALSIFIED;
175        }
176        min_a = a;
177        max_a = a;
178        min_b = b;
179        max_b = b;
180        min_c = c;
181        max_c = c;
182        if (debug.isLoggable(Level.FINE)) {
183            debug.fine(
184                wrapper.ppt.name() + ": Initial a (" + a + ") and b (" + b + ") and c (" + c + ")");
185        }
186
187        // Check all values against a, b, and c.
188        if (Global.fuzzy.eq(a, 0)) {
189          wrapper.log("falsified, a == 0");
190          values_seen--;
191          return InvariantStatus.FALSIFIED;
192        }
193
194        for (int i = 0; i < MINPAIRS; i++) {
195          // if the point is close enough in either x or y it is close enough
196          // The double check is used because lines with different slopes may
197          // indicate an unreasonably high x or y diff when the actual point
198          // is very close.  It would probably be better to calculate the
199          // actual distance from the line, but it would be unclear what to
200          // compare this to (average or X and Y?).  This seems like a
201          // reasonable compromise.
202          if (!Global.fuzzy.eq(-b * y_cache[i],  a * x_cache[i] + c)
203              && !Global.fuzzy.eq(x_cache[i], (-b * y_cache[i] - c) / a)) {
204            if (debug.isLoggable(Level.FINE)) {
205              debug.fine(
206                  "Suppressing LinearBinaryCoreFloat ("
207                  + wrapper.format()
208                  + ") at index "
209                  + i
210                  + ": "
211                  + y_cache[i]
212                  + " != "
213                  + a
214                  + "*"
215                  + x_cache[i]
216                  + "+"
217                  + b);
218              debug.fine("    ");
219            }
220            values_seen--;
221            wrapper.log("falsified, point in cache doesn't match");
222            return InvariantStatus.FALSIFIED;
223          }
224        }
225      }
226    } else {
227      // Check the new value against a and b. (See previous explanation for double
228      // EQUAL check.)
229      if (!Global.fuzzy.eq(y, (-c - a * x)/b) && !Global.fuzzy.eq(x, (-c - b * y)/ a)) {
230        // see if this new point is off one end or the other
231        if (x < min_x) {
232          min_x = x;
233          min_y = y;
234        } else if (x > max_x) {
235          max_x = x;
236          max_y = y;
237        }
238
239        double delta_x = max_x - min_x;
240        // should be fixed in the future to permit vertical lines
241        if (Global.fuzzy.eq(delta_x, 0)) {
242          return InvariantStatus.FALSIFIED;
243        }
244
245        // calculate a new a, b, c possibly using a new end point
246
247        double[] res = new double[3];
248        boolean ok = find_bi_linear(min_x, max_x, min_y, max_y, res);
249        double new_a = 0;
250        double new_b = 0;
251        double new_c = 0;
252        if (ok) {
253          new_a = res[0];
254          new_b = res[1];
255          new_c = res[2];
256        }
257
258        // if the a or b is a new min/max remember it.
259        if (new_a < min_a) min_a = new_a;
260        if (new_a > max_a) max_a = new_a;
261        if (new_b < min_b) min_b = new_b;
262        if (new_b > max_b) max_b = new_b;
263        if (new_c < min_c) min_c = new_c;
264        if (new_c > max_c) max_c = new_c;
265
266        // pick a new a, b, c as the average of their endpoints
267        new_a = (min_a + max_a) / 2;
268        new_b = (min_b + max_b) / 2;
269        new_c = (min_c + max_c) / 2;
270        if (debug.isLoggable(Level.FINE)) {
271            debug.fine(wrapper.ppt.name() + ": Trying new a (" + new_a
272                         + ") and b (" + new_b + ")");
273        }
274
275        // if the new a, b, c are 'equal' to min/max a, b, c and
276        // this point fits, then this new equation is good enough both
277        // for existing points and the new point.
278
279        if (Global.fuzzy.eq(new_a, min_a)
280            && Global.fuzzy.eq(new_a, max_a)
281            && Global.fuzzy.eq(new_b, min_b)
282            && Global.fuzzy.eq(new_b, max_b)
283            && Global.fuzzy.eq(new_c, min_c)
284            && Global.fuzzy.eq(new_c, max_c)
285            && Global.fuzzy.eq(-new_b * y, new_a * x + new_c)) {
286          a = new_a;
287          b = new_b;
288          c = new_c;
289          if (debug.isLoggable(Level.FINE)) {
290            debug.fine(
291                wrapper.ppt.name() + ": New a (" + a + ") and b (" + b + ") and c (" + c + ")");
292          }
293        } else {
294          if (debug.isLoggable(Level.FINE)) {
295            debug.fine("Suppressing LinearBinaryCoreFloat ("
296                        + wrapper.format() + ") at new value: "
297                        + a + " * x + " + b + " * y + " + c + " != 0");
298//                        + y + " != " + a + "*" + x + "+" + b);
299          }
300          return InvariantStatus.FALSIFIED;
301        }
302        //??? wrapper.destroyAndFlow();
303        //??? return;
304      }
305    }
306    return InvariantStatus.NO_CHANGE;
307  }
308
309  /**
310   * Returns a 2-element int array of the indices of the most separated pair of points between the
311   * points represented by x_array and y_array. Requires that x_array and y_array are the same
312   * length.
313   */
314  static int[] maxsep_point(double[] x_array, double[] y_array) {
315    // Find the most separated pair.
316    // Do I really need to check in two dimensions, or would one be enough?
317
318    assert x_array.length == y_array.length;
319
320    // indices of the most-separated pair of points
321    int max_i = -1;
322    int max_j = -1;
323    // (square of the) distance between the most separated pair
324    double max_separation = 0;
325    for (int i = 0; i < x_array.length - 1; i++) {
326      for (int j = i + 1; j < x_array.length; j++) {
327        // not long, lest we get wraparound
328        double xsep = x_array[i] - x_array[j];
329        double ysep = y_array[i] - y_array[j];
330        double separation = xsep * xsep + ysep * ysep;
331
332        // Roundoff error might result in 0.
333        // assert separation > 0;
334
335        if (separation > max_separation) {
336          max_separation = separation;
337          max_i = i;
338          max_j = j;
339        }
340      }
341    }
342    return new int[] {max_i, max_j};
343  }
344  /**
345   * Returns a 2-element int array of the indices of the most separated pair of points between the
346   * points within this.
347   */
348  int[] maxsep_indices() {
349    return maxsep_point(x_cache, y_cache);
350  }
351
352  //  /**
353  //   * Given ((x0,y0),(x1,y1)), finds a and b such that y = ax + b.
354  //   * Places a and b (if they exist) into result at indexes 0 and 1
355  //   * respectively.
356  //   * @return true if such an a and b exist.
357  //   */
358  /**
359   * Given ((x0,y0),(x1,y1)), finds a,b and c such that ax + by + c = 0. Places a, b and c (if they
360   * exist) into result at indexes 0, 1 and 2 respectively.
361   *
362   * @return true if such an a and b exist
363   */
364  private static boolean find_bi_linear(
365      double x0, double x1, double y0, double y1, double[] result) {
366    if (x1 - x0 == 0) {         // not "x0 == x1", due to roundoff
367      // x being constant would have been discovered elsewhere (and this
368      // invariant would not have been instantiated).
369      return false;
370    }
371    if (y1 - y0 == 0) {         // not "y0 == y1", due to roundoff
372      // y being constant would have been discovered elsewhere (and this
373      // invariant would not have been instantiated).
374      return false;
375    }
376
377    double i_var = y1 - y0;
378    double j_var = x0 - x1;
379    double Stand_const = y0 * x1 - x0 * y1;
380
381    // Put the coefficients in lowest terms by dividing by the gcd.
382    // If the gcd is 1, no harm done -- divide by 1.
383    // Type is double to ensure floating-point division.
384    double myGCD = MathPlume.gcd(MathPlume.gcd(i_var, j_var), Stand_const);
385    double standard_i_var = i_var / myGCD;
386    double standard_j_var = j_var / myGCD;
387    double standard_Stand_const = Stand_const / myGCD;
388
389    // Ensure postive x coefficient by negating if "x" term is negative.
390    if (i_var < 0) {
391      standard_i_var = -standard_i_var;
392      standard_j_var = -standard_j_var;
393      standard_Stand_const = -standard_Stand_const;
394    }
395
396    result[0] = standard_i_var;
397    result[1] = standard_j_var;
398    result[2] = standard_Stand_const;
399
400    return true;
401  }
402
403  /** Given ((x0,y0),(x1,y1)), set a, b and c such that ax + by + c = 0. */
404  private InvariantStatus set_bi_linear(double x0, double x1, double y0, double y1) {
405    double[] AandBandC = new double[3];
406    if (!find_bi_linear(x0, x1, y0, y1, AandBandC)) {
407      if (debug.isLoggable(Level.FINE)) {
408        debug.fine("Suppressing LinearBinaryCoreFloat due to equal x values: (" + x0 + "," + y0 + "), (" + x1 + "," + y1 + ")");
409      }
410      return InvariantStatus.FALSIFIED;
411    }
412    a = AandBandC[0];
413    b = AandBandC[1];
414    c = AandBandC[2];
415    wrapper.log("x0=%s, x1=%s, y0=%s, y1=%s", x0, x1, y0, y1);
416    wrapper.log("a=%s, b=%s, c=%s", a, b, c);
417    return InvariantStatus.NO_CHANGE;
418  }
419
420  public boolean enoughSamples(@GuardSatisfied LinearBinaryCoreFloat this) {
421    return values_seen >= MINPAIRS;
422  }
423
424  public double computeConfidence() {
425    return Invariant.conf_is_ge(values_seen, MINPAIRS);
426  }
427
428  public String repr(@GuardSatisfied LinearBinaryCoreFloat this) {
429    return "LinearBinaryCoreFloat" + wrapper.varNames() + ": a=" + a
430      + ",b=" + b
431      + ",c=" + c
432      + ",values_seen=" + values_seen;
433  }
434
435  // Format one term of an equation.
436  // Variable "first" indicates whether this is the leading term
437  // Variable "varname" is the name of the variable; may be null
438  // for the constant term.
439  public static String formatTerm(double coeff, @Nullable String varname, boolean first) {
440    double ncoeff = coeff;
441
442    if (ncoeff == 0) {
443      return "";
444    }
445    String sign;
446    if (ncoeff < 0) {
447      if (first) {
448        sign = "- ";
449      } else {
450        sign = " - ";
451      }
452    } else if (first) {
453      sign = "";
454    } else {
455      sign = " + ";
456    }
457
458    //we want to ensure that the positive value is used because the sign has already been determined
459    String coeff_string =
460        (ncoeff == (int)ncoeff) ? "" + Math.abs((int)ncoeff) : "" + Math.abs(ncoeff);
461    if (varname == null) {
462      return sign + coeff_string;
463    }
464    if (ncoeff == 1 || ncoeff == -1) {
465      return sign + varname;
466    } else {
467      return sign + coeff_string + " * " + varname;
468    }
469  }
470
471  @SideEffectFree
472  public String format_using(
473      OutputFormat format, String vix, String viy, double u, double v, double w) {
474
475    // recall that a, b, c in the linear binary equation means ax + by + c = 0;
476    if (u == 0 && v == 0 && w == 0) {
477      return wrapper.format_too_few_samples(format, "0 * " + vix + "+ 0 * " + viy + " + 0 == 0");
478    }
479
480    if (format.isJavaFamily()) {
481
482        return "daikon.Quant.fuzzy.eq(0, "
483            + formatTerm(u, vix, true)
484            + formatTerm(v, viy, false)
485            + formatTerm(w, null, false)
486            + ")";
487
488    }
489
490    if ((format == OutputFormat.DAIKON)
491        || (format == OutputFormat.ESCJAVA)
492        || (format == OutputFormat.CSHARPCONTRACT)) {
493      String eq = " == ";
494      return formatTerm(u, vix, true)
495        + formatTerm(v, viy, false)
496        + formatTerm(w, null, false)
497        + eq
498        + "0";
499    }
500
501    if (format == OutputFormat.SIMPLIFY) {
502      return format_simplify(vix, viy, u, v, w);
503    }
504    throw new Error("Unknown OutputFormat: " + format);
505  }
506
507  public static String format_simplify(String str_x, String str_y, double a, double b, double c) {
508
509      int ia = (int) a;
510      int ib = (int) b;
511      int ic = (int) c;
512
513      String str_a, str_b, str_c;
514      if (ia == a && ib == b && ic == c) {
515        // integer
516        str_a = Invariant.simplify_format_long(ia);
517        str_b = Invariant.simplify_format_long(ib);
518        str_c = Invariant.simplify_format_long(ic);
519      } else {
520        // floating point; probably not very useful
521        str_a = Invariant.simplify_format_double(a);
522        str_b = Invariant.simplify_format_double(b);
523        str_c = Invariant.simplify_format_double(c);
524      }
525
526      // ax + by + c = 0
527
528      String str_ax = (a == 1.0) ? str_x : "(* " + str_a + " " + str_x + ")";
529      String str_by = (b == 1.0) ? str_y : "(* " + str_b + " " + str_y + ")";
530      String str_axby = "(+ " +  str_ax + " " + str_by + ")";
531      String str_axpc = (c == 0.0) ? str_axby : "(+ " + str_axby + " " + str_c + ")";
532
533      return "(EQ 0 " + str_axpc + ")";
534  }
535
536  @SideEffectFree
537  public String format_using(OutputFormat format, String xname, String yname) {
538    String result = format_using(format, xname, yname, a, b, c);
539    if (result != null) {
540      return result;
541    } else {
542      return wrapper.format_unimplemented(format);
543    }
544  }
545
546  // Format as "x = cy+d" instead of as "y = ax+b". [now that we print in standard format, is
547  // there a point to this method?
548  public String format_reversed_using(OutputFormat format, String xname, String yname) {
549    assert a == 1 || a == -1;
550    return format_using(format, yname, xname, b, a, c);
551  }
552
553  /**
554   * In general, we can't merge formulas, but we can merge invariants with too few samples to have
555   * formed a line with invariants with enough samples. And those will appear to have different
556   * formulas.
557   */
558  public boolean mergeFormulasOk() {
559    return true;
560  }
561
562  /**
563   * Merge the linear binary cores in cores to form a new core. Any core in the list that has seen
564   * enough points to define a line, must define the same line. Any cores that have not yet seen
565   * enough points, will have each of their points applied to that invariant. The merged core is
566   * returned. Null is returned if the cores don't describe the same line
567   *
568   * @param cores list of LinearBinary cores to merge. They should all be permuted to match the
569   *     variable order in ppt.
570   */
571  public @Nullable LinearBinaryCoreFloat merge(List<LinearBinaryCoreFloat> cores, Invariant wrapper) {
572
573    // Look for any active lines.  All must define the same line
574    LinearBinaryCoreFloat result = null;
575    for (LinearBinaryCoreFloat c : cores) {
576      if (!c.isActive()) {
577        continue;
578      }
579      if (result == null) {
580        result = c.clone();
581      } else {
582        if (!Global.fuzzy.eq(result.a, c.a)
583            || !Global.fuzzy.eq(result.b, c.b)
584            || !Global.fuzzy.eq(result.c, c.c)) {
585          return null;
586        }
587      }
588    }
589
590    // If no active lines were found, created an empty core
591    if (result == null) {
592      result = new LinearBinaryCoreFloat(wrapper);
593    } else {
594      result.wrapper = wrapper;
595    }
596
597    // Merge in any points from non-active cores
598    for (LinearBinaryCoreFloat c : cores) {
599      if (c.isActive()) {
600        continue;
601      }
602      for (int j = 0; j < c.values_seen; j++) {
603        if (result.add_modified(c.x_cache[j], c.y_cache[j], 1) == InvariantStatus.FALSIFIED) {
604          //        if (wrapper.falsified)
605          return null;
606        }
607      }
608    }
609
610    return result;
611  }
612
613  @Pure
614  public boolean isSameFormula(LinearBinaryCoreFloat other) {
615    boolean thisMeaningless = values_seen < MINPAIRS;
616    boolean otherMeaningless = other.values_seen < MINPAIRS;
617
618    if (thisMeaningless && otherMeaningless) {
619      return true;
620    } else {
621      return (values_seen >= MINPAIRS)
622        && (other.values_seen >= MINPAIRS)
623        && (a == other.a)
624        && (b == other.b)
625        && (c == other.c);
626    }
627  }
628
629  @Pure
630  public boolean isExclusiveFormula(LinearBinaryCoreFloat other) {
631    if ((values_seen < MINPAIRS) || (other.values_seen < MINPAIRS)) {
632      return false;
633    }
634
635      return (-a / b == -other.a / other.b) && (-c / b != -other.c / other.b);
636  }
637}