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