001package org.clafer.choco.constraint.propagator;
002
003import org.clafer.collection.Triple;
004import org.clafer.common.Util;
005import solver.constraints.Propagator;
006import solver.constraints.PropagatorPriority;
007import solver.exception.ContradictionException;
008import solver.variables.EventType;
009import solver.variables.IntVar;
010import solver.variables.SetVar;
011import solver.variables.Variable;
012import util.ESat;
013
014/**
015 * Sums a set and |set| &le n. This implementation <b>assumes</b> that the
016 * envelope and kernel are sorted, undefined behaviour otherwise.
017 *
018 * @author jimmy
019 */
020public class PropSetSum extends Propagator<Variable> {
021
022    private final SetVar set;
023    private final IntVar setCard;
024    private final IntVar sum;
025
026    public PropSetSum(SetVar set, IntVar setCard, IntVar sum) {
027        super(new Variable[]{set, setCard, sum}, PropagatorPriority.LINEAR, false);
028        this.set = set;
029        this.setCard = setCard;
030        this.sum = sum;
031    }
032
033    private boolean isSetVar(int idx) {
034        return idx == 0;
035    }
036
037    private boolean isSetCardVar(int idx) {
038        return idx == 1;
039    }
040
041    private boolean isSumVar(int idx) {
042        return idx == 2;
043    }
044
045    @Override
046    public int getPropagationConditions(int vIdx) {
047        if (isSetVar(vIdx)) {
048            return EventType.ADD_TO_KER.mask + EventType.REMOVE_FROM_ENVELOPE.mask;
049        }
050        assert isSumVar(vIdx) || isSetCardVar(vIdx);
051        return EventType.INSTANTIATE.mask + EventType.BOUND.mask;
052    }
053
054    private Triple<int[], int[], int[]> findKerSmallestLargest(int n) {
055        final int[] ker = PropUtil.iterateKer(set);
056        final int[] smallest = new int[n];
057        final int[] largest = new int[n];
058
059        int chooseSize = set.getEnvelopeSize() - ker.length;
060        int highStart = chooseSize - n;
061
062        int index = 0;
063        for (int i = set.getEnvelopeFirst(); i != SetVar.END; i = set.getEnvelopeNext()) {
064            if (!Util.in(i, ker)) {
065                if (index < smallest.length) {
066                    smallest[index] = i;
067                }
068                index++;
069                if (index > highStart) {
070                    largest[chooseSize - index] = i;
071                }
072            }
073        }
074        return new Triple<>(ker, smallest, largest);
075    }
076
077    @Override
078    public void propagate(int evtmask) throws ContradictionException {
079        boolean changed;
080        do {
081            changed = false;
082            final int envSize = set.getEnvelopeSize();
083            final int kerSize = set.getKernelSize();
084
085            setCard.updateLowerBound(kerSize, aCause);
086            setCard.updateUpperBound(envSize, aCause);
087
088            int lbEnd = setCard.getLB() - kerSize;
089            int ubEnd = setCard.getUB() - kerSize;
090
091            final Triple<int[], int[], int[]> triple = findKerSmallestLargest(ubEnd);
092            int[] ker = triple.getFst();
093            int[] smallest = triple.getSnd();
094            int[] largest = triple.getThd();
095
096            int kerSum = Util.sum(ker);
097            int low = kerSum;
098            int lowEnd;
099            int lowCandidate = 0; // Remove this value when adding another.
100            for (lowEnd = 0; lowEnd < ubEnd; lowEnd++) {
101                int val = smallest[lowEnd];
102                if (!(lowEnd < lbEnd || val < 0)) {
103                    // Still room left. Don't remove lowCandidate if it's negative.
104                    lowCandidate = Math.max(lowCandidate, 0);
105                    break;
106                }
107                lowCandidate = val;
108                low += val;
109            }
110            int high = kerSum;
111            int highStart;
112            int highCandidate = 0; // Remove this value when adding another.
113            for (highStart = 0; highStart < ubEnd; highStart++) {
114                int val = largest[highStart];
115                if (!(highStart < lbEnd || val > 0)) {
116                    // Still room left. Don't remove highCandidate if it's positive.
117                    highCandidate = Math.min(highCandidate, 0);
118                    break;
119                }
120                highCandidate = val;
121                high += val;
122            }
123            highStart = envSize - kerSize - highStart;
124
125            sum.updateLowerBound(low, aCause);
126            sum.updateUpperBound(high, aCause);
127
128            int lb = sum.getLB();
129            int ub = sum.getUB();
130
131            int index = 0;
132            for (int i = set.getEnvelopeFirst(); i != SetVar.END; i = set.getEnvelopeNext()) {
133                if (!Util.in(i, ker)) {
134                    if (index >= lowEnd) {
135                        int val = low + i - lowCandidate;
136                        // Adding i will never be under the upper bound.
137                        if (val > ub) {
138                            changed |= set.removeFromEnvelope(i, aCause);
139                        }
140                    }
141                    if (index < highStart) {
142                        int val = high + i - highCandidate;
143                        // Adding i will never be over the lower bound.
144                        if (val < lb) {
145                            changed |= set.removeFromEnvelope(i, aCause);
146                        }
147                    }
148                    index++;
149                }
150            }
151        } while (changed);
152    }
153
154    @Override
155    public void propagate(int idxVarInProp, int mask) throws ContradictionException {
156        forcePropagate(EventType.FULL_PROPAGATION);
157    }
158
159    @Override
160    public ESat isEntailed() {
161        int envSize = set.getEnvelopeSize();
162        int kerSize = set.getKernelSize();
163        if (kerSize > setCard.getUB()) {
164            return ESat.FALSE;
165        }
166        int[] ker = PropUtil.iterateKer(set);
167        int low = 0;
168        int high = 0;
169        // The number of elements seen in env but not in ker.
170        int index = 0;
171        int lowEnd = Math.max(setCard.getLB(), envSize) - kerSize;
172        int highStart = envSize - kerSize - lowEnd;
173        for (int i = set.getEnvelopeFirst(); i != SetVar.END; i = set.getEnvelopeNext()) {
174            if (Util.in(i, ker)) {
175                low += i;
176                high += i;
177            } else {
178                if (index < lowEnd && i < 0) {
179                    low += i;
180                }
181                if (index >= highStart && i > 0) {
182                    high += i;
183                }
184                index++;
185            }
186        }
187        if (sum.getLB() > high || sum.getUB() < low) {
188            return ESat.FALSE;
189        }
190        return low == high && sum.instantiated() ? ESat.TRUE : ESat.UNDEFINED;
191    }
192
193    @Override
194    public String toString() {
195        return "sum(" + set + ") = " + sum + " where " + setCard;
196    }
197}