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