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