001package org.clafer.choco.constraint.propagator; 002 003import gnu.trove.iterator.TIntIntIterator; 004import gnu.trove.map.hash.TIntIntHashMap; 005import java.util.Arrays; 006import solver.constraints.Propagator; 007import solver.constraints.PropagatorPriority; 008import solver.exception.ContradictionException; 009import solver.variables.EventType; 010import solver.variables.IntVar; 011import solver.variables.SetVar; 012import solver.variables.Variable; 013import util.ESat; 014 015/** 016 * 017 * @author jimmy 018 */ 019public class PropJoinFunctionCard extends Propagator<Variable> { 020 021 private final SetVar take; 022 private final IntVar takeCard; 023 private final IntVar[] refs; 024 private final IntVar toCard; 025 private final Integer globalCardinality; 026 027 public PropJoinFunctionCard(SetVar take, IntVar takeCard, IntVar[] refs, IntVar toCard, Integer globalCardinality) { 028 super(buildArray(take, takeCard, toCard, refs), PropagatorPriority.LINEAR, false); 029 this.take = take; 030 this.takeCard = takeCard; 031 this.refs = refs; 032 this.toCard = toCard; 033 this.globalCardinality = globalCardinality; 034 } 035 036 private static Variable[] buildArray(SetVar take, IntVar takeCard, IntVar toCard, IntVar[] refs) { 037 Variable[] array = new Variable[refs.length + 3]; 038 array[0] = take; 039 array[1] = takeCard; 040 array[2] = toCard; 041 System.arraycopy(refs, 0, array, 3, refs.length); 042 return array; 043 } 044 045 private boolean isTakeVar(int idx) { 046 return idx == 0; 047 } 048 049 private boolean isTakeCardVar(int idx) { 050 return idx == 1; 051 } 052 053 private boolean isToCardVar(int idx) { 054 return idx == 2; 055 } 056 057 private boolean isRefVar(int idx) { 058 return idx >= 3; 059 } 060 061 private int getRefVarIndex(int idx) { 062 assert isRefVar(idx); 063 return idx - 3; 064 } 065 066 public boolean hasGlobalCardinality() { 067 return globalCardinality != null; 068 } 069 070 public int getGlobalCardinality() { 071 assert hasGlobalCardinality(); 072 return globalCardinality.intValue(); 073 } 074 075 @Override 076 public boolean advise(int idxVarInProp, int mask) { 077 if (isRefVar(idxVarInProp)) { 078 return take.envelopeContains(getRefVarIndex(idxVarInProp)); 079 } 080 return super.advise(idxVarInProp, mask); 081 } 082 083 @Override 084 public int getPropagationConditions(int vIdx) { 085 if (isTakeVar(vIdx)) { 086 return EventType.ADD_TO_KER.mask + EventType.REMOVE_FROM_ENVELOPE.mask; 087 } 088 if (isRefVar(vIdx)) { 089 return EventType.INSTANTIATE.mask; 090 } 091 assert isTakeCardVar(vIdx) || isToCardVar(vIdx); 092 return EventType.BOUND.mask + EventType.INSTANTIATE.mask; 093 } 094 095 private int countAdditionalSameRefsAllowed(TIntIntHashMap map) { 096 assert hasGlobalCardinality(); 097 int gc = getGlobalCardinality(); 098 099 int allowed = 0; 100 101 if (gc != 1) { 102 TIntIntIterator iter = map.iterator(); 103 for (int i = map.size(); i-- > 0;) { 104 iter.advance(); 105 assert iter.value() > 0; 106 assert iter.value() <= gc; 107 allowed += gc - iter.value(); 108 } 109 assert !iter.hasNext(); 110 } 111 112 return allowed; 113 } 114 115 private TIntIntHashMap countRefs() { 116 TIntIntHashMap map = new TIntIntHashMap(take.getKernelSize()); 117 for (int i = take.getKernelFirst(); i != SetVar.END; i = take.getKernelNext()) { 118 IntVar ref = refs[i]; 119 if (ref.instantiated()) { 120 map.adjustOrPutValue(ref.getValue(), 1, 1); 121 } 122 } 123 return map; 124 } 125 126 private TIntIntHashMap constrainGlobalCardinality() throws ContradictionException { 127 assert hasGlobalCardinality(); 128 int[] ker = PropUtil.iterateKer(take); 129 TIntIntHashMap map = new TIntIntHashMap(ker.length); 130 for (int i = 0; i < ker.length; i++) { 131 constrainGlobalCardinality(ker, i, i, map); 132 } 133 return map; 134 } 135 136 private void constrainGlobalCardinality(int[] ker, int index, int explored, TIntIntHashMap map) throws ContradictionException { 137 assert hasGlobalCardinality(); 138 assert index <= explored; 139 assert explored < ker.length; 140 141 IntVar a = refs[ker[index]]; 142 if (a.instantiated()) { 143 int value = a.getValue(); 144 int count = map.adjustOrPutValue(value, 1, 1); 145 int gc = getGlobalCardinality(); 146 147 if (count == gc) { 148 for (int j = 0; j < explored; j++) { 149 IntVar b = refs[ker[j]]; 150 if (!b.instantiatedTo(value) && b.removeValue(value, aCause)) { 151 constrainGlobalCardinality(ker, j, explored, map); 152 } 153 } 154 for (int j = explored + 1; j < ker.length; j++) { 155 refs[ker[j]].removeValue(value, aCause); 156 } 157 } else if (count > gc) { 158 contradiction(a, "Above global cardinality"); 159 } 160 } 161 } 162 163 private static int divRoundUp(int a, int b) { 164 assert a >= 0; 165 assert b > 0; 166 167 return (a + b - 1) / b; 168 } 169 170 @Override 171 public void propagate(int evtmask) throws ContradictionException { 172 takeCard.updateLowerBound(take.getKernelSize(), aCause); 173 takeCard.updateUpperBound(take.getEnvelopeSize(), aCause); 174 boolean changed; 175 do { 176 changed = false; 177 TIntIntHashMap map = hasGlobalCardinality() ? constrainGlobalCardinality() : countRefs(); 178 int instCard = map.size(); 179 int kerSize = take.getKernelSize(); 180 181 int minUninstantiated; 182 int maxUninstantiated; 183 int minCard; 184 int maxCard; 185 boolean cardChanged; 186 do { 187 cardChanged = false; 188// minUninstantiated = Math.max(0, takeCard.getLB() - kerSize); 189// maxUninstantiated = Math.max(0, takeCard.getUB() - kerSize); 190 int kerUninstantiated = 0; 191 for (int i = take.getEnvelopeFirst(); i != SetVar.END; i = take.getEnvelopeNext()) { 192 if (!refs[i].instantiated()) { 193 if (take.kernelContains(i)) { 194 kerUninstantiated++; 195 } 196 } 197 } 198 assert takeCard.getLB() >= kerSize; 199 minUninstantiated = takeCard.getLB() - kerSize + kerUninstantiated; 200 maxUninstantiated = takeCard.getUB() - kerSize + kerUninstantiated; 201 minCard = instCard 202 + (hasGlobalCardinality() 203 ? divRoundUp(Math.max(0, minUninstantiated - countAdditionalSameRefsAllowed(map)), getGlobalCardinality()) 204 : 0); 205 maxCard = instCard + maxUninstantiated; 206 207 toCard.updateLowerBound(minCard, aCause); 208 toCard.updateUpperBound(maxCard, aCause); 209 210 if (hasGlobalCardinality()) { 211 cardChanged |= takeCard.updateUpperBound(toCard.getUB() * getGlobalCardinality(), aCause); 212 } 213 cardChanged |= takeCard.updateLowerBound(toCard.getLB(), aCause); 214 } while (cardChanged); 215 216 217 if (maxUninstantiated != 0) { 218 if (instCard == toCard.getUB()) { 219 // The rest must be duplicates. 220 for (int i = take.getKernelFirst(); i != SetVar.END; i = take.getKernelNext()) { 221 IntVar ref = refs[i]; 222 assert !ref.instantiated() || map.contains(ref.getValue()); 223 if (!ref.instantiated()) { 224 changed |= PropUtil.domSubsetSet(ref, map.keySet(), aCause) && ref.instantiated(); 225 } 226 } 227 } 228 if (maxCard == toCard.getLB()) { 229 // No more duplicate values. 230 for (int i = take.getKernelFirst(); i != SetVar.END; i = take.getKernelNext()) { 231 IntVar ref = refs[i]; 232 if (!ref.instantiated()) { 233 TIntIntIterator iter = map.iterator(); 234 for (int j = map.size(); j-- > 0;) { 235 iter.advance(); 236 changed |= ref.removeValue(iter.key(), aCause) && ref.instantiated(); 237 } 238 assert !iter.hasNext(); 239 } 240 } 241 } 242 } 243 } while (changed); 244 } 245 246 @Override 247 public void propagate(int idxVarInProp, int mask) throws ContradictionException { 248 forcePropagate(EventType.FULL_PROPAGATION); 249 } 250 251 @Override 252 public ESat isEntailed() { 253 TIntIntHashMap map = new TIntIntHashMap(); 254 int gc = hasGlobalCardinality() ? getGlobalCardinality() : Integer.MAX_VALUE; 255 for (int i = take.getKernelFirst(); i != SetVar.END; i = take.getKernelNext()) { 256 IntVar ref = refs[i]; 257 if (ref.instantiated()) { 258 if (map.adjustOrPutValue(ref.getValue(), 1, 1) > gc) { 259 return ESat.FALSE; 260 } 261 } 262 } 263 264 boolean completelyInstantiated = take.instantiated() && takeCard.instantiated() && toCard.instantiated(); 265 int instCard = map.size(); 266 int minUninstantiated = Math.max(0, takeCard.getLB() - take.getKernelSize()); 267 int maxUninstantiated = Math.max(0, takeCard.getUB() - take.getKernelSize()); 268 for (int i = take.getEnvelopeFirst(); i != SetVar.END; i = take.getEnvelopeNext()) { 269 if (i >= 0 && i < refs.length && !refs[i].instantiated()) { 270 completelyInstantiated = false; 271 if (take.kernelContains(i)) { 272 minUninstantiated++; 273 } 274 maxUninstantiated++; 275 } 276 } 277 int minCard = instCard 278 + (hasGlobalCardinality() 279 ? divRoundUp(Math.max(0, minUninstantiated - countAdditionalSameRefsAllowed(map)), getGlobalCardinality()) 280 : 0); 281 int maxCard = instCard + maxUninstantiated; 282 283 if (toCard.getUB() < minCard) { 284 return ESat.FALSE; 285 } 286 if (toCard.getLB() > maxCard) { 287 return ESat.FALSE; 288 } 289 290 return completelyInstantiated ? ESat.TRUE : ESat.UNDEFINED; 291 } 292 293 @Override 294 public String toString() { 295 return "joinFunctionCard(" + take + ", " + takeCard + ", " + Arrays.toString(refs) + ", " + toCard + ", " + globalCardinality + ")"; 296 } 297}