001package org.clafer.choco.constraint.propagator; 002 003import java.util.Arrays; 004import solver.constraints.Propagator; 005import solver.constraints.PropagatorPriority; 006import solver.exception.ContradictionException; 007import solver.variables.EventType; 008import solver.variables.IntVar; 009import util.ESat; 010 011/** 012 * strings[i] < strings[j] iff ints[i] <= ints[j] strings[i] = strings[j] 013 * iff ints[i] = ints[j] 014 * 015 * 0 < ints[i] < |{ints}| 016 * 017 * @author jimmy 018 */ 019public class PropLexChainChannel extends Propagator<IntVar> { 020 021 private final IntVar[][] strings; 022 private final IntVar[] ints; 023 024 public PropLexChainChannel(IntVar[][] strings, IntVar[] ints) { 025 super(buildArray(strings, ints), PropagatorPriority.CUBIC, false); 026 this.strings = strings; 027 this.ints = ints; 028 } 029 030 private static IntVar[] buildArray(IntVar[][] strings, IntVar[] ints) { 031 if (strings.length != ints.length) { 032 throw new IllegalArgumentException(); 033 } 034 IntVar[] array = new IntVar[strings.length * strings[0].length + ints.length]; 035 System.arraycopy(ints, 0, array, 0, ints.length); 036 int i = ints.length; 037 for (IntVar[] string : strings) { 038 System.arraycopy(string, 0, array, i, string.length); 039 i += string.length; 040 } 041 assert i == array.length; 042 return array; 043 } 044 045 @Override 046 public int getPropagationConditions(int vIdx) { 047 return EventType.BOUND.mask + EventType.INSTANTIATE.mask; 048 } 049 050 private static Ordering compareString(IntVar[] a, IntVar[] b) { 051 return compareString(a, b, 0); 052 } 053 054 private static Ordering compareString(IntVar[] a, IntVar[] b, int index) { 055 if (index == a.length) { 056 return a.length == b.length ? Ordering.EQ : Ordering.LT; 057 } 058 if (index == b.length) { 059 assert a.length != b.length; 060 return Ordering.GT; 061 } 062 Ordering ord = compare(a[index], b[index]); 063 switch (ord) { 064 case EQ: 065 return compareString(a, b, index + 1); 066 case LE: 067 switch (compareString(a, b, index + 1)) { 068 case LT: 069 return Ordering.LT; 070 case LE: 071 case EQ: 072 return Ordering.LE; 073 default: 074 return Ordering.UNKNOWN; 075 } 076 case GE: 077 switch (compareString(a, b, index + 1)) { 078 case GT: 079 return Ordering.GT; 080 case GE: 081 case EQ: 082 return Ordering.GE; 083 default: 084 return Ordering.UNKNOWN; 085 } 086 default: 087 return ord; 088 } 089 } 090 091 private static Ordering compare(IntVar a, IntVar b) { 092 int aLb = a.getLB(); 093 int aUb = a.getUB(); 094 int bLb = b.getLB(); 095 int bUb = b.getUB(); 096 if (aLb == bUb && aUb == bLb) { 097 return Ordering.EQ; 098 } 099 if (aLb > bUb) { 100 return Ordering.GT; 101 } 102 if (aLb >= bUb) { 103 return Ordering.GE; 104 } 105 if (aUb < bLb) { 106 return Ordering.LT; 107 } 108 if (aUb <= bLb) { 109 return Ordering.LE; 110 } 111 return Ordering.UNKNOWN; 112 } 113 114 private boolean equalString(IntVar[] a, IntVar[] b) throws ContradictionException { 115 boolean changed = false; 116 for (int i = 0; i < a.length; i++) { 117 changed |= equal(a[i], b[i]); 118 } 119 return changed; 120 } 121 122 private boolean equal(IntVar a, IntVar b) throws ContradictionException { 123 // Don't use short-circuit or 124 return a.updateLowerBound(b.getLB(), aCause) 125 | a.updateUpperBound(b.getUB(), aCause) 126 | b.updateLowerBound(a.getLB(), aCause) 127 | b.updateUpperBound(a.getUB(), aCause); 128 } 129 130 private boolean lessThanString(IntVar[] a, IntVar[] b) throws ContradictionException { 131 return lessThanString(a, b, 0); 132 } 133 134 private boolean lessThanString(IntVar[] a, IntVar[] b, int index) throws ContradictionException { 135 if (index == a.length) { 136 if (index == b.length) { 137 contradiction(a[0], "a = b"); 138 } 139 return false; 140 } 141 if (index == b.length) { 142 assert a.length != b.length; 143 contradiction(a[index], "a > b"); 144 } 145 switch (compare(a[index], b[index])) { 146 case EQ: 147 return lessThanString(a, b, index + 1); 148 case LT: 149 return false; 150 case GT: 151 contradiction(a[index], "a > b"); 152 return true; 153 case LE: 154 case GE: 155 case UNKNOWN: 156 switch (compareString(a, b, index + 1)) { 157 case EQ: 158 case GT: 159 case GE: 160 return lessThan(a[index], b[index]); 161 case LT: 162 case LE: 163 case UNKNOWN: 164 return lessThanEqual(a[index], b[index]); 165 default: 166 throw new IllegalStateException(); 167 } 168 default: 169 throw new IllegalStateException(); 170 } 171 } 172 173 private boolean lessThan(IntVar a, IntVar b) throws ContradictionException { 174 // Don't use short-circuit or 175 return a.updateUpperBound(b.getUB() - 1, aCause) 176 | b.updateLowerBound(a.getLB() + 1, aCause); 177 } 178 179 private boolean lessThanEqualString(IntVar[] a, IntVar[] b) throws ContradictionException { 180 return lessThanEqualString(a, b, 0); 181 } 182 183 private boolean lessThanEqualString(IntVar[] a, IntVar[] b, int index) throws ContradictionException { 184 if (index == a.length) { 185 return false; 186 } 187 if (index == b.length) { 188 assert a.length != b.length; 189 contradiction(a[index], "a > b"); 190 } 191 switch (compare(a[index], b[index])) { 192 case EQ: 193 return lessThanEqualString(a, b, index + 1); 194 case LT: 195 return false; 196 case GT: 197 contradiction(a[index], "a > b"); 198 return true; 199 case LE: 200 case GE: 201 case UNKNOWN: 202 switch (compareString(a, b, index + 1)) { 203 case EQ: 204 case LT: 205 case LE: 206 case GE: 207 case UNKNOWN: 208 return lessThanEqual(a[index], b[index]); 209 case GT: 210 return lessThan(a[index], b[index]); 211 default: 212 throw new IllegalStateException(); 213 } 214 default: 215 throw new IllegalStateException(); 216 } 217 } 218 219 private boolean lessThanEqual(IntVar a, IntVar b) throws ContradictionException { 220 // Don't use short-circuit or 221 return a.updateUpperBound(b.getUB(), aCause) 222 | b.updateLowerBound(a.getLB(), aCause); 223 } 224 225 private boolean propagateSmallest(boolean[] notSmallest, boolean notSmaller[], boolean[] lessThanEqual, int smallest) throws ContradictionException { 226 boolean changed = false; 227 boolean stop = true; 228 boolean lessThanEqualStop = false; 229 boolean[] notNextSmallest = new boolean[strings.length]; 230 for (int i = 0; i < notSmallest.length; i++) { 231 if (!notSmallest[i]) { 232 notSmaller[i] = false; 233 stop = false; 234 lessThanEqualStop |= lessThanEqual[i]; 235 changed |= ints[i].instantiateTo(smallest, aCause); 236 } 237 notNextSmallest[i] = !notSmaller[i]; 238 } 239 if (stop || lessThanEqualStop) { 240 return changed; 241 } 242 for (int i = 0; i < strings.length; i++) { 243 if (notSmaller[i]) { 244 for (int j = i + 1; j < strings.length; j++) { 245 if (notSmaller[j]) { 246 Ordering ord = compareString(strings[i], strings[j]); 247 switch (ord) { 248 case LE: 249 lessThanEqual[i] = true; 250 // fallthrough 251 case LT: 252 notNextSmallest[j] = true; 253 break; 254 case GE: 255 lessThanEqual[j] = true; 256 // fallthrough 257 case GT: 258 notNextSmallest[i] = true; 259 break; 260 case UNKNOWN: 261 notNextSmallest[i] = true; 262 notNextSmallest[j] = true; 263 break; 264 } 265 } 266 } 267 } 268 } 269 changed |= propagateSmallest(notNextSmallest, notSmaller, lessThanEqual, smallest + 1); 270 return changed; 271 } 272 273 // Idempotent. 274 private boolean propagateStrings() throws ContradictionException { 275 int eqs = 0; 276 boolean[] notSmallest = new boolean[strings.length]; 277 boolean[] lessThanEqual = new boolean[strings.length]; 278 boolean changed = false; 279 for (int i = 0; i < strings.length; i++) { 280 boolean equivalenceClass = false; 281 for (int j = i + 1; j < strings.length; j++) { 282 Ordering ord = compareString(strings[i], strings[j]); 283 switch (ord) { 284 case EQ: 285 changed |= equal(ints[i], ints[j]); 286 equivalenceClass = true; 287 break; 288 case LE: 289 changed |= lessThanEqual(ints[i], ints[j]); 290 lessThanEqual[i] = true; 291 notSmallest[j] = true; 292 break; 293 case LT: 294 changed |= lessThan(ints[i], ints[j]); 295 notSmallest[j] = true; 296 break; 297 case GE: 298 changed |= lessThanEqual(ints[j], ints[i]); 299 lessThanEqual[j] = true; 300 notSmallest[i] = true; 301 break; 302 case GT: 303 changed |= lessThan(ints[j], ints[i]); 304 notSmallest[i] = true; 305 break; 306 case UNKNOWN: 307 notSmallest[i] = true; 308 notSmallest[j] = true; 309 break; 310 } 311 } 312 if (equivalenceClass) { 313 eqs++; 314 } 315 } 316 changed |= propagateSmallest(notSmallest, notSmallest, lessThanEqual, 0); 317 for (int i = 0; i < ints.length; i++) { 318 changed |= ints[i].updateUpperBound(ints.length - 1 - eqs, aCause); 319 } 320 return changed; 321 } 322 323 // Idempotent. 324 private boolean propagateInts() throws ContradictionException { 325 boolean changed = false; 326 boolean repeat; 327 do { 328 repeat = false; 329 for (int i = 0; i < ints.length; i++) { 330 for (int j = i + 1; j < ints.length; j++) { 331 switch (compare(ints[i], ints[j])) { 332 case EQ: 333 repeat |= equalString(strings[i], strings[j]); 334 break; 335 case LT: 336 repeat |= lessThanString(strings[i], strings[j]); 337 break; 338 case LE: 339 repeat |= lessThanEqualString(strings[i], strings[j]); 340 break; 341 case GT: 342 repeat |= lessThanString(strings[j], strings[i]); 343 break; 344 case GE: 345 repeat |= lessThanEqualString(strings[j], strings[i]); 346 break; 347 } 348 } 349 } 350 changed |= repeat; 351 } while (repeat); 352 return changed; 353 } 354 355 @Override 356 public void propagate(int evtmask) throws ContradictionException { 357 propagateStrings(); 358 while (propagateInts() && propagateStrings()); 359 } 360 361 @Override 362 public void propagate(int idxVarInProp, int mask) throws ContradictionException { 363 forcePropagate(EventType.FULL_PROPAGATION); 364 } 365 366 @Override 367 public ESat isEntailed() { 368 boolean[] values = new boolean[ints.length]; 369 for (int i = 0; i < strings.length; i++) { 370 for (int j = i + 1; j < strings.length; j++) { 371 Ordering intOrdering = compare(ints[i], ints[j]); 372 Ordering ord = compareString(strings[i], strings[j]); 373 if (intOrdering.contradicts(ord)) { 374 return ESat.FALSE; 375 } 376 } 377 if (ints[i].instantiated()) { 378 int value = ints[i].getValue(); 379 if (value < 0 || value >= values.length) { 380 return ESat.FALSE; 381 } 382 values[value] = true; 383 } 384 } 385 if (isCompletelyInstantiated()) { 386 int i = 0; 387 while (i < values.length && values[i]) { 388 i++; 389 } 390 for (; i < values.length; i++) { 391 if (values[i]) { 392 return ESat.FALSE; 393 } 394 } 395 return ESat.TRUE; 396 } 397 return ESat.UNDEFINED; 398 } 399 400 @Override 401 public String toString() { 402 return "lexChainChannel(" + Arrays.deepToString(strings) + ", " + Arrays.toString(ints) + ")"; 403 } 404 405 private static enum Ordering { 406 407 EQ, 408 LT, 409 LE, 410 GT, 411 GE, 412 UNKNOWN; 413 414 boolean contradicts(Ordering ord) { 415 switch (this) { 416 case EQ: 417 return LT.equals(ord) || GT.equals(ord); 418 case LT: 419 return EQ.equals(ord) || GE.equals(ord) || GT.equals(ord); 420 case LE: 421 return GT.equals(ord); 422 case GT: 423 return EQ.equals(ord) || LE.equals(ord) || LT.equals(ord); 424 case GE: 425 return LT.equals(ord); 426 case UNKNOWN: 427 return false; 428 default: 429 throw new IllegalStateException(); 430 } 431 } 432 } 433}