1 /*
2  * Subcircuit.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.*;
34 import org.jacop.util.SophisticatedLengauerTarjan;
35 
36 import java.util.*;
37 import java.util.concurrent.atomic.AtomicInteger;
38 
39 /**
40  * Subcircuit constraint assures that all variables build a
41  * subcircuit. Value of every variable x[i] points to the next variable in
42  * the subcircuit. If a variable does not belong to a subcircuit it has value of
43  * its position, i.e., x[i] = i.
44  *
45  * @author Krzysztof Kuchcinski and Radoslaw Szymanek
46  * @version 4.8
47  */
48 
49 public class Subcircuit extends Alldiff {
50 
51     static AtomicInteger idNumber = new AtomicInteger(0);
52 
53     Store store;
54 
55     boolean firstConsistencyCheck = true;
56 
57     boolean useSCC = true, useDominance = true;
58 
59     int idd = 0;
60 
61     int sccLength = 0;
62 
63     int[] val;
64 
65     Hashtable<Var, Integer> valueIndex = new Hashtable<Var, Integer>();
66 
67     int firstConsistencyLevel;
68 
69     SophisticatedLengauerTarjan graphDominance;
70 
71 
72     /**
73      * It constructs a circuit constraint.
74      *
75      * @param list variables which must form a circuit.
76      */
Subcircuit(IntVar[] list)77     public Subcircuit(IntVar[] list) {
78 
79         checkInputForNullness("list", list);
80         checkInputForDuplication("list", list);
81 
82         this.numberId = idNumber.incrementAndGet();
83         this.list = Arrays.copyOf(list, list.length);
84         this.graphDominance = new SophisticatedLengauerTarjan(list.length + 1);
85 
86         this.queueIndex = 2;
87 
88         int i = 0;
89         for (Var v : list)
90             valueIndex.put(v, i++);
91 
92         val = new int[list.length];
93 
94         stack = new int[list.length];
95         stack_pointer = 0;
96 
97         String scc = System.getProperty("sub_circuit_scc_pruning");
98         String dominance = System.getProperty("sub_circuit_dominance_pruning");
99         if (scc != null)
100             useSCC = Boolean.parseBoolean(scc);
101         if (dominance != null)
102             useDominance = Boolean.parseBoolean(dominance);
103         if (useSCC == false && useDominance == false)
104             throw new java.lang.IllegalArgumentException("Wrong property configuration for Subcircuit");
105 
106         setScope(list);
107     }
108 
109     /**
110      * It constructs a circuit constraint.
111      *
112      * @param list variables which must form a circuit.
113      */
Subcircuit(List<IntVar> list)114     public Subcircuit(List<IntVar> list) {
115         this(list.toArray(new IntVar[list.size()]));
116     }
117 
118     int sccCounter = 0;
119 
consistency(Store store)120     @Override public void consistency(Store store) {
121 
122         if (firstConsistencyCheck) {
123             for (int i = 0; i < list.length; i++)
124                 list[i].domain.in(store.level, list[i], 1, list.length);
125 
126             firstConsistencyCheck = false;
127             firstConsistencyLevel = store.level;
128 
129         }
130 
131         do {
132 
133             store.propagationHasOccurred = false;
134 
135             LinkedHashSet<IntVar> fdvs = variableQueue;
136             variableQueue = new LinkedHashSet<IntVar>();
137 
138             alldifferent(store, fdvs);
139 
140         } while (store.propagationHasOccurred);
141 
142         if (useSCC) {
143             sccsBasedPruning(store); // strongly connected components
144 
145             if (store.propagationHasOccurred)
146                 sccCounter = 0;
147 
148             // if 10 consecutive applications of SCC based pruning did
149             // not give any pruning try domianance based pruning
150             if (useDominance && sccCounter++ > 10) {
151                 sccCounter = 0;
152                 dominanceFilter(); // filter based on dominance of nodes
153             }
154         } else if (useDominance)
155             dominanceFilter(); // filter based on dominance of nodes
156 
157         if (store.propagationHasOccurred)
158             store.addChanged(this);
159 
160     }
161 
alldifferent(Store store, LinkedHashSet<IntVar> fdvs)162     void alldifferent(Store store, LinkedHashSet<IntVar> fdvs) {
163 
164         for (IntVar changedVar : fdvs) {
165             if (changedVar.singleton()) {
166                 for (IntVar var : list)
167                     if (var != changedVar)
168                         var.domain.inComplement(store.level, var, changedVar.min());
169             }
170         }
171 
172     }
173 
getConsistencyPruningEvent(Var var)174     @Override public int getConsistencyPruningEvent(Var var) {
175 
176         // If consistency function mode
177         if (consistencyPruningEvents != null) {
178             Integer possibleEvent = consistencyPruningEvents.get(var);
179             if (possibleEvent != null)
180                 return possibleEvent;
181         }
182         return IntDomain.ANY;
183     }
184 
needsListPruning()185     boolean needsListPruning() {
186 
187         for (IntVar el : list) {
188             if (!(el.min() >= 1 && el.max() <= list.length))
189                 return true;
190         }
191         return false;
192     }
193 
194     // registers the constraint in the constraint store
impose(Store store)195     @Override public void impose(Store store) {
196 
197         this.store = store;
198 
199         super.impose(store);
200 
201         if (!needsListPruning())
202             firstConsistencyCheck = false;
203     }
204 
205 
satisfied()206     @Override public boolean satisfied() {
207 
208         if (grounded.value() != list.length)
209             return false;
210 
211         boolean sat = super.satisfied(); // alldifferent
212 
213         if (sat) {
214             // check if there are subcricuits that together cover all nodes
215             sat = sccs(store) == list.length;
216         }
217         return sat;
218     }
219 
toString()220     @Override public String toString() {
221 
222         StringBuffer result = new StringBuffer(id());
223         result.append(" : subcircuit([");
224 
225         for (int i = 0; i < list.length; i++) {
226             result.append(list[i]);
227             if (i < list.length - 1)
228                 result.append(", ");
229         }
230         result.append("])");
231 
232         return result.toString();
233     }
234 
235     // --- Strongly Connected Conmponents
236 
237     // Uses Trajan's algorithm to find strongly connected components
238     // Based on the algorithm from the book
239     // Robert Sedgewick, Algorithms, 1988, p. 482.
240 
241     int[] stack;  // stack for strongly connected compoents algorithm
242     int stack_pointer;
243 
244     BitSet cycleVar;
245 
sccsBasedPruning(Store store)246     private void sccsBasedPruning(Store store) {
247 
248         java.util.Arrays.fill(val, 0);
249 
250         idd = 0;
251         BitSet realCycle = null;
252 
253         for (int i = 0; i < list.length; i++) {
254 
255             sccLength = 0;
256 
257             if (val[i] == 0) {
258 
259                 visit(i);
260 
261                 if (sccLength == 1)
262                     // the scc is of size one => it must be self-cycle
263                     list[i].domain.in(store.level, list[i], i + 1, i + 1);
264                 // check if more than 1 sub-cycle possible
265                 for (int cv = cycleVar.nextSetBit(0); cv >= 0; cv = cycleVar.nextSetBit(cv + 1)) {
266                     if (!list[cv].domain.contains(cv + 1))
267                         if (realCycle != null) // second sub-cycle under creation -> wrong!
268                             throw store.failException;
269                         else {
270                             realCycle = cycleVar;
271                             break;
272                         }
273                 }
274             }
275         }
276 
277         if (realCycle != null && realCycle.cardinality() < list.length) {
278             // possible cycle found, the rest must be self-loop
279             for (int j = realCycle.nextClearBit(0); j < list.length; j = realCycle.nextClearBit(j + 1))
280                 list[j].domain.in(store.level, list[j], j + 1, j + 1);
281         }
282     }
283 
284 
sccs(Store store)285     private int sccs(Store store) {
286 
287         int totalNodes = 0;
288 
289         java.util.Arrays.fill(val, 0);
290 
291         idd = 0;
292 
293         for (int i = 0; i < list.length; i++) {
294 
295             sccLength = 0;
296 
297             if (val[i] == 0) {
298 
299                 visit(i);
300 
301                 totalNodes += sccLength;
302 
303             }
304         }
305 
306         return totalNodes;
307     }
308 
visit(int k)309     private int visit(int k) {
310 
311         idd++;
312         val[k] = idd;
313         int min = idd;
314 
315         // stack push
316         stack[stack_pointer++] = k;
317 
318         for (ValueEnumeration e = list[k].dom().valueEnumeration(); e.hasMoreElements(); ) {
319 
320             int t = e.nextElement() - 1;
321 
322             int m;
323             if (val[t] == 0)
324                 m = visit(t);
325             else
326                 m = val[t];
327             if (m < min)
328                 min = m;
329         }
330 
331         if (min == val[k]) {
332 
333             cycleVar = new BitSet(list.length);
334             sccLength = 0;
335 
336             int n;
337             do {
338                 // stack pop
339                 n = stack[--stack_pointer];
340                 cycleVar.set(n);
341 
342                 val[n] = list.length + 1;
343 
344                 sccLength++;
345             } while (n != k);
346         }
347 
348         return min;
349     }
350 
351     Random random = new Random(0);
352 
dominanceFilter()353     private void dominanceFilter() {
354         int n = list.length;
355 
356         // find possible roots
357         int[] possibleRoots = new int[n];
358         int pr = 0;
359         for (int v = 0; v < n; v++) {
360             if (!list[v].dom().contains(v + 1)) {
361                 possibleRoots[pr++] = v;
362                 // break;  // find only first root
363             }
364         }
365 
366         if (pr > 0) {
367             if (!graphDominance(possibleRoots[random.nextInt(pr)]))
368                 reversedGraphDominance(possibleRoots[random.nextInt(pr)]);
369         }
370     }
371 
graphDominance(int root)372     private boolean graphDominance(int root) {
373 
374         int n = list.length;
375         boolean pruning = false;
376 
377         graphDominance.init();
378 
379         // create graph
380         for (int v = 0; v < n; v++) {
381             for (ValueEnumeration e = list[v].dom().valueEnumeration(); e.hasMoreElements(); ) {
382                 int w = e.nextElement() - 1;
383                 if (v == root || v == w)
384                     graphDominance.addArc(n, w);
385                 else
386                     graphDominance.addArc(v, w);
387             }
388         }
389 
390         if (graphDominance.dominators(n)) {
391             for (int v = 0; v < n; v++) {
392                 if (v != root)
393                     for (ValueEnumeration e = list[v].domain.valueEnumeration(); e.hasMoreElements(); ) {
394                         int w = e.nextElement() - 1;
395                         if (v != w && graphDominance.dominatedBy(v, w)) {
396                             pruning = true;
397                             // no back to dominator
398                             list[v].domain.inComplement(store.level, list[v], w + 1);
399                             // no back loop for dominator
400                             list[w].domain.inComplement(store.level, list[w], w + 1);
401                         }
402                     }
403             }
404         } else  // root does not reach all nodes -> FAIL
405             throw store.failException;
406 
407         return pruning;
408     }
409 
reversedGraphDominance(int root)410     private boolean reversedGraphDominance(int root) {
411 
412         int n = list.length;
413         boolean pruning = false;
414 
415         graphDominance.init();
416 
417         // create graph
418         // int root = possibleRoots[random.nextInt(pr)];
419         for (int v = 0; v < n; v++) {
420             for (ValueEnumeration e = list[v].dom().valueEnumeration(); e.hasMoreElements(); ) {
421                 int w = e.nextElement() - 1;
422                 if (w == root || v == w)
423                     graphDominance.addArc(n, v);
424                 else
425                     graphDominance.addArc(w, v);
426             }
427         }
428 
429         if (graphDominance.dominators(n)) {
430             for (int v = 0; v < n; v++) {
431                 if (v != root)
432                     for (ValueEnumeration e = list[v].domain.valueEnumeration(); e.hasMoreElements(); ) {
433                         int w = e.nextElement() - 1;
434                         if (v != w && w != root && graphDominance.dominatedBy(w, v)) {
435                             pruning = true;
436                             // no back loop to dominator
437                             list[v].domain.inComplement(store.level, list[v], w + 1);
438                             // no self loop
439                             list[v].domain.inComplement(store.level, list[v], v + 1);
440                         }
441                     }
442             }
443         } else  // root does not reach all nodes -> FAIL
444             throw store.failException;
445 
446         return pruning;
447     }
448 }
449