001    package net.sf.cpsolver.exam.model;
002    
003    import java.util.Enumeration;
004    import java.util.HashSet;
005    import java.util.Iterator;
006    import java.util.Set;
007    
008    import net.sf.cpsolver.ifs.model.Constraint;
009    import net.sf.cpsolver.ifs.model.Value;
010    
011    /**
012     * Distribution binary constraint. 
013     * <br><br>
014     * The following binary distribution constraints are implemented
015     * <ul>
016     *  <li> Same room
017     *  <li> Different room
018     *  <li> Same period
019     *  <li> Different period
020     *  <li> Precedence
021     * </ul>
022     * <br><br>
023     * 
024     * @version
025     * ExamTT 1.1 (Examination Timetabling)<br>
026     * Copyright (C) 2008 Tomáš Müller<br>
027     * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
028     * Lazenska 391, 76314 Zlin, Czech Republic<br>
029     * <br>
030     * This library is free software; you can redistribute it and/or
031     * modify it under the terms of the GNU Lesser General Public
032     * License as published by the Free Software Foundation; either
033     * version 2.1 of the License, or (at your option) any later version.
034     * <br><br>
035     * This library is distributed in the hope that it will be useful,
036     * but WITHOUT ANY WARRANTY; without even the implied warranty of
037     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
038     * Lesser General Public License for more details.
039     * <br><br>
040     * You should have received a copy of the GNU Lesser General Public
041     * License along with this library; if not, write to the Free Software
042     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
043     */
044    public class ExamDistributionConstraint extends Constraint {
045        /** Same room constraint type */
046        public static final int sDistSameRoom = 0;
047        /** Different room constraint type */
048        public static final int sDistDifferentRoom = 1;
049        /** Same period constraint type */
050        public static final int sDistSamePeriod = 2;
051        /** Different period constraint type */
052        public static final int sDistDifferentPeriod = 3;
053        /** Precedence constraint type */
054        public static final int sDistPrecedence = 4;
055        /** Precedence constraint type (reverse order) */
056        public static final int sDistPrecedenceRev = 5;
057        /** Distribution type name */
058        public static final String[] sDistType = new String[] {
059            "same-room",
060            "different-room",
061            "same-period",
062            "different-period",
063            "precedence",
064            "precedence-rev"
065        };
066        
067        private int iType = -1;
068        private boolean iHard = true;
069        private int iWeight = 0;
070        
071        /**
072         * Constructor
073         * @param id constraint unique id
074         * @param type constraint type
075         * @param hard true if the constraint is hard (cannot be violated)
076         * @param weight if not hard, penalty for violation
077         */
078        public ExamDistributionConstraint(long id, int type, boolean hard, int weight) {
079            iId = id;
080            iType = type;
081            iHard = hard;
082            iWeight = weight;
083        }
084    
085        /**
086         * Constructor
087         * @param id constraint unique id
088         * @param type constraint type (EX_SAME_PREF, EX_SAME_ROOM, or EX_PRECEDENCE)
089         * @param pref preference (R/P for required/prohibited, or -2, -1, 0, 1, 2 for preference (from preferred to discouraged)) 
090         */
091        public ExamDistributionConstraint(long id, String type, String pref) {
092            iId = id;
093            boolean neg = "P".equals(pref) || "2".equals(pref) || "1".equals(pref);
094            if ("EX_SAME_PER".equals(type)) {
095                iType = (neg?sDistDifferentPeriod:sDistSamePeriod);
096            } else if ("EX_SAME_ROOM".equals(type)) {
097                iType = (neg?sDistDifferentRoom:sDistSameRoom);
098            } else if ("EX_PRECEDENCE".equals(type)) {
099                iType = (neg?sDistPrecedenceRev:sDistPrecedence);
100            } else
101                throw new RuntimeException("Unkown type "+type);
102            if ("P".equals(pref) || "R".equals(pref))
103                iHard = true;
104            else {
105                iHard = false;
106                iWeight = Integer.parseInt(pref) * Integer.parseInt(pref);
107            }
108        }
109    
110        /**
111         * Constructor
112         * @param id constraint unique id
113         * @param type constraint type name
114         */
115        public ExamDistributionConstraint(long id, String type, boolean hard, int weight) {
116            iId = id;
117            for (int i=0;i<sDistType.length;i++)
118                if (sDistType[i].equals(type)) iType = i;
119            if (iType<0)
120                throw new RuntimeException("Unknown type '"+type+"'.");
121            iHard = hard;
122            iWeight = weight;
123        }
124        
125        /**
126         * True if hard (must be satisfied), false for soft (should be satisfied)
127         */
128        public boolean isHard() { return iHard; }
129        
130        /**
131         * If not hard, penalty for violation
132         */
133        public int getWeight() { return iWeight; }
134        
135        /**
136         * Constraint type
137         */
138        public int getType() {
139            return iType;
140        }
141    
142        /**
143         * Constraint type name
144         */
145        public String getTypeString() {
146            return sDistType[iType];
147        }
148    
149        /**
150         * String representation -- constraint type name (exam 1, exam 2)
151         */
152        public String toString() {
153            return getTypeString()+" ("+variables()+")";
154        }
155        
156        /**
157         * Compute conflicts -- there is a conflict if the other variable is
158         * assigned and {@link ExamDistributionConstraint#check(ExamPlacement, ExamPlacement)} is false
159         */
160        public void computeConflicts(Value value, Set conflicts) {
161            boolean before = true;
162            ExamPlacement givenPlacement = (ExamPlacement)value;
163            for (Enumeration e=variables().elements();e.hasMoreElements();) {
164                Exam exam = (Exam)e.nextElement();
165                if (exam.equals(value.variable())) {
166                    before = false; continue;
167                }
168                ExamPlacement placement = (ExamPlacement)exam.getAssignment();
169                if (placement==null) continue;
170                if (!check(before?placement:givenPlacement, before?givenPlacement:placement))
171                    conflicts.add(placement);
172            }
173        }
174        
175        /**
176         * Check for conflict -- there is a conflict if the other variable is
177         * assigned and {@link ExamDistributionConstraint#check(ExamPlacement, ExamPlacement)} is false
178         */
179        public boolean inConflict(Value value) {
180            boolean before = true;
181            ExamPlacement givenPlacement = (ExamPlacement)value;
182            for (Enumeration e=variables().elements();e.hasMoreElements();) {
183                Exam exam = (Exam)e.nextElement();
184                if (exam.equals(value.variable())) {
185                    before = false; continue;
186                }
187                ExamPlacement placement = (ExamPlacement)exam.getAssignment();
188                if (placement==null) continue;
189                if (!check(before?placement:givenPlacement, before?givenPlacement:placement)) return true;
190            }
191            return false;
192        }
193        
194        /**
195         * Consistency check -- {@link ExamDistributionConstraint#check(ExamPlacement, ExamPlacement)} is called
196         */
197        public boolean isConsistent(Value value1, Value value2) {
198            ExamPlacement first = (ExamPlacement)value1;
199            ExamPlacement second = (ExamPlacement)value2;
200            boolean before = (variables().indexOf(value1.variable())<variables().indexOf(value2.variable()));
201            return check(before?first:second, before?second:first);
202        }
203        
204        /**
205         * Check assignments of the given exams 
206         * @param first assignment of the first exam 
207         * @param second assignment of the  second exam
208         * @return true, if the constraint is satisfied
209         */
210        public boolean check(ExamPlacement first, ExamPlacement second) {
211            switch (getType()) {
212            case sDistPrecedence :
213                return first.getPeriod().getIndex()<second.getPeriod().getIndex();
214            case sDistPrecedenceRev :
215                return first.getPeriod().getIndex()>second.getPeriod().getIndex();
216            case sDistSamePeriod :
217                return first.getPeriod().getIndex()==second.getPeriod().getIndex();
218            case sDistDifferentPeriod :
219                return first.getPeriod().getIndex()!=second.getPeriod().getIndex();
220            case sDistSameRoom :
221                return first.getRoomPlacements().containsAll(second.getRoomPlacements()) || second.getRoomPlacements().containsAll(first.getRoomPlacements());
222            case sDistDifferentRoom :
223                for (Iterator i=first.getRoomPlacements().iterator();i.hasNext();) 
224                    if (second.getRoomPlacements().contains(i.next())) return false;
225                return true;
226            default :
227                return false;
228            }
229        }
230        
231        /**
232         * Compare with other constraint for equality
233         */
234        public boolean equals(Object o) {
235            if (o==null || !(o instanceof ExamDistributionConstraint)) return false;
236            ExamDistributionConstraint c = (ExamDistributionConstraint)o;
237            return getType()==c.getType() && getId()==c.getId();
238        }
239        
240        /**
241         * Return true if this is hard constraint or this is a soft constraint without any violation
242         */
243        public boolean isSatisfied() {
244            return isSatisfied(null);
245        }
246        
247        /**
248         * Return true if this is hard constraint or this is a soft constraint without any violation
249         * @param p exam assignment to be made
250         */
251        public boolean isSatisfied(ExamPlacement p) {
252            if (isHard()) return true;
253            switch (getType()) {
254            case sDistPrecedence :
255                ExamPeriod last = null;
256                for (Enumeration e=variables().elements();e.hasMoreElements();) {
257                    Exam exam = (Exam)e.nextElement();
258                    ExamPlacement placement = (p!=null && exam.equals(p.variable())?p:(ExamPlacement)exam.getAssignment());
259                    if (placement==null) continue;
260                    if (last==null || last.getIndex()<placement.getPeriod().getIndex())
261                        last = placement.getPeriod();
262                    else
263                        return false;
264                }
265                return true;
266            case sDistPrecedenceRev :
267                last = null;
268                for (Enumeration e=variables().elements();e.hasMoreElements();) {
269                    Exam exam = (Exam)e.nextElement();
270                    ExamPlacement placement = (p!=null && exam.equals(p.variable())?p:(ExamPlacement)exam.getAssignment());
271                    if (placement==null) continue;
272                    if (last==null || last.getIndex()>placement.getPeriod().getIndex())
273                        last = placement.getPeriod();
274                    else
275                        return false;
276                }
277                return true;
278            case sDistSamePeriod :
279                ExamPeriod period = null;
280                for (Enumeration e=variables().elements();e.hasMoreElements();) {
281                    Exam exam = (Exam)e.nextElement();
282                    ExamPlacement placement = (p!=null && exam.equals(p.variable())?p:(ExamPlacement)exam.getAssignment());
283                    if (placement==null) continue;
284                    if (period==null)
285                        period = placement.getPeriod();
286                    else if (period.getIndex()!=placement.getPeriod().getIndex())
287                        return false;
288                }
289                return true;
290            case sDistDifferentPeriod :
291                HashSet periods = new HashSet();
292                for (Enumeration e=variables().elements();e.hasMoreElements();) {
293                    Exam exam = (Exam)e.nextElement();
294                    ExamPlacement placement = (p!=null && exam.equals(p.variable())?p:(ExamPlacement)exam.getAssignment());
295                    if (placement==null) continue;
296                    if (!periods.add(placement.getPeriod())) return false;
297                }
298                return true;
299            case sDistSameRoom :
300                Set rooms = null;
301                for (Enumeration e=variables().elements();e.hasMoreElements();) {
302                    Exam exam = (Exam)e.nextElement();
303                    ExamPlacement placement = (p!=null && exam.equals(p.variable())?p:(ExamPlacement)exam.getAssignment());
304                    if (placement==null) continue;
305                    if (rooms==null)
306                        rooms = placement.getRoomPlacements();
307                    else if (!rooms.containsAll(placement.getRoomPlacements()) || !placement.getRoomPlacements().containsAll(rooms))
308                        return false;
309                }
310                return true;
311            case sDistDifferentRoom :
312                HashSet allRooms = new HashSet();
313                for (Enumeration e=variables().elements();e.hasMoreElements();) {
314                    Exam exam = (Exam)e.nextElement();
315                    ExamPlacement placement = (p!=null && exam.equals(p.variable())?p:(ExamPlacement)exam.getAssignment());
316                    if (placement==null) continue;
317                    for (Iterator i=placement.getRoomPlacements().iterator();i.hasNext();) {
318                        ExamRoomPlacement room = (ExamRoomPlacement)i.next();
319                        if (!allRooms.add(room)) return false;
320                    }
321                }
322                return true;
323            default :
324                return false;
325            }
326        }
327        
328        
329        boolean iIsSatisfied = true;
330        public void assigned(long iteration, Value value) {
331            super.assigned(iteration, value);
332            if (!isHard() && iIsSatisfied!=isSatisfied()) {
333                iIsSatisfied = !iIsSatisfied;
334                ((ExamModel)value.variable().getModel()).addDistributionPenalty(iIsSatisfied?-getWeight():getWeight());
335            }
336        }
337        
338        public void unassigned(long iteration, Value value) {
339            super.unassigned(iteration, value);
340            if (!isHard() && iIsSatisfied!=isSatisfied()) {
341                iIsSatisfied = !iIsSatisfied;
342                ((ExamModel)value.variable().getModel()).addDistributionPenalty(iIsSatisfied?-getWeight():getWeight());
343            }
344        }
345        
346        /** True if the constraint is related to rooms */
347        public boolean isRoomRelated() {
348            return iType==sDistSameRoom || iType==sDistDifferentRoom;
349        }
350    
351        /** True if the constraint is related to periods */
352        public boolean isPeriodRelated() {
353            return !isRoomRelated();
354        }
355    }