001    package ifs.extension;
002    
003    import ifs.model.*;
004    import ifs.solver.*;
005    import ifs.util.*;
006    
007    import java.util.*;
008    
009    /**
010     * MAC propagation.
011     * <br><br>
012     * During the arc consistency maintenance, when a value is deleted from a variable’s domain, the reason (forming an 
013     * explanation) can be computed and attached to the deleted value. Once a variable (say Vx with the assigned value vx) 
014     * is unassigned during the search, all deleted values which contain a pair Vx = vx in their explanations need to be 
015     * recomputed. Such value can be either still inconsistent with the current (partial) solution (a different explanation 
016     * is attached to it in this case) or it can be returned back to its variable's domain. Arc consistency is maintained 
017     * after each iteration step, i.e., the selected assignment is propagated into the not yet assigned variables. When a 
018     * value vx is assigned to a variable Vx, an explanation Vx != vx' &#8592; Vx = vx is attached to all values vx' of the 
019     * variable Vx, different from vx.
020     * <br><br>
021     * In the case of forward checking (only constraints going from assigned variables to unassigned variables are revised), 
022     * computing explanations is rather easy. A value vx is deleted from the domain of the variable Vx only if there is a 
023     * constraint which prohibits the assignment Vx=vx because of the existing assignments (e.g., Vy = vy, … Vz = vz). 
024     * An explanation for the deletion of this value vx is then Vx != vx &#8592; (Vy = vy & ... Vz = vz), where Vy = vy & ... Vz = vz 
025     * are assignments contained in the prohibiting constraint. In case of arc consistency, a value vx is deleted from 
026     * the domain of the variable Vx if there is a constraint which does not permit the assignment Vx = vx with other 
027     * possible assignments of the other variables in the constraint. This means that there is no support value (or 
028     * combination of values) for the value vx of the variable Vx in the constraint. An explanation is then a union of 
029     * explanations of all possible support values for the assignment Vx = vx of this constraint which were deleted. 
030     * The reason is that if one of these support values is returned to its variable's domain, this value vx may 
031     * be returned as well (i.e., the reason for its deletion has vanished, a new reason needs to be computed). 
032     * <br><br>
033     * As for the implementation, we only need to enforce arc consistency of the initial solution and to extend unassign 
034     * and assign methods. Procedure {@link MacPropagation#afterAssigned(long, Value)} enforces arc consistency of the 
035     * solution with the selected assignment variable = value and the procedure {@link MacPropagation#afterUnassigned(long, Value)}
036     * "undoes" the assignment variable = value. It means that explanations of all values which were deleted and which 
037     * contain assignment variable = value in their explanations need to be recomputed. This can be done via returning 
038     * all these values into their variables’ domains followed by arc consistency maintenance over their variables.
039     * <br><br>
040     * Parameters:
041     * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
042     * <tr><td>MacPropagation.JustForwardCheck</td><td>{@link Boolean}</td><td>If true, only forward checking instead of full arc consistency is maintained during the search.</td></tr>
043     * </table>
044     *
045     * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomáš Müller</a>
046     * @version 1.0
047     */
048    public class MacPropagation extends Extension {
049        private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(MacPropagation.class);
050        private boolean iJustForwardCheck = false;
051        
052        /** List of constraints on which arc-consistency is to be maintained */
053        protected Vector iConstraints = null;
054        /** Current iteration */
055        protected long iIteration = 0;
056        
057        /** Constructor */
058        public MacPropagation(Solver solver, DataProperties properties) {
059            super(solver, properties);
060            iJustForwardCheck = properties.getPropertyBoolean("MacPropagation.JustForwardCheck", false);
061        }
062        
063        /** Adds a constraint on which arc-consistency is to be maintained */
064        public void addConstraint(Constraint constraint) {
065            if (iConstraints == null) iConstraints = new FastVector();
066            iConstraints.addElement(constraint);
067        }
068        
069        /** Returns true, if arc-consistency is to be maintained on the given constraint */
070        public boolean contains(Constraint constraint) {
071            if (iConstraints == null) return true;
072            return iConstraints.contains(constraint);
073        }
074        
075        /** Before a value is unassigned: until the value is inconsistent with the current solution, 
076         * an assignment from its explanation is picked and unassigned. 
077         */
078        public void beforeAssigned(long iteration, Value value) {
079            iIteration = iteration;
080            if (value == null) return;
081            if (!isGood(value)) {
082                while (!isGood(value) && !noGood(value).isEmpty()) {
083                    Value noGoodValue = (Value)noGood(value).iterator().next();
084                    noGoodValue.variable().unassign(iteration);
085                }
086            }
087            if (!isGood(value)) {
088                sLogger.warn("Going to assign a bad value "+value+" with empty no-good.");
089            }
090        }
091        
092        /** After a value is assigned: explanations of other values of the value's variable are reset (to contain only the assigned value), 
093         * propagation over the assigned variable takes place.
094         */
095        public void afterAssigned(long iteration, Value value) {
096            iIteration = iteration;
097            if (!isGood(value)) {
098                sLogger.warn(value.variable().getName() + " = " + value.getName() + " -- not good value assigned (noGood:" + noGood(value) + ")");
099                setGood(value);
100            }
101            
102            HashSet noGood = new HashSet(1);
103            noGood.add(value);
104            for (Enumeration i = value.variable().values().elements(); i.hasMoreElements(); ) {
105                Value anotherValue = (Value)i.nextElement();
106                if (anotherValue.equals(value)) continue;
107                setNoGood(anotherValue, noGood);
108            }
109            propagate(value.variable());
110        }
111        
112        /** After a value is unassigned: explanations of all values of unassigned variable are recomputed ({@link Value#conflicts()}), propagation
113         * undo over the unassigned variable takes place.
114         */
115        public void afterUnassigned(long iteration, Value value) {
116            iIteration = iteration;
117            if (!isGood(value))
118                sLogger.error(value.variable().getName() + " = " + value.getName() + " -- not good value unassigned (noGood:" + noGood(value) + ")");
119            for (Enumeration i = value.variable().values().elements(); i.hasMoreElements(); ) {
120                Value anotherValue = (Value)i.nextElement();
121                if (!isGood(anotherValue)) {
122                    Set noGood = anotherValue.conflicts();
123                    if (noGood == null)
124                        setGood(anotherValue);
125                    else
126                        setNoGood(anotherValue, noGood);
127                }
128            }
129            undoPropagate(value.variable());
130        }
131        
132        /** Initialization. Enforce arc-consistency over the current (initial) solution. AC3 algorithm is used. */
133        public boolean init(Solver solver) {
134            boolean isOk = true;
135            Progress.getInstance().save();
136            Progress.getInstance().setPhase("Initializing propagation:", 3 * getModel().variables().size());
137            for (Enumeration i1 = getModel().variables().elements(); i1.hasMoreElements(); ) {
138                Variable aVariable = (Variable)i1.nextElement();
139                supportValues(aVariable).clear();
140                goodValues(aVariable).clear();
141            }
142            for (Enumeration i1 = getModel().variables().elements(); i1.hasMoreElements(); ) {
143                Variable aVariable = (Variable)i1.nextElement();
144                for (Enumeration i2 = aVariable.values().elements(); i2.hasMoreElements(); ) {
145                    Value aValue = (Value)i2.nextElement();
146                    Set noGood = aValue.conflicts();
147                    initNoGood(aValue, noGood);
148                    if (noGood == null) {
149                        goodValues(aVariable).add(aValue);
150                    }
151                    else {
152                    }
153                }
154                Progress.getInstance().incProgress();
155            }
156            ifs.util.Queue queue = new ifs.util.Queue(getModel().variables().size() + 1);
157            for (Enumeration i1 = getModel().variables().elements(); i1.hasMoreElements(); ) {
158                Variable aVariable = (Variable)i1.nextElement();
159                for (Enumeration i2 = aVariable.hardConstraints().elements(); i2.hasMoreElements(); ) {
160                    Constraint constraint = (Constraint)i2.nextElement();
161                    propagate(constraint, aVariable, queue);
162                }
163                Progress.getInstance().incProgress();
164            }
165            if (!iJustForwardCheck)
166                propagate(queue);
167            for (Enumeration i1 = getModel().variables().elements(); i1.hasMoreElements(); ) {
168                Variable aVariable = (Variable)i1.nextElement();
169                Vector values2delete = new FastVector();
170                for (Enumeration i2 = aVariable.values().elements(); i2.hasMoreElements(); ) {
171                    Value aValue = (Value)i2.nextElement();
172                    if (!isGood(aValue) && noGood(aValue).isEmpty()) {
173                        values2delete.addElement(aValue);
174                    }
175                }
176                Object[] vals = values2delete.toArray();
177                for (int i = 0; i < vals.length; i++)
178                    aVariable.removeValue(0, (Value)vals[i]);
179                if (aVariable.values().isEmpty()) {
180                    sLogger.error(aVariable.getName() + " has empty domain!");
181                    isOk = false;
182                }
183                Progress.getInstance().incProgress();
184            }
185            Progress.getInstance().restore();
186            return isOk;
187        }
188        
189        /** Propagation over the given variable. */
190        protected void propagate(Variable variable) {
191            ifs.util.Queue queue = new ifs.util.Queue(variable.getModel().variables().size() + 1);
192            if (variable.getAssignment() != null) {
193                for (Enumeration i = variable.hardConstraints().elements(); i.hasMoreElements(); ) {
194                    Constraint constraint = (Constraint)i.nextElement();
195                    if (contains(constraint))
196                        propagate(constraint, variable.getAssignment(), queue);
197                }
198            }
199            else {
200                for (Enumeration i = variable.hardConstraints().elements(); i.hasMoreElements(); ) {
201                    Constraint constraint = (Constraint)i.nextElement();
202                    if (contains(constraint))
203                        propagate(constraint, variable, queue);
204                }
205            }
206            if (!iJustForwardCheck && !queue.isEmpty())
207                propagate(queue);
208        }
209        
210        /** Propagation over the queue of variables. */
211        protected void propagate(ifs.util.Queue queue) {
212            while (!queue.isEmpty()) {
213                Variable aVariable = (Variable)queue.get();
214                for (Enumeration i = aVariable.hardConstraints().elements(); i.hasMoreElements(); ) {
215                    Constraint constraint = (Constraint)i.nextElement();
216                    if (contains(constraint))
217                        propagate(constraint, aVariable, queue);
218                }
219            }
220        }
221        
222        /** Propagation undo over the given variable. All values having given variable in thair explanations needs to be
223         * recomputed. This is done in two phases: 
224         * 1) values that contain this variable in explanation are returned back to domains (marked as good)
225         * 2) propagation over variables which contains a value that was marked as good takes place
226         */
227        public void undoPropagate(Variable variable) {
228            Hashtable undoVars = new Hashtable();
229            while (!supportValues(variable).isEmpty()) {
230                Value value = (Value)supportValues(variable).iterator().next();
231                Set noGood = value.conflicts();
232                if (noGood == null) {
233                    setGood(value);
234                    Vector values = (Vector)undoVars.get(value.variable());
235                    if (values == null) {
236                        values = new FastVector();
237                        undoVars.put(value.variable(), values);
238                    }
239                    values.addElement(value);
240                }
241                else {
242                    setNoGood(value, noGood);
243                    if (noGood.isEmpty())
244                        ((Variable)value.variable()).removeValue(iIteration, value);
245                }
246            }
247            
248            ifs.util.Queue queue = new ifs.util.Queue(variable.getModel().variables().size() + 1);
249            for (Enumeration e = undoVars.keys(); e.hasMoreElements();) {
250                Variable aVariable = (Variable)e.nextElement();
251                Vector values = (Vector)undoVars.get(aVariable);
252                boolean add = false;
253                for (Enumeration e1 = aVariable.constraintVariables().keys(); e1.hasMoreElements(); )
254                    if (propagate((Variable)e1.nextElement(), aVariable, values))
255                        add = true;
256                if (add)
257                    queue.put(aVariable);
258            }
259            for (Enumeration e1 = variable.constraintVariables().keys(); e1.hasMoreElements(); )
260                if (propagate((Variable)e1.nextElement(), variable) && !queue.contains(variable))
261                    queue.put(variable);
262            if (!iJustForwardCheck)
263                propagate(queue);
264        }
265        
266        protected boolean propagate(Variable aVariable, Variable anotherVariable, Vector adepts) {
267            if (goodValues(aVariable).isEmpty())
268                return false;
269            boolean ret = false;
270            Vector conflicts = null;
271            for (Enumeration i = ((Vector)anotherVariable.constraintVariables().get(aVariable)).elements(); i.hasMoreElements();) {
272                Constraint constraint = (Constraint)i.nextElement();
273                for (Iterator i1 = goodValues(aVariable).iterator(); i1.hasNext();) {
274                    Value aValue = (Value)i1.next();
275                    if (conflicts == null)
276                        conflicts = conflictValues(constraint, aValue, adepts);
277                    else
278                        conflicts = conflictValues(constraint, aValue, conflicts);
279                    if (conflicts == null || conflicts.isEmpty())
280                        break;
281                }
282                if (conflicts != null && !conflicts.isEmpty())
283                    for (Enumeration i1 = conflicts.elements(); i1.hasMoreElements();) {
284                        Value conflictValue = (Value)i1.nextElement();
285                        Set reason = reason(constraint, aVariable, conflictValue);
286                        //sLogger.debug("  "+conflictValue+" become nogood (c:"+constraint.getName()+", r:"+reason+")");
287                        setNoGood(conflictValue, reason);
288                        adepts.removeElement(conflictValue);
289                        if (reason.isEmpty())
290                            ((Variable)conflictValue.variable()).removeValue(iIteration,conflictValue);
291                        ret = true;
292                    }
293            }
294            return ret;
295        }
296        
297        protected boolean propagate(Variable aVariable, Variable anotherVariable) {
298            if (goodValues(anotherVariable).isEmpty())
299                return false;
300            return propagate(aVariable, anotherVariable, new FastVector(goodValues(anotherVariable)));
301        }
302        
303        /** support values of a variable */
304        private Set supportValues(Variable variable) {
305            Set[] ret = (Set[])variable.getExtra();
306            if (ret == null) {
307                ret = new HashSet[] { new HashSet(1000), new HashSet()};
308                variable.setExtra(ret);
309            }
310            return ret[0];
311        }
312        
313        /** good values of a variable (values not removed from variables domain)*/
314        public Set goodValues(Variable variable) {
315            Set[] ret = (Set[])variable.getExtra();
316            if (ret == null) {
317                ret = new HashSet[] { new HashSet(1000), new HashSet()};
318                variable.setExtra(ret);
319            }
320            return ret[1];
321        }
322        
323        /** notification that a nogood value becomes good or vice versa */
324        private void goodnessChanged(Value value) {
325            if (isGood(value)) {
326                goodValues(value.variable()).add(value);
327            }
328            else {
329                goodValues(value.variable()).remove(value);
330            }
331        }
332        /** removes support of a variable */
333        private void removeSupport(Variable variable, Value value) {
334            supportValues(variable).remove(value);
335        }
336        /** adds support of a variable */
337        private void addSupport(Variable variable, Value value) {
338            supportValues(variable).add(value);
339        }
340        
341        /** variables explanation */
342        public Set noGood(Value value) {
343            return (Set)value.getExtra();
344        }
345        /** is variable good */
346        public boolean isGood(Value value) {
347            return (value.getExtra() == null);
348        }
349        /** sets value to be good */
350        protected void setGood(Value value) {
351            Set noGood = noGood(value);
352            if (noGood != null)
353                for (Iterator i = noGood.iterator(); i.hasNext();)
354                    removeSupport(((Value)i.next()).variable(), value);
355            value.setExtra(null);
356            goodnessChanged(value);
357        }
358        /** sets values explanation (initialization) */
359        private void initNoGood(Value value, Set reason) {
360            value.setExtra(reason);
361        }
362        /** sets value's explanation*/
363        public void setNoGood(Value value, Set reason) {
364            Set noGood = noGood(value);
365            if (noGood != null)
366                for (Iterator i = noGood.iterator(); i.hasNext();)
367                    removeSupport(((Value)i.next()).variable(), value);
368            value.setExtra(reason);
369            for (Iterator i = reason.iterator(); i.hasNext();) {
370                Value aValue = (Value)i.next();
371                addSupport(aValue.variable(), value);
372            }
373            goodnessChanged(value);
374        }
375        
376        /** propagation over a constraint */
377        private void propagate(Constraint constraint, Value anAssignedValue, ifs.util.Queue queue) {
378            HashSet reason = new HashSet(1);
379            reason.add(anAssignedValue);
380            Collection conflicts = conflictValues(constraint, anAssignedValue);
381            if (conflicts != null && !conflicts.isEmpty())
382                for (Iterator i1 = conflicts.iterator(); i1.hasNext();) {
383                    Value conflictValue = (Value)i1.next();
384                    //sLogger.debug("  "+conflictValue+" become nogood (c:"+constraint.getName()+", r:"+reason+")");
385                    setNoGood(conflictValue, reason);
386                    if (!queue.contains(conflictValue.variable()))
387                        queue.put(conflictValue.variable());
388                }
389        }
390        
391        /** propagation over a constraint */
392        private void propagate(Constraint constraint, Variable aVariable, ifs.util.Queue queue) {
393            if (goodValues(aVariable).isEmpty())
394                return;
395            Vector conflicts = conflictValues(constraint, aVariable);
396            
397            if (conflicts != null && !conflicts.isEmpty()) {
398                for (Enumeration i1 = conflicts.elements(); i1.hasMoreElements(); ) {
399                    Value conflictValue = (Value)i1.nextElement();
400                    if (!queue.contains(conflictValue.variable()))
401                        queue.put(conflictValue.variable());
402                    Set reason = reason(constraint, aVariable, conflictValue);
403                    //sLogger.debug("  "+conflictValue+" become nogood (c:"+constraint.getName()+", r:"+reason+")");
404                    setNoGood(conflictValue, reason);
405                    if (reason.isEmpty())
406                        ((Variable)conflictValue.variable()).removeValue(iIteration, conflictValue);
407                }
408            }
409        }
410        
411        private Vector conflictValues(Constraint constraint, Value aValue) {
412            Vector ret = new FastVector();
413            
414            for (Enumeration i1 = constraint.variables().elements(); i1.hasMoreElements();) {
415                Variable variable = (Variable)i1.nextElement();
416                if (variable.equals(aValue.variable()))
417                    continue;
418                if (variable.getAssignment() != null)
419                    continue;
420                
421                for (Iterator i2 = goodValues(variable).iterator();i2.hasNext();) {
422                    Value value = (Value)i2.next();
423                    if (!constraint.isConsistent(aValue, value))
424                        ret.addElement(value);
425                }
426            }
427            return ret;
428        }
429        
430        private Vector conflictValues(Constraint constraint, Value aValue, Vector values) {
431            Vector ret = new FastVector(values.size());
432            
433            for (Enumeration i1 = values.elements(); i1.hasMoreElements();) {
434                Value value = (Value)i1.nextElement();
435                if (!constraint.isConsistent(aValue, value))
436                    ret.addElement(value);
437            }
438            return ret;
439        }
440        
441        private Vector conflictValues(Constraint constraint, Variable aVariable) {
442            Vector conflicts = null;
443            for (Iterator i1 = goodValues(aVariable).iterator(); i1.hasNext();) {
444                Value aValue = (Value)i1.next();
445                if (conflicts == null)
446                    conflicts = conflictValues(constraint, aValue);
447                else
448                    conflicts = conflictValues(constraint, aValue, conflicts);
449                if (conflicts == null || conflicts.isEmpty())
450                    return null;
451            }
452            return conflicts;
453        }
454        
455        private HashSet reason(Constraint constraint, Variable aVariable, Value aValue) {
456            HashSet ret = new HashSet();
457            for (Enumeration i1 = aVariable.values().elements(); i1.hasMoreElements();) {
458                Value value = (Value)i1.nextElement();
459                if (constraint.isConsistent(aValue, value)) {
460                    if (noGood(value) == null)
461                        sLogger.error("Something went wrong: value " + value + " cannot participate in a reason.");
462                    else
463                        ret.addAll(noGood(value));
464                }
465            }
466            return ret;
467        }
468        
469    }