001// ***** This file is automatically generated from LinearTernaryCore.java.jpp
002
003package daikon.inv.ternary.threeScalar;
004
005import daikon.*;
006import daikon.inv.*;
007import daikon.inv.binary.twoScalar.*;
008import daikon.inv.unary.scalar.*;
009import java.io.Serializable;
010import java.util.*;
011import java.util.logging.Level;
012import java.util.logging.Logger;
013import org.checkerframework.checker.lock.qual.GuardSatisfied;
014import org.checkerframework.checker.nullness.qual.Nullable;
015import org.checkerframework.dataflow.qual.Pure;
016import org.checkerframework.dataflow.qual.SideEffectFree;
017import org.plumelib.util.ArraysPlume;
018import org.plumelib.util.MathPlume;
019import typequals.prototype.qual.NonPrototype;
020import typequals.prototype.qual.Prototype;
021
022/**
023 * The LinearTernaryCore class acts as the backend for the invariant (ax + by + cz + d = 0) by
024 * processing samples and computing coefficients. The resulting coefficients a, b, c, and d are
025 * mutually relatively prime, and the coefficient a is always positive.
026 */
027
028// Originally, LinearTernaryCore code was not dealing with degenerate
029// linear ternary invariants correctly, namely when the plane
030// (ax+by+cz+d=0) is parallel to one of the axes, so one of the
031// coefficients was 0.  In this case, the invariant can be described
032// by a binary invariant (eliminate the 0 coefficient variable).
033// LinearTernaryCore's inability to deal with this degenerate form was
034// (and still is) masked by the suppression of a linear ternary
035// invariant via a binary invariant and a constant (the code threw an
036// exception when seeing this case and falsifies the invariant).  As
037// more samples are processed, the ternary invariant may "evolve" from
038// a degenerate form to a full ternary invariant.  The
039// linearintervention method deals with the degenerate form and the
040// planarintervention focuses on the full ternary invariant.
041
042@SuppressWarnings({"nullness", // ***** TEMPORARY:  def_points is confused (is packed initially with non-nulls at beginning, but is not all non-nulls; and later may not be packed); needs review/refactoring/rewriting
043    "UnnecessaryParentheses"  // generated code, parens are sometimes necessary
044    })
045public final class LinearTernaryCoreFloat implements Serializable, Cloneable {
046  static final long serialVersionUID = 20030822L;
047
048  /** Debug tracer. */
049  static final Logger debug = Logger.getLogger("daikon.inv.ternary.threeScalar.LinearTernaryCoreFloat");
050
051  // ax + by + cz + d = 0; first argument is x, second is y, third is z
052  // Although the coefficients are guaranteed to be integers, we use doubles to
053  // store the values because regression tests have shown that a long is
054  // not sufficient
055  public double a = 0;
056  public double b = 0;
057  public double c = 0;
058  public double d = 0;
059  public double min_a;
060  public double max_a;
061  public double min_b;
062  public double max_b;
063  public double min_c;
064  public double max_c;
065  public double min_d;
066  public double max_d;
067  public double separation = 0;
068
069  // coefficients for the invariant in 3D space
070  public double[] coefficients = new double[4];
071
072  // The values of the flags indicate whether a line exists
073  // with the current samples (NO_LINE) and which variables are
074  // constant (constant variables indicate that the 'plane' is
075  // parallel to that axis).  For example, XY_CONSTANT means that the
076  // so far, the x and y values have all been constant and only z has
077  // been varying so the plane is actually a line.
078  // NO_CONSTANT_VARIABLES indicates that the values have not been
079  // constant but do form a plane between variables.
080  public enum Flag {NO_LINE, XY_CONSTANT, XZ_CONSTANT, YZ_CONSTANT,
081                    X_CONSTANT, Y_CONSTANT, Z_CONSTANT, NO_CONSTANT_VARIABLES};
082
083  // The flag indicates whether a line exists in the 3D space instead
084  // of a plane If the flag value is not NO_LINE, then a line does
085  // currently exist among the values seen and the flag specifies
086  // which variables are constant to make the invariant a line rather
087  // than a plane.  The value NO_LINE indicates either that not enough
088  // values have been seen to make any decision or that a plane
089  // exists.
090  public Flag line_flag = Flag.NO_LINE;
091
092  // Inner class to store points in for more convenient access.
093  public static class Point implements Serializable, Cloneable {
094    static final long serialVersionUID = 20050923L;
095
096    public double x;
097    public double y;
098    public double z;
099    public Point() {
100      this(0, 0, 0);
101    }
102    public Point(double x, double y, double z) {
103      this.x = x; this.y = y; this.z = z;
104    }
105    @Pure
106    public boolean equals(double x, double y, double z) {
107      return (this.x == x) && (this.y == y) && (this.z == z);
108    }
109    @SideEffectFree
110    @Override
111    protected Point clone(@GuardSatisfied Point this) throws CloneNotSupportedException {
112      return (Point) super.clone();
113    }
114    @SideEffectFree
115    @Override
116    public String toString(@GuardSatisfied Point this) {
117      return "(" + x + ", " + y + " ," + z + ")";
118    }
119  }
120
121  // Points that define the plane.  The first three are normally the
122  // current definition.  The next is a new point for consideration.
123  // However, any slot may be set to null at any time, and points may be
124  // duplicated at any time.
125  // (The invariant appears to be a bit stronger than that, since there are
126  // places that dereference the array unconditionally, assuming that its
127  // elements are non-null.  This should be clarified/refactored.)
128  public @Nullable Point[] def_points = new Point[MINTRIPLES];
129
130  public Invariant wrapper;
131
132  // The number of distinct values (not samples) seen.
133  public int values_seen = 0;
134
135  static final int MINTRIPLES = 5;
136
137  public LinearTernaryCoreFloat(Invariant wrapper) {
138    this.wrapper = wrapper;
139  }
140
141  @SideEffectFree
142  @Override
143  public LinearTernaryCoreFloat clone(@GuardSatisfied LinearTernaryCoreFloat this) {
144    try {
145      LinearTernaryCoreFloat result = (LinearTernaryCoreFloat) super.clone();
146      result.def_points = new Point[MINTRIPLES];
147      for (int i = 0; i < MINTRIPLES; i++) {
148        Point p = def_points[i];
149        if (p != null) {
150          result.def_points[i] = p.clone();
151        }
152      }
153      return result;
154    } catch (CloneNotSupportedException e) {
155      throw new Error(); // can't happen
156    }
157  }
158
159  /**
160   * Reorganize our already-seen state as if the variables had shifted order underneath us
161   * (rearrangement given by the permutation).
162   */
163  public void permute(int[] permutation) {
164    assert permutation.length == 3;
165    assert ArraysPlume.fnIsPermutation(permutation);
166    // Fix a, b, c
167    double[] clever = new double[] { a, b, c };
168    double[] pclever = new double[3];
169    pclever[permutation[0]] = clever[0];
170    pclever[permutation[1]] = clever[1];
171    pclever[permutation[2]] = clever[2];
172    if (isActive()) {
173      // Seen enough values, so permuting a, b, c is useful
174      if ( a == 0 || b == 0 || c == 0) {
175        throw new Error("Active LTs never have 0 coefficients");
176        // // Can't handle this form once rotated.  But if we've seen
177        // // enough values, yet a, b or c is zero, then this is covered by
178        // // a LinearBinary or a constant, so let me just destroy myself.
179        // values_seen = Integer.MAX_VALUE;
180        // a = b = c = d = 0;
181        // if (debug.isLoggable(Level.FINE)) {
182        //   debug.fine ("  Ternary invariant destroyed because a, b, or c = 0");
183        // }
184        // return;
185      } else {
186        // double d = -1.0 / pclever[2];
187        a = pclever[0];
188        b = pclever[1];
189        c = pclever[2];
190
191        // we still need to guarantee a positive x coefficient
192        if (a < 0) {
193          a = -a;
194          b = -b;
195          c = -c;
196          d = -d;
197        }
198      }
199    }
200
201    // Fix caches
202    {
203      double[] temp = new double[3];
204      for (int i = 0; i < MINTRIPLES; i++) {
205        Point p = def_points[i];
206        if (p == null) {
207          continue;
208        }
209        wrapper.log("orig def_points[%s] = %s", i, p);
210        temp[permutation[0]] = p.x;
211        temp[permutation[1]] = p.y;
212        temp[permutation[2]] = p.z;
213        p.x = temp[0];
214        p.y = temp[1];
215        p.z = temp[2];
216        wrapper.log("permuted def_points[%s] = %s", i, p);
217      }
218    }
219    // Assert that caches sync with a,b,c?
220    // This would be a good sanity check, but it would be nontrivial
221    // because we don't keep track of when a, b, and c are really
222    // valid for all of the points we've cached. If we were
223    // falsified and then resurrected, we might have samples that
224    // didn't fit even before the permutation. -smcc
225  }
226
227  /**
228   * Returns true if the invariant is currently active. We become active after MINTRIPLES
229   * values have been seen and a line is not calculated. In addition, the coefficients (a, b, c)
230   * must all be non-zero, else the invariant would be described by a LinearBinary or constant
231   * invariant. Before that, a, b, and c are uninitialized.
232   */
233  // this is the check used throughout the file as a shortcut because
234  // the invariant must have seen enough samples and not be a line for all the
235  // other calculations and comparisons done to be sensible, meaning that
236  // a full linear ternary invariant exists, not a degenerate one
237  @Pure
238  public boolean isActive(@GuardSatisfied LinearTernaryCoreFloat this) {
239    return line_flag == Flag.NO_LINE && values_seen >= MINTRIPLES && a != 0 && b != 0 && c != 0;
240  }
241
242  /**
243   * Sets up the invariant from a LinearBinary invariant and a constant value for the third
244   * variable. Points are taken from the LinearBinary cache and its min_x/y and max_x/y points and
245   * combined with the constant value.
246   *
247   * @return InvariantStatus.NO_CHANGE if the invariant is valid, InvariantStatus.FALSIFIED if one
248   * of the points invalidated the LinearTernary invariant
249   */
250  public InvariantStatus setup(LinearBinaryFloat lb, VarInfo con_var,
251                                double con_val) {
252
253    if (Debug.logOn()) {
254      wrapper.log("setup from lb %s con var %s con_val %s",
255                   lb, con_var, con_val);
256    }
257
258    int con_index = con_var.varinfo_index;
259    int lb_v1_index = lb.ppt.var_infos[0].varinfo_index;
260    int lb_v2_index = lb.ppt.var_infos[1].varinfo_index;
261
262    double[] con_vals = new double [lb.core.values_seen + 2];
263    double[] lb1_vals = new double [lb.core.values_seen + 2];
264    double[] lb2_vals = new double [lb.core.values_seen + 2];
265
266    int mi = lb.core.values_seen;
267    for (int i = 0; i < mi; i++) {
268      con_vals[i] = con_val;
269      lb1_vals[i] = lb.core.x_cache[i];
270      lb2_vals[i] = lb.core.y_cache[i];
271    }
272    if (lb.isActive()) {
273      con_vals[mi] = con_val;
274      lb1_vals[mi] = lb.core.min_x;
275      lb2_vals[mi] = lb.core.min_y;
276      con_vals[mi + 1] = con_val;
277      lb1_vals[mi + 1] = lb.core.max_x;
278      lb2_vals[mi + 1] = lb.core.max_y;
279      mi += 2;
280    }
281
282    InvariantStatus sts = InvariantStatus.NO_CHANGE;
283
284    for (int i = 0; i < mi; i++ ) {
285      if (con_index < lb_v1_index) {
286        sts = add_modified(con_vals[i], lb1_vals[i], lb2_vals[i], 1);
287
288      } else if (con_index < lb_v2_index) {
289        sts = add_modified(lb1_vals[i], con_vals[i], lb2_vals[i], 1);
290
291      } else {
292        sts = add_modified(lb1_vals[i], lb2_vals[i], con_vals[i], 1);
293
294      }
295      if (sts != InvariantStatus.NO_CHANGE) {
296        break;
297      }
298    }
299
300    if (sts != InvariantStatus.NO_CHANGE) {
301      System.out.println("lb.core.values_seen=" + lb.core.values_seen);
302      for (int i = 0; i < mi; i++ ) {
303        System.out.printf("LTCore: vals %s %s %s%n", con_vals[i], lb1_vals[i],
304                           lb2_vals[i]);
305      }
306      System.out.println("in inv " + wrapper.format() + " " + wrapper.ppt);
307      assert sts == InvariantStatus.NO_CHANGE;
308    }
309
310    return sts;
311  }
312
313  /**
314   * Sets up the invariant from a OneOf and two constants. Points are taken from the OneOf cache
315   * and the constant values.
316   *
317   * @return InvariantStatus.NO_CHANGE if the invariant is valid, or InvariantStatus.FALSIFIED if
318   * one of the points invalidated the LinearTernary invariant
319   */
320
321  public InvariantStatus setup(OneOfFloat oo, VarInfo v1, double con1,
322                                VarInfo v2, double con2) {
323
324    int oo_index = oo.ppt.var_infos[0].varinfo_index;
325    int con1_index = v1.varinfo_index;
326    int con2_index = v2.varinfo_index;
327
328    InvariantStatus sts = InvariantStatus.NO_CHANGE;
329    for (int i = 0; i < oo.num_elts(); i++ ) {
330      if (oo_index < con1_index) {
331        sts = add_modified(((Double)oo.elt(i)).doubleValue(), con1, con2, 1);
332      } else if (oo_index < con2_index) {
333        sts = add_modified(con1, ((Double)oo.elt(i)).doubleValue(), con2, 1);
334      } else {
335        sts = add_modified(con1, con2, ((Double)oo.elt(i)).doubleValue(), 1);
336      }
337      if (sts != InvariantStatus.NO_CHANGE) {
338        break;
339      }
340    }
341
342    if (Debug.logOn()) {
343      wrapper.log("setup from OneOf %s v1=%s v2=%s status = %s",
344                   oo, con1, con2, sts);
345    }
346
347    return sts;
348  }
349
350  /**
351   * Looks for points that define a plane (ax + by + cz + d = 0). Collects MINTRIPLE points before
352   * attempting to define a line through the points. Once the equation for the line is found, each
353   * subsequent point is compared to it. If the point does not match, recalculates the maximally
354   * separated points and attempts to fit the points to the new line. If the points do not fit the
355   * line, attempts to define the plane (to hopefully get at least some spread between the points,
356   * so that small errors don't get magnified). Once the equation for the plane is found, each
357   * subsequent point is compared to it. If the point does not match the point is examined to see if
358   * it is maximally separated when compared to the points originally used to define the
359   * plane. If it is, it is used to recalculate the coefficients (a, b, c). If those
360   * coefficients are relatively close to the original coefficients (within the ratio defined by
361   * Global.fuzzy) then the new coefficients are used.
362   *
363   * @see org.plumelib.util.FuzzyFloat
364   */
365  public InvariantStatus add_modified(double x, double y, double z, int count) {
366
367    if (Debug.logDetail()) {
368      wrapper.log("Adding point, x=%s y=%s z=%s to invariant", x, y, z);
369    }
370
371    if (values_seen < MINTRIPLES) {
372      // We delay computation of a and b until we have seen several triples
373      // so that we can compute a and b based on a far-separated triple.  If
374      // the points in a triple are nearby, then roundoff errors in the
375      // computation of the slope can be non-negligible.
376
377      // skip points we've already seen
378      for (int i = 0; i < values_seen; i++) {
379        if (def_points[i].equals(x, y, z)) {
380          return InvariantStatus.NO_CHANGE;
381        }
382      }
383
384      def_points[values_seen] = new Point(x, y, z);
385      wrapper.log("Add: (%s, %s, %s)", x, y, z);
386      values_seen++;
387      wrapper.log("Values seen: %s", values_seen);
388
389      // check to see if we've seen enough values to create the equation
390      if (values_seen == MINTRIPLES) {
391
392        // Try to fit the points to a line first.
393        // The points may form a degenerate linear ternary invariant
394        // especially if one of the variables is constant
395        linearIntervention(def_points);
396
397        // if line can not be formed, try to form a plane
398        if (line_flag == Flag.NO_LINE) {
399          InvariantStatus stat = planarIntervention(def_points);
400          return stat;
401        } else {
402
403          // points fit in a line
404          return InvariantStatus.NO_CHANGE;
405        }
406      } else {
407
408        // still haven't seen enough values
409        return InvariantStatus.NO_CHANGE;
410      }
411    } else {
412
413      // at this point either a line or a plane must have been formed
414
415      // if line already broken, fit to plane equation
416      if (line_flag == Flag.NO_LINE) {
417        // If the new value doesn't fit the equation
418        if (!Global.fuzzy.eq(-d, a * x + b * y + c * z)) {
419
420          // Try to find small changes that will fit better.
421          if (!try_new_equation(x, y, z)) {
422            if (Invariant.logOn() || debug.isLoggable(Level.FINE)) {
423              wrapper.log(debug, "destroying  (" + wrapper.format()
424                           + ") where x=" + x + " y=" + y + " z=" + z
425                           + " a=" + a + " b=" + b + " c=" + c
426                           + " values_seen=" + values_seen);
427            }
428            // jiggling the values did not work
429            return InvariantStatus.FALSIFIED;
430          }
431        } else {
432
433          // point fits the current plane equation
434          return InvariantStatus.NO_CHANGE;
435        }
436      } else {
437
438        // try to fit to the current line
439        if (!try_points_linear((x), (y), (z))) {
440          // put the crucial point in, so that it can be tested against the new line
441          def_points[MINTRIPLES - 1] = new Point(x, y, z);
442
443          // try to fit the points onto a line again (not sure if this
444          // is really necessary, maybe the fuzzy equals really does
445          // make a difference)
446          linearIntervention(def_points);
447        }
448
449        // new point has broken the line, try to fit to a plane
450        if (line_flag == Flag.NO_LINE) {
451          InvariantStatus stat = planarIntervention(def_points);
452          return stat;
453        } else {
454
455          // fuzzy equals does make a difference
456          return InvariantStatus.NO_CHANGE;
457        }
458
459      }
460
461    }
462    // should never get here because values_seen is either < or >= MINTRIPLES
463    // return InvariantStatus.NO_CHANGE;
464    throw new Error("at end of add_modified");
465  }
466
467  /**
468   * Attempts to fit the points in def_point to a plane and calculates the coefficients (a, b, c, d)
469   * if possible.
470   *
471   * @param def_point array of points. Must have at least 3 elements.
472   *
473   * @return the status of the invariant
474   *     (whether the plane fitting was successful)
475   */
476  private InvariantStatus planarIntervention(@Nullable Point[] def_point) {
477
478    // make a copy of the array so that the points don't get clobbered
479    // because we need to check all the points in def_points against the
480    // coefficients calculated
481
482    @Nullable Point[] dummy = new @Nullable Point[def_point.length];
483    for (int i = 0; i < def_point.length; i++) {
484      dummy[i] = def_point[i];
485    }
486
487    // Find the three points with the maximum separation
488    maxsep_triples(dummy);
489    // Now, first 3 values of dummy are the maximum-separated 3 points.
490
491    // Null points only show up in the cross-checker, not the regression
492    // tests, as of 5/25/2010.
493    // Sometimes the points are still null.
494    if (dummy[0] == null) {
495      return InvariantStatus.FALSIFIED;
496    }
497
498    // Calculate the coefficients of the equation (a, b, c, d).
499    // Requires that the first 3 elements of dummy must be non-null.
500    double[] coef = calc_tri_linear(dummy);
501
502    a = coef[0];
503    b = coef[1];
504    c = coef[2];
505    d = coef[3];
506
507    wrapper.log("Calculated tri linear coeff: (%s), b (%s), c(%s), and d(%s)",
508                 a, b, c, d);
509    // If one of these coefficients is zero (except for d), this
510    // should be a LinearBinary, not a LinearTernary, term.  (It
511    // might not show up as LinearBinary because there might not
512    // have been enough samples; but a random varying third
513    // variable can create enough samples.)  Also, throw out the
514    // invariant if any of the coefficients would be not-a-number.
515    if ( a == 0|| b == 0 || c == 0
516        || Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c)) {
517
518      wrapper.log("problematic coefficient: invariant falsified");
519      return InvariantStatus.FALSIFIED;
520    }
521
522    // Check all values against a, b, and c.
523    if (!wrapper.is_false()) {
524      for (int i = 0; i < values_seen; i++) {
525
526        // Global.fuzzy.eq works by comparing ratios between numbers,
527        // so comparing the sum of everything to 0 won't work.
528        //  wrapper.log("point: " + def_point[i].x + " "
529        //  + def_point[i].y + " " + def_point[i].z);
530        //  wrapper.log("compare: " + -c * def_point[i].z);
531        //  wrapper.log( "to: " + (a*def_point[i].x
532        //  + b*def_point[i].y + d));
533
534        wrapper.log("Trying at index %s: 0 != %s*%s+%s*%s+%s*%s+%s",
535                     i, a, def_point[i].x,
536                        b, def_point[i].y,
537                        c, def_point[i].z,
538                        d);
539
540        if (!Global.fuzzy.eq(-d, a * def_point[i].x + b * def_point[i].y + c * def_point[i].z)) {
541
542          if (Debug.logOn() || debug.isLoggable(Level.FINE)) {
543            wrapper.log(debug, "Destroying at index " + i + ": 0 != "
544                         + a + "*" + def_point[i].x
545                         + "+" + b + "*" + def_point[i].y + "+" + c
546                         + "*" + def_point[i].z
547                         + "+" + d);
548          }
549
550          return InvariantStatus.FALSIFIED;
551        }
552      }
553      if (Debug.logOn()) {
554        wrapper.log("equation = %s*x %s*y %s*z = %s", a, b, c, -d);
555      }
556
557      // Discard the points not used to define the coefficients.
558      for (int ii = 3; ii < MINTRIPLES; ii++) {
559        def_point[ii] = null;
560      }
561    }
562
563    // if it passes all the checks, then no change to the Invariant
564    return InvariantStatus.NO_CHANGE;
565  }
566
567  /**
568   * Attempts to fit the points in def_point to a line and calculates the corresponding line
569   * classification (line_flag) and coefficients (coefficients[]) if possible. If not (points in
570   * def_point form a plane) resets line_flag to Flag.NO_LINE and coefficients[] elements to -999.
571   *
572   * @param def_point array of points. Must have at least 2 elements.
573   *
574   */
575  private void linearIntervention(@Nullable Point[] def_point) {
576
577    // find the max separation points
578    Point[] maxsep_doubles = maxsep_doubles(def_point);
579
580    // maxsep_doubles[0] could be null when enough points have a NaN
581    if (maxsep_doubles == null) {
582      // line_flag == Flag.NO_LINE so the invariant
583      // will try to fit a plane to the points and fail
584      return;
585    } else {
586      // find the equation (coefficients now stored in coefficients[])
587      calc_bi_linear(maxsep_doubles[0], maxsep_doubles[1]);
588
589      // This seems to imply that all elements of def_points are non-null.
590      // Is that true?
591      // check the other points against the coefficients
592      for (int i = 0; i < def_point.length; i++) {
593        // try to fit the current point to the current line
594        boolean ok = try_points_linear((def_point[i].x), (def_point[i].y), (def_point[i].z));
595
596        if (!ok) {
597          line_flag = Flag.NO_LINE;
598          break;
599        }
600      }
601    }
602
603  }
604
605  /**
606   * Attempts to fit the new point (x,y,z) onto the current line (indicated by line_flag and
607   * coefficients[]). Method should only be called if the current points define a line (line_flag is
608   * not Flag.NO_LINE)
609   *
610   * @param x x component of the point
611   * @param x y component of the point
612   * @param x z component of the point
613   */
614  private boolean try_points_linear(double x, double y, double z) {
615
616    switch(line_flag) {
617    case XY_CONSTANT:
618      return Global.fuzzy.eq(coefficients[0], x) && Global.fuzzy.eq(coefficients[1], y);
619
620    case XZ_CONSTANT:
621      return Global.fuzzy.eq(coefficients[0], x) && Global.fuzzy.eq(coefficients[1], z);
622
623    case YZ_CONSTANT:
624      return Global.fuzzy.eq(coefficients[0], y) && Global.fuzzy.eq(coefficients[1], z);
625
626    case X_CONSTANT:
627      return Global.fuzzy.eq(coefficients[0], x) && Global.fuzzy.eq(z, coefficients[1] * y + coefficients[2]);
628
629    case Y_CONSTANT:
630      return Global.fuzzy.eq(coefficients[0], y) && Global.fuzzy.eq(z, coefficients[1] * x + coefficients[2]);
631
632    case Z_CONSTANT:
633      return Global.fuzzy.eq(coefficients[0], z) && Global.fuzzy.eq(y, coefficients[1] * x + coefficients[2]);
634
635    case NO_CONSTANT_VARIABLES:
636      return Global.fuzzy.eq(y, coefficients[0] * x + coefficients[1]) && Global.fuzzy.eq(z, coefficients[2] * x + coefficients[3]);
637
638    // error if the code gets here because that means, we are trying to fit the points to a linear
639    // when the points already form a plane
640    default:
641      throw new RuntimeException("try_points_linear called when points already form a plane (line_flag = NO_LINE)");
642    }
643  }
644
645  /**
646   * Returns the two points that have the maximum separation in pa.
647   *
648   * @param pa array of points. Must have at least 2 elements. Can be any length and can contain
649   *      nulls (which will be ignored).
650   * @return a 2-element array containing the most-separated elements. Returns null if no pair of
651   *      elements is NaN-free.
652   */
653  private Point @Nullable [] maxsep_doubles(@Nullable Point[] pa) {
654
655    Point p1 = null;
656    Point p2 = null;
657
658    // max_separation is the separation metric for the most separated
659    // pair of points.  We use the sum of the separations as the
660    // metric.
661    double max_separation = Double.MIN_VALUE;
662
663    for (int i = 0; i < pa.length - 1; i++) {
664      for (int j = i + 1; j < pa.length; j++) {
665        double separation = separation(pa[i], pa[j]);
666        if (separation > max_separation) {
667          max_separation = separation;
668          p1 = pa[i];
669          p2 = pa[j];
670        }
671      }
672    }
673
674    // This can happen if enough values are NaN.
675    // if (p1 == null) {
676    //   throw new IllegalArgumentException(Arrays.toString(pa));
677    // }
678
679    if (Debug.logDetail()) {
680      wrapper.log("maxsep_doubles = %s %s", p1, p2);
681    }
682    if (p1 == null) {
683      return null;
684    }
685    return new Point[] { p1, p2 };
686  }
687
688  /**
689   * Calculates the coefficients for the line in 3D
690   * and sets the coefficients[] by side effect.
691   *
692   * @param p0 first point on the line
693   * @param p1 second point on the line
694   */
695  //
696  //  the line can fall under 3 categories
697  //
698  //  1. parallel to an axis which means that 2 of the following are true
699  //  x = A
700  //  y = B
701  //  z = C
702  //
703  //  and the two that are true form a line in the 3rd dimension (the one that's
704  //  not constant)
705  //
706  //  2. parallel to the plane of an axis, which means that one of the following
707  //  is true
708  //  x = A
709  //  y = B
710  //  z = C
711  //
712  //  and the line is just a line in the non-constant variable plane projected
713  //  onto the third dimension
714  //  and the equation for that line is:
715  //  x = A [suppose that x is the constant one]
716  //  y = Bz + C
717  //
718  //
719  //  3. none of the above and the equation of the line is just:
720  //  y = Ax + B
721  //  z = Cx + D
722  //
723  //  Note that A, B, C, and D are coefficients that are stored in coefficients[]
724
725  private void calc_bi_linear(Point p0, Point p1) {
726
727    if ((p0.x == p1.x) && (p0.y == p1.y) && (p0.z != p1.z)) {
728      // x and y have constant values
729      // line is defined by x = A, y = B
730      line_flag = Flag.XY_CONSTANT;
731      coefficients[0] = p0.x;
732      coefficients[1] = p0.y;
733      coefficients[2] = 0;
734      coefficients[3] = 0;
735      return;
736    }
737    if ((p0.x == p1.x) && (p0.z == p1.z) && (p0.y != p1.y)) {
738      // x and z have constant values
739      // line is defined by x = A, z = C
740      line_flag = Flag.XZ_CONSTANT;
741      coefficients[0] = p0.x;
742      coefficients[1] = p0.z;
743      coefficients[2] = 0;
744      coefficients[3] = 0;
745      return;
746    }
747    if ((p0.y == p1.y) && (p0.z == p1.z) && (p0.x != p1.x)) {
748      // y and z have constant values
749      // line is defined by y = B, z = C
750      line_flag = Flag.YZ_CONSTANT;
751      coefficients[0] = p0.y;
752      coefficients[1] = p0.z;
753      coefficients[2] = 0;
754      coefficients[3] = 0;
755      return;
756    }
757    if ((p0.x == p1.x) && (p0.y != p1.y) && (p0.z != p1.z)) {
758      // x has a constant value
759      // plane (degenerate) is defined by x = A, z = By + C
760      line_flag = Flag.X_CONSTANT;
761      coefficients[0] = p0.x;
762      coefficients[1] = (p1.z - p0.z)/(p1.y - p0.y);
763      coefficients[2] = (p0.z * p1.y - p0.y * p1.z)/(p1.y - p0.y);
764      coefficients[3] = 0;
765      return;
766    }
767    if ((p0.y == p1.y) && (p0.x != p1.x) && (p0.z != p1.z)) {
768      // y has a constant value
769      // plane (degenerate) is defined by y = B, z = Ax + C
770      line_flag = Flag.Y_CONSTANT;
771      coefficients[0] = p0.y;
772      coefficients[1] = (p1.z - p0.z)/(p1.x - p0.x);
773      coefficients[2] = (p0.z * p1.x - p0.x * p1.z)/(p1.x - p0.x);
774      coefficients[3] = 0;
775      return;
776    }
777    if ((p0.z == p1.z) && (p0.x != p1.x) && (p0.y != p1.y)) {
778      // z has a constant value
779      // plane (degenerate) is defined by z = C, y = Ax + B
780      line_flag = Flag.Z_CONSTANT;
781      coefficients[0] = p0.z;
782      coefficients[1] = (p1.y - p0.y)/(p1.x - p0.x);
783      coefficients[2] = (p0.y * p1.x - p0.x * p1.y)/(p1.x - p0.x);
784      coefficients[3] = 0;
785      return;
786    }
787    if ((p0.x != p1.x) && (p0.y != p1.y) && (p0.z != p1.z)) {
788      // no variables have a constant value
789      // plane (degenerate) is defined by y = Ax + B, z = Cx + D
790      line_flag = Flag.NO_CONSTANT_VARIABLES;
791      coefficients[0] = (p1.y - p0.y)/(p1.x - p0.x);
792      coefficients[1] = (p0.y * p1.x - p0.x * p1.y)/(p1.x - p0.x);
793      coefficients[2] = (p1.z - p0.z)/(p1.x - p0.x);
794      coefficients[3] = (p0.z * p1.x - p0.x * p1.z)/(p1.x - p0.x);
795    }
796  }
797
798  /**
799   *  Calculates new coefficients for the new point. Uses the
800   *  new coefficients if they are relatively close to the previous
801   *  ones. Kills off the invariant if they are not.
802   *
803   *  @return true if the new equation worked, false otherwise
804   */
805  public boolean try_new_equation(double x, double y, double z) {
806
807    // Calculate max separation using this point and the existing 3 points.
808    def_points[3] = new Point(x, y, z);
809    double sep = maxsep_triples(def_points);
810
811    // If this point increased the separation, recalculate a, b, and c.
812    if (sep > separation) {
813      separation = sep;
814      double[] coef;
815      try {
816        // Requires that the first 3 elements of def_points must be non-null.
817        coef = calc_tri_linear(def_points);
818        if (Debug.logDetail()) {
819          wrapper.log("Calc new plane with points %s %s %s %s",
820                       def_points[0], def_points[1], def_points[2], def_points[3]);
821        }
822      } catch (Exception e) {
823        return false;
824      }
825
826      // if the a, b, or c is a new min/max remember it.
827      if (coef[0] < min_a) min_a = coef[0];
828      if (coef[0] > max_a) max_a = coef[0];
829      if (coef[1] < min_b) min_b = coef[1];
830      if (coef[1] > max_b) max_b = coef[1];
831      if (coef[2] < min_c) min_c = coef[2];
832      if (coef[2] > max_c) max_c = coef[2];
833      if (coef[3] < min_d) min_d = coef[3];
834      if (coef[3] > max_d) max_d = coef[3];
835
836      // Pick a new a, b, and c as the average of their endpoints
837      a = (min_a + max_a) / 2;
838      b = (min_b + max_b) / 2;
839      c = (min_c + max_c) / 2;
840      d = (min_d + max_d) / 2;
841      if (Invariant.logOn() || debug.isLoggable(Level.FINE)) {
842        wrapper.log(debug, wrapper.ppt.name() + ": Trying new a (" + a
843                     + "), b (" + b + "), c (" + c + "), and d (" + d + ")");
844      }
845      wrapper.log("min max: Trying new a (%s), b (%s), c (%s), and d (%s)",
846                   a, b, c, d);
847      // if the new coefficients are 'equal' to their min and max and
848      // this point fits, then this new equation is good enough both
849      // for existing points and the new point.
850      if (Global.fuzzy.eq(a, min_a) && Global.fuzzy.eq(a, max_a)
851          && Global.fuzzy.eq(b, min_b) && Global.fuzzy.eq(b, max_b)
852          && Global.fuzzy.eq(c, min_c) && Global.fuzzy.eq(c, max_c)
853          && Global.fuzzy.eq(d, min_d) && Global.fuzzy.eq(d, max_d)
854          && Global.fuzzy.eq(-d, a * x + b * y + c * z)) {
855        if (debug.isLoggable(Level.FINE)) {
856          debug.fine(wrapper.ppt.name() + ": New a (" + a + ") and b ("
857                      + b + ") and c (" + c + ")");
858        }
859        return true;
860      } else {
861        return false;
862      }
863    } else { // this point doesn't increase the separation
864
865      return false;
866    }
867  }
868
869  /**
870   * Calculates the separation between p1 and p2.
871   *
872   * @param p1 first point
873   * @param p2 second point
874   *
875   * @return the distance between p1 and p2, or 0 if either point is null, or NaN if either point
876   * contains a NaN
877   */
878  double separation(Point p1, Point p2) {
879
880      // Potential problem:  This may return 0, for two reasons.  First, x1-x2
881      // might be zero due to underflow, even if x1!=x2.  Second, squaring a
882      // small x1-x2 might result in 0, even if x1-x2!=0.
883
884    // make sure both points are specified
885    if ((p1 == null) || (p2 == null)) {
886      return 0;
887    }
888
889    double xsep = (p1.x - p2.x);
890    double ysep = (p1.y - p2.y);
891    double zsep = (p1.z - p2.z);
892    return Math.sqrt(xsep * xsep + ysep * ysep + zsep * zsep);
893  }
894
895  /**
896   * Calculates the three points that have the maximum separation in pa and places them as the first
897   * three elements of pa.
898   *
899   * @param pa array of points. Must have at least 3 elements. Can be any length and can contain
900   *     nulls (which will be ignored). Is side-effected so that the first three elements contain
901   *     the points with the maximum total separation; this may introduce duplicates into the array.
902   *     The first three elements are null if no triple of elements is NaN-free.
903   * @return the maximum separation found
904   */
905  double maxsep_triples(@Nullable Point[] pa) {
906
907    // cache values for the (square of the) distance between each pair of
908    // points, to avoid duplicating work
909    double[][] separations = new double[pa.length][pa.length];
910    for (int i = 0; i < pa.length - 1; i++) {
911      for (int j = i + 1; j < pa.length; j++) {
912        separations[i][j] = separation(pa[i], pa[j]);
913      }
914    }
915
916    Point p1 = null;
917    Point p2 = null;
918    Point p3 = null;
919
920    // max_separation is the separation metric for the most separated
921    // triple of points.  We use the sum of the separations as the
922    // metric.  (The metric "min of the three separations" does not work
923    // because it doesn't choose a unique set of points, making the
924    // result dependent on the order in which the points are seen; more
925    // seriously, it may choose a collinear set of points even when a
926    // non-collinear set exists.)
927    double max_separation = Double.MIN_VALUE;
928
929    for (int i = 0; i < pa.length - 2; i++) {
930      for (int j = i + 1; j < pa.length - 1; j++) {
931        double sep_i_j = separations[i][j];
932        for (int k = j + 1; k < pa.length; k++) {
933
934          // using the sum of separations is valid, as long as
935          // separation is the actual distance between points and not
936          // the square of the distance
937          double separation = sep_i_j + separations[i][k] + separations[j][k];
938          if (separation > max_separation) {
939            max_separation = separation;
940            p1 = pa[i];
941            p2 = pa[j];
942            p3 = pa[k];
943          }
944        }
945      }
946    }
947
948    pa[0] = p1;
949    pa[1] = p2;
950    pa[2] = p3;
951
952    if (Debug.logDetail()) {
953      wrapper.log("maxsep_triples = %s %s %s", pa[0], pa[1], pa[2]);
954    }
955    return max_separation;
956  }
957
958  // Given ((x0,y0,z0),(x1,y1,z1), (x2,y2,z2), calculate a, b, c and d
959  // such that ax + by + cz + d = 0.
960  // a, b, c and d are mutually prime, integers and a is positive.
961  //
962  // A visual explanation of the math can be found in:
963  // http://www.efm.leeds.ac.uk/CIVE/CIVE2599/cive2599-summary-overheads-full.pdf
964  //
965  // The standard form of a plane can be calculated using a vector
966  // normal "n" <n1, n2, n3> to the plane and a vector from the origin
967  // to the plane "r" e.g. <x0, y0, z0>.
968  //
969  // The vector normal to the plane can be calculated by doing the cross product
970  // of two line segments on the plane (<point2 - point0> and <point1 - point0>)
971  //
972  // Given the normal vector and a point on the plane, the standard form
973  // of the plane is:
974  // n1*x + n2*y + n3*z = p where p is the dot product of r and n
975  //
976  //
977
978  /**
979   * Calculates the coefficients (a, b, c) and constant (d) for the equation ax + by + cz + d = 0
980   * using the first three points in points.
981   *
982   * @param points array of points to use to calculate the coefficients. Only the first three points
983   *     (all of which must be non-null) are used.
984   *
985   * @return a four element array where a is the first element, b the second, c the third, and d is
986   * the fourth. All elements are mutually prime, integers and a is positive.
987   */
988  // TODO: should just pass in the first three elements rather than passing
989  // in the point array.
990  public double[] calc_tri_linear(@Nullable Point[] points) { // TODO: remove annotation after refactoring method
991
992    Point p0 = points[0];
993    Point p1 = points[1];
994    Point p2 = points[2];
995
996    // the following code is taken from the source page of
997    // http://jblanco_60.tripod.com/plane.html (the math behind the
998    // implementation is detailed above)
999
1000    double px = p0.x;
1001    double py = p0.y;
1002    double pz = p0.z;
1003    double qx = p1.x;
1004    double qy = p1.y;
1005    double qz = p1.z;
1006    double rx = p2.x;
1007    double ry = p2.y;
1008    double rz = p2.z;
1009
1010    // define the i, j, k components of the two line segments on the plane
1011    double a1 = (qx - px);
1012    double a2 = (qy - py);
1013    double a3 = (qz - pz);
1014
1015    double b1 = (rx - px);
1016    double b2 = (ry - py);
1017    double b3 = (rz - pz);
1018
1019    // calculate the i, j, k components of the cross product
1020    double i_var = (a2 * b3) - (b2 * a3);
1021    double j_var = -((a1 * b3) - (b1 * a3));
1022    double k_var = (a1 * b2) - (b1 * a2);
1023
1024    // Calculate the value of the constant.  Note that given the format
1025    // of the point-normal form, we multiply by the negative of P(x,y,z)
1026
1027    double Stand_const = ((i_var*(-px)) + (j_var*(-py)) + (k_var*(-pz)));
1028
1029    // Put the coefficients in lowest terms by dividing by the gcd.
1030    // If the gcd is 1, no harm done -- divide by 1.
1031    // Type is double to ensure floating-point division.
1032    double myGCD = MathPlume.gcd(MathPlume.gcd(i_var, j_var) , MathPlume.gcd(k_var, Stand_const));
1033    double standard_i_var = i_var / myGCD;
1034    double standard_j_var = j_var / myGCD;
1035    double standard_k_var = k_var / myGCD;
1036    double standard_Stand_const = Stand_const / myGCD;
1037
1038    if (wrapper != null) {
1039      wrapper.log("Preliminary: %s %s GCD: %s", i_var, j_var, MathPlume.gcd(i_var, j_var));
1040      wrapper.log("Preliminary: %s %s GCD: %s", k_var, Stand_const, MathPlume.gcd(k_var, Stand_const));
1041      wrapper.log("GCD: %s", myGCD);
1042    }
1043
1044    // Ensure positive x coefficient by negating if "x" term is negative.
1045    if (i_var < 0) {
1046      standard_i_var = - standard_i_var;
1047      standard_j_var = - standard_j_var;
1048      standard_k_var = - standard_k_var;
1049      standard_Stand_const = - standard_Stand_const;
1050    }
1051
1052    double[] coef = new double[] {standard_i_var,
1053                                  standard_j_var,
1054                                  standard_k_var, standard_Stand_const };
1055
1056    return coef;
1057  }
1058
1059  public boolean enoughSamples(@GuardSatisfied LinearTernaryCoreFloat this) {
1060    return isActive();
1061  }
1062
1063  // If the values form a line,
1064  // we have no confidence that it's a plane.
1065  // The line is less interesting because the invariant
1066  // is already defined by a linear binary invariant.
1067  public double computeConfidence() {
1068    if (isActive()) {
1069      return Invariant.conf_is_ge(values_seen, MINTRIPLES);
1070    } else {
1071      return 0;
1072    }
1073
1074  }
1075
1076  public String repr(@GuardSatisfied LinearTernaryCoreFloat this) {
1077    return "LinearTernaryCoreFloat" + wrapper.varNames() + ": a=" + a
1078      + ",b=" + b
1079      + ",c=" + c
1080      + ",d=" + d
1081      + ",values_seen=" + values_seen;
1082  }
1083
1084  public String point_repr(Point p) {
1085    if (p == null) {
1086      return "null";
1087    } else {
1088      return "<" + p.x + "," + p.y + "," + p.z + ">";
1089    }
1090  }
1091
1092  public String cache_repr() {
1093    StringJoiner result = new StringJoiner("; ");
1094    for (int i = 0; i < MINTRIPLES; i++) {
1095      result.add(point_repr(def_points[i]));
1096    }
1097    return result.toString();
1098  }
1099
1100  // In this class for convenience (avoid prefixing "LinearBinaryCore").
1101  static String formatTerm(double coeff, @Nullable String varname, boolean first) {
1102    return LinearBinaryCore.formatTerm(coeff, varname, first);
1103  }
1104
1105  @SideEffectFree
1106  public String format_using(OutputFormat format,
1107                             String vix, String viy, String viz,
1108                             double a, double b, double c, double d) {
1109
1110    if ((a == 0) && (b == 0) && (c == 0) && (d == 0)) {
1111      return wrapper.format_too_few_samples(format, null);
1112    }
1113
1114    if (format == OutputFormat.SIMPLIFY) {
1115      return format_simplify(vix, viy, viz, a, b, c, d);
1116    }
1117
1118    if ((format.isJavaFamily())) {
1119
1120        return
1121          // Add a 1 to each side of the equation because FuzzyFloat
1122          // doesn't treat 0 as 'fuzzy' when compared against another
1123          // number.
1124          "daikon.Quant.fuzzy.eq("
1125          + formatTerm(a, vix, true)
1126          + formatTerm(b, viy, (a == 0))
1127          + formatTerm(c, viz, (a == 0) && (b == 0))
1128          + formatTerm(d, null, (a == 0) && (b == 0) && (c == 0))
1129          + " + 1, 1)";
1130
1131      }
1132
1133    if ((format == OutputFormat.DAIKON)
1134        || (format == OutputFormat.ESCJAVA)
1135        || (format == OutputFormat.CSHARPCONTRACT)) {
1136        String eq = " == ";
1137        return formatTerm(a, vix, true)
1138          + formatTerm(b, viy, (a == 0))
1139          + formatTerm(c, viz, (a == 0) && (b == 0))
1140          + formatTerm(d, null, (a == 0) && (b == 0) && (c == 0))
1141          + eq + "0";
1142      }
1143
1144    throw new Error("unrecognized output format " + format);
1145    // return null;
1146  }
1147
1148  public static String format_simplify(String vix, String viy, String viz,
1149                                       double da, double db, double dc, double dd) {
1150    int ia = (int) da;
1151    int ib = (int) db;
1152    int ic = (int) dc;
1153    int id = (int) dd;
1154
1155    String str_a;
1156    String str_b;
1157    String str_c;
1158    String str_d;
1159    if (ia != da || ib != db || ic != dc || id != dd) {
1160      // floating point
1161
1162      // Disabled for the moment, since mixing integers and floating
1163      // literals seems to give Simplify indigestion. For instance:
1164      // (BG_PUSH (<= w 3))
1165      // (BG_PUSH (EQ 0 (* w 2.0d0)))
1166      // (<= w 1)
1167
1168      // str_a = Invariant.simplify_format_double(da);
1169      // str_b = Invariant.simplify_format_double(db);
1170      // str_c = Invariant.simplify_format_double(dc);
1171      // str_d = Invariant.simplify_format_double(dd);
1172      return "(AND)"; // really s/b format_unimplemented
1173    } else {
1174      // integer
1175      str_a = Invariant.simplify_format_long(ia);
1176      str_b = Invariant.simplify_format_long(ib);
1177      str_c = Invariant.simplify_format_long(ic);
1178      str_d = Invariant.simplify_format_long(id);
1179    }
1180
1181    String str_z = viz;
1182    String str_x = vix;
1183    String str_y = viy;
1184    String str_ax = (da == 1.0) ? str_x : "(* " + str_a + " " + str_x + ")";
1185    String str_by = (db == 1.0) ? str_y : "(* " + str_b + " " + str_y + ")";
1186    String str_cz = (dc == 1.0) ? str_z : "(* " + str_c + " " + str_z + ")";
1187    String str_axPbyPcz = "(+ " + str_ax + " " + str_by + " " + str_cz + ")";
1188    String str_axPbyPczPd = (dd == 0.0) ? str_axPbyPcz :
1189      "(+ " + str_axPbyPcz + " " + str_d + ")";
1190    return "(EQ 0 " + str_axPbyPczPd + ")";
1191  }
1192
1193  @SideEffectFree
1194  public String format_using(OutputFormat format,
1195                             String vix, String viy, String viz
1196                             ) {
1197    String result = format_using(format, vix, viy, viz, a, b, c, d);
1198    if (result != null) {
1199      return result;
1200    }
1201
1202    return wrapper.format_unimplemented(format);
1203  }
1204
1205  // // Format as "x = cy+d" instead of as "y = ax+b".
1206  // public String format_reversed(String x, String y) {
1207  //   assert a == 1 || a == -1;
1208  //   return format(y, x, a, -b/a);
1209  // }
1210
1211  @Pure
1212  public boolean isSameFormula(LinearTernaryCoreFloat other) {
1213
1214    // If both have yet to see enough values
1215    if (!isActive() && !other.isActive()) {
1216
1217      // Same formula if all of the points match
1218      if (values_seen != other.values_seen) {
1219        return false;
1220      }
1221      // This and elsewhere seem to assume that values_seen < MINTRIPLES;
1222      // is that necessarily true?
1223      for (int ii = 0; ii < values_seen; ii++) {
1224        // used to use !=, but that test seems wrong
1225        if (!def_points[ii].equals(other.def_points[ii])) {
1226          return false;
1227        }
1228      }
1229      return true;
1230    } else {
1231      return ((values_seen >= MINTRIPLES)
1232              && (other.values_seen >= MINTRIPLES)
1233              && (a == other.a)
1234              && (b == other.b)
1235              && (c == other.c)
1236              && (d == other.d));
1237    }
1238  }
1239
1240  @Pure
1241  public boolean isExclusiveFormula(LinearTernaryCoreFloat other) {
1242    if (!isActive()
1243        || !other.isActive()) {
1244      return false;
1245    }
1246
1247    return ((a == other.a)
1248            && (b != other.b)
1249            && (c != other.c)
1250            && (d != other.d));
1251  }
1252
1253  /**
1254   * In general, we can't merge formulas, but we can merge invariants with too few samples to have
1255   * formed a plane with invariants with enough samples. And those will appear to have different
1256   * formulas.
1257   */
1258  public boolean mergeFormulasOk() {
1259    return true;
1260  }
1261
1262  /**
1263   * Merge the linear ternary cores in cores to form a new core. Any core in the list that has seen
1264   * enough points to define a plane, must define the same plane. Any cores that have not yet seen
1265   * enough points, will have each of their points applied to that invariant. The merged core is
1266   * returned. Null is returned if the cores don't describe the same plane
1267   *
1268   * @param cores list of LinearTernary cores to merge. They should all be permuted to match the
1269   *     variable order in ppt.
1270   */
1271  public @Nullable LinearTernaryCoreFloat merge(List<LinearTernaryCoreFloat> cores, Invariant wrapper) {
1272
1273    // Look for any active planes.  All must define the same plane
1274    LinearTernaryCoreFloat first = null;
1275    for (LinearTernaryCoreFloat c : cores) {
1276      if (!c.isActive()) {
1277        continue;
1278      }
1279      if (first == null) {
1280        first = c.clone();
1281      } else {
1282        if (!Global.fuzzy.eq(first.a, c.a)
1283            || !Global.fuzzy.eq(first.b, c.b)
1284            || !Global.fuzzy.eq(first.c, c.c)
1285            || !Global.fuzzy.eq(first.d, c.d))
1286          return null;
1287      }
1288    }
1289
1290    // If no active planes were found, create an empty core
1291    if (first == null) {
1292      first = new LinearTernaryCoreFloat(wrapper);
1293    } else {
1294      first.wrapper = wrapper;
1295    }
1296
1297    // Merge in any points from non-active cores
1298    // Note that while null points can't show up in a core that is not active
1299    // because it hasn't seen enough values, they can show up if the core
1300    // is not active because it is a line.
1301    for (LinearTernaryCoreFloat c : cores) {
1302      if (c.isActive()) {
1303        continue;
1304      }
1305      for (int j = 0; j < c.values_seen; j++) {
1306        Point cp = c.def_points[j];
1307        if (cp == null) {
1308          continue;
1309        }
1310        if (Debug.logDetail()) {
1311          wrapper.log("Adding point %s from %s", cp, c.wrapper.ppt);
1312        }
1313        InvariantStatus stat = first.add_modified(cp.x, cp.y, cp.z, 1);
1314        if (stat == InvariantStatus.FALSIFIED) {
1315          return null;
1316        }
1317        // if (wrapper.is_false())
1318        //  return null;
1319      }
1320    }
1321
1322    return first;
1323  }
1324}