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