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 LinearBinaryCore 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.LinearBinaryCore");
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 long 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 long[] x_cache = new long[MINPAIRS];
042  public long[] y_cache = new long[MINPAIRS];
043
044  @SuppressWarnings("fields.uninitialized") // delayed initialization
045  public LinearBinaryCore(Invariant wrapper) {
046    this.wrapper = wrapper;
047  }
048
049  @SideEffectFree
050  @Override
051  public LinearBinaryCore clone(@GuardSatisfied LinearBinaryCore this) {
052    try {
053      LinearBinaryCore result = (LinearBinaryCore) 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    long[] 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    long 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(long x, long 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 ((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 (!(-b * y_cache[i] ==  a * x_cache[i] + c)
203              && !(x_cache[i] == (-b * y_cache[i] - c) / a)) {
204            if (debug.isLoggable(Level.FINE)) {
205              debug.fine(
206                  "Suppressing LinearBinaryCore ("
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 (!(y == (-c - a * x)/b) && !(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        long delta_x = max_x - min_x;
240        // should be fixed in the future to permit vertical lines
241        if ((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            && (-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 LinearBinaryCore ("
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(long[] x_array, long[] 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        if (separation > max_separation) {
333          max_separation = separation;
334          max_i = i;
335          max_j = j;
336        }
337      }
338    }
339    return new int[] {max_i, max_j};
340  }
341  /**
342   * Returns a 2-element int array of the indices of the most separated pair of points between the
343   * points within this.
344   */
345  int[] maxsep_indices() {
346    return maxsep_point(x_cache, y_cache);
347  }
348
349  //  /**
350  //   * Given ((x0,y0),(x1,y1)), finds a and b such that y = ax + b.
351  //   * Places a and b (if they exist) into result at indexes 0 and 1
352  //   * respectively.
353  //   * @return true if such an a and b exist.
354  //   */
355  /**
356   * Given ((x0,y0),(x1,y1)), finds a,b and c such that ax + by + c = 0. Places a, b and c (if they
357   * exist) into result at indexes 0, 1 and 2 respectively.
358   *
359   * @return true if such an a and b exist
360   */
361  private static boolean find_bi_linear(
362      long x0, long x1, long y0, long y1, double[] result) {
363    if (x1 - x0 == 0) {         // not "x0 == x1", due to roundoff
364      // x being constant would have been discovered elsewhere (and this
365      // invariant would not have been instantiated).
366      return false;
367    }
368    if (y1 - y0 == 0) {         // not "y0 == y1", due to roundoff
369      // y being constant would have been discovered elsewhere (and this
370      // invariant would not have been instantiated).
371      return false;
372    }
373
374    long i_var = y1 - y0;
375    long j_var = x0 - x1;
376    long Stand_const = y0 * x1 - x0 * y1;
377
378    // Put the coefficients in lowest terms by dividing by the gcd.
379    // If the gcd is 1, no harm done -- divide by 1.
380    // Type is double to ensure floating-point division.
381    double myGCD = MathPlume.gcd(MathPlume.gcd(i_var, j_var), Stand_const);
382    double standard_i_var = i_var / myGCD;
383    double standard_j_var = j_var / myGCD;
384    double standard_Stand_const = Stand_const / myGCD;
385
386    // Ensure postive x coefficient by negating if "x" term is negative.
387    if (i_var < 0) {
388      standard_i_var = -standard_i_var;
389      standard_j_var = -standard_j_var;
390      standard_Stand_const = -standard_Stand_const;
391    }
392
393    result[0] = standard_i_var;
394    result[1] = standard_j_var;
395    result[2] = standard_Stand_const;
396
397    return true;
398  }
399
400  /** Given ((x0,y0),(x1,y1)), set a, b and c such that ax + by + c = 0. */
401  private InvariantStatus set_bi_linear(long x0, long x1, long y0, long y1) {
402    double[] AandBandC = new double[3];
403    if (!find_bi_linear(x0, x1, y0, y1, AandBandC)) {
404      if (debug.isLoggable(Level.FINE)) {
405        debug.fine("Suppressing LinearBinaryCore due to equal x values: (" + x0 + "," + y0 + "), (" + x1 + "," + y1 + ")");
406      }
407      return InvariantStatus.FALSIFIED;
408    }
409    a = AandBandC[0];
410    b = AandBandC[1];
411    c = AandBandC[2];
412    wrapper.log("x0=%s, x1=%s, y0=%s, y1=%s", x0, x1, y0, y1);
413    wrapper.log("a=%s, b=%s, c=%s", a, b, c);
414    return InvariantStatus.NO_CHANGE;
415  }
416
417  public boolean enoughSamples(@GuardSatisfied LinearBinaryCore this) {
418    return values_seen >= MINPAIRS;
419  }
420
421  public double computeConfidence() {
422    return Invariant.conf_is_ge(values_seen, MINPAIRS);
423  }
424
425  public String repr(@GuardSatisfied LinearBinaryCore this) {
426    return "LinearBinaryCore" + wrapper.varNames() + ": a=" + a
427      + ",b=" + b
428      + ",c=" + c
429      + ",values_seen=" + values_seen;
430  }
431
432  // Format one term of an equation.
433  // Variable "first" indicates whether this is the leading term
434  // Variable "varname" is the name of the variable; may be null
435  // for the constant term.
436  public static String formatTerm(double coeff, @Nullable String varname, boolean first) {
437    double ncoeff = coeff;
438
439    if (ncoeff == 0) {
440      return "";
441    }
442    String sign;
443    if (ncoeff < 0) {
444      if (first) {
445        sign = "- ";
446      } else {
447        sign = " - ";
448      }
449    } else if (first) {
450      sign = "";
451    } else {
452      sign = " + ";
453    }
454
455    //we want to ensure that the positive value is used because the sign has already been determined
456    String coeff_string =
457        (ncoeff == (int)ncoeff) ? "" + Math.abs((int)ncoeff) : "" + Math.abs(ncoeff);
458    if (varname == null) {
459      return sign + coeff_string;
460    }
461    if (ncoeff == 1 || ncoeff == -1) {
462      return sign + varname;
463    } else {
464      return sign + coeff_string + " * " + varname;
465    }
466  }
467
468  @SideEffectFree
469  public String format_using(
470      OutputFormat format, String vix, String viy, double u, double v, double w) {
471
472    // recall that a, b, c in the linear binary equation means ax + by + c = 0;
473    if (u == 0 && v == 0 && w == 0) {
474      return wrapper.format_too_few_samples(format, "0 * " + vix + "+ 0 * " + viy + " + 0 == 0");
475    }
476
477    if (format.isJavaFamily()) {
478
479        int a = (int) u;
480        int b = (int) v;
481        int c = (int) w;
482        return formatTerm(a, vix, true)
483          + formatTerm(b, viy, false)
484          + formatTerm(c, null, false)
485          + " == 0";
486
487    }
488
489    if ((format == OutputFormat.DAIKON)
490        || (format == OutputFormat.ESCJAVA)
491        || (format == OutputFormat.CSHARPCONTRACT)) {
492      String eq = " == ";
493      return formatTerm(u, vix, true)
494        + formatTerm(v, viy, false)
495        + formatTerm(w, null, false)
496        + eq
497        + "0";
498    }
499
500    if (format == OutputFormat.SIMPLIFY) {
501      return format_simplify(vix, viy, u, v, w);
502    }
503    throw new Error("Unknown OutputFormat: " + format);
504  }
505
506  public static String format_simplify(String str_x, String str_y, double a, double b, double c) {
507
508      int ia = (int) a;
509      int ib = (int) b;
510      int ic = (int) c;
511
512      String str_a, str_b, str_c;
513      if (ia == a && ib == b && ic == c) {
514        // integer
515        str_a = Invariant.simplify_format_long(ia);
516        str_b = Invariant.simplify_format_long(ib);
517        str_c = Invariant.simplify_format_long(ic);
518      } else {
519        // floating point; probably not very useful
520        str_a = Invariant.simplify_format_double(a);
521        str_b = Invariant.simplify_format_double(b);
522        str_c = Invariant.simplify_format_double(c);
523      }
524
525      // ax + by + c = 0
526
527      String str_ax = (a == 1.0) ? str_x : "(* " + str_a + " " + str_x + ")";
528      String str_by = (b == 1.0) ? str_y : "(* " + str_b + " " + str_y + ")";
529      String str_axby = "(+ " +  str_ax + " " + str_by + ")";
530      String str_axpc = (c == 0.0) ? str_axby : "(+ " + str_axby + " " + str_c + ")";
531
532      return "(EQ 0 " + str_axpc + ")";
533  }
534
535  @SideEffectFree
536  public String format_using(OutputFormat format, String xname, String yname) {
537    String result = format_using(format, xname, yname, a, b, c);
538    if (result != null) {
539      return result;
540    } else {
541      return wrapper.format_unimplemented(format);
542    }
543  }
544
545  // Format as "x = cy+d" instead of as "y = ax+b". [now that we print in standard format, is
546  // there a point to this method?
547  public String format_reversed_using(OutputFormat format, String xname, String yname) {
548    assert a == 1 || a == -1;
549    return format_using(format, yname, xname, b, a, c);
550  }
551
552  /**
553   * In general, we can't merge formulas, but we can merge invariants with too few samples to have
554   * formed a line with invariants with enough samples. And those will appear to have different
555   * formulas.
556   */
557  public boolean mergeFormulasOk() {
558    return true;
559  }
560
561  /**
562   * Merge the linear binary cores in cores to form a new core. Any core in the list that has seen
563   * enough points to define a line, must define the same line. Any cores that have not yet seen
564   * enough points, will have each of their points applied to that invariant. The merged core is
565   * returned. Null is returned if the cores don't describe the same line
566   *
567   * @param cores list of LinearBinary cores to merge. They should all be permuted to match the
568   *     variable order in ppt.
569   */
570  public @Nullable LinearBinaryCore merge(List<LinearBinaryCore> cores, Invariant wrapper) {
571
572    // Look for any active lines.  All must define the same line
573    LinearBinaryCore result = null;
574    for (LinearBinaryCore c : cores) {
575      if (!c.isActive()) {
576        continue;
577      }
578      if (result == null) {
579        result = c.clone();
580      } else {
581        if (!Global.fuzzy.eq(result.a, c.a)
582            || !Global.fuzzy.eq(result.b, c.b)
583            || !Global.fuzzy.eq(result.c, c.c)) {
584          return null;
585        }
586      }
587    }
588
589    // If no active lines were found, created an empty core
590    if (result == null) {
591      result = new LinearBinaryCore(wrapper);
592    } else {
593      result.wrapper = wrapper;
594    }
595
596    // Merge in any points from non-active cores
597    for (LinearBinaryCore c : cores) {
598      if (c.isActive()) {
599        continue;
600      }
601      for (int j = 0; j < c.values_seen; j++) {
602        if (result.add_modified(c.x_cache[j], c.y_cache[j], 1) == InvariantStatus.FALSIFIED) {
603          //        if (wrapper.falsified)
604          return null;
605        }
606      }
607    }
608
609    return result;
610  }
611
612  @Pure
613  public boolean isSameFormula(LinearBinaryCore other) {
614    boolean thisMeaningless = values_seen < MINPAIRS;
615    boolean otherMeaningless = other.values_seen < MINPAIRS;
616
617    if (thisMeaningless && otherMeaningless) {
618      return true;
619    } else {
620      return (values_seen >= MINPAIRS)
621        && (other.values_seen >= MINPAIRS)
622        && (a == other.a)
623        && (b == other.b)
624        && (c == other.c);
625    }
626  }
627
628  @Pure
629  public boolean isExclusiveFormula(LinearBinaryCore other) {
630    if ((values_seen < MINPAIRS) || (other.values_seen < MINPAIRS)) {
631      return false;
632    }
633
634      return (-a / b == -other.a / other.b) && (-c / b != -other.c / other.b);
635  }
636}