1 /* 2 * LinearInt.java 3 * This file is part of JaCoP. 4 * <p> 5 * JaCoP is a Java Constraint Programming solver. 6 * <p> 7 * Copyright (C) 2000-2008 Krzysztof Kuchcinski and Radoslaw Szymanek 8 * <p> 9 * This program is free software: you can redistribute it and/or modify 10 * it under the terms of the GNU Affero General Public License as published by 11 * the Free Software Foundation, either version 3 of the License, or 12 * (at your option) any later version. 13 * <p> 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU Affero General Public License for more details. 18 * <p> 19 * Notwithstanding any other provision of this License, the copyright 20 * owners of this work supplement the terms of this License with terms 21 * prohibiting misrepresentation of the origin of this work and requiring 22 * that modified versions of this work be marked in reasonable ways as 23 * different from the original version. This supplement of the license 24 * terms is in accordance with Section 7 of GNU Affero General Public 25 * License version 3. 26 * <p> 27 * You should have received a copy of the GNU Affero General Public License 28 * along with this program. If not, see <http://www.gnu.org/licenses/>. 29 */ 30 31 package org.jacop.constraints; 32 33 import org.jacop.core.IntDomain; 34 import org.jacop.core.IntVar; 35 import org.jacop.core.Store; 36 37 import java.util.*; 38 import java.util.concurrent.atomic.AtomicInteger; 39 import java.util.stream.IntStream; 40 import java.util.stream.Stream; 41 42 /** 43 * LinearInt constraint implements the weighted summation over several 44 * variables . 45 * <p> 46 * sum(i in 1..N)(ai*xi) = b 47 * <p> 48 * It provides the weighted sum from all variables on the list. 49 * The weights are integers. 50 * <p> 51 * This implementaiton is based on 52 * "Bounds Consistency Techniques for Long Linear Constraints" 53 * by Warwick Harvey and Joachim Schimpf 54 * 55 * @author Krzysztof Kuchcinski 56 * @version 4.8 57 */ 58 59 public class LinearInt extends PrimitiveConstraint { 60 61 Store store; 62 63 static AtomicInteger idNumber = new AtomicInteger(0); 64 65 boolean reified = true; 66 67 /** 68 * Defines relations 69 */ 70 final static byte eq = 0, le = 1, lt = 2, ne = 3, gt = 4, ge = 5; 71 72 /** 73 * Defines negated relations 74 */ 75 final static byte[] negRel = {ne, //eq=0, 76 gt, //le=1, 77 ge, //lt=2, 78 eq, //ne=3, 79 le, //gt=4, 80 lt //ge=5; 81 }; 82 83 /** 84 * It specifies what relations is used by this constraint 85 */ 86 87 public byte relationType; 88 89 /** 90 * It specifies a list of variables being summed. 91 */ 92 IntVar x[]; 93 94 /** 95 * It specifies a list of weights associated with the variables being summed. 96 */ 97 long a[]; 98 99 /** 100 * It specifies variable for the overall sum. 101 */ 102 long b; 103 104 /** 105 * It specifies the index of the last positive coefficient. 106 */ 107 int pos; 108 109 /** 110 * It specifies the number of variables/coefficients. 111 */ 112 int l; 113 114 /** 115 * It specifies "variability" of each variable 116 */ 117 long[] I; 118 119 /** 120 * It specifies sum of lower bounds (min values) and sum of upper bounds (max values) 121 */ 122 long sumMin, sumMax; 123 LinearInt()124 protected LinearInt() { 125 } 126 127 /** 128 * @param store current store 129 * @param list variables which are being multiplied by weights. 130 * @param weights weight for each variable. 131 * @param rel the relation, one of "==", "{@literal <}", "{@literal >}", "{@literal <=}", "{@literal >=}", "{@literal !=}" 132 * @param sum the sum of weighted variables. 133 * @deprecated LinearInt constraint does not use Store parameter any longer. 134 */ LinearInt(Store store, IntVar[] list, int[] weights, String rel, int sum)135 @Deprecated public LinearInt(Store store, IntVar[] list, int[] weights, String rel, int sum) { 136 checkInputForNullness("list", list); 137 checkInputForNullness("weights", weights); 138 commonInitialization(store, list, weights, rel, sum); 139 numberId = idNumber.incrementAndGet(); 140 } 141 142 /** 143 * It constructs the constraint LinearInt. 144 * 145 * @param store current store 146 * @param list list which are being multiplied by weights. 147 * @param weights weight for each variable. 148 * @param rel the relation, one of "==", "{@literal <}", "{@literal >}", "{@literal <=}", "{@literal >=}", "{@literal !=}" 149 * @param sum the sum of weighted list. 150 * @deprecated LinearInt constraint does not use Store parameter any longer. 151 */ LinearInt(Store store, List<? extends IntVar> list, List<Integer> weights, String rel, int sum)152 @Deprecated public LinearInt(Store store, List<? extends IntVar> list, List<Integer> weights, String rel, int sum) { 153 checkInputForNullness(new String[] {"list", "weights"}, new Object[] {list, weights}); 154 commonInitialization(store, list.toArray(new IntVar[list.size()]), weights.stream().mapToInt(i -> i).toArray(), rel, sum); 155 numberId = idNumber.incrementAndGet(); 156 } 157 158 /** 159 * @param store current store 160 * @param list variables which are being multiplied by weights. 161 * @param weights weight for each variable. 162 * @param rel the relation, one of "==", "{@literal <}", "{@literal >}", "{@literal <=}", "{@literal >=}", "{@literal !=}" 163 * @param sum the sum of weighted variables. 164 * @deprecated LinearInt constraint does not use Store parameter any longer. 165 */ LinearInt(Store store, IntVar[] list, int[] weights, String rel, IntVar sum)166 @Deprecated public LinearInt(Store store, IntVar[] list, int[] weights, String rel, IntVar sum) { 167 checkInputForNullness("list", list); 168 checkInputForNullness("weights", weights); 169 commonInitialization(store, Stream.concat(Arrays.stream(list), Stream.of(sum)).toArray(IntVar[]::new), 170 IntStream.concat(Arrays.stream(weights), IntStream.of(-1)).toArray(), rel, 0); 171 numberId = idNumber.incrementAndGet(); 172 } 173 174 // ======== new constructors =============== 175 176 /** 177 * @param list variables which are being multiplied by weights. 178 * @param weights weight for each variable. 179 * @param rel the relation, one of "==", "{@literal <}", "{@literal >}", "{@literal <=}", "{@literal >=}", "{@literal !=}" 180 * @param sum the sum of weighted variables. 181 */ LinearInt(IntVar[] list, int[] weights, String rel, int sum)182 public LinearInt(IntVar[] list, int[] weights, String rel, int sum) { 183 checkInputForNullness("list", list); 184 checkInputForNullness("weights", weights); 185 commonInitialization(list[0].getStore(), list, weights, rel, sum); 186 numberId = idNumber.incrementAndGet(); 187 } 188 189 /** 190 * It constructs the constraint LinearInt. 191 * 192 * @param list list which are being multiplied by weights. 193 * @param weights weight for each variable. 194 * @param rel the relation, one of "==", "{@literal <}", "{@literal >}", "{@literal <=}", "{@literal >=}", "{@literal !=}" 195 * @param sum the sum of weighted list. 196 */ LinearInt(List<? extends IntVar> list, List<Integer> weights, String rel, int sum)197 public LinearInt(List<? extends IntVar> list, List<Integer> weights, String rel, int sum) { 198 checkInputForNullness(new String[] {"list", "weights"}, new Object[] {list, weights}); 199 commonInitialization(list.get(0).getStore(), list.toArray(new IntVar[list.size()]), weights.stream().mapToInt(i -> i).toArray(), 200 rel, sum); 201 numberId = idNumber.incrementAndGet(); 202 } 203 204 /** 205 * @param list variables which are being multiplied by weights. 206 * @param weights weight for each variable. 207 * @param rel the relation, one of "==", "{@literal <}", "{@literal >}", "{@literal <=}", "{@literal >=}", "{@literal !=}" 208 * @param sum the sum of weighted variables. 209 */ LinearInt(IntVar[] list, int[] weights, String rel, IntVar sum)210 public LinearInt(IntVar[] list, int[] weights, String rel, IntVar sum) { 211 checkInputForNullness("list", list); 212 checkInputForNullness("weights", weights); 213 commonInitialization(sum.getStore(), Stream.concat(Arrays.stream(list), Stream.of(sum)).toArray(IntVar[]::new), 214 IntStream.concat(Arrays.stream(weights), IntStream.of(-1)).toArray(), rel, 0); 215 numberId = idNumber.incrementAndGet(); 216 } 217 commonInitialization(Store store, IntVar[] list, int[] weights, String rel, int sum)218 protected void commonInitialization(Store store, IntVar[] list, int[] weights, String rel, int sum) { 219 220 this.relationType = relation(rel); 221 222 if (list.length != weights.length) 223 throw new IllegalArgumentException("LinearInt has list and weights arguments of different length."); 224 225 this.store = store; 226 this.b = sum; 227 228 LinkedHashMap<IntVar, Long> parameters = new LinkedHashMap<IntVar, Long>(); 229 230 for (int i = 0; i < list.length; i++) { 231 if (weights[i] != 0) { 232 if (list[i].singleton()) 233 this.b -= (long) list[i].value() * weights[i]; 234 else if (parameters.get(list[i]) != null) { 235 // variable ordered in the scope of the Propagations constraint. 236 Long coeff = parameters.get(list[i]); 237 Long sumOfCoeff = coeff + weights[i]; 238 parameters.put(list[i], sumOfCoeff); 239 } else 240 parameters.put(list[i], (long) weights[i]); 241 242 } 243 } 244 int size = 0; 245 for (Long e : parameters.values()) 246 if (e != 0) 247 size++; 248 249 this.x = new IntVar[size]; 250 this.a = new long[size]; 251 252 int i = 0; 253 Set<Map.Entry<IntVar, Long>> entries = parameters.entrySet(); 254 255 for (Map.Entry<IntVar, Long> e : entries) { 256 IntVar var = e.getKey(); 257 long coeff = e.getValue(); 258 if (coeff > 0) { 259 this.x[i] = var; 260 this.a[i] = coeff; 261 i++; 262 } 263 } 264 pos = i; 265 for (Map.Entry<IntVar, Long> e : entries) { 266 IntVar var = e.getKey(); 267 long coeff = e.getValue(); 268 if (coeff < 0) { 269 this.x[i] = var; 270 this.a[i] = coeff; 271 i++; 272 } 273 } 274 275 this.l = x.length; 276 this.I = new long[l]; 277 278 checkForOverflow(); 279 280 if (l <= 3) 281 queueIndex = 0; 282 else 283 queueIndex = 1; 284 285 setScope(list); 286 287 } 288 consistency(Store store)289 @Override public void consistency(Store store) { 290 291 propagate(relationType); 292 } 293 notConsistency(Store store)294 @Override public void notConsistency(Store store) { 295 propagate(negRel[relationType]); 296 } 297 propagate(int rel)298 public void propagate(int rel) { 299 300 computeInit(); 301 302 do { 303 304 store.propagationHasOccurred = false; 305 306 switch (rel) { 307 case eq: 308 309 pruneLtEq(b); 310 pruneGtEq(b); 311 312 // if (!reified) 313 // if (sumMax <= b && sumMin >= b) 314 // removeConstraint(); 315 316 break; 317 318 case le: 319 pruneLtEq(b); 320 321 if (!reified) 322 if (sumMax <= b) 323 removeConstraint(); 324 break; 325 326 case lt: 327 pruneLtEq(b - 1L); 328 329 if (!reified) 330 if (sumMax < b) 331 removeConstraint(); 332 break; 333 case ne: 334 pruneNeq(); 335 336 if (!reified) 337 // if (sumMin == sumMax && (sumMin > b || sumMax < b)) 338 if (sumMin > b || sumMax < b) 339 removeConstraint(); 340 break; 341 case gt: 342 343 pruneGtEq(b + 1L); 344 345 if (!reified) 346 if (sumMin > b) 347 removeConstraint(); 348 break; 349 case ge: 350 351 pruneGtEq(b); 352 353 if (!reified) 354 if (sumMin >= b) 355 removeConstraint(); 356 357 break; 358 default: 359 throw new RuntimeException("Internal error in " + getClass().getName()); 360 } 361 362 } while (store.propagationHasOccurred); 363 } 364 365 getDefaultNestedConsistencyPruningEvent()366 @Override protected int getDefaultNestedConsistencyPruningEvent() { 367 return IntDomain.BOUND; 368 } 369 getDefaultNestedNotConsistencyPruningEvent()370 @Override protected int getDefaultNestedNotConsistencyPruningEvent() { 371 return IntDomain.BOUND; 372 } 373 getDefaultConsistencyPruningEvent()374 @Override public int getDefaultConsistencyPruningEvent() { 375 return IntDomain.BOUND; 376 } 377 getDefaultNotConsistencyPruningEvent()378 @Override protected int getDefaultNotConsistencyPruningEvent() { 379 return IntDomain.BOUND; 380 } 381 impose(Store store)382 @Override public void impose(Store store) { 383 384 reified = false; 385 386 super.impose(store); 387 388 } 389 computeInit()390 void computeInit() { 391 long f = 0, e = 0; 392 long min, max; 393 int i = 0; 394 // positive weights 395 for (; i < pos; i++) { 396 IntDomain xd = x[i].dom(); 397 min = (long) xd.min() * a[i]; 398 max = (long) xd.max() * a[i]; 399 f += min; 400 e += max; 401 I[i] = (max - min); 402 } 403 // negative weights 404 for (; i < l; i++) { 405 IntDomain xd = x[i].dom(); 406 min = (long) xd.max() * a[i]; 407 max = (long) xd.min() * a[i]; 408 f += min; 409 e += max; 410 I[i] = (max - min); 411 } 412 sumMin = f; 413 sumMax = e; 414 } 415 416 pruneLtEq(long b)417 void pruneLtEq(long b) { 418 419 if (sumMin > b) 420 throw store.failException; 421 422 long min, max; 423 int i = 0; 424 // positive weights 425 for (; i < pos; i++) { 426 if (I[i] > b - sumMin) { 427 min = x[i].min() * a[i]; 428 max = min + I[i]; 429 if (pruneMax(x[i], divRoundDown(b - sumMin + min, a[i]))) { 430 long newMax = (long) x[i].max() * a[i]; 431 sumMax -= max - newMax; 432 I[i] = newMax - min; 433 } 434 } 435 } 436 // negative weights 437 for (; i < l; i++) { 438 if (I[i] > b - sumMin) { 439 min = x[i].max() * a[i]; 440 max = min + I[i]; 441 if (pruneMin(x[i], divRoundUp(-(b - sumMin + min), -a[i]))) { 442 long newMax = (long) x[i].min() * a[i]; 443 sumMax -= max - newMax; 444 I[i] = newMax - min; 445 } 446 } 447 } 448 } 449 pruneGtEq(long b)450 void pruneGtEq(long b) { 451 452 if (sumMax < b) 453 throw store.failException; 454 455 long min, max; 456 int i = 0; 457 // positive weights 458 for (; i < pos; i++) { 459 if (I[i] > -(b - sumMax)) { 460 max = x[i].max() * a[i]; 461 min = max - I[i]; 462 if (pruneMin(x[i], divRoundUp(b - sumMax + max, a[i]))) { 463 long nmin = (long) x[i].min() * a[i]; 464 sumMin += nmin - min; 465 I[i] = max - nmin; 466 } 467 } 468 } 469 // negative weights 470 for (; i < l; i++) { 471 if (I[i] > -(b - sumMax)) { 472 max = x[i].min() * a[i]; 473 min = max - I[i]; 474 if (pruneMax(x[i], divRoundDown(-(b - sumMax + max), -a[i]))) { 475 long newMin = (long) x[i].max() * a[i]; 476 sumMin += newMin - min; 477 I[i] = max - newMin; 478 } 479 } 480 } 481 } 482 pruneNeq()483 void pruneNeq() { 484 485 if (sumMin == sumMax && b == sumMin) 486 throw store.failException; 487 488 long min, max; 489 int i = 0; 490 // positive weights 491 for (; i < pos; i++) { 492 min = x[i].min() * a[i]; 493 max = min + I[i]; 494 495 if (pruneNe(x[i], b - sumMax + max, b - sumMin + min, a[i])) { 496 long newMin = (long) x[i].min() * a[i]; 497 long newMax = (long) x[i].max() * a[i]; 498 sumMin += newMin - min; 499 sumMax += newMax - max; 500 I[i] = newMax - newMin; 501 } 502 } 503 // negative weights 504 for (; i < l; i++) { 505 min = x[i].max() * a[i]; 506 max = min + I[i]; 507 508 if (pruneNe(x[i], b - sumMin + min, b - sumMax + max, a[i])) { 509 long newMin = (long) x[i].max() * a[i]; 510 long newMax = (long) x[i].min() * a[i]; 511 sumMin += newMin - min; 512 sumMax += newMax - max; 513 I[i] = newMax - newMin; 514 } 515 } 516 } 517 pruneMin(IntVar x, long min)518 private boolean pruneMin(IntVar x, long min) { 519 if (min > (long) x.min()) { 520 x.domain.inMin(store.level, x, long2int(min)); 521 return true; 522 } else 523 return false; 524 } 525 pruneMax(IntVar x, long max)526 private boolean pruneMax(IntVar x, long max) { 527 if (max < (long) x.max()) { 528 x.domain.inMax(store.level, x, long2int(max)); 529 return true; 530 } else 531 return false; 532 } 533 pruneNe(IntVar x, long min, long max, long a)534 private boolean pruneNe(IntVar x, long min, long max, long a) { 535 536 if (min == max && min % a == 0L) { 537 538 long d = min / a; 539 540 boolean boundsChanged = false; 541 542 if (d == x.min() || d == x.max()) 543 boundsChanged = true; 544 545 x.domain.inComplement(store.level, x, long2int(d)); 546 547 return boundsChanged; 548 } 549 return false; 550 } 551 satisfiedEq()552 public boolean satisfiedEq() { 553 554 long sMin = 0L, sMax = 0L; 555 int i = 0; 556 for (; i < pos; i++) { 557 sMin += (long) x[i].min() * a[i]; 558 sMax += (long) x[i].max() * a[i]; 559 } 560 for (; i < l; i++) { 561 sMin += (long) x[i].max() * a[i]; 562 sMax += (long) x[i].min() * a[i]; 563 } 564 565 return sMin == sMax && sMin == b; 566 } 567 satisfiedNeq()568 public boolean satisfiedNeq() { 569 570 long sMax = 0L, sMin = 0L; 571 int i = 0; 572 for (; i < pos; i++) { 573 sMin += (long) x[i].min() * a[i]; 574 sMax += (long) x[i].max() * a[i]; 575 } 576 for (; i < l; i++) { 577 sMin += (long) x[i].max() * a[i]; 578 sMax += (long) x[i].min() * a[i]; 579 } 580 581 return sMin > b || sMax < b; 582 } 583 satisfiedLtEq(long b)584 public boolean satisfiedLtEq(long b) { 585 586 long sMax = 0; 587 int i = 0; 588 for (; i < pos; i++) { 589 sMax += (long) x[i].max() * a[i]; 590 } 591 for (; i < l; i++) { 592 sMax += (long) x[i].min() * a[i]; 593 } 594 595 return sMax <= b; 596 } 597 satisfiedGtEq(long b)598 public boolean satisfiedGtEq(long b) { 599 600 long sMin = 0; 601 int i = 0; 602 for (; i < pos; i++) { 603 sMin += (long) x[i].min() * a[i]; 604 } 605 for (; i < l; i++) { 606 sMin += (long) x[i].max() * a[i]; 607 } 608 609 return sMin >= b; 610 } 611 612 satisfied()613 @Override public boolean satisfied() { 614 return entailed(relationType); 615 } 616 notSatisfied()617 @Override public boolean notSatisfied() { 618 return entailed(negRel[relationType]); 619 } 620 entailed(int rel)621 private boolean entailed(int rel) { 622 623 switch (rel) { 624 case eq: 625 return satisfiedEq(); 626 case le: 627 return satisfiedLtEq(b); 628 case lt: 629 return satisfiedLtEq(b - 1); 630 case ne: 631 return satisfiedNeq(); 632 case gt: 633 return satisfiedGtEq(b + 1); 634 case ge: 635 return satisfiedGtEq(b); 636 } 637 638 return false; 639 } 640 divRoundDown(long a, long b)641 private long divRoundDown(long a, long b) { 642 // return Math.floorDiv(a,b); 643 if (a >= 0) 644 return a / b; 645 else // a < 0 646 return (a - b + 1) / b; 647 } 648 divRoundUp(long a, long b)649 private long divRoundUp(long a, long b) { 650 // return -Math.floorDiv(-a,b); 651 if (a >= 0) 652 return (a + b - 1) / b; 653 else // a < 0 654 return a / b; 655 } 656 relation(String r)657 public byte relation(String r) { 658 if (r.equals("==")) 659 return eq; 660 else if (r.equals("=")) 661 return eq; 662 else if (r.equals("<")) 663 return lt; 664 else if (r.equals("<=")) 665 return le; 666 else if (r.equals("=<")) 667 return le; 668 else if (r.equals("!=")) 669 return ne; 670 else if (r.equals(">")) 671 return gt; 672 else if (r.equals(">=")) 673 return ge; 674 else if (r.equals("=>")) 675 return ge; 676 else { 677 System.err.println("Wrong relation symbol in LinearInt constraint " + r + "; assumed =="); 678 return eq; 679 } 680 } 681 rel2String()682 public String rel2String() { 683 switch (relationType) { 684 case eq: 685 return "=="; 686 case lt: 687 return "<"; 688 case le: 689 return "<="; 690 case ne: 691 return "!="; 692 case gt: 693 return ">"; 694 case ge: 695 return ">="; 696 } 697 698 return "?"; 699 } 700 checkForOverflow()701 void checkForOverflow() { 702 703 long sMin = 0, sMax = 0; 704 int i = 0; 705 for (; i < pos; i++) { 706 long n1 = Math.multiplyExact((long) x[i].min(), a[i]); 707 long n2 = Math.multiplyExact((long) x[i].max(), a[i]); 708 709 sMin = Math.addExact(sMin, n1); 710 sMax = Math.addExact(sMax, n2); 711 } 712 for (; i < l; i++) { 713 long n1 = Math.multiplyExact((long) x[i].max(), a[i]); 714 long n2 = Math.multiplyExact((long) x[i].min(), a[i]); 715 716 sMin = Math.addExact(sMin, n1); 717 sMax = Math.addExact(sMax, n2); 718 } 719 } 720 toString()721 @Override public String toString() { 722 723 StringBuffer result = new StringBuffer(id()); 724 result.append(" : LinearInt( [ "); 725 726 for (int i = 0; i < x.length; i++) { 727 result.append(x[i]); 728 if (i < x.length - 1) 729 result.append(", "); 730 } 731 result.append("], ["); 732 733 for (int i = 0; i < a.length; i++) { 734 result.append(a[i]); 735 if (i < a.length - 1) 736 result.append(", "); 737 } 738 739 result.append("], ").append(rel2String()).append(", ").append(b).append(" )"); 740 741 return result.toString(); 742 743 } 744 } 745