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}