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