1 /*
2  * ElementVariable.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 
32 package org.jacop.constraints;
33 
34 import org.jacop.api.SatisfiedPresent;
35 import org.jacop.api.Stateful;
36 import org.jacop.api.UsesQueueVariable;
37 import org.jacop.core.*;
38 
39 import java.util.*;
40 import java.util.concurrent.atomic.AtomicInteger;
41 import java.util.stream.Stream;
42 
43 /**
44  * ElementVariable constraint defines a relation
45  * list[index - indexOffset] = value.
46  * <p>
47  * The first element of the list corresponds to index - indexOffset = 1.
48  * By default indexOffset is equal 0 so first value within a list corresponds to index equal 1.
49  * <p>
50  * If index has a domain from 0 to list.length-1 then indexOffset has to be equal -1 to
51  * make addressing of list array starting from 1.
52  *
53  * @author Krzysztof Kuchcinski and Radoslaw Szymanek
54  * @version 4.8
55  */
56 
57 public class ElementVariable extends Constraint implements UsesQueueVariable, Stateful, SatisfiedPresent {
58 
59     static AtomicInteger idNumber = new AtomicInteger(0);
60 
61     boolean firstConsistencyCheck = true;
62     int firstConsistencyLevel;
63 
64     /**
65      * It specifies variable index within an element constraint list[index - indexOffset] = value.
66      */
67     public IntVar index;
68 
69     /**
70      * It specifies variable value within an element constraint list[index - indexOffset] = value.
71      */
72     public IntVar value;
73 
74     /**
75      * It specifies indexOffset within an element constraint list[index - indexOffset] = value.
76      */
77     public final int indexOffset;
78 
79     /**
80      * It specifies list of variables within an element constraint list[index - indexOffset] = value.
81      * The list is addressed by positive integers ({@code >=1}) if indexOffset is equal to 0.
82      */
83     public IntVar list[];
84 
85     boolean indexHasChanged = false;
86 
87     IntDomain indexRange;
88 
89     LinkedHashSet<IntVar> variableQueue = new LinkedHashSet<IntVar>();
90 
91     Map<IntVar, Integer> mapping = Var.createEmptyPositioning();
92 
93     Map<IntVar, List<Integer>> duplicates = Var.createEmptyPositioning();
94 
95     /**
96      * It constructs an element constraint.
97      *
98      * @param index       variable index
99      * @param list        list of variables from which an index-th element is taken
100      * @param value       a value of the index-th element from list
101      * @param indexOffset shift applied to index variable.
102      */
ElementVariable(IntVar index, IntVar[] list, IntVar value, int indexOffset)103     public ElementVariable(IntVar index, IntVar[] list, IntVar value, int indexOffset) {
104 
105         checkInputForNullness(new String[] {"index", "value"}, new Object[] {index, value});
106         checkInputForNullness("list", list);
107 
108         queueIndex = 2;
109 
110         this.indexOffset = indexOffset;
111         this.numberId = idNumber.incrementAndGet();
112         this.index = index;
113         this.value = value;
114         this.list = Arrays.copyOf(list, list.length);
115         this.indexRange = new IntervalDomain(1 + this.indexOffset, list.length + this.indexOffset);
116 
117         setScope(Stream.concat(Stream.of(index), Stream.concat(Arrays.stream(list), Stream.of(value))));
118 
119     }
120 
121     /**
122      * It constructs an element constraint.
123      *
124      * @param index variable index
125      * @param list  list of variables from which an index-th element is taken
126      * @param value a value of the index-th element from list
127      */
ElementVariable(IntVar index, List<? extends IntVar> list, IntVar value)128     public ElementVariable(IntVar index, List<? extends IntVar> list, IntVar value) {
129         this(index, list.toArray(new IntVar[list.size()]), value, 0);
130     }
131 
132     /**
133      * It constructs an element constraint.
134      *
135      * @param index       variable index
136      * @param list        list of variables from which an index-th element is taken
137      * @param value       a value of the index-th element from list
138      * @param indexOffset shift applied to index variable.
139      */
ElementVariable(IntVar index, List<? extends IntVar> list, IntVar value, int indexOffset)140     public ElementVariable(IntVar index, List<? extends IntVar> list, IntVar value, int indexOffset) {
141         this(index, list.toArray(new IntVar[list.size()]), value, indexOffset);
142     }
143 
144     /**
145      * It constructs an element constraint.
146      *
147      * @param index variable index
148      * @param list  list of variables from which an index-th element is taken
149      * @param value a value of the index-th element from list
150      */
ElementVariable(IntVar index, IntVar[] list, IntVar value)151     public ElementVariable(IntVar index, IntVar[] list, IntVar value) {
152         this(index, list, value, 0);
153     }
154 
removeLevel(int level)155     @Override public void removeLevel(int level) {
156         if (level == firstConsistencyLevel)
157             firstConsistencyCheck = true;
158         indexHasChanged = false;
159         valueHasChanged = false;
160         variableQueue.clear();
161     }
162 
163     // For each variable from the list it specifies the values it supports
164     IntDomain[] supports;
165 
166     private boolean valueHasChanged;
167 
168     Random generator = new Random(2);
169 
consistency(Store store)170     @Override public void consistency(Store store) {
171 
172         if (index.singleton()) {
173             // index is singleton.
174 
175             int position = index.value() - 1 - indexOffset;
176             value.domain.in(store.level, value, list[position].domain);
177             list[position].domain.in(store.level, list[position], value.domain);
178 
179         } else {
180             // index is not singleton.
181 
182 
183             if (firstConsistencyCheck) {
184 
185                 index.domain.in(store.level, index, indexRange);
186 
187                 IntDomain valDomain = new IntervalDomain();
188                 for (ValueEnumeration e = index.domain.valueEnumeration(); e.hasMoreElements(); ) {
189                     int position = e.nextElement() - 1 - indexOffset;
190                     valDomain.addDom(list[position].domain);
191                 }
192                 value.domain.in(store.level, value, valDomain);
193 
194                 firstConsistencyCheck = false;
195                 firstConsistencyLevel = store.level;
196                 valueHasChanged = true;
197                 indexHasChanged = true;
198                 for (IntVar var : list)
199                     variableQueue.add(var);
200 
201                 supports = new IntDomain[list.length];
202                 IntDomain temp = value.domain.cloneLight();
203                 for (int i = list.length - 1; i >= 0; i--) {
204                     if (!temp.isEmpty()) {
205                         supports[i] = temp.intersect(list[i].domain);
206                         if (!supports[i].isEmpty())
207                             temp = temp.subtract(supports[i]);
208                     } else
209                         supports[i] = new IntervalDomain();
210                 }
211             }
212 
213             // IntDomain valDomain = new IntervalDomain();
214             int valMin = IntDomain.MaxInt, valMax = IntDomain.MinInt;
215             for (ValueEnumeration e = index.domain.valueEnumeration(); e.hasMoreElements(); ) {
216                 int position = e.nextElement() - 1 - indexOffset;
217                 // valDomain.addDom(list[position].domain);
218                 int min = list[position].domain.min();
219                 int max = list[position].domain.max();
220                 valMin = (valMin > min) ? min : valMin;
221                 valMax = (valMax < max) ? max : valMax;
222             }
223             value.domain.in(store.level, value, valMin, valMax);
224             // value.domain.in(store.level, value, valDomain);
225 
226             // Consequtive execution of the consistency function.
227 
228             if (valueHasChanged) {
229 
230                 for (ValueEnumeration e = index.domain.valueEnumeration(); e.hasMoreElements(); ) {
231 
232                     int position = e.nextElement() - 1 - indexOffset;
233 
234                     if (!list[position].domain.isIntersecting(value.domain)) {
235 
236                         index.domain.inComplement(store.level, index, position + 1 + indexOffset);
237 
238                         list[position].removeConstraint(this);
239 
240                     }
241                 }
242             }
243 
244             if (indexHasChanged) {
245 
246                 IntDomain nextValueDomain = new IntervalDomain();
247                 int checkTrigger = value.getSize() - 1;
248                 boolean propagation = true;
249                 for (ValueEnumeration e = index.domain.valueEnumeration(); e.hasMoreElements(); ) {
250                     nextValueDomain.unionAdapt(list[e.nextElement() - 1 - indexOffset].dom());
251                     if (nextValueDomain.getSize() > checkTrigger) {
252                         if (nextValueDomain.contains(value.domain)) {
253                             propagation = false;
254                             break;
255                         } else
256                             checkTrigger = nextValueDomain.getSize();
257                     }
258                 }
259 
260                 if (propagation)
261                     value.domain.in(store.level, value, nextValueDomain);
262 
263             }
264 
265             if (!variableQueue.isEmpty()) {
266 
267                 Iterator<IntVar> itr = variableQueue.iterator();
268 
269                 // TODO, what if one variable occurs multiple times in list? Only one
270                 // occurence in the list can be active, the other ones have to be ignored.
271 
272                 while (itr.hasNext()) {
273 
274                     IntVar changedVar = itr.next();
275                     int position = mapping.get(changedVar);
276 
277                     // reason about possible changes to value variable.
278                     if (!supports[position].isEmpty()) {
279                         // changed variable supports some values in Value variable.
280                         IntDomain lostSupports = supports[position].subtract(changedVar.domain);
281                         lostSupports.intersectAdapt(value.domain);
282                         if (!lostSupports.isEmpty()) {
283                             for (ValueEnumeration enumer = lostSupports.valueEnumeration(); enumer.hasMoreElements(); ) {
284                                 int lostSupport = enumer.nextElement();
285                                 int endingPosition = generator.nextInt(list.length - 1);
286                                 int nextSupportPosition = -1;
287                                 for (int i = endingPosition + 1; ; ) {
288                                     if (i == list.length)
289                                         i = 0;
290                                     if (list[i].domain.contains(lostSupport)) {
291                                         nextSupportPosition = i;
292                                         break;
293                                     }
294                                     if (i == endingPosition)
295                                         break;
296                                     i++;
297                                 }
298                                 if (nextSupportPosition != -1) {
299                                     supports[nextSupportPosition].unionAdapt(lostSupport);
300                                     supports[position].subtractAdapt(lostSupport);
301                                 } else {
302                                     value.domain.inComplement(store.level, value, lostSupport);
303                                 }
304 
305                             }
306                         }
307                     }
308 
309                     // reason about possible changes to index variable.
310                     if (!changedVar.domain.isIntersecting(value.domain)) {
311 
312                         index.domain.inComplement(store.level, index, position + 1 + indexOffset);
313                         list[position].removeConstraint(this);
314 
315                         List<Integer> array = duplicates.get(changedVar);
316                         if (array != null)
317                             for (int additionalPosition : array)
318                                 index.domain.inComplement(store.level, index, additionalPosition + 1 + indexOffset);
319 
320                     }
321                 }
322 
323             }
324 
325             if (indexHasChanged && index.singleton()) {
326                 // index is singleton.
327 
328                 int position = index.value() - 1 - indexOffset;
329                 value.domain.in(store.level, value, list[position].domain);
330                 list[position].domain.in(store.level, list[position], value.domain);
331             }
332 
333             indexHasChanged = false;
334             valueHasChanged = false;
335             variableQueue.clear();
336 
337         }
338     }
339 
getDefaultConsistencyPruningEvent()340     @Override public int getDefaultConsistencyPruningEvent() {
341         return IntDomain.ANY;
342     }
343 
impose(Store store)344     @Override public void impose(Store store) {
345 
346         super.impose(store);
347 
348         for (int i = 0; i < list.length; i++) {
349             Integer oldInteger = mapping.put(list[i], i);
350             if (oldInteger != null) {
351                 List<Integer> array = duplicates.get(list[i]);
352                 if (array != null)
353                     array.add(i);
354                 else {
355                     array = new ArrayList<Integer>();
356                     array.add(i);
357                     duplicates.put(list[i], array);
358                 }
359             }
360         }
361 
362     }
363 
queueVariable(int level, Var var)364     @Override public void queueVariable(int level, Var var) {
365 
366         if (var == index) {
367             indexHasChanged = true;
368             return;
369         }
370 
371         if (var == value) {
372             valueHasChanged = true;
373             return;
374         }
375 
376         variableQueue.add((IntVar) var);
377 
378     }
379 
satisfied()380     @Override public boolean satisfied() {
381         boolean sat = value.singleton();
382         if (sat) {
383             int v = value.min();
384             ValueEnumeration e = index.domain.valueEnumeration();
385             while (sat && e.hasMoreElements()) {
386                 IntVar fdv = list[e.nextElement() - 1 - indexOffset];
387                 sat = fdv.singleton() && fdv.min() == v;
388             }
389         }
390         return sat;
391     }
392 
toString()393     @Override public String toString() {
394 
395         StringBuffer result = new StringBuffer(id());
396 
397         result.append(" : elementVariable").append("( ").append(index).append(", [");
398 
399         for (int i = 0; i < list.length; i++) {
400             result.append(list[i]);
401 
402             if (i < list.length - 1)
403                 result.append(", ");
404         }
405 
406         result.append("], ").append(value).append(" )");
407 
408         return result.toString();
409 
410     }
411 
412 }
413