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.Variable; 012import util.ESat; 013 014/** 015 * 016 * @author jimmy 017 */ 018public class PropArrayToSetCard extends Propagator<Variable> { 019 020 private final IntVar[] as; 021 private final IntVar sCard; 022 private final Integer globalCardinality; 023 024 public PropArrayToSetCard(IntVar[] as, IntVar sCard, Integer globalCardinality) { 025 super(buildArray(sCard, as), PropagatorPriority.LINEAR, false); 026 if (as.length == 0) { 027 throw new IllegalArgumentException(); 028 } 029 this.as = as; 030 this.sCard = sCard; 031 this.globalCardinality = globalCardinality; 032 } 033 034 private static Variable[] buildArray(IntVar sCard, IntVar[] as) { 035 Variable[] array = new Variable[as.length + 1]; 036 array[0] = sCard; 037 System.arraycopy(as, 0, array, 1, as.length); 038 return array; 039 } 040 041 private boolean isAVar(int idx) { 042 return idx >= 1; 043 } 044 045 private int getAVarIndex(int idx) { 046 assert isAVar(idx); 047 return idx - 1; 048 } 049 050 private boolean isSCardVar(int idx) { 051 return idx == 0; 052 } 053 054 public boolean hasGlobalCardinality() { 055 return globalCardinality != null; 056 } 057 058 public int getGlobalCardinality() { 059 assert hasGlobalCardinality(); 060 return globalCardinality.intValue(); 061 } 062 063 @Override 064 public int getPropagationConditions(int vIdx) { 065 if (isAVar(vIdx)) { 066 return EventType.INSTANTIATE.mask; 067 } 068 assert isSCardVar(vIdx); 069 return EventType.BOUND.mask + EventType.INSTANTIATE.mask; 070 } 071 072 private int countAdditionalSameRefsAllowed(TIntIntHashMap map) { 073 assert hasGlobalCardinality(); 074 int gc = getGlobalCardinality(); 075 076 int allowed = 0; 077 078 if (gc != 1) { 079 TIntIntIterator iter = map.iterator(); 080 for (int i = map.size(); i-- > 0;) { 081 iter.advance(); 082 assert iter.value() > 0; 083 assert iter.value() <= gc; 084 allowed += gc - iter.value(); 085 } 086 assert !iter.hasNext(); 087 } 088 089 return allowed; 090 } 091 092 private TIntIntHashMap countRefs() { 093 TIntIntHashMap map = new TIntIntHashMap(as.length); 094 for (IntVar a : as) { 095 if (a.instantiated()) { 096 map.adjustOrPutValue(a.getValue(), 1, 1); 097 } 098 } 099 return map; 100 } 101 102 private TIntIntHashMap constrainGlobalCardinality() throws ContradictionException { 103 assert hasGlobalCardinality(); 104 TIntIntHashMap map = new TIntIntHashMap(as.length); 105 for (int i = 0; i < as.length; i++) { 106 constrainGlobalCardinality(i, i, map); 107 } 108 assert map.size() == countRefs().size(); 109 return map; 110 } 111 112 private void constrainGlobalCardinality(int index, int explored, TIntIntHashMap map) throws ContradictionException { 113 assert hasGlobalCardinality(); 114 assert index <= explored; 115 assert explored < as.length; 116 117 IntVar a = as[index]; 118 if (a.instantiated()) { 119 int value = a.getValue(); 120 int count = map.adjustOrPutValue(value, 1, 1); 121 int gc = getGlobalCardinality(); 122 123 if (count == gc) { 124 for (int j = 0; j < explored; j++) { 125 IntVar b = as[j]; 126 if (!b.instantiatedTo(value) && b.removeValue(value, aCause)) { 127 constrainGlobalCardinality(j, explored, map); 128 } 129 } 130 for (int j = explored + 1; j < as.length; j++) { 131 as[j].removeValue(value, aCause); 132 } 133 } else if (count > gc) { 134 contradiction(a, "Above global cardinality"); 135 } 136 } 137 } 138 139 private static int divRoundUp(int a, int b) { 140 assert a >= 0; 141 assert b > 0; 142 143 return (a + b - 1) / b; 144 } 145 146 @Override 147 public void propagate(int evtmask) throws ContradictionException { 148 boolean changed; 149 do { 150 changed = false; 151 TIntIntHashMap map = hasGlobalCardinality() ? constrainGlobalCardinality() : countRefs(); 152 int instCard = map.size(); 153 int uninstantiated = 0; 154 for (IntVar a : as) { 155 if (!a.instantiated()) { 156 uninstantiated++; 157 } 158 } 159 int minCard = Math.max(1, instCard 160 + (hasGlobalCardinality() 161 ? divRoundUp(Math.max(0, uninstantiated - countAdditionalSameRefsAllowed(map)), getGlobalCardinality()) 162 : 0)); 163 int maxCard = instCard + uninstantiated; 164 165 sCard.updateLowerBound(minCard, aCause); 166 sCard.updateUpperBound(maxCard, aCause); 167 168 if (uninstantiated != 0) { 169 if (instCard == sCard.getUB()) { 170 // The rest must be duplicates. 171 for (IntVar a : as) { 172 assert !a.instantiated() || map.contains(a.getValue()); 173 if (!a.instantiated()) { 174 changed |= PropUtil.domSubsetSet(a, map.keySet(), aCause) && a.instantiated(); 175 } 176 } 177 } 178 if (maxCard == sCard.getLB()) { 179 // No more duplicate values. 180 for (IntVar a : as) { 181 if (!a.instantiated()) { 182 TIntIntIterator iter = map.iterator(); 183 for (int i = map.size(); i-- > 0;) { 184 iter.advance(); 185 changed |= a.removeValue(iter.key(), aCause) && a.instantiated(); 186 } 187 assert !iter.hasNext(); 188 } 189 } 190 } 191 } 192 } while (changed); 193 } 194 195 @Override 196 public void propagate(int idxVarInProp, int mask) throws ContradictionException { 197 forcePropagate(EventType.FULL_PROPAGATION); 198 } 199 200 @Override 201 public ESat isEntailed() { 202 TIntIntHashMap map = new TIntIntHashMap(); 203 int gc = hasGlobalCardinality() ? getGlobalCardinality() : Integer.MAX_VALUE; 204 int uninstantiated = 0; 205 for (IntVar a : as) { 206 if (a.instantiated()) { 207 if (map.adjustOrPutValue(a.getValue(), 1, 1) > gc) { 208 return ESat.FALSE; 209 } 210 } else { 211 uninstantiated++; 212 } 213 } 214 215 int instCard = map.size(); 216 int minCard = instCard 217 + (hasGlobalCardinality() 218 ? divRoundUp(Math.max(0, uninstantiated - countAdditionalSameRefsAllowed(map)), getGlobalCardinality()) 219 : 0); 220 int maxCard = instCard + uninstantiated; 221 222 if (sCard.getUB() < minCard) { 223 return ESat.FALSE; 224 } 225 if (sCard.getLB() > maxCard) { 226 return ESat.FALSE; 227 } 228 229 return uninstantiated == 0 ? ESat.TRUE : ESat.UNDEFINED; 230 } 231 232 @Override 233 public String toString() { 234 return "arrayToSetCard(" + Arrays.toString(as) + ", " + sCard + ", " + globalCardinality + ")"; 235 } 236}