1 /*
2  * Among.java
3  * This file is part of JaCoP.
4  * <p>
5  * JaCoP is a Java Constraint Programming solver.
6  * <p>
7  * Copyright (C) 2008 Polina Maakeva 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.api.SatisfiedPresent;
34 import org.jacop.api.Stateful;
35 import org.jacop.api.UsesQueueVariable;
36 import org.jacop.core.*;
37 
38 import java.util.*;
39 import java.util.concurrent.atomic.AtomicInteger;
40 import java.util.stream.Stream;
41 
42 /**
43  * Among constraint in its simplest form. It establishes the following
44  * relation. The given number N of X`s take values from the supplied set
45  * of values Kset.
46  * <p>
47  * This constraint implements a simple and polynomial algorithm to establish
48  * GAC as presented in different research papers. There are number of
49  * improvements (iterative execution, optimization of computational load upon
50  * backtracking) to improve the constraint further.
51  *
52  * @author Polina Makeeva and Radoslaw Szymanek
53  * @version 4.8
54  */
55 
56 public class Among extends Constraint implements UsesQueueVariable, Stateful, SatisfiedPresent {
57 
58     private static final boolean debugAll = false;
59 
60     final static AtomicInteger idNumber = new AtomicInteger(0);
61 
62     /**
63      * It specifies the list of variables whose values are checked.
64      */
65     final public IntVar[] list;
66 
67     /**
68      * It specifies a set of values which if assigned to a variable from a list makes variable counted.
69      */
70     final public IntervalDomain kSet;
71 
72     /**
73      * It is a idNumber variable.
74      */
75     final public IntVar n;
76 
77     // number if x that belongs to K (Kset)
78     // As search progress this time stamp can only increase
79     // because if X was in between lbS and ubS than
80     // it can have the between (x intersects S <> empty and x doesn't belong to
81     // S) values being shrinked.
82     private TimeStamp<Integer> lowerBorder;
83 
84     // number of x who may still intersect K (Kset)
85     private TimeStamp<Integer> upperBorder;
86 
87     LinkedHashSet<IntVar> variableQueue = new LinkedHashSet<IntVar>();
88 
89     private Map<IntVar, Integer> position;
90 
91     /**
92      * It constructs an Among constraint.
93      *
94      * @param list variables which are compared to Kset
95      * @param kSet set of integer values against which we check if variables are equal to.
96      * @param n    number of possible variables equal to a value from Kset.
97      */
Among(IntVar[] list, IntervalDomain kSet, IntVar n)98     public Among(IntVar[] list, IntervalDomain kSet, IntVar n) {
99 
100         checkInputForNullness(new String[] {"list", "kSet", "n"}, new Object[][] {list, {kSet}, {n}});
101         checkInputForDuplication("list", list);
102 
103         this.queueIndex = 1;
104         numberId = idNumber.incrementAndGet();
105         this.list = Arrays.copyOf(list, list.length);
106         this.kSet = kSet.clone();
107         this.n = n;
108 
109         setScope(Stream.concat(Arrays.stream(list), Stream.of(n)));
110 
111     }
112 
113     /**
114      * It constructs an Among constraint.
115      *
116      * @param list variables which are compared to Kset
117      * @param kSet set of integer values against which we check if variables are equal to.
118      * @param n    number of possible variables equal to a value from Kset.
119      */
Among(List<IntVar> list, IntervalDomain kSet, IntVar n)120     public Among(List<IntVar> list, IntervalDomain kSet, IntVar n) {
121         this(list.toArray(new IntVar[list.size()]), kSet, n);
122     }
123 
removeLevel(int level)124     @Override public void removeLevel(int level) {
125         variableQueue.clear();
126     }
127 
consistency(Store store)128     @Override public void consistency(Store store) {
129         // ----------------------------------------------------------
130         if (debugAll) {
131             System.out.println("LEVEL : " + store.level);
132             System.out.println(this);
133         }
134         // ----------------------------------------------------------
135 
136         int currentLB = lowerBorder.value();
137         // Refer to the algorithm where ubS = n - |{ x | dom(x) intersect S =
138         // empty set } |
139         int currentUB = upperBorder.value();
140 
141         // For the variable that signaled the change of domain
142         // Count those that entered lbS, or ubS
143         for (IntVar var : variableQueue) {
144 
145             int posVar = position.get(var);
146 
147             if (posVar < currentLB || posVar > currentUB)
148                 continue;
149 
150             if (kSet.contains(var.domain)) {
151 
152                 assert posVar >= currentLB : "Variable " + var + " counted for lowerbound multiple times";
153 
154                 if (posVar != currentLB) {
155                     list[posVar] = list[currentLB];
156                     list[currentLB] = var;
157                     position.put(var, currentLB);
158                     position.put(list[posVar], posVar);
159                 }
160                 currentLB++;
161 
162                 // If variable entered lb then it would stay there
163                 // and we can detach the constrain from it
164                 var.removeConstraint(this);
165 
166             }
167             if (!kSet.isIntersecting(var.domain)) {
168 
169                 assert posVar <= currentUB : "Variable " + var + " counted for upperbound multiple times";
170 
171                 if (posVar != currentUB) {
172                     list[posVar] = list[currentUB - 1];
173                     list[currentUB - 1] = var;
174                     position.put(var, currentUB - 1);
175                     position.put(list[posVar], posVar);
176                 }
177                 currentUB--;
178 
179                 // If the variable entered not ub then it will stay there
180                 // and we can detach the constrain from it
181                 var.removeConstraint(this);
182 
183             }
184         }
185 
186         variableQueue.clear();
187 
188         // ----------------------------------------------------------
189         if (debugAll) {
190             System.out.println("lbS = " + currentLB);
191             System.out.println("ubS = " + currentUB);
192             System.out.println(" domain of N " + n.domain + " is in [ " + currentLB + ", " + currentUB + " ]");
193         }
194         // ----------------------------------------------------------
195 
196         // Changed KK, 2015-10-17;
197         // Not needed, in method will fail in such case
198         // if (Math.max(n.min(), currentLB) > Math.min(n.max(), currentUB))
199         // 	throw Store.failException;
200         if (currentLB > currentUB)
201             throw Store.failException;
202 
203         // n.domain.in(store.level, n, Math.max(n.min(), currentLB), Math.min(n.max(),
204         // 		currentUB));
205 
206         // Changed KK, 2015-10-17;
207         // Math.max is not needed since method in is doing
208         // intersection between new domain and original domain
209         n.domain.in(store.level, n, currentLB, currentUB);
210 
211         // Just in case LB or UB have changed.
212         upperBorder.update(currentUB);
213         lowerBorder.update(currentLB);
214 
215         if (currentLB == n.min() && n.domain.singleton()) {
216             // If the number of X that belong to S is equal to N.value than we
217             // have to subtract
218             // the K set from the rest of x that do not belong to S
219             for (int i = currentLB; i < currentUB; i++) {
220                 IntVar var = list[i];
221                 if (!kSet.contains(var.domain)) {
222                     if (debugAll) {
223                         System.out.println("lb >> The value before in of " + var.id + ": " + var.domain);
224                         System.out.println("lb >> subtrack " + kSet);
225                         System.out.println("lb >> equals " + var.domain.subtract(kSet));
226                     }
227                     var.domain.in(store.level, var, var.domain.subtract(kSet));
228                     var.removeConstraint(this);
229                     if (debugAll)
230                         System.out.println("lb >> The value after in of " + var.id + ": " + var.domain);
231                 }
232             }
233 
234             // since the constraint is satisfied UB is equal to LB.
235             upperBorder.update(currentLB);
236 
237             // The constrain became satisfied
238             if (debugAll)
239                 System.out.println("Simple Among is satisfied");
240         }
241 
242         if (currentUB == n.min() && n.domain.singleton()) {
243             // If the number intersecting X is equal to desired number N than we
244             // have
245             // to intersect the domains of X with K set.
246             for (int i = currentLB; i < currentUB; i++) {
247                 IntVar var = list[i];
248                 var.domain.in(store.level, var, kSet);
249                 var.removeConstraint(this);
250             }
251 
252             // since the constraint is satisfied LB is equal to UB.
253             lowerBorder.update(currentUB);
254 
255             // The constrain became satisfied
256             if (debugAll)
257                 System.out.println("Simple Among is satisfied");
258         }
259 
260         if (debugAll)
261             System.out.println(this);
262     }
263 
getDefaultConsistencyPruningEvent()264     @Override public int getDefaultConsistencyPruningEvent() {
265         return IntDomain.ANY;
266     }
267 
impose(Store store)268     @Override public void impose(Store store) {
269 
270         super.impose(store);
271 
272         this.lowerBorder = new TimeStamp<>(store, 0);
273         this.upperBorder = new TimeStamp<>(store, list.length);
274 
275         position = Var.positionMapping(list, false, this.getClass());
276 
277 
278     }
279 
queueVariable(int level, Var var)280     @Override public void queueVariable(int level, Var var) {
281         if (debugAll)
282             System.out.println("Var " + var + ((IntVar) var).recentDomainPruning());
283 
284         if (var != n)
285             variableQueue.add((IntVar) var);
286     }
287 
satisfied()288     @Override public boolean satisfied() {
289         return (Objects.equals(lowerBorder.value(), upperBorder.value()) && n.min() == lowerBorder.value() && n.singleton());
290     }
291 
toString()292     @Override public String toString() {
293 
294         StringBuilder result = new StringBuilder(id());
295 
296         result.append(": Among([");
297 
298         for (IntVar var : this.list)
299             result.append(var).append(" ");
300 
301         result.append("], ").append(this.kSet).append(", ");
302         result.append(n).append(")\n");
303 
304         return result.toString();
305     }
306 
307 }
308