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}