001package org.clafer.choco.constraint.propagator;
002
003import gnu.trove.iterator.TIntIterator;
004import gnu.trove.list.array.TIntArrayList;
005import java.util.Arrays;
006import memory.structure.IndexedBipartiteSet;
007import org.clafer.collection.MutableBoolean;
008import solver.constraints.Propagator;
009import solver.constraints.PropagatorPriority;
010import solver.exception.ContradictionException;
011import solver.variables.EventType;
012import solver.variables.IntVar;
013import solver.variables.SetVar;
014import solver.variables.Variable;
015import solver.variables.delta.IIntDeltaMonitor;
016import solver.variables.delta.monitor.SetDeltaMonitor;
017import util.ESat;
018import util.procedure.IntProcedure;
019
020/**
021 * <p>
022 * Join a unary set relation with a binary function. This propagator is a
023 * specialized version of {@link PropJoinRelation}. The {@code take} variable is
024 * the unary relation and the {@code ref} variables are the function. The
025 * {@code to} variable is the result of the join.
026 * </p>
027 * <p>
028 * Here is how the binary function is encoded. Consider the function:
029 * {@code (0, 1), (1, 3), (2, 1)}. This is encoded as 3 different {@code ref}
030 * variables: {@code ref0={1}, ref1={3}, ref2={1}}.
031 * </p>
032 *
033 * @author jimmy
034 */
035public class PropJoinFunction extends Propagator<Variable> {
036
037    private final SetVar take;
038    private final SetDeltaMonitor takeD;
039    private final IndexedBipartiteSet dontCare;
040    private final IntVar[] refs;
041    private final IIntDeltaMonitor[] refsD;
042    private final SetVar to;
043    private final SetDeltaMonitor toD;
044
045    public PropJoinFunction(SetVar take, IntVar[] refs, SetVar to) {
046        super(buildArray(take, to, refs), PropagatorPriority.QUADRATIC, true);
047        this.take = take;
048        this.takeD = take.monitorDelta(aCause);
049        this.dontCare = new IndexedBipartiteSet(take.getSolver().getEnvironment(), PropUtil.iterateEnv(take));
050        this.refs = refs;
051        this.refsD = PropUtil.monitorDeltas(refs, aCause);
052        this.to = to;
053        this.toD = to.monitorDelta(aCause);
054    }
055
056    private static Variable[] buildArray(SetVar take, SetVar to, IntVar[] refs) {
057        Variable[] array = new Variable[refs.length + 2];
058        array[0] = take;
059        array[1] = to;
060        System.arraycopy(refs, 0, array, 2, refs.length);
061        return array;
062    }
063
064    private boolean isTakeVar(int idx) {
065        return idx == 0;
066    }
067
068    private boolean isToVar(int idx) {
069        return idx == 1;
070    }
071
072    private boolean isRefVar(int idx) {
073        return idx >= 2;
074    }
075
076    private int getRefVarIndex(int idx) {
077        assert isRefVar(idx);
078        return idx - 2;
079    }
080
081    @Override
082    public boolean advise(int idxVarInProp, int mask) {
083        if (isRefVar(idxVarInProp)) {
084            return dontCare.contains(getRefVarIndex(idxVarInProp));
085        }
086        return super.advise(idxVarInProp, mask);
087    }
088
089    @Override
090    public int getPropagationConditions(int vIdx) {
091        if (isTakeVar(vIdx)) {
092            return EventType.ADD_TO_KER.mask + EventType.REMOVE_FROM_ENVELOPE.mask;
093        }
094        if (isToVar(vIdx)) {
095            return EventType.ADD_TO_KER.mask + EventType.REMOVE_FROM_ENVELOPE.mask;
096        }
097        assert isRefVar(vIdx);
098        return EventType.INT_ALL_MASK();
099    }
100
101    private boolean findMate(int toEnv) throws ContradictionException {
102        boolean inKer = to.kernelContains(toEnv);
103        int mate = -1;
104        for (int j = take.getEnvelopeFirst(); j != SetVar.END; j = take.getEnvelopeNext()) {
105            if (refs[j].contains(toEnv)) {
106                // Found a second mate.
107                if (mate != -1 || !inKer) {
108                    mate = -2;
109                    break;
110                }
111                mate = j;
112            }
113        }
114        if (mate == -1) {
115            // No mates.
116            to.removeFromEnvelope(toEnv, aCause);
117        } else if (mate != -2 && inKer) {
118            // One mate.
119            take.addToKernel(mate, aCause);
120            return refs[mate].instantiateTo(toEnv, aCause);
121        }
122        return false;
123    }
124
125    private void findMates() throws ContradictionException {
126        for (int i = to.getEnvelopeFirst(); i != SetVar.END; i = to.getEnvelopeNext()) {
127            if (findMate(i)) {
128                findMates();
129                return;
130            }
131        }
132    }
133
134    @Override
135    public void propagate(int evtmask) throws ContradictionException {
136        // Prune take
137        for (int i = take.getEnvelopeFirst(); i != SetVar.END; i = take.getEnvelopeNext()) {
138            if (i < 0 || i >= refs.length || !PropUtil.isDomIntersectEnv(refs[i], to)) {
139                take.removeFromEnvelope(i, aCause);
140                dontCare.remove(i);
141            }
142        }
143
144        // Pick to and prune refs
145        for (int i = take.getKernelFirst(); i != SetVar.END; i = take.getKernelNext()) {
146            PropUtil.domSubsetEnv(refs[i], to, aCause);
147            if (refs[i].instantiated()) {
148                int value = refs[i].getValue();
149                to.addToKernel(value, aCause);
150            }
151        }
152
153        // Prune to
154        findMates();
155    }
156
157    @Override
158    public void propagate(int idxVarInProp, int mask) throws ContradictionException {
159        if (isTakeVar(idxVarInProp)) {
160            takeD.freeze();
161            takeD.forEach(pruneToOnTakeEnv, EventType.REMOVE_FROM_ENVELOPE);
162            takeD.forEach(pickToAndPruneChildOnTakeKer, EventType.ADD_TO_KER);
163            takeD.unfreeze();
164        } else if (isToVar(idxVarInProp)) {
165            toD.freeze();
166            toD.forEach(pruneRefOnToEnv, EventType.REMOVE_FROM_ENVELOPE);
167            toD.forEach(pickTakeOnToKer, EventType.ADD_TO_KER);
168            toD.unfreeze();
169            if ((EventType.REMOVE_FROM_ENVELOPE.mask & mask) != 0) {
170                TIntArrayList removed = null;
171                for (int i = take.getEnvelopeFirst(); i != SetVar.END; i = take.getEnvelopeNext()) {
172                    if (!PropUtil.isDomIntersectEnv(refs[i], to)) {
173                        take.removeFromEnvelope(i, aCause);
174                        dontCare.remove(i);
175                        // Cannot call findMate here because we inside iterating take env.
176                        // Queue up the even to do later.
177                        if (removed == null) {
178                            removed = new TIntArrayList(1);
179                        }
180                        removed.add(i);
181                    }
182                }
183                if (removed != null) {
184                    TIntIterator it = removed.iterator();
185                    while (it.hasNext()) {
186                        IntVar ref = refs[it.next()];
187                        int ub = ref.getUB();
188                        for (int i = ref.getLB(); i <= ub; i = ref.nextValue(i)) {
189                            if (to.envelopeContains(i)) {
190                                if (findMate(i)) {
191                                    findMates();
192                                }
193                            }
194                        }
195                    }
196                }
197            }
198        } else {
199            assert isRefVar(idxVarInProp);
200            final int id = getRefVarIndex(idxVarInProp);
201            final IIntDeltaMonitor refD = refsD[id];
202            final MutableBoolean bool = new MutableBoolean();
203            refD.freeze();
204            IntProcedure pruneToOnRefRem = new IntProcedure() {
205                @Override
206                public void execute(int refRem) throws ContradictionException {
207                    if (bool.isClear()) {
208                        if (to.envelopeContains(refRem)) {
209                            if (findMate(refRem)) {
210                                bool.set();
211                                findMates();
212                            }
213                        }
214                    }
215                }
216            };
217            refD.forEach(pruneToOnRefRem, EventType.REMOVE);
218            refD.unfreeze();
219            IntVar ref = refs[id];
220            if (EventType.isRemove(mask)) {
221                if (!PropUtil.isDomIntersectEnv(ref, to)) {
222                    take.removeFromEnvelope(id, aCause);
223                    dontCare.remove(id);
224                }
225            }
226            if (ref.instantiated()) {
227                if (take.kernelContains(id)) {
228                    to.addToKernel(ref.getValue(), aCause);
229                }
230            }
231        }
232    }
233    private final IntProcedure pruneToOnTakeEnv = new IntProcedure() {
234        @Override
235        public void execute(int takeEnv) throws ContradictionException {
236            assert !take.envelopeContains(takeEnv);
237
238            dontCare.remove(takeEnv);
239            IntVar ref = refs[takeEnv];
240            int ub = ref.getUB();
241            for (int i = ref.getLB(); i <= ub; i = ref.nextValue(i)) {
242                if (to.envelopeContains(i)) {
243                    if (findMate(i)) {
244                        findMates();
245                        return;
246                    }
247                }
248            }
249        }
250    };
251    private final IntProcedure pickToAndPruneChildOnTakeKer = new IntProcedure() {
252        @Override
253        public void execute(int takeKer) throws ContradictionException {
254            assert take.kernelContains(takeKer);
255
256            IntVar ref = refs[takeKer];
257            PropUtil.domSubsetEnv(ref, to, aCause);
258            if (ref.instantiated()) {
259                to.addToKernel(ref.getValue(), aCause);
260            }
261        }
262    };
263    private final IntProcedure pruneRefOnToEnv = new IntProcedure() {
264        @Override
265        public void execute(int toEnv) throws ContradictionException {
266            assert !to.envelopeContains(toEnv);
267
268            for (int takeKer = take.getKernelFirst(); takeKer != SetVar.END; takeKer = take.getKernelNext()) {
269                IntVar ref = refs[takeKer];
270                if (ref.removeValue(toEnv, aCause) && ref.instantiated()) {
271                    to.addToKernel(ref.getValue(), aCause);
272                }
273            }
274        }
275    };
276    private final IntProcedure pickTakeOnToKer = new IntProcedure() {
277        @Override
278        public void execute(int toVal) throws ContradictionException {
279            assert to.kernelContains(toVal);
280            if (findMate(toVal)) {
281                findMates();
282            }
283        }
284    };
285
286    @Override
287    public ESat isEntailed() {
288        for (int i = take.getKernelFirst(); i != SetVar.END; i = take.getKernelNext()) {
289            if (i < 0 || i >= refs.length || !PropUtil.isDomIntersectEnv(refs[i], to)) {
290                return ESat.FALSE;
291            }
292            if (refs[i].instantiated()) {
293                int value = refs[i].getValue();
294                if (!to.envelopeContains(value)) {
295                    return ESat.FALSE;
296                }
297            }
298        }
299        boolean completelyInstantiated = take.instantiated() && to.instantiated();
300        int count = 0;
301        IntVar[] taken = new IntVar[take.getEnvelopeSize()];
302        for (int i = take.getEnvelopeFirst(); i != SetVar.END; i = take.getEnvelopeNext()) {
303            if (i >= 0 && i < refs.length) {
304                IntVar ref = refs[i];
305                completelyInstantiated = completelyInstantiated && ref.instantiated();
306                taken[count++] = ref;
307            }
308        }
309        if (count < taken.length) {
310            taken = Arrays.copyOf(taken, count);
311        }
312        for (int i = to.getKernelFirst(); i != SetVar.END; i = to.getKernelNext()) {
313            if (!PropUtil.domsContain(taken, i)) {
314                return ESat.FALSE;
315            }
316        }
317        return completelyInstantiated ? ESat.TRUE : ESat.UNDEFINED;
318    }
319
320    @Override
321    public String toString() {
322        return "joinFunction(" + take + ", " + Arrays.toString(refs) + ", " + to + ")";
323    }
324}