001package org.cpsolver.coursett.constraint;
002
003import java.util.ArrayList;
004import java.util.BitSet;
005import java.util.Enumeration;
006import java.util.HashSet;
007import java.util.HashMap;
008import java.util.Iterator;
009import java.util.List;
010import java.util.Set;
011import java.util.regex.Matcher;
012import java.util.regex.Pattern;
013
014import org.cpsolver.coursett.Constants;
015import org.cpsolver.coursett.criteria.DistributionPreferences;
016import org.cpsolver.coursett.model.Lecture;
017import org.cpsolver.coursett.model.Placement;
018import org.cpsolver.coursett.model.RoomLocation;
019import org.cpsolver.coursett.model.TimeLocation;
020import org.cpsolver.coursett.model.TimeLocation.IntEnumeration;
021import org.cpsolver.coursett.model.TimetableModel;
022import org.cpsolver.ifs.assignment.Assignment;
023import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
024import org.cpsolver.ifs.assignment.context.ConstraintWithContext;
025import org.cpsolver.ifs.model.Constraint;
026import org.cpsolver.ifs.model.GlobalConstraint;
027import org.cpsolver.ifs.model.Model;
028import org.cpsolver.ifs.model.WeakeningConstraint;
029import org.cpsolver.ifs.util.DataProperties;
030import org.cpsolver.ifs.util.DistanceMetric;
031import org.cpsolver.ifs.util.ToolBox;
032
033
034/**
035 * Group constraint. <br>
036 * This constraint expresses relations between several classes, e.g., that two
037 * sections of the same lecture can not be taught at the same time, or that some
038 * classes have to be taught one immediately after another. It can be either
039 * hard or soft. <br>
040 * <br>
041 * Following constraints are now supported:
042 * <table border='1' summary='Related Solver Parameters'>
043 * <tr>
044 * <th>Constraint</th>
045 * <th>Comment</th>
046 * </tr>
047 * <tr>
048 * <td>SAME_TIME</td>
049 * <td>Same time: given classes have to be taught in the same hours. If the
050 * classes are of different length, the smaller one cannot start before the
051 * longer one and it cannot end after the longer one.</td>
052 * </tr>
053 * <tr>
054 * <td>SAME_DAYS</td>
055 * <td>Same days: given classes have to be taught in the same day. If the
056 * classes are of different time patterns, the days of one class have to form a
057 * subset of the days of the other class.</td>
058 * </tr>
059 * <tr>
060 * <td>BTB</td>
061 * <td>Back-to-back constraint: given classes have to be taught in the same room
062 * and they have to follow one strictly after another.</td>
063 * </tr>
064 * <tr>
065 * <td>BTB_TIME</td>
066 * <td>Back-to-back constraint: given classes have to follow one strictly after
067 * another, but they can be taught in different rooms.</td>
068 * </tr>
069 * <tr>
070 * <td>DIFF_TIME</td>
071 * <td>Different time: given classes cannot overlap in time.</td>
072 * </tr>
073 * <tr>
074 * <td>NHB(1), NHB(1.5), NHB(2), ... NHB(8)</td>
075 * <td>Number of hours between: between the given classes, the exact number of
076 * hours have to be kept.</td>
077 * </tr>
078 * <tr>
079 * <td>SAME_START</td>
080 * <td>Same starting hour: given classes have to start in the same hour.</td>
081 * </tr>
082 * <tr>
083 * <td>SAME_ROOM</td>
084 * <td>Same room: given classes have to be placed in the same room.</td>
085 * </tr>
086 * <tr>
087 * <td>NHB_GTE(1)</td>
088 * <td>Greater than or equal to 1 hour between: between the given classes, the
089 * number of hours have to be one or more.</td>
090 * </tr>
091 * <tr>
092 * <td>NHB_LT(6)</td>
093 * <td>Less than 6 hours between: between the given classes, the number of hours
094 * have to be less than six.</td>
095 * </tr>
096 * </table>
097 * 
098 * @version CourseTT 1.3 (University Course Timetabling)<br>
099 *          Copyright (C) 2006 - 2014 Tomáš Müller<br>
100 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
101 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
102 * <br>
103 *          This library is free software; you can redistribute it and/or modify
104 *          it under the terms of the GNU Lesser General Public License as
105 *          published by the Free Software Foundation; either version 3 of the
106 *          License, or (at your option) any later version. <br>
107 * <br>
108 *          This library is distributed in the hope that it will be useful, but
109 *          WITHOUT ANY WARRANTY; without even the implied warranty of
110 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
111 *          Lesser General Public License for more details. <br>
112 * <br>
113 *          You should have received a copy of the GNU Lesser General Public
114 *          License along with this library; if not see
115 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
116 */
117public class GroupConstraint extends ConstraintWithContext<Lecture, Placement, GroupConstraint.GroupConstraintContext> {
118    private Long iConstraintId;
119    private int iPreference;
120    private ConstraintTypeInterface iType;
121    private boolean iIsRequired;
122    private boolean iIsProhibited;
123    private int iDayOfWeekOffset = 0;
124    private boolean iPrecedenceConsiderDatePatterns = true;
125    private boolean iPrecedenceSkipSameDatePatternCheck = true;
126    private boolean iMaxNHoursADayConsiderDatePatterns = true;
127    private boolean iMaxNHoursADayPrecideComputation = false;
128    private int iForwardCheckMaxDepth = 2;
129    private int iForwardCheckMaxDomainSize = 1000;
130    private int iNrWorkDays = 5;
131    private int iFirstWorkDay = 0;
132    private String iOnlineRoom = null;
133    
134    /**
135     * Group constraints that can be checked on pairs of classes (e.g., same room means any two classes are in the same room),
136     * only need to implement this interface.
137     */
138    public static interface PairCheck {
139        /**
140         * Check whether the constraint is satisfied for the given two assignments (required / preferred case)
141         * @param gc Calling group constraint 
142         * @param plc1 First placement
143         * @param plc2 Second placement
144         * @return true if constraint is satisfied
145         */
146        public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2);
147        /**
148         * Check whether the constraint is satisfied for the given two assignments (prohibited / discouraged case)
149         * @param gc Calling group constraint 
150         * @param plc1 First placement
151         * @param plc2 Second placement
152         * @return true if constraint is satisfied
153         */
154        public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2);
155    }
156    
157    /**
158     * Group constraints that can be checked on pairs of classes (e.g., same room means any two classes are in the same room),
159     * only need to implement this interface. Unlike {@link PairCheck}, this check is also given current assignment.
160     */
161    public static interface AssignmentPairCheck {
162        /**
163         * Check whether the constraint is satisfied for the given two assignments (required / preferred case)
164         * @param assignment current assignment
165         * @param gc Calling group constraint 
166         * @param plc1 First placement
167         * @param plc2 Second placement
168         * @return true if constraint is satisfied
169         */
170        public boolean isSatisfied(Assignment<Lecture, Placement> assignment, GroupConstraint gc, Placement plc1, Placement plc2);
171        /**
172         * Check whether the constraint is satisfied for the given two assignments (prohibited / discouraged case)
173         * @param assignment current assignment
174         * @param gc Calling group constraint 
175         * @param plc1 First placement
176         * @param plc2 Second placement
177         * @return true if constraint is satisfied
178         */
179        public boolean isViolated(Assignment<Lecture, Placement> assignment, GroupConstraint gc, Placement plc1, Placement plc2);
180    }
181    
182    /**
183     * Group constraints that can have parameters need to implement this interface instead of {@link AssignmentPairCheck} or {@link PairCheck}.
184     */
185    public static interface AssignmentParameterPairCheck<P> {
186        /**
187         * Check whether the constraint is satisfied for the given two assignments (required / preferred case)
188         * @param assignment current assignment
189         * @param parameter constraint dependent parameter(s)
190         * @param gc Calling group constraint 
191         * @param plc1 First placement
192         * @param plc2 Second placement
193         * @return true if constraint is satisfied
194         */
195        public boolean isSatisfied(Assignment<Lecture, Placement> assignment, P parameter, GroupConstraint gc, Placement plc1, Placement plc2);
196        /**
197         * Check whether the constraint is satisfied for the given two assignments (prohibited / discouraged case)
198         * @param assignment current assignment
199         * @param parameter constraint dependent parameter(s)
200         * @param gc Calling group constraint 
201         * @param plc1 First placement
202         * @param plc2 Second placement
203         * @return true if constraint is satisfied
204         */
205        public boolean isViolated(Assignment<Lecture, Placement> assignment, P parameter, GroupConstraint gc, Placement plc1, Placement plc2);
206        
207        /**
208         * Create constraint type with the parameters taken from the provided reference
209         * @param reference constraint reference, including parameter(s)
210         * @param referenceRegExp reference regular expression defined on the constraint type
211         * @return constraint type with the parameter
212         */
213        public ParametrizedConstraintType<P> create(String reference, String referenceRegExp);
214    }
215    
216    /**
217     * Group constraint building blocks (individual constraints that need more than {@link PairCheck})
218     */
219    public static enum Flag {
220        /** Back-to-back constraint (sequence check) */
221        BACK_TO_BACK,
222        /** Can share room flag */
223        CAN_SHARE_ROOM,
224        /** Maximum hours a day (number of slots a day check) */
225        MAX_HRS_DAY,
226        /** Children cannot overlap */
227        CH_NOTOVERLAP;
228        /** Bit number (to combine flags) */
229        int flag() { return 1 << ordinal(); }
230    }
231    
232    /**
233     * Constraint type interface
234     */
235    public static interface ConstraintTypeInterface extends AssignmentPairCheck {
236        /** Constraint type
237         * @return constraint type
238         */
239        public ConstraintType type();
240        
241        /** Constraint reference
242         * @return constraint reference
243         **/
244        public String reference();
245        
246        /** Constraint name
247         * @return constraint name
248         **/
249        public String getName();
250        
251        /** Minimum (gap) parameter
252         * @return minimum gap (first constraint parameter)
253         **/
254        public int getMin();
255        
256        /** Maximum (gap, hours a day) parameter 
257         * @return maximum gap (second constraint parameter) 
258         **/
259        public int getMax();
260        
261        /** Flag check (true if contains given flag) 
262         * @param f a flag to check
263         * @return true if present
264         **/
265        public boolean is(Flag f);
266    }
267    
268    /**
269     * Constraint type with a parameter
270     */
271    public static class ParametrizedConstraintType<P> implements ConstraintTypeInterface {
272        private String iReference;
273        private ConstraintType iType;
274        private Integer iMin = null, iMax = null;
275        private String iName = null;
276        private P iParameter;
277        
278        /**
279         * Constructor
280         * @param type constraint type
281         * @param parameter parameter parsed from the reference using {@link AssignmentParameterPairCheck#create(String, String)}
282         * @param reference constraint reference with parameters
283         */
284        public ParametrizedConstraintType(ConstraintType type, P parameter, String reference) {
285            iType = type; iParameter = parameter; iReference = reference;
286        }
287
288        @Override
289        @SuppressWarnings("unchecked")
290        public boolean isSatisfied(Assignment<Lecture, Placement> assignment, GroupConstraint gc, Placement plc1, Placement plc2) {
291            return ((AssignmentParameterPairCheck<P>)iType.iAssignmentPairCheck).isSatisfied(assignment, iParameter, gc, plc1, plc2);
292        }
293
294        @Override
295        @SuppressWarnings("unchecked")
296        public boolean isViolated(Assignment<Lecture, Placement> assignment, GroupConstraint gc, Placement plc1, Placement plc2) {
297            return ((AssignmentParameterPairCheck<P>)iType.iAssignmentPairCheck).isViolated(assignment, iParameter, gc, plc1, plc2);
298        }
299
300        /**
301         * Return constraint's parameter
302         * @return constraint's parameter
303         */
304        public P getParameter() { return iParameter; }
305        @Override
306        public ConstraintType type() { return iType; }
307        @Override
308        public String reference() { return iReference; }
309        @Override
310        public String getName() { return (iName == null ? iType.getName() : iName); }
311        @Override
312        public int getMin() { return (iMin == null ? iType.getMin() : iMin); }
313        @Override
314        public int getMax() { return (iMax == null ? iType.getMax() : iMax); }
315        @Override
316        public boolean is(Flag f) { return iType.is(f); }
317        public ParametrizedConstraintType<P> setMin(int min) { iMin = min; return this; }
318        public ParametrizedConstraintType<P> setMax(int max) { iMax = max; return this; }
319        public ParametrizedConstraintType<P> setName(String name) { iName = name; return this; }
320    }
321    
322    /**
323     * Group constraint type.
324     */
325    public static enum ConstraintType implements ConstraintTypeInterface {
326        /**
327         * Same Time: Given classes must be taught at the same time of day (independent of the actual day the classes meet).
328         * For the classes of the same length, this is the same constraint as same start. For classes of different length,
329         * the shorter one cannot start before, nor end after, the longer one.<BR>
330         * When prohibited or (strongly) discouraged: one class may not meet on any day at a time of day that overlaps with
331         * that of the other. For example, one class can not meet M 7:30 while the other meets F 7:30. Note the difference
332         * here from the different time constraint that only prohibits the actual class meetings from overlapping.
333         */
334        SAME_TIME("SAME_TIME", "Same Time", new PairCheck() {
335            @Override
336            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
337                return sameHours(plc1.getTimeLocation().getStartSlot(), plc1.getTimeLocation().getLength(),
338                        plc2.getTimeLocation().getStartSlot(), plc2.getTimeLocation().getLength());
339            }
340            @Override
341            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
342                return !(plc1.getTimeLocation().shareHours(plc2.getTimeLocation()));
343            }}),
344        /**
345         * Same Days: Given classes must be taught on the same days. In case of classes of different time patterns, a class
346         * with fewer meetings must meet on a subset of the days used by the class with more meetings. For example, if one
347         * class pattern is 3x50, all others given in the constraint can only be taught on Monday, Wednesday, or Friday.
348         * For a 2x100 class MW, MF, WF is allowed but TTh is prohibited.<BR>
349         * When prohibited or (strongly) discouraged: any pair of classes classes cannot be taught on the same days (cannot
350         *  overlap in days). For instance, if one class is MFW, the second has to be TTh.
351         */
352        SAME_DAYS("SAME_DAYS", "Same Days", new PairCheck() {
353            @Override
354            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
355                return sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray());
356            }
357            @Override
358            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
359                return !plc1.getTimeLocation().shareDays(plc2.getTimeLocation());
360            }}),
361        /**
362         * Back-To-Back &amp; Same Room: Classes must be offered in adjacent time segments and must be placed in the same room.
363         * Given classes must also be taught on the same days.<BR>
364         * When prohibited or (strongly) discouraged: classes cannot be back-to-back. There must be at least half-hour
365         * between these classes, and they must be taught on the same days and in the same room.
366         */
367        BTB("BTB", "Back-To-Back & Same Room", new PairCheck() {
368            @Override
369            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
370                return
371                    plc1.sameRooms(plc2) &&
372                    sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray());
373            }
374            @Override
375            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
376                return
377                    plc1.sameRooms(plc2) &&
378                    sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray());
379            }}, Flag.BACK_TO_BACK),
380        /**
381         * Back-To-Back: Classes must be offered in adjacent time segments but may be placed in different rooms. Given classes
382         * must also be taught on the same days.<BR>
383         * When prohibited or (strongly) discouraged: no pair of classes can be taught back-to-back. They may not overlap in time,
384         * but must be taught on the same days. This means that there must be at least half-hour between these classes. 
385         */
386        BTB_TIME("BTB_TIME", "Back-To-Back", new PairCheck() {
387            @Override
388            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
389                return sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray());
390            }
391            @Override
392            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
393                return sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray());
394            }}, Flag.BACK_TO_BACK),
395        /**
396         * Different Time: Given classes cannot overlap in time. They may be taught at the same time of day if they are on
397         * different days. For instance, MF 7:30 is compatible with TTh 7:30.<BR>
398         * When prohibited or (strongly) discouraged: every pair of classes in the constraint must overlap in time. 
399         */
400        DIFF_TIME("DIFF_TIME", "Different Time", new PairCheck() {
401            @Override
402            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
403                return !plc1.getTimeLocation().hasIntersection(plc2.getTimeLocation());
404            }
405            @Override
406            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
407                return plc1.getTimeLocation().hasIntersection(plc2.getTimeLocation());
408            }}),
409        /**
410         * 1 Hour Between: Given classes must have exactly 1 hour in between the end of one and the beginning of another.
411         * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR>
412         * When prohibited or (strongly) discouraged: classes can not have 1 hour in between. They may not overlap in time
413         * but must be taught on the same days.
414         */
415        NHB_1("NHB(1)", "1 Hour Between", 10, 12, BTB_TIME.check(), Flag.BACK_TO_BACK),
416        /**
417         * 2 Hours Between: Given classes must have exactly 2 hours in between the end of one and the beginning of another.
418         * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR>
419         * When prohibited or (strongly) discouraged: classes can not have 2 hours in between. They may not overlap in time
420         * but must be taught on the same days.
421         */
422        NHB_2("NHB(2)", "2 Hours Between", 20, 24, BTB_TIME.check(), Flag.BACK_TO_BACK),
423        /**
424         * 3 Hours Between: Given classes must have exactly 3 hours in between the end of one and the beginning of another.
425         * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR>
426         * When prohibited or (strongly) discouraged: classes can not have 3 hours in between. They may not overlap in time
427         * but must be taught on the same days.
428         */
429        NHB_3("NHB(3)", "3 Hours Between", 30, 36, BTB_TIME.check(), Flag.BACK_TO_BACK),
430        /**
431         * 4 Hours Between: Given classes must have exactly 4 hours in between the end of one and the beginning of another.
432         * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR>
433         * When prohibited or (strongly) discouraged: classes can not have 4 hours in between. They may not overlap in time
434         * but must be taught on the same days.
435         */
436        NHB_4("NHB(4)", "4 Hours Between", 40, 48, BTB_TIME.check(), Flag.BACK_TO_BACK),
437        /**
438         * 5 Hours Between: Given classes must have exactly 5 hours in between the end of one and the beginning of another.
439         * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR>
440         * When prohibited or (strongly) discouraged: classes can not have 5 hours in between. They may not overlap in time
441         * but must be taught on the same days.
442         */
443        NHB_5("NHB(5)", "5 Hours Between", 50, 60, BTB_TIME.check(), Flag.BACK_TO_BACK),
444        /**
445         * 6 Hours Between: Given classes must have exactly 6 hours in between the end of one and the beginning of another.
446         * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR>
447         * When prohibited or (strongly) discouraged: classes can not have 6 hours in between. They may not overlap in time
448         * but must be taught on the same days.
449         */
450        NHB_6("NHB(6)", "6 Hours Between", 60, 72, BTB_TIME.check(), Flag.BACK_TO_BACK),
451        /**
452         * 7 Hours Between: Given classes must have exactly 7 hours in between the end of one and the beginning of another.
453         * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR>
454         * When prohibited or (strongly) discouraged: classes can not have 7 hours in between. They may not overlap in time
455         * but must be taught on the same days.
456         */
457        NHB_7("NHB(7)", "7 Hours Between", 70, 84, BTB_TIME.check(), Flag.BACK_TO_BACK),
458        /**
459         * 8 Hours Between: Given classes must have exactly 8 hours in between the end of one and the beginning of another.
460         * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR>
461         * When prohibited or (strongly) discouraged: classes can not have 8 hours in between. They may not overlap in time
462         * but must be taught on the same days.
463         */
464        NHB_8("NHB(8)", "8 Hours Between", 80, 96, BTB_TIME.check(), Flag.BACK_TO_BACK),
465        /**
466         * Same Start Time: Given classes must start during the same half-hour period of a day (independent of the actual
467         * day the classes meet). For instance, MW 7:30 is compatible with TTh 7:30 but not with MWF 8:00.<BR>
468         * When prohibited or (strongly) discouraged: any pair of classes in the given constraint cannot start during the
469         * same half-hour period of any day of the week.
470         */
471        SAME_START("SAME_START", "Same Start Time", new PairCheck() {
472            @Override
473            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
474                return
475                    (plc1.getTimeLocation().getStartSlot() % Constants.SLOTS_PER_DAY) == 
476                    (plc2.getTimeLocation().getStartSlot() % Constants.SLOTS_PER_DAY);
477            }
478            @Override
479            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
480                return
481                    (plc1.getTimeLocation().getStartSlot() % Constants.SLOTS_PER_DAY) != 
482                    (plc2.getTimeLocation().getStartSlot() % Constants.SLOTS_PER_DAY);
483            }}),
484        /**
485         * Same Room: Given classes must be taught in the same room.<BR>
486         * When prohibited or (strongly) discouraged: any pair of classes in the constraint cannot be taught in the same room.
487         */
488        SAME_ROOM("SAME_ROOM", "Same Room", new PairCheck() {
489            @Override
490            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
491                return plc1.sameRooms(plc2);
492            }
493            @Override
494            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
495                return !plc1.sameRooms(plc2);
496            }}),
497        /**
498         * At Least 1 Hour Between: Given classes have to have 1 hour or more in between.<BR>
499         * When prohibited or (strongly) discouraged: given classes have to have less than 1 hour in between.
500         */
501        NHB_GTE_1("NHB_GTE(1)", "At Least 1 Hour Between", 6, 288, BTB_TIME.check(), Flag.BACK_TO_BACK),
502        /**
503         * Less Than 6 Hours Between: Given classes must have less than 6 hours from end of first class to the beginning of
504         * the next. Given classes must also be taught on the same days.<BR>
505         * When prohibited or (strongly) discouraged: given classes must have 6 or more hours between. This constraint does
506         * not carry over from classes taught at the end of one day to the beginning of the next.
507         */
508        NHB_LT_6("NHB_LT(6)", "Less Than 6 Hours Between", 0, 72, BTB_TIME.check(), Flag.BACK_TO_BACK),
509        /**
510         * 1.5 Hour Between: Given classes must have exactly 90 minutes in between the end of one and the beginning of another.
511         * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR>
512         * When prohibited or (strongly) discouraged: classes can not have 90 minutes in between. They may not overlap in time
513         * but must be taught on the same days.
514         */
515        NHB_1_5("NHB(1.5)", "1.5 Hour Between", 15, 18, BTB_TIME.check(), Flag.BACK_TO_BACK),
516        /**
517         * 4.5 Hours Between: Given classes must have exactly 4.5 hours in between the end of one and the beginning of another.
518         * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR>
519         * When prohibited or (strongly) discouraged: classes can not have 4.5 hours in between. They may not overlap in time
520         * but must be taught on the same days.
521         */
522        NHB_4_5("NHB(4.5)", "4.5 Hours Between", 45, 54, BTB_TIME.check(), Flag.BACK_TO_BACK),
523        /**
524         * Same Students: Given classes are treated as they are attended by the same students, i.e., they cannot overlap in time
525         * and if they are back-to-back the assigned rooms cannot be too far (student limit is used).
526         */
527        SAME_STUDENTS("SAME_STUDENTS", "Same Students", new PairCheck() {
528            @Override
529            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
530                return !JenrlConstraint.isInConflict(plc1, plc2, ((TimetableModel)gc.getModel()).getDistanceMetric(), ((TimetableModel)gc.getModel()).getStudentWorkDayLimit());
531            }
532            @Override
533            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
534                return true;
535            }}),
536        /**
537         * Same Instructor: Given classes are treated as they are taught by the same instructor, i.e., they cannot overlap in time
538         * and if they are back-to-back the assigned rooms cannot be too far (instructor limit is used).<BR>
539         * If the constraint is required and the classes are back-to-back, discouraged and strongly discouraged distances between
540         * assigned rooms are also considered.
541         */
542        SAME_INSTR("SAME_INSTR", "Same Instructor", new PairCheck() {
543            @Override
544            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
545                TimeLocation t1 = plc1.getTimeLocation(), t2 = plc2.getTimeLocation();
546                if (t1.shareDays(t2) && t1.shareWeeks(t2)) {
547                    if (t1.shareHours(t2)) return false; // overlap
548                    DistanceMetric m = ((TimetableModel)gc.getModel()).getDistanceMetric();
549                    if ((t1.getStartSlot() + t1.getLength() == t2.getStartSlot() || t2.getStartSlot() + t2.getLength() == t1.getStartSlot())) {
550                        if (Placement.getDistanceInMeters(m, plc1, plc2) > m.getInstructorProhibitedLimit())
551                            return false;
552                    } else if (m.doComputeDistanceConflictsBetweenNonBTBClasses()) {
553                        if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot() && 
554                            Placement.getDistanceInMinutes(m, plc1, plc2) > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength()))
555                            return false;
556                        if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot() &&
557                            Placement.getDistanceInMinutes(m, plc1, plc2) > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength()))
558                            return false;
559                    }
560                }
561                return true;
562            }
563            @Override
564            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
565                return true;
566            }}),
567        /**
568         * Can Share Room: Given classes can share the room (use the room in the same time) if the room is big enough.
569         */
570        CAN_SHARE_ROOM("CAN_SHARE_ROOM", "Can Share Room", Flag.CAN_SHARE_ROOM),
571        /**
572         * Precedence: Given classes have to be taught in the given order (the first meeting of the first class has to end before
573         * the first meeting of the second class etc.)<BR>
574         * When prohibited or (strongly) discouraged: classes have to be taught in the order reverse to the given one.
575         */
576        PRECEDENCE("PRECEDENCE", "Precedence", new PairCheck() {
577            @Override
578            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
579                return gc.isPrecedence(plc1, plc2, true, true);
580            }
581            @Override
582            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
583                return gc.isPrecedence(plc1, plc2, false, true);
584            }}),
585        /**
586         * Back-To-Back Day: Classes must be offered on adjacent days and may be placed in different rooms.<BR>
587         * When prohibited or (strongly) discouraged: classes can not be taught on adjacent days. They also can not be taught
588         * on the same days. This means that there must be at least one day between these classes.
589         */
590        BTB_DAY("BTB_DAY", "Back-To-Back Day", new PairCheck() {
591            @Override
592            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
593                return
594                    !sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) &&
595                    gc.isBackToBackDays(plc1.getTimeLocation(), plc2.getTimeLocation());
596            }
597            @Override
598            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
599                return
600                    !sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) &&
601                    !gc.isBackToBackDays(plc1.getTimeLocation(), plc2.getTimeLocation());
602            }}),
603        /**
604         * Meet Together: Given classes are meeting together (same as if the given classes require constraints Can Share Room,
605         * Same Room, Same Time and Same Days all together).
606         */
607        MEET_WITH("MEET_WITH", "Meet Together", new PairCheck() {
608            @Override
609            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
610                return
611                        plc1.sameRooms(plc2) &&
612                        sameHours(plc1.getTimeLocation().getStartSlot(), plc1.getTimeLocation().getLength(),
613                                plc2.getTimeLocation().getStartSlot(), plc2.getTimeLocation().getLength()) &&
614                        sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray());
615                        
616            }
617            @Override
618            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
619                return true;
620            }}, Flag.CAN_SHARE_ROOM),
621        /**
622         * More Than 1 Day Between: Given classes must have two or more days in between.<br>
623         * When prohibited or (strongly) discouraged: given classes must be offered on adjacent days or with at most one day in between.
624         */
625        NDB_GT_1("NDB_GT_1", "More Than 1 Day Between", new PairCheck() {
626            @Override
627            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
628                return
629                    !sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) &&
630                    gc.isNrDaysBetweenGreaterThanOne(plc1.getTimeLocation(), plc2.getTimeLocation());
631            }
632            @Override
633            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
634                return
635                    !sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) &&
636                    !gc.isNrDaysBetweenGreaterThanOne(plc1.getTimeLocation(), plc2.getTimeLocation());
637            }}),
638        /**
639         * Children Cannot Overlap: If parent classes do not overlap in time, children classes can not overlap in time as well.<BR>
640         * Note: This constraint only needs to be put on the parent classes. Preferred configurations are Required All Classes
641         * or Pairwise (Strongly) Preferred.
642         */
643        CH_NOTOVERLAP("CH_NOTOVERLAP", "Children Cannot Overlap", new AssignmentPairCheck() {
644            @Override
645            public boolean isSatisfied(Assignment<Lecture, Placement> assignment, GroupConstraint gc, Placement plc1, Placement plc2) {
646                return gc.isChildrenNotOverlap(assignment, plc1.variable(), plc1, plc2.variable(), plc2);
647            }
648            @Override
649            public boolean isViolated(Assignment<Lecture, Placement> assignment, GroupConstraint gc, Placement plc1, Placement plc2) {
650                return true;
651            }}),
652        /**
653         * Next Day: The second class has to be placed on the following day of the first class (if the first class is on Friday,
654         * second class have to be on Monday).<br>
655         * When prohibited or (strongly) discouraged: The second class has to be placed on the previous day of the first class
656         * (if the first class is on Monday, second class have to be on Friday).<br>
657         * Note: This constraint works only between pairs of classes.
658         */
659        FOLLOWING_DAY("FOLLOWING_DAY", "Next Day", new PairCheck() {
660            @Override
661            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
662                return gc.isFollowingDay(plc1, plc2, true);
663            }
664            @Override
665            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
666                return gc.isFollowingDay(plc1, plc2, false);
667            }}),
668        /**
669         * Two Days After: The second class has to be placed two days after the first class (Monday &rarr; Wednesday, Tuesday &rarr; 
670         * Thurday, Wednesday &rarr; Friday, Thursday &rarr; Monday, Friday &rarr; Tuesday).<br>
671         * When prohibited or (strongly) discouraged: The second class has to be placed two days before the first class (Monday &rarr;
672         * Thursday, Tuesday &rarr; Friday, Wednesday &rarr; Monday, Thursday &rarr; Tuesday, Friday &rarr; Wednesday).<br>
673         * Note: This constraint works only between pairs of classes.
674         */
675        EVERY_OTHER_DAY("EVERY_OTHER_DAY", "Two Days After", new PairCheck() {
676            @Override
677            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
678                return gc.isEveryOtherDay(plc1, plc2, true);
679            }
680            @Override
681            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
682                return gc.isEveryOtherDay(plc1, plc2, false);
683            }}),
684        /**
685         * At Most 3 Hours A Day: Classes are to be placed in a way that there is no more than three hours in any day.
686         */
687       MAX_HRS_DAY_3("MAX_HRS_DAY(3)", "At Most 3 Hours A Day", 36, null, Flag.MAX_HRS_DAY),        
688       /**
689        * At Most 4 Hours A Day: Classes are to be placed in a way that there is no more than four hours in any day.
690        */
691       MAX_HRS_DAY_4("MAX_HRS_DAY(4)", "At Most 4 Hours A Day", 48, null, Flag.MAX_HRS_DAY),        
692       /**
693         * At Most 5 Hours A Day: Classes are to be placed in a way that there is no more than five hours in any day.
694         */
695       MAX_HRS_DAY_5("MAX_HRS_DAY(5)", "At Most 5 Hours A Day", 60, null, Flag.MAX_HRS_DAY),        
696       /**
697        * At Most 6 Hours A Day: Classes are to be placed in a way that there is no more than six hours in any day.
698        */
699       MAX_HRS_DAY_6("MAX_HRS_DAY(6)", "At Most 6 Hours A Day", 72, null, Flag.MAX_HRS_DAY),
700       /**
701        * At Most 7 Hours A Day: Classes are to be placed in a way that there is no more than seven hours in any day.
702        */
703       MAX_HRS_DAY_7("MAX_HRS_DAY(7)", "At Most 7 Hours A Day", 84, null, Flag.MAX_HRS_DAY),
704       /**
705        * At Most 8 Hours A Day: Classes are to be placed in a way that there is no more than eight hours in any day.
706        */
707       MAX_HRS_DAY_8("MAX_HRS_DAY(8)", "At Most 8 Hours A Day", 96, null, Flag.MAX_HRS_DAY),
708       /**
709        * At Most 9 Hours A Day: Classes are to be placed in a way that there is no more than nine hours in any day.
710        */
711       MAX_HRS_DAY_9("MAX_HRS_DAY(9)", "At Most 9 Hours A Day", 108, null, Flag.MAX_HRS_DAY),
712       /**
713        * At Most 10 Hours A Day: Classes are to be placed in a way that there is no more than ten hours in any day.
714        */
715       MAX_HRS_DAY_10("MAX_HRS_DAY(10)", "At Most 10 Hours A Day", 120, null, Flag.MAX_HRS_DAY),
716        /**
717         * At Most X Hours A Day: Classes are to be placed in a way that there is no more than given number of hours in any day.
718         */
719        MAX_HRS_DAY("MAX_HRS_DAY\\(([0-9\\.]+)\\)", "At Most N Hours A Day", new AssignmentParameterPairCheck<Integer>() {
720            @Override
721            public boolean isSatisfied(Assignment<Lecture, Placement> assignment, Integer parameter, GroupConstraint gc, Placement plc1, Placement plc2) {
722                return true;
723            }
724            @Override
725            public boolean isViolated(Assignment<Lecture, Placement> assignment, Integer parameter, GroupConstraint gc, Placement plc1, Placement plc2) {
726                return true;
727            }
728            @Override
729            public ParametrizedConstraintType<Integer> create(String reference, String regexp) {
730                Matcher matcher = Pattern.compile(regexp).matcher(reference);
731                if (matcher.find()) {
732                    double hours = Double.parseDouble(matcher.group(1));
733                    int slots = (int)Math.round(12.0 * hours);
734                    return new ParametrizedConstraintType<Integer>(ConstraintType.MAX_HRS_DAY, slots, reference)
735                            .setName("At Most " + matcher.group(1) + " Hours A Day")
736                            .setMin(slots).setMax(slots);
737                }
738                return null;
739            }}, Flag.MAX_HRS_DAY),
740        /**
741         * Given classes must be taught during the same weeks (i.e., must have the same date pattern).<br>
742         * When prohibited or (strongly) discouraged: any two classes must have non overlapping date patterns.
743         */
744        SAME_WEEKS("SAME_WEEKS", "Same Weeks", new PairCheck() {
745            @Override
746            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
747                return plc1.getTimeLocation().getWeekCode().equals(plc2.getTimeLocation().getWeekCode());
748            }
749            @Override
750            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
751                return !plc1.getTimeLocation().shareWeeks(plc2.getTimeLocation());
752            }}),
753        /**
754         * Classes (of different courses) are to be attended by the same students. For instance,
755         * if class A1 (of a course A) and class B1 (of a course B) are linked, a student requesting
756         * both courses must attend A1 if and only if he also attends B1. This is a student sectioning
757         * constraint that is interpreted as Same Students constraint during course timetabling.
758         */
759        LINKED_SECTIONS("LINKED_SECTIONS", "Linked Classes", SAME_STUDENTS.check()),
760        /**
761         * Back-To-Back Precedence: Given classes have to be taught in the given order, on the same days,
762         * and in adjacent time segments.
763         * When prohibited or (strongly) discouraged: Given classes have to be taught in the given order,
764         * on the same days, but cannot be back-to-back.
765         */
766        BTB_PRECEDENCE("BTB_PRECEDENCE", "Back-To-Back Precedence", new PairCheck() {
767            @Override
768            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
769                return gc.isPrecedence(plc1, plc2, true, false) && sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray());
770            }
771            @Override
772            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
773                return gc.isPrecedence(plc1, plc2, true, false) && sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray());
774            }}, Flag.BACK_TO_BACK),   
775            
776        /**
777         * Same Days-Time: Given classes must be taught at the same time of day and on the same days.
778         * It is the combination of Same Days and Same Time distribution preferences.     
779         * When prohibited or (strongly) discouraged: Any pair of classes classes cannot be taught on the same days
780         * during the same time.
781         */             
782        SAME_DAYS_TIME("SAME_D_T", "Same Days-Time", new PairCheck() {
783            @Override
784            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
785                return sameHours(plc1.getTimeLocation().getStartSlot(), plc1.getTimeLocation().getLength(),
786                        plc2.getTimeLocation().getStartSlot(), plc2.getTimeLocation().getLength()) &&
787                        sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray());
788            }
789            @Override
790            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
791                return !plc1.getTimeLocation().shareHours(plc2.getTimeLocation()) ||
792                        !plc1.getTimeLocation().shareDays(plc2.getTimeLocation());
793            }}),
794        /**
795         * Same Days-Room-Time: Given classes must be taught at the same time of day, on the same days and in the same room.
796         * It is the combination of Same Days, Same Time and Same Room distribution preferences.
797         * Note that this constraint is the same as Meet Together constraint, except it does not allow room sharing. In other words,
798         * it is only useful when these classes are taught during non-overlapping date patterns.
799         * When prohibited or (strongly) discouraged: Any pair of classes classes cannot be taught on the same days 
800         * during the same time in the same room.
801         */            
802        SAME_DAYS_ROOM_TIME("SAME_D_R_T", "Same Days-Room-Time", new PairCheck() {
803            @Override
804            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
805                return sameHours(plc1.getTimeLocation().getStartSlot(), plc1.getTimeLocation().getLength(),
806                        plc2.getTimeLocation().getStartSlot(), plc2.getTimeLocation().getLength()) &&
807                        sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) &&
808                        plc1.sameRooms(plc2);
809            }
810            @Override
811            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
812                return !plc1.getTimeLocation().shareHours(plc2.getTimeLocation()) ||
813                        !plc1.getTimeLocation().shareDays(plc2.getTimeLocation()) ||
814                        !plc1.sameRooms(plc2);
815            }}),
816        /**
817         * 6 Hour Work Day: Classes are to be placed in a way that there is no more than six hours between the start of the first class and the end of the class one on any day.
818         */
819        WORKDAY_6("WORKDAY(6)", "6 Hour Work Day", 72, new PairCheck() {
820            @Override
821            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
822                TimeLocation t1 = plc1.getTimeLocation(), t2 = plc2.getTimeLocation();
823                if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) return true;
824                return Math.max(t1.getStartSlot() + t1.getLength(), t2.getStartSlot() + t2.getLength()) - Math.min(t1.getStartSlot(), t2.getStartSlot()) <= gc.getType().getMax();
825            }
826            @Override
827            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { return true; }
828            }),
829        /**
830         * 7 Hour Work Day: Classes are to be placed in a way that there is no more than seven hours between the start of the first class and the end of the class one on any day.
831         */
832        WORKDAY_7("WORKDAY(7)", "7 Hour Work Day", 84, WORKDAY_6.check()),
833        /**
834         * 8 Hour Work Day: Classes are to be placed in a way that there is no more than eight hours between the start of the first class and the end of the class one on any day.
835         */
836        WORKDAY_8("WORKDAY(8)", "8 Hour Work Day", 96, WORKDAY_6.check()),
837        /**
838         * 9 Hour Work Day: Classes are to be placed in a way that there is no more than nine hours between the start of the first class and the end of the class one on any day.
839         */
840        WORKDAY_9("WORKDAY(9)", "9 Hour Work Day", 108, WORKDAY_6.check()),
841        /**
842         * 10 Hour Work Day: Classes are to be placed in a way that there is no more than ten hours between the start of the first class and the end of the class one on any day.
843         */
844        WORKDAY_10("WORKDAY(10)", "10 Hour Work Day", 120, WORKDAY_6.check()),
845        /**
846         * 11 Hour Work Day: Classes are to be placed in a way that there is no more than eleven hours between the start of the first class and the end of the class one on any day.
847         */
848        WORKDAY_11("WORKDAY(11)", "11 Hour Work Day", 132, WORKDAY_6.check()),
849        /**
850         * 12 Hour Work Day: Classes are to be placed in a way that there is no more than twelve hours between the start of the first class and the end of the class one on any day.
851         */
852        WORKDAY_12("WORKDAY(12)", "12 Hour Work Day", 144, WORKDAY_6.check()),
853        /**
854         * 4 Hour Work Day: Classes are to be placed in a way that there is no more than four hours between the start of the first class and the end of the class one on any day.
855         */
856        WORKDAY_4("WORKDAY(4)", "4 Hour Work Day", 48, WORKDAY_6.check()),
857        /**
858         * 5 Hour Work Day: Classes are to be placed in a way that there is no more than five hours between the start of the first class and the end of the class one on any day.
859         */
860        WORKDAY_5("WORKDAY(5)", "5 Hour Work Day", 60, WORKDAY_6.check()),
861          /**
862         * Work Day: Classes are to be placed in a way that there is no more than given number of hours between the start of the first class and the end of the class one on any day.
863         */
864        WORKDAY("WORKDAY\\(([0-9\\.]+)\\)", "Work Day", new AssignmentParameterPairCheck<Integer>() {
865            @Override
866            public boolean isSatisfied(Assignment<Lecture, Placement> assignment, Integer parameter, GroupConstraint gc, Placement plc1, Placement plc2) {
867                TimeLocation t1 = plc1.getTimeLocation(), t2 = plc2.getTimeLocation();
868                if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) return true;
869                return Math.max(t1.getStartSlot() + t1.getLength(), t2.getStartSlot() + t2.getLength()) - Math.min(t1.getStartSlot(), t2.getStartSlot()) <= parameter;
870            }
871            @Override
872            public boolean isViolated(Assignment<Lecture, Placement> assignment, Integer parameter, GroupConstraint gc, Placement plc1, Placement plc2) {
873                return true;
874            }
875            @Override
876            public ParametrizedConstraintType<Integer> create(String reference, String regexp) {
877                Matcher matcher = Pattern.compile(regexp).matcher(reference);
878                if (matcher.find()) {
879                    double hours = Double.parseDouble(matcher.group(1));
880                    int slots = (int)Math.round(12.0 * hours);
881                    return new ParametrizedConstraintType<Integer>(ConstraintType.WORKDAY, slots, reference)
882                            .setName(matcher.group(1) + " Hour Work Day").setMin(slots).setMax(slots);
883                }
884                return null;
885            }}),
886        /**
887         * Meet Together & Same Weeks: Given classes are meeting together (same as if the given classes require constraints Can Share Room,
888         * Same Room, Same Time, Same Days and Same Weeks all together).
889         */
890        MEET_WITH_WEEKS("MEET_WITH_WEEKS", "Meet Together & Same Weeks", new PairCheck() {
891            @Override
892            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
893                return
894                        plc1.sameRooms(plc2) &&
895                        sameHours(plc1.getTimeLocation().getStartSlot(), plc1.getTimeLocation().getLength(), plc2.getTimeLocation().getStartSlot(), plc2.getTimeLocation().getLength()) &&
896                        sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) &&
897                        plc1.getTimeLocation().getWeekCode().equals(plc2.getTimeLocation().getWeekCode());
898            }
899            @Override
900            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
901                return true;
902            }}, Flag.CAN_SHARE_ROOM),
903        MIN_GAP("MIN_GAP\\(([0-9\\.]+)\\)", "Mininal Gap Between Classes", new AssignmentParameterPairCheck<Integer>() {
904            @Override
905            public boolean isSatisfied(Assignment<Lecture, Placement> assignment, Integer parameter, GroupConstraint gc, Placement plc1, Placement plc2) {
906                TimeLocation t1 = plc1.getTimeLocation(), t2 = plc2.getTimeLocation();
907                if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) return true;
908                return t1.getStartSlot() + t1.getLength() + parameter <= t2.getStartSlot() ||
909                        t2.getStartSlot() + t2.getLength() + parameter <= t1.getStartSlot();
910            }
911            @Override
912            public boolean isViolated(Assignment<Lecture, Placement> assignment, Integer parameter, GroupConstraint gc, Placement plc1, Placement plc2) { return true; }
913            @Override
914            public ParametrizedConstraintType<Integer> create(String reference, String regexp) {
915                Matcher matcher = Pattern.compile(regexp).matcher(reference);
916                if (matcher.find()) {
917                    double hours = Double.parseDouble(matcher.group(1));
918                    int slots = (int)Math.round(12.0 * hours);
919                    return new ParametrizedConstraintType<Integer>(ConstraintType.MIN_GAP, slots, reference)
920                            .setName("At Least " + matcher.group(1) + " Hours Between Classes")
921                            .setMin(slots).setMax(slots);
922                }
923                return null;
924            }}),
925        /**
926         * Given classes must be taught on weeks that are back-to-back (the gap between the two assigned date patterns is less than a week).<br>
927         * When prohibited or (strongly) discouraged: any two classes must have at least a week gap in between.
928         */
929        BTB_WEEKS("BTB_WEEKS", "Back-To-Back Weeks", new PairCheck() {
930            @Override
931            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
932                if (gc.variables().size() <= 2) {
933                    return gc.isBackToBackWeeks(plc1.getTimeLocation(), plc2.getTimeLocation());
934                } else {
935                    int totalWeeks = 0;
936                    for (Lecture l: gc.variables())
937                        totalWeeks += l.getMinWeeks();
938                    return gc.isMaxWeekSpan(plc1.getTimeLocation(), plc2.getTimeLocation(), totalWeeks);
939                }
940            }
941            @Override
942            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
943                return gc.isNotBackToBackWeeks(plc1.getTimeLocation(), plc2.getTimeLocation());
944            }}),
945        /**
946         * Given classes must be taught on weeks that are back-to-back and in the given order.<br>
947         * When prohibited or (strongly) discouraged: given classes must be taught on weeks in the given order with at least one week between any two following classes.
948         */
949        FOLLOWING_WEEKS("FOLLOWING_WEEKS", "Following Weeks", new PairCheck() {
950            @Override
951            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
952                return gc.isFollowingWeeksBTB(plc1, plc2, true);
953            }
954            @Override
955            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
956                return gc.isFollowingWeeksBTB(plc1, plc2, false);
957            }}),
958        /**
959         * Given classes must be taught on the same dates. If one of the classes meets more often, the class meeting less often can only meet on the dates when the other class is meeting.<br>
960         * When prohibited or (strongly) discouraged: given classes cannot be taught on the same days (there cannot be a date when both classes are meeting).<br>
961         * Note: unlike with the same days/weeks constraint, this constraint consider individual meeting dates of both classes.
962         */
963        SAME_DATES("SAME_DATES", "Same Dates", new PairCheck() {
964            @Override
965            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
966                return gc.isSameDates(plc1.getTimeLocation(), plc2.getTimeLocation());
967            }
968            @Override
969            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
970                return gc.isDifferentDates(plc1.getTimeLocation(), plc2.getTimeLocation());
971            }}),
972        /**
973         * Same Days-Room-Start: Given classes must start at the same time of day, on the same days and in the same room.
974         * It is the combination of Same Days, Same Start and Same Room distribution preferences.
975         * When prohibited or (strongly) discouraged: Any pair of classes classes cannot be taught on the same days 
976         * during the same time in the same room.
977         */
978        SAME_DAYS_ROOM_START("SAME_DAY_ROOM_START", "Same Days-Room-Start", new PairCheck() {
979            @Override
980            public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
981                return (plc1.getTimeLocation().getStartSlot() % Constants.SLOTS_PER_DAY) == 
982                        (plc2.getTimeLocation().getStartSlot() % Constants.SLOTS_PER_DAY) &&
983                        sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) &&
984                        plc1.sameRooms(plc2);
985            }
986            @Override
987            public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
988                return !plc1.getTimeLocation().shareHours(plc2.getTimeLocation()) ||
989                        !plc1.getTimeLocation().shareDays(plc2.getTimeLocation()) ||
990                        !plc1.sameRooms(plc2);
991            }}),
992        /**
993         * Overnight: The constraint has two parameters: hours and distance in minutes. There is a problem when
994         * an evening class is followed by a morning class the next day and the time in between is less then the
995         * given number of hours, but only when the distance in minutes between them is greater than the
996         * given number of minutes.
997         */
998        DAYBREAK("DAYBREAK\\(([0-9\\.]+),(-?[0-9]+)\\)", "Daybreak", new AssignmentParameterPairCheck<Integer[]>() {
999            @Override
1000            public boolean isSatisfied(Assignment<Lecture, Placement> assignment, Integer[] param, GroupConstraint gc, Placement plc1, Placement plc2) {
1001                TimeLocation t1 = plc1.getTimeLocation();
1002                TimeLocation t2 = plc2.getTimeLocation();
1003                if (288 + t2.getStartSlot() - t1.getStartSlot() - t1.getLength() < gc.getType().getMin()) { // close to each other
1004                    if (gc.isNextDay(t1, t2)) { // next day
1005                        if (gc.getType().getMax() < 0) { // no distance check
1006                            return false;
1007                        } else {
1008                            DistanceMetric m = ((TimetableModel)gc.getModel()).getDistanceMetric();
1009                            if (Placement.getDistanceInMinutes(m, plc1, plc2) > gc.getType().getMax()) { // distance check
1010                                return false;
1011                            }
1012                        }
1013                    }
1014                } else if (288 + t1.getStartSlot() - t2.getStartSlot() - t2.getLength() < gc.getType().getMin()) { // close to each other, but the other way around
1015                    if (gc.isNextDay(t2, t1)) { // next day
1016                        if (gc.getType().getMax() < 0) { // no distance check
1017                            return false;
1018                        } else {
1019                            DistanceMetric m = ((TimetableModel)gc.getModel()).getDistanceMetric();
1020                            if (Placement.getDistanceInMinutes(m, plc2, plc1) > gc.getType().getMax()) { // distance check
1021                                return false;
1022                            }
1023                        }
1024                    }
1025                }
1026                return true;
1027            }
1028            @Override
1029            public boolean isViolated(Assignment<Lecture, Placement> assignment, Integer[] parameter, GroupConstraint gc, Placement plc1, Placement plc2) {
1030                return true;
1031            }
1032            @Override
1033            public ParametrizedConstraintType<Integer[]> create(String reference, String regexp) {
1034                Matcher matcher = Pattern.compile(regexp).matcher(reference);
1035                if (matcher.find()) {
1036                    double hours = Double.parseDouble(matcher.group(1));
1037                    int distanceSlots = (int)Math.round(12.0 * hours);
1038                    int distanceInMinutes = Integer.parseInt(matcher.group(2));
1039                    return new ParametrizedConstraintType<Integer[]>(ConstraintType.DAYBREAK, 
1040                            new Integer[] {distanceSlots, distanceInMinutes}, reference)
1041                            .setName("Daybreak of " + ( distanceSlots / 12.0) + " hours" + (distanceInMinutes >= 0 ? " when over " + distanceInMinutes + " mins": ""))
1042                            .setMin(distanceSlots).setMax(distanceInMinutes);
1043                }
1044                return null;
1045            }}),
1046            /**
1047             * Online/Offline Room: Given classes, if scheduled on the same day, must be all in the online room or
1048             * none of them can be in the online room. This means there is a conflict when two classes
1049             * are placed on the same day, but one is in online room and the other is not.
1050             */
1051            ONLINE_ROOM("ONLINE_ROOM", "Online/Offline Room", new PairCheck() {
1052                @Override
1053                public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
1054                    TimeLocation t1 = plc1.getTimeLocation();
1055                    TimeLocation t2 = plc2.getTimeLocation();
1056                    if (t1.shareDays(t2) && t1.shareWeeks(t2)) {
1057                        return gc.isOnline(plc1) == gc.isOnline(plc2);
1058                    } else {
1059                        // different days > do not care
1060                        return true;
1061                    }
1062                }
1063                @Override
1064                public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { return true; }
1065            }),
1066            /**
1067             * Same Days-Time-Weeks: Given classes must be taught at the same time of day, on the same days and on the same weeks
1068             * (i.e., must have the same date pattern).
1069             * It is the combination of Same Days, Same Time, and Same Weeks distribution preferences.
1070             * When prohibited or (strongly) discouraged: Any pair of classes classes cannot be taught on the same days
1071             * during the same time and during overlapping date patterns. In other words, the given classes cannot overlap.
1072             */
1073            SAME_DATE_TIME_WEEKS("SAME_DTW", "Same Days-Time-Weeks", new PairCheck() {
1074                @Override
1075                public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) {
1076                    return sameHours(plc1.getTimeLocation().getStartSlot(), plc1.getTimeLocation().getLength(),
1077                            plc2.getTimeLocation().getStartSlot(), plc2.getTimeLocation().getLength()) &&
1078                            sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) &&
1079                            plc1.getTimeLocation().getWeekCode().equals(plc2.getTimeLocation().getWeekCode());
1080                }
1081                @Override
1082                public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) {
1083                    return !plc1.getTimeLocation().shareHours(plc2.getTimeLocation()) ||
1084                            !plc1.getTimeLocation().shareDays(plc2.getTimeLocation()) ||
1085                            !plc1.getTimeLocation().shareWeeks(plc2.getTimeLocation());
1086                }}),
1087        ;
1088        
1089        String iReference, iName;
1090        int iFlag = 0;
1091        Flag[] iFlags = null;
1092        int iMin = 0, iMax = 0;
1093        PairCheck iCheck = null;
1094        AssignmentPairCheck iAssignmentCheck = null;
1095        AssignmentParameterPairCheck<?> iAssignmentPairCheck = null;
1096        ConstraintType(String reference, String name, Flag... flags) {
1097            iReference = reference;
1098            iName = name;
1099            iFlags = flags;
1100            for (Flag f: flags)
1101                iFlag |= f.flag();
1102        }
1103        ConstraintType(String reference, String name, PairCheck check, Flag... flags) {
1104            this(reference, name, flags);
1105            iCheck = check;
1106        }
1107        ConstraintType(String reference, String name, AssignmentPairCheck check, Flag... flags) {
1108            this(reference, name, flags);
1109            iAssignmentCheck = check;
1110        }
1111        ConstraintType(String reference, String name, int limit, PairCheck check, Flag... flags) {
1112            this(reference, name, check, flags);
1113            iMin = iMax = limit;
1114        }
1115        ConstraintType(String reference, String name, int min, int max, PairCheck check, Flag... flags) {
1116            this(reference, name, check, flags);
1117            iMin = min;
1118            iMax = max;
1119        }
1120        ConstraintType(String reference, String name, AssignmentParameterPairCheck<?> check, Flag... flags) {
1121            this(reference, name, flags);
1122            iAssignmentPairCheck = check;
1123        }
1124        
1125        /**
1126         * Constraint type
1127         * @return constraint type
1128         */
1129        @Override
1130        public ConstraintType type() { return this; }
1131
1132        /** Constraint reference
1133         * @return constraint reference
1134         **/
1135        @Override
1136        public String reference() { return iReference; }
1137        
1138        /** Constraint name
1139         * @return constraint name
1140         **/
1141        @Override
1142        public String getName() { return iName; }
1143        
1144        /** Minimum (gap) parameter
1145         * @return minimum gap (first constraint parameter)
1146         **/
1147        @Override
1148        public int getMin() { return iMin; }
1149        
1150        /** Maximum (gap, hours a day) parameter 
1151         * @return maximum gap (second constraint parameter) 
1152         **/
1153        @Override
1154        public int getMax() { return iMax; }
1155        
1156        /** Flag check (true if contains given flag) 
1157         * @param f a flag to check
1158         * @return true if present
1159         **/
1160        @Override
1161        public boolean is(Flag f) { return (iFlag & f.flag()) != 0; }
1162
1163        /** Constraint type from reference 
1164         * @param reference constraint reference
1165         * @return constraint of the reference
1166         * @deprecated use {@link GroupConstraint#getConstraintType(String)} instead
1167         **/
1168        @Deprecated
1169        public static ConstraintType get(String reference) {
1170            for (ConstraintType t: ConstraintType.values())
1171                if (t.reference().equals(reference)) return t;
1172            return null;
1173        }
1174        
1175        /** True if a required or preferred constraint is satisfied between a pair of placements 
1176         * @param assignment current assignment
1177         * @param gc current constraint
1178         * @param plc1 first placement
1179         * @param plc2 second placement
1180         * @return true if the two placements are consistent with the constraint if preferred or required 
1181         **/ 
1182        @Override
1183        public boolean isSatisfied(Assignment<Lecture, Placement> assignment, GroupConstraint gc, Placement plc1, Placement plc2) {
1184            if (iCheck != null && !iCheck.isSatisfied(gc, plc1, plc2))
1185                return false;
1186            if (iAssignmentCheck != null && assignment != null && !iAssignmentCheck.isSatisfied(assignment, gc, plc1, plc2))
1187                return false;
1188            return true;
1189        }
1190        
1191        /** True if a prohibited or discouraged constraint is satisfied between a pair of placements 
1192         * @param assignment current assignment
1193         * @param gc current constraint
1194         * @param plc1 first placement
1195         * @param plc2 second placement
1196         * @return true if the two placements are consistent with the constraint if discouraged or prohibited 
1197         **/ 
1198        @Override
1199        public boolean isViolated(Assignment<Lecture, Placement> assignment, GroupConstraint gc, Placement plc1, Placement plc2) { 
1200            if (iCheck != null && !iCheck.isViolated(gc, plc1, plc2))
1201                return false;
1202            if (iAssignmentCheck != null && assignment != null && !iAssignmentCheck.isViolated(assignment, gc, plc1, plc2))
1203                return false;
1204            return true;
1205        }
1206        /** Pair check */
1207        private PairCheck check() { return iCheck; }
1208    }
1209    
1210    /** Constraint type from reference 
1211     * @param reference constraint reference
1212     * @return constraint of the reference
1213     **/
1214    public static ConstraintTypeInterface getConstraintType(String reference) {
1215        for (ConstraintType t: ConstraintType.values()) {
1216            if (t.reference().equals(reference)) return t;
1217            if (t.iAssignmentPairCheck != null && reference.matches(t.reference()))
1218                return t.iAssignmentPairCheck.create(reference, t.reference());
1219        }
1220        return null;
1221    }    
1222
1223    public GroupConstraint() {
1224    }
1225    
1226    @Override
1227    public void setModel(Model<Lecture, Placement> model) {
1228        super.setModel(model);
1229        if (model != null) {
1230            DataProperties config = ((TimetableModel)model).getProperties();
1231            iDayOfWeekOffset = config.getPropertyInt("DatePattern.DayOfWeekOffset", 0);
1232            iPrecedenceConsiderDatePatterns = config.getPropertyBoolean("Precedence.ConsiderDatePatterns", true);
1233            iPrecedenceSkipSameDatePatternCheck = config.getPropertyBoolean("Precedence.SkipSameDatePatternCheck", true);
1234            iForwardCheckMaxDepth = config.getPropertyInt("ForwardCheck.MaxDepth", iForwardCheckMaxDepth);
1235            iForwardCheckMaxDomainSize = config.getPropertyInt("ForwardCheck.MaxDomainSize", iForwardCheckMaxDomainSize);
1236            iMaxNHoursADayPrecideComputation = config.getPropertyBoolean("MaxNHoursADay.PreciseComputation", iMaxNHoursADayPrecideComputation);
1237            iMaxNHoursADayConsiderDatePatterns = config.getPropertyBoolean("MaxNHoursADay.ConsiderDatePatterns", iMaxNHoursADayConsiderDatePatterns);
1238            iNrWorkDays = (config.getPropertyInt("General.LastWorkDay", 4) - config.getPropertyInt("General.FirstWorkDay", 0) + 1);
1239            if (iNrWorkDays <= 0) iNrWorkDays += 7;
1240            if (iNrWorkDays > 7) iNrWorkDays -= 7;
1241            iFirstWorkDay = config.getPropertyInt("General.FirstWorkDay", 0);
1242            iOnlineRoom = config.getProperty("General.OnlineRoom", "(?i)ONLINE|");
1243        }
1244    }
1245
1246    @Override
1247    public void addVariable(Lecture lecture) {
1248        if (!variables().contains(lecture))
1249            super.addVariable(lecture);
1250        if (getType().is(Flag.CH_NOTOVERLAP)) {
1251            if (lecture.getChildrenSubpartIds() != null) {
1252                for (Long subpartId: lecture.getChildrenSubpartIds()) {
1253                    for (Lecture ch : lecture.getChildren(subpartId)) {
1254                        if (!variables().contains(ch))
1255                            super.addVariable(ch);
1256                    }
1257                }
1258            }
1259        }
1260    }
1261
1262    @Override
1263    public void removeVariable(Lecture lecture) {
1264        if (variables().contains(lecture))
1265            super.removeVariable(lecture);
1266        if (getType().is(Flag.CH_NOTOVERLAP)) {
1267            if (lecture.getChildrenSubpartIds() != null) {
1268                for (Long subpartId: lecture.getChildrenSubpartIds()) {
1269                    for (Lecture ch : lecture.getChildren(subpartId)) {
1270                        if (variables().contains(ch))
1271                            super.removeVariable(ch);
1272                    }
1273                }
1274            }
1275        }
1276    }
1277
1278    /**
1279     * Constructor
1280     * 
1281     * @param id
1282     *            constraint id
1283     * @param type
1284     *            constraString type (e.g, {@link ConstraintType#SAME_TIME})
1285     * @param preference
1286     *            time preference ("R" for required, "P" for prohibited, "-2",
1287     *            "-1", "1", "2" for soft preference)
1288     */
1289    public GroupConstraint(Long id, ConstraintTypeInterface type, String preference) {
1290        iConstraintId = id;
1291        iType = type;
1292        iIsRequired = preference.equals(Constants.sPreferenceRequired);
1293        iIsProhibited = preference.equals(Constants.sPreferenceProhibited);
1294        iPreference = Constants.preference2preferenceLevel(preference);
1295    }
1296
1297    /** Constraint id 
1298     * @return constraint unique id
1299     **/
1300    public Long getConstraintId() {
1301        return iConstraintId;
1302    }
1303
1304    @Override
1305    public long getId() {
1306        return (iConstraintId == null ? -1 : iConstraintId.longValue());
1307    }
1308    
1309    /** Generated unique id 
1310     * @return generated unique id
1311     **/
1312    protected long getGeneratedId() {
1313        return iId;
1314    }
1315
1316    /** Return constraint type (e.g, {@link ConstraintType#SAME_TIME}) 
1317     * @return constraint type
1318     **/
1319    public ConstraintTypeInterface getType() {
1320        return iType;
1321    }
1322
1323    /**
1324     * Set constraint type
1325     * @param type constraint type
1326     */
1327    public void setType(ConstraintType type) {
1328        iType = type;
1329    }
1330
1331    /** Is constraint required 
1332     * @return true if required
1333     **/
1334    public boolean isRequired() {
1335        return iIsRequired;
1336    }
1337
1338    /** Is constraint prohibited 
1339     * @return true if prohibited
1340     **/
1341    public boolean isProhibited() {
1342        return iIsProhibited;
1343    }
1344
1345    /**
1346     * Prolog reference: "R" for required, "P" for prohibited", "-2",.."2" for
1347     * preference
1348     * @return prolog preference
1349     */
1350    public String getPrologPreference() {
1351        return Constants.preferenceLevel2preference(iPreference);
1352    }
1353
1354    @Override
1355    public boolean isConsistent(Placement value1, Placement value2) {
1356        if (!isHard())
1357            return true;
1358        if (!isSatisfiedPair(null, value1, value2))
1359            return false;
1360        if (getType().is(Flag.BACK_TO_BACK)) {
1361            HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>();
1362            assignments.put(value1.variable(), value1);
1363            assignments.put(value2.variable(), value2);
1364            if (!isSatisfiedSeq(null, assignments, null))
1365                return false;
1366        }
1367        if (getType().is(Flag.MAX_HRS_DAY)) {
1368            HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>();
1369            assignments.put(value1.variable(), value1);
1370            assignments.put(value2.variable(), value2);
1371            for (int dayCode: Constants.DAY_CODES) {
1372                if (iMaxNHoursADayPrecideComputation) {
1373                    for (IntEnumeration dates = value1.getTimeLocation().getDates(iDayOfWeekOffset); dates.hasMoreElements(); ) {
1374                        int date = dates.nextElement();
1375                        if (!value2.getTimeLocation().hasDate(date, iDayOfWeekOffset)) continue;
1376                        if (nrSlotsADay(null, date, assignments, null) > getType().getMax()) return false;
1377                    }
1378                } else if (iMaxNHoursADayConsiderDatePatterns) {
1379                    for (BitSet week: ((TimetableModel)getModel()).getWeeks()) {
1380                        if (!value1.getTimeLocation().shareWeeks(week) && !value2.getTimeLocation().shareWeeks(week)) continue;
1381                        if (nrSlotsADay(null, dayCode, week, assignments, null) > getType().getMax()) return false;
1382                    }
1383                } else {
1384                    if (nrSlotsADay(null, dayCode, null, assignments, null) > getType().getMax()) return false;
1385                }
1386            }
1387        }
1388        return true;
1389    }
1390
1391    @Override
1392    public void computeConflicts(Assignment<Lecture, Placement> assignment, Placement value, Set<Placement> conflicts) {
1393        computeConflicts(assignment, value, conflicts, true);
1394    }
1395    
1396    @Override
1397    public void computeConflictsNoForwardCheck(Assignment<Lecture, Placement> assignment, Placement value, Set<Placement> conflicts) {
1398        computeConflicts(assignment, value, conflicts, false);
1399    }
1400    
1401    public void computeConflicts(Assignment<Lecture, Placement> assignment, Placement value, Set<Placement> conflicts, boolean fwdCheck) {
1402        if (!isHard())
1403            return;
1404        for (Lecture v : variables()) {
1405            if (v.equals(value.variable()))
1406                continue; // ignore this variable
1407            Placement p = assignment.getValue(v);
1408            if (p == null)
1409                continue; // there is an unassigned variable -- great, still a chance to get violated
1410            if (!isSatisfiedPair(assignment, p, value))
1411                conflicts.add(p);
1412        }
1413        if (getType().is(Flag.BACK_TO_BACK)) {
1414            HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>();
1415            assignments.put(value.variable(), value);
1416            if (!isSatisfiedSeq(assignment, assignments, conflicts))
1417                conflicts.add(value);
1418        }
1419        if (getType().is(Flag.MAX_HRS_DAY)) {
1420            HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>();
1421            assignments.put(value.variable(), value);
1422            for (int dayCode: Constants.DAY_CODES) {
1423                if (iMaxNHoursADayPrecideComputation) {
1424                    for (IntEnumeration dates = value.getTimeLocation().getDates(iDayOfWeekOffset); dates.hasMoreElements(); ) {
1425                        int date = dates.nextElement();
1426                        if (nrSlotsADay(assignment, date, assignments, conflicts) > getType().getMax()) {
1427                            List<Placement> adepts = new ArrayList<Placement>();
1428                            for (Lecture l: variables()) {
1429                                if (l.equals(value.variable()) || l.isConstant()) continue;
1430                                Placement p = assignment.getValue(l);
1431                                if (p == null || conflicts.contains(p) || p.getTimeLocation() == null) continue;
1432                                if (!p.getTimeLocation().hasDate(date, iDayOfWeekOffset)) continue;
1433                                adepts.add(p);
1434                            }
1435                            do {
1436                                if (adepts.isEmpty()) { conflicts.add(value); break; }
1437                                Placement conflict = ToolBox.random(adepts);
1438                                adepts.remove(conflict);
1439                                conflicts.add(conflict);
1440                            } while (nrSlotsADay(assignment, date, assignments, conflicts) > getType().getMax());
1441                        }
1442                    }
1443                } else if (iMaxNHoursADayConsiderDatePatterns) {
1444                    for (BitSet week: ((TimetableModel)getModel()).getWeeks()) {
1445                        if (!value.getTimeLocation().shareWeeks(week)) continue;
1446                        if (nrSlotsADay(assignment, dayCode, week, assignments, conflicts) > getType().getMax()) {
1447                            List<Placement> adepts = new ArrayList<Placement>();
1448                            for (Lecture l: variables()) {
1449                                if (l.equals(value.variable()) || l.isConstant()) continue;
1450                                Placement p = assignment.getValue(l);
1451                                if (p == null || conflicts.contains(p) || p.getTimeLocation() == null) continue;
1452                                if ((p.getTimeLocation().getDayCode() & dayCode) == 0 || !p.getTimeLocation().shareWeeks(week)) continue;
1453                                adepts.add(p);
1454                            }
1455                            do {
1456                                if (adepts.isEmpty()) { conflicts.add(value); break; }
1457                                Placement conflict = ToolBox.random(adepts);
1458                                adepts.remove(conflict);
1459                                conflicts.add(conflict);
1460                            } while (nrSlotsADay(assignment, dayCode, week, assignments, conflicts) > getType().getMax());
1461                        }
1462                    }
1463                } else {
1464                    if (nrSlotsADay(assignment, dayCode, null, assignments, conflicts) > getType().getMax()) {
1465                        List<Placement> adepts = new ArrayList<Placement>();
1466                        for (Lecture l: variables()) {
1467                            if (l.equals(value.variable()) || l.isConstant()) continue;
1468                            Placement p = assignment.getValue(l);
1469                            if (p == null || conflicts.contains(p) || p.getTimeLocation() == null) continue;
1470                            if ((p.getTimeLocation().getDayCode() & dayCode) == 0) continue;
1471                            adepts.add(p);
1472                        }
1473                        do {
1474                            if (adepts.isEmpty()) { conflicts.add(value); break; }
1475                            Placement conflict = ToolBox.random(adepts);
1476                            adepts.remove(conflict);
1477                            conflicts.add(conflict);
1478                        } while (nrSlotsADay(assignment, dayCode, null, assignments, conflicts) > getType().getMax());
1479                    }
1480                }
1481            }
1482        }
1483        
1484        // Forward checking
1485        if (fwdCheck) forwardCheck(assignment, value, conflicts, new HashSet<GroupConstraint>(), iForwardCheckMaxDepth - 1);
1486    }
1487    
1488    public void forwardCheck(Assignment<Lecture, Placement> assignment, Placement value, Set<Placement> conflicts, Set<GroupConstraint> ignore, int depth) {
1489        try {
1490            if (depth < 0) return;
1491            ignore.add(this);
1492            
1493            List<Placement> neededSize = null;
1494            
1495            for (Lecture lecture: variables()) {
1496                if (conflicts.contains(value)) break; // already conflicting
1497
1498                if (lecture.equals(value.variable())) continue; // Skip this lecture
1499                Placement current = assignment.getValue(lecture);
1500                if (current != null) { // Has assignment, check whether it is conflicting
1501                    if (isSatisfiedPair(assignment, value, current)) {
1502                        // Increase needed size if the assignment is of the same room and overlapping in time
1503                        if (canShareRoom() && sameRoomAndOverlaps(value, current)) {
1504                            if (neededSize == null) neededSize = new ArrayList<Placement>(); 
1505                            neededSize.add(current);
1506                        }
1507                        continue;
1508                    }
1509                    conflicts.add(current);
1510                }
1511                
1512                // Look for supporting assignments assignment
1513                boolean shareRoomAndOverlaps = canShareRoom();
1514                Placement support = null;
1515                int nrSupports = 0;
1516                if (lecture.nrValues() >= iForwardCheckMaxDomainSize) {
1517                    // ignore variables with large domains
1518                    return;
1519                }
1520                List<Placement> values = lecture.values(assignment);
1521                if (values.isEmpty()) {
1522                    // ignore variables with empty domain
1523                    return;
1524                }
1525                for (Placement other: values) {
1526                    if (nrSupports < 2) {
1527                        if (isSatisfiedPair(assignment, value, other)) {
1528                            if (support == null) support = other;
1529                            nrSupports ++;
1530                            if (shareRoomAndOverlaps && !sameRoomAndOverlaps(value, other))
1531                                shareRoomAndOverlaps = false;
1532                        }
1533                    } else if (shareRoomAndOverlaps && !sameRoomAndOverlaps(value, other) && isSatisfiedPair(assignment, value, other)) {
1534                        shareRoomAndOverlaps = false;
1535                    }
1536                    if (nrSupports > 1 && !shareRoomAndOverlaps)
1537                        break;
1538                }
1539                
1540                // No supporting assignment -> fail
1541                if (nrSupports == 0) {
1542                    conflicts.add(value); // other class cannot be assigned with this value
1543                    return;
1544                }
1545                // Increase needed size if all supporters are of the same room and in overlapping times
1546                if (shareRoomAndOverlaps && support != null) {
1547                    if (neededSize == null) neededSize = new ArrayList<Placement>(); 
1548                    neededSize.add(support);
1549                }
1550
1551                // Only one supporter -> propagate the new assignment over other hard constraints of the lecture
1552                if (nrSupports == 1) {
1553                    for (Constraint<Lecture, Placement> other: lecture.hardConstraints()) {
1554                        if (other instanceof WeakeningConstraint) continue;
1555                        if (other instanceof GroupConstraint) {
1556                            GroupConstraint gc = (GroupConstraint)other;
1557                            if (depth > 0 && !ignore.contains(gc))
1558                                gc.forwardCheck(assignment, support, conflicts, ignore, depth - 1);
1559                        } else {
1560                            other.computeConflicts(assignment, support, conflicts);
1561                        }
1562                    }
1563                    for (GlobalConstraint<Lecture, Placement> other: getModel().globalConstraints()) {
1564                        if (other instanceof WeakeningConstraint) continue;
1565                        other.computeConflicts(assignment, support, conflicts);
1566                    }
1567
1568                    if (conflicts.contains(support))
1569                        conflicts.add(value);
1570                }
1571            }
1572            
1573            if (canShareRoom() && neededSize != null) {
1574                if (value.getRoomLocations() != null) {
1575                    for (RoomLocation room: value.getRoomLocations())
1576                        if (room.getRoomConstraint() != null && !room.getRoomConstraint().checkRoomSize(value, neededSize)) {
1577                            // room is too small to fit all meet with classes
1578                            conflicts.add(value);
1579                        }
1580                } else if (value.getRoomLocation() != null) {
1581                    RoomLocation room = value.getRoomLocation();
1582                    if (room.getRoomConstraint() != null && !room.getRoomConstraint().checkRoomSize(value, neededSize)) {
1583                        // room is too small to fit all meet with classes
1584                        conflicts.add(value);
1585                    }
1586                }
1587            }
1588        } finally {
1589            ignore.remove(this);
1590        }
1591    }
1592
1593    @Override
1594    public boolean inConflict(Assignment<Lecture, Placement> assignment, Placement value) {
1595        if (!isHard())
1596            return false;
1597        for (Lecture v : variables()) {
1598            if (v.equals(value.variable()))
1599                continue; // ignore this variable
1600            Placement p = assignment.getValue(v);
1601            if (p == null)
1602                continue; // there is an unassigned variable -- great, still a chance to get violated
1603            if (!isSatisfiedPair(assignment, p, value))
1604                return true;
1605        }
1606        if (getType().is(Flag.BACK_TO_BACK)) {
1607            HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>();
1608            assignments.put(value.variable(), value);
1609            if (!isSatisfiedSeq(assignment, assignments, null))
1610                return true;
1611        }
1612        if (getType().is(Flag.MAX_HRS_DAY)) {
1613            HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>();
1614            assignments.put(value.variable(), value);
1615            for (int dayCode: Constants.DAY_CODES) {
1616                if (iMaxNHoursADayPrecideComputation) {
1617                    for (IntEnumeration dates = value.getTimeLocation().getDates(iDayOfWeekOffset); dates.hasMoreElements(); ) {
1618                        int date = dates.nextElement();
1619                        if (nrSlotsADay(assignment,date, assignments, null) > getType().getMax())
1620                            return true;
1621                    }
1622                } else if (iMaxNHoursADayConsiderDatePatterns) {
1623                    for (BitSet week: ((TimetableModel)getModel()).getWeeks()) {
1624                        if (!value.getTimeLocation().shareWeeks(week)) continue;
1625                        if (nrSlotsADay(assignment, dayCode, week, assignments, null) > getType().getMax())
1626                            return true;
1627                    }
1628                } else {
1629                    if (nrSlotsADay(assignment, dayCode, null, assignments, null) > getType().getMax()) return true;
1630                }
1631            }
1632        }
1633        
1634        if (!forwardCheck(assignment, value, new HashSet<GroupConstraint>(), iForwardCheckMaxDepth - 1)) return true;
1635        
1636        return false;
1637    }
1638    
1639    public boolean forwardCheck(Assignment<Lecture, Placement> assignment, Placement value, Set<GroupConstraint> ignore, int depth) {
1640        try {
1641            if (depth < 0) return true;
1642            ignore.add(this);
1643            
1644            int neededSize = value.variable().maxRoomUse();
1645            
1646            for (Lecture lecture: variables()) {
1647                if (lecture.equals(value.variable())) continue; // Skip this lecture
1648                Placement current = assignment.getValue(lecture);
1649                if (current != null) { // Has assignment, check whether it is conflicting
1650                    if (isSatisfiedPair(assignment, value, current)) {
1651                        // Increase needed size if the assignment is of the same room and overlapping in time
1652                        if (canShareRoom() && sameRoomAndOverlaps(value, current)) {
1653                            neededSize += lecture.maxRoomUse();
1654                        }
1655                        continue;
1656                    }
1657                    return false;
1658                }
1659                
1660                // Look for supporting assignments assignment
1661                boolean shareRoomAndOverlaps = canShareRoom();
1662                Placement support = null;
1663                int nrSupports = 0;
1664                if (lecture.nrValues() >= iForwardCheckMaxDomainSize) {
1665                    // ignore variables with large domains
1666                    return true;
1667                }
1668                List<Placement> values = lecture.values(assignment);
1669                if (values.isEmpty()) {
1670                    // ignore variables with empty domain
1671                    return true;
1672                }
1673                for (Placement other: lecture.values(assignment)) {
1674                    if (nrSupports < 2) {
1675                        if (isSatisfiedPair(assignment, value, other)) {
1676                            if (support == null) support = other;
1677                            nrSupports ++;
1678                            if (shareRoomAndOverlaps && !sameRoomAndOverlaps(value, other))
1679                                shareRoomAndOverlaps = false;
1680                        }
1681                    } else if (shareRoomAndOverlaps && !sameRoomAndOverlaps(value, other) && isSatisfiedPair(assignment, value, other)) {
1682                        shareRoomAndOverlaps = false;
1683                    }
1684                    if (nrSupports > 1 && !shareRoomAndOverlaps)
1685                        break;
1686                }
1687
1688                // No supporting assignment -> fail
1689                if (nrSupports == 0) {
1690                    return false; // other class cannot be assigned with this value
1691                }
1692                // Increase needed size if all supporters are of the same room and in overlapping times
1693                if (shareRoomAndOverlaps) {
1694                    neededSize += lecture.maxRoomUse();
1695                }
1696
1697                // Only one supporter -> propagate the new assignment over other hard constraints of the lecture
1698                if (nrSupports == 1) {
1699                    for (Constraint<Lecture, Placement> other: lecture.hardConstraints()) {
1700                        if (other instanceof WeakeningConstraint) continue;
1701                        if (other instanceof GroupConstraint) {
1702                            GroupConstraint gc = (GroupConstraint)other;
1703                            if (depth > 0 && !ignore.contains(gc) && !gc.forwardCheck(assignment, support, ignore, depth - 1)) return false;
1704                        } else {
1705                            if (other.inConflict(assignment, support)) return false;
1706                        }
1707                    }
1708                    for (GlobalConstraint<Lecture, Placement> other: getModel().globalConstraints()) {
1709                        if (other instanceof WeakeningConstraint) continue;
1710                        if (other.inConflict(assignment, support)) return false;
1711                    }
1712                }
1713            }
1714            
1715            if (canShareRoom() && neededSize > value.getRoomSize()) {
1716                // room is too small to fit all meet with classes
1717                return false;
1718            }
1719         
1720            return true;
1721        } finally {
1722            ignore.remove(this);
1723        }
1724    }
1725
1726    /** Constraint preference (0 if prohibited or required) 
1727     * @return constraint preference (if soft)
1728     **/
1729    public int getPreference() {
1730        return iPreference;
1731    }
1732
1733    /**
1734     * Current constraint preference (0 if prohibited or required, depends on
1735     * current satisfaction of the constraint)
1736     * @param assignment current assignment
1737     * @return current preference
1738     */
1739    public int getCurrentPreference(Assignment<Lecture, Placement> assignment) {
1740        if (isHard()) return 0; // no preference
1741        if (countAssignedVariables(assignment) < 2) return - Math.abs(iPreference); // not enough variable
1742        if (getType().is(Flag.MAX_HRS_DAY)) { // max hours a day
1743            int over = 0;
1744            for (int dayCode: Constants.DAY_CODES) {
1745                if (iMaxNHoursADayPrecideComputation) {
1746                    Set<Integer> allDates = new HashSet<Integer>();
1747                    for (Lecture v1 : variables()) {
1748                        Placement p1 = assignment.getValue(v1);
1749                        if (p1 == null) continue;
1750                        for (IntEnumeration dates = p1.getTimeLocation().getDates(iDayOfWeekOffset); dates.hasMoreElements(); ) {
1751                            int date = dates.nextElement();
1752                            if (allDates.add(date))
1753                                over += Math.max(0, nrSlotsADay(assignment, date, null, null) - getType().getMax());
1754                        }
1755                    }
1756                } else if (iMaxNHoursADayConsiderDatePatterns) {
1757                    for (BitSet week: ((TimetableModel)getModel()).getWeeks())
1758                        over += Math.max(0, nrSlotsADay(assignment, dayCode, week, null, null) - getType().getMax());
1759                } else {
1760                    over += Math.max(0, nrSlotsADay(assignment, dayCode, null, null, null) - getType().getMax());
1761                }
1762            }
1763            return (over > 0 ? Math.abs(iPreference) * over / 12 : - Math.abs(iPreference));
1764        }
1765        int nrViolatedPairs = 0;
1766        for (Lecture v1 : variables()) {
1767            Placement p1 = assignment.getValue(v1);
1768            if (p1 == null) continue;
1769            for (Lecture v2 : variables()) {
1770                Placement p2 = assignment.getValue(v2);
1771                if (p2 == null || v1.getId() >= v2.getId()) continue;
1772                if (!isSatisfiedPair(assignment, p1, p2)) nrViolatedPairs++;
1773            }
1774        }
1775        if (getType().is(Flag.BACK_TO_BACK)) {
1776            Set<Placement> conflicts = new HashSet<Placement>();
1777            if (isSatisfiedSeq(assignment, new HashMap<Lecture, Placement>(), conflicts))
1778                nrViolatedPairs += conflicts.size();
1779            else
1780                nrViolatedPairs = variables().size();
1781        }
1782        return (nrViolatedPairs > 0 ? Math.abs(iPreference) * nrViolatedPairs : - Math.abs(iPreference));
1783    }
1784
1785    /** Current constraint preference change (if given placement is assigned) 
1786     * @param assignment current assignment
1787     * @param placement placement that is being considered
1788     * @return change in the current preference, if assigned 
1789     **/
1790    public int getCurrentPreference(Assignment<Lecture, Placement> assignment, Placement placement) {
1791        if (isHard()) return 0; // no preference
1792        if (countAssignedVariables(assignment) + (assignment.getValue(placement.variable()) == null ? 1 : 0) < 2) return 0; // not enough variable
1793        if (getType().is(Flag.MAX_HRS_DAY)) {
1794            HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>();
1795            assignments.put(placement.variable(), placement);
1796            HashMap<Lecture, Placement> unassignments = new HashMap<Lecture, Placement>();
1797            unassignments.put(placement.variable(), null);
1798            int after = 0;
1799            int before = 0;
1800            for (int dayCode: Constants.DAY_CODES) {
1801                if (iMaxNHoursADayPrecideComputation) {
1802                    for (IntEnumeration dates = placement.getTimeLocation().getDates(iDayOfWeekOffset); dates.hasMoreElements(); ) {
1803                        int date = dates.nextElement();
1804                        after += Math.max(0, nrSlotsADay(assignment, date, assignments, null) - getType().getMax());
1805                        before += Math.max(0, nrSlotsADay(assignment, date, unassignments, null) - getType().getMax());
1806                    }
1807                } else if (iMaxNHoursADayConsiderDatePatterns) {
1808                    for (BitSet week: ((TimetableModel)getModel()).getWeeks()) {
1809                        after += Math.max(0, nrSlotsADay(assignment, dayCode, week, assignments, null) - getType().getMax());
1810                        before += Math.max(0, nrSlotsADay(assignment, dayCode, week, unassignments, null) - getType().getMax());
1811                    }
1812                } else {
1813                    after += Math.max(0, nrSlotsADay(assignment, dayCode, null, assignments, null) - getType().getMax());
1814                    before += Math.max(0, nrSlotsADay(assignment, dayCode, null, unassignments, null) - getType().getMax());
1815                }
1816            }
1817            return (after > 0 ? Math.abs(iPreference) * after / 12 : - Math.abs(iPreference)) - (before > 0 ? Math.abs(iPreference) * before / 12 : - Math.abs(iPreference));
1818        }
1819        
1820        int nrViolatedPairsAfter = 0;
1821        int nrViolatedPairsBefore = 0;
1822        for (Lecture v1 : variables()) {
1823            for (Lecture v2 : variables()) {
1824                if (v1.getId() >= v2.getId()) continue;
1825                Placement p1 = (v1.equals(placement.variable()) ? null : assignment.getValue(v1));
1826                Placement p2 = (v2.equals(placement.variable()) ? null : assignment.getValue(v2));
1827                if (p1 != null && p2 != null && !isSatisfiedPair(assignment, p1, p2))
1828                    nrViolatedPairsBefore ++;
1829                if (v1.equals(placement.variable())) p1 = placement;
1830                if (v2.equals(placement.variable())) p2 = placement;
1831                if (p1 != null && p2 != null && !isSatisfiedPair(assignment, p1, p2))
1832                    nrViolatedPairsAfter ++;
1833            }
1834        }
1835        
1836        if (getType().is(Flag.BACK_TO_BACK)) {
1837            HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>();
1838            assignments.put(placement.variable(), placement);
1839            Set<Placement> conflicts = new HashSet<Placement>();
1840            if (isSatisfiedSeq(assignment, assignments, conflicts))
1841                nrViolatedPairsAfter += conflicts.size();
1842            else
1843                nrViolatedPairsAfter = variables().size();
1844            
1845            HashMap<Lecture, Placement> unassignments = new HashMap<Lecture, Placement>();
1846            unassignments.put(placement.variable(), null);
1847            Set<Placement> previous = new HashSet<Placement>();
1848            if (isSatisfiedSeq(assignment, unassignments, previous))
1849                nrViolatedPairsBefore += previous.size();
1850            else
1851                nrViolatedPairsBefore = variables().size();
1852        }
1853        
1854        return (nrViolatedPairsAfter > 0 ? Math.abs(iPreference) * nrViolatedPairsAfter : - Math.abs(iPreference)) -
1855                (nrViolatedPairsBefore > 0 ? Math.abs(iPreference) * nrViolatedPairsBefore : - Math.abs(iPreference));
1856    }
1857
1858    @Override
1859    public String toString() {
1860        StringBuffer sb = new StringBuffer();
1861        sb.append(getName());
1862        sb.append(" between ");
1863        for (Iterator<Lecture> e = variables().iterator(); e.hasNext();) {
1864            Lecture v = e.next();
1865            sb.append(v.getName());
1866            if (e.hasNext())
1867                sb.append(", ");
1868        }
1869        return sb.toString();
1870    }
1871
1872    @Override
1873    public boolean isHard() {
1874        return iIsRequired || iIsProhibited;
1875    }
1876
1877    @Override
1878    public String getName() {
1879        return getType().getName();
1880    }
1881
1882
1883    private boolean isPrecedence(Placement p1, Placement p2, boolean firstGoesFirst, boolean considerDatePatterns) {
1884        int ord1 = variables().indexOf(p1.variable());
1885        int ord2 = variables().indexOf(p2.variable());
1886        TimeLocation t1 = null, t2 = null;
1887        if (ord1 < ord2) {
1888            if (firstGoesFirst) {
1889                t1 = p1.getTimeLocation();
1890                t2 = p2.getTimeLocation();
1891            } else {
1892                t2 = p1.getTimeLocation();
1893                t1 = p2.getTimeLocation();
1894            }
1895        } else {
1896            if (!firstGoesFirst) {
1897                t1 = p1.getTimeLocation();
1898                t2 = p2.getTimeLocation();
1899            } else {
1900                t2 = p1.getTimeLocation();
1901                t1 = p2.getTimeLocation();
1902            }
1903        }
1904        if (considerDatePatterns && iPrecedenceConsiderDatePatterns) {
1905            if (iPrecedenceSkipSameDatePatternCheck) {
1906                int m1 = t1.getFirstMeeting(iDayOfWeekOffset), m2 = t2.getFirstMeeting(iDayOfWeekOffset);
1907                if (m1 != m2) return m1 < m2;
1908            } else {
1909                boolean sameDatePattern = (t1.getDatePatternId() != null ? t1.getDatePatternId().equals(t2.getDatePatternId()) : t1.getWeekCode().equals(t2.getWeekCode()));
1910                if (!sameDatePattern) {
1911                    int m1 = t1.getFirstMeeting(iDayOfWeekOffset), m2 = t2.getFirstMeeting(iDayOfWeekOffset);
1912                    if (m1 != m2) return m1 < m2;
1913                }
1914            }
1915        }
1916        if (iFirstWorkDay != 0) {
1917            for (int i = 0; i < Constants.DAY_CODES.length; i++) {
1918                int idx = (i + iFirstWorkDay) % 7;
1919                boolean a = (t1.getDayCode() & Constants.DAY_CODES[idx]) != 0;
1920                boolean b = (t2.getDayCode() & Constants.DAY_CODES[idx]) != 0;
1921                if (b && !a) return false; // second start first
1922                if (a && !b) return true;  // first start first
1923                if (a && b) return t1.getStartSlot() + t1.getLength() <= t2.getStartSlot(); // same day: check times
1924            }
1925        }
1926        return t1.getStartSlots().nextElement() + t1.getLength() <= t2.getStartSlots().nextElement();
1927    }
1928
1929    private boolean isBackToBackDays(TimeLocation t1, TimeLocation t2) {
1930        int f1 = -1, f2 = -1, e1 = -1, e2 = -1;
1931        for (int i = 0; i < Constants.DAY_CODES.length; i++) {
1932            int idx = (i + iFirstWorkDay) % 7;
1933            if ((t1.getDayCode() & Constants.DAY_CODES[idx]) != 0) {
1934                if (f1 < 0)
1935                    f1 = i;
1936                e1 = i;
1937            }
1938            if ((t2.getDayCode() & Constants.DAY_CODES[idx]) != 0) {
1939                if (f2 < 0)
1940                    f2 = i;
1941                e2 = i;
1942            }
1943        }
1944        return (e1 + 1 == f2) || (e2 + 1 == f1);
1945    }
1946    
1947    private boolean isNrDaysBetweenGreaterThanOne(TimeLocation t1, TimeLocation t2) {
1948        int f1 = -1, f2 = -1, e1 = -1, e2 = -1;
1949        for (int i = 0; i < Constants.DAY_CODES.length; i++) {
1950            int idx = (i + iFirstWorkDay) % 7;
1951            if ((t1.getDayCode() & Constants.DAY_CODES[idx]) != 0) {
1952                if (f1 < 0)
1953                    f1 = i;
1954                e1 = i;
1955            }
1956            if ((t2.getDayCode() & Constants.DAY_CODES[idx]) != 0) {
1957                if (f2 < 0)
1958                    f2 = i;
1959                e2 = i;
1960            }
1961        }
1962        return (e1 - f2 > 2) || (e2 - f1 > 2);
1963    }
1964
1965    private boolean isFollowingDay(Placement p1, Placement p2, boolean firstGoesFirst) {
1966        int ord1 = variables().indexOf(p1.variable());
1967        int ord2 = variables().indexOf(p2.variable());
1968        TimeLocation t1 = null, t2 = null;
1969        if (ord1 < ord2) {
1970            if (firstGoesFirst) {
1971                t1 = p1.getTimeLocation();
1972                t2 = p2.getTimeLocation();
1973            } else {
1974                t2 = p1.getTimeLocation();
1975                t1 = p2.getTimeLocation();
1976            }
1977        } else {
1978            if (!firstGoesFirst) {
1979                t1 = p1.getTimeLocation();
1980                t2 = p2.getTimeLocation();
1981            } else {
1982                t2 = p1.getTimeLocation();
1983                t1 = p2.getTimeLocation();
1984            }
1985        }
1986        int f1 = -1, f2 = -1, e1 = -1;
1987        for (int i = 0; i < Constants.DAY_CODES.length; i++) {
1988            int idx = (i + iFirstWorkDay) % 7;
1989            if ((t1.getDayCode() & Constants.DAY_CODES[idx]) != 0) {
1990                if (f1 < 0)
1991                    f1 = i;
1992                e1 = i;
1993            }
1994            if ((t2.getDayCode() & Constants.DAY_CODES[idx]) != 0) {
1995                if (f2 < 0)
1996                    f2 = i;
1997            }
1998        }
1999        return ((e1 + 1) % iNrWorkDays == f2);
2000    }
2001    
2002    private boolean isNextDay(TimeLocation t1, TimeLocation t2) {
2003        if (iPrecedenceConsiderDatePatterns) {
2004            for (Enumeration<Integer> e = t1.getDates(iDayOfWeekOffset); e.hasMoreElements(); ) {
2005                Integer date = e.nextElement();
2006                if (t2.hasDate(date + 1, iDayOfWeekOffset)) return true;
2007            }
2008            return false;
2009        }
2010        for (int i = 0; i < Constants.DAY_CODES.length; i++) {
2011            int i1 = (i + iFirstWorkDay) % 7;
2012            int i2 = (1 + i1) % 7;
2013            boolean a = (t1.getDayCode() & Constants.DAY_CODES[i1]) != 0;
2014            boolean b = (t2.getDayCode() & Constants.DAY_CODES[i2]) != 0;
2015            if (a && b) return true;
2016        }
2017        return false;
2018    }
2019
2020    private boolean isEveryOtherDay(Placement p1, Placement p2, boolean firstGoesFirst) {
2021        int ord1 = variables().indexOf(p1.variable());
2022        int ord2 = variables().indexOf(p2.variable());
2023        TimeLocation t1 = null, t2 = null;
2024        if (ord1 < ord2) {
2025            if (firstGoesFirst) {
2026                t1 = p1.getTimeLocation();
2027                t2 = p2.getTimeLocation();
2028            } else {
2029                t2 = p1.getTimeLocation();
2030                t1 = p2.getTimeLocation();
2031            }
2032        } else {
2033            if (!firstGoesFirst) {
2034                t1 = p1.getTimeLocation();
2035                t2 = p2.getTimeLocation();
2036            } else {
2037                t2 = p1.getTimeLocation();
2038                t1 = p2.getTimeLocation();
2039            }
2040        }
2041        int f1 = -1, f2 = -1, e1 = -1;
2042        for (int i = 0; i < Constants.DAY_CODES.length; i++) {
2043            int idx = (i + iFirstWorkDay) % 7;
2044            if ((t1.getDayCode() & Constants.DAY_CODES[idx]) != 0) {
2045                if (f1 < 0)
2046                    f1 = i;
2047                e1 = i;
2048            }
2049            if ((t2.getDayCode() & Constants.DAY_CODES[idx]) != 0) {
2050                if (f2 < 0)
2051                    f2 = i;
2052            }
2053        }
2054        return ((e1 + 2) % iNrWorkDays == f2);
2055    }
2056
2057    private static boolean sameDays(int[] days1, int[] days2) {
2058        if (days2.length < days1.length)
2059            return sameDays(days2, days1);
2060        int i2 = 0;
2061        for (int i1 = 0; i1 < days1.length; i1++) {
2062            int d1 = days1[i1];
2063            while (true) {
2064                if (i2 == days2.length)
2065                    return false;
2066                int d2 = days2[i2];
2067                if (d1 == d2)
2068                    break;
2069                i2++;
2070                if (i2 == days2.length)
2071                    return false;
2072            }
2073            i2++;
2074        }
2075        return true;
2076    }
2077    
2078    private static boolean sameRoomAndOverlaps(Placement p1, Placement p2) {
2079        return p1.shareRooms(p2) && p1.getTimeLocation() != null && p2.getTimeLocation() != null && p1.getTimeLocation().hasIntersection(p2.getTimeLocation());
2080    }
2081
2082    private static boolean sameHours(int start1, int len1, int start2, int len2) {
2083        if (len1 > len2)
2084            return sameHours(start2, len2, start1, len1);
2085        start1 %= Constants.SLOTS_PER_DAY;
2086        start2 %= Constants.SLOTS_PER_DAY;
2087        return (start1 >= start2 && start1 + len1 <= start2 + len2);
2088    }
2089    
2090    private static boolean canFill(int totalGap, int gapMin, int gapMax, List<Integer> lengths) {
2091        if (gapMin <= totalGap && totalGap <= gapMax)
2092            return true;
2093        if (totalGap < 2 * gapMin)
2094            return false;
2095        for (int i = 0; i < lengths.size(); i++) {
2096            int length = lengths.get(i);
2097            lengths.remove(i);
2098            for (int gap = gapMin; gap <= gapMax; gap++)
2099                if (canFill(totalGap - gap - length, gapMin, gapMax, lengths))
2100                    return true;
2101            lengths.add(i, length);
2102        }
2103        return false;
2104    }
2105
2106    private boolean isSatisfiedSeq(Assignment<Lecture, Placement> assignment, HashMap<Lecture, Placement> assignments, Set<Placement> conflicts) {
2107        if (conflicts == null)
2108            return isSatisfiedSeqCheck(assignment, assignments, conflicts);
2109        else {
2110            Set<Placement> bestConflicts = isSatisfiedRecursive(assignment, 0, assignments, conflicts,
2111                    new HashSet<Placement>(), null);
2112            if (bestConflicts == null)
2113                return false;
2114            conflicts.addAll(bestConflicts);
2115            return true;
2116        }
2117    }
2118
2119    private Set<Placement> isSatisfiedRecursive(Assignment<Lecture, Placement> assignment, int idx, HashMap<Lecture, Placement> assignments,
2120            Set<Placement> conflicts, Set<Placement> newConflicts, Set<Placement> bestConflicts) {
2121        if (idx == variables().size() && newConflicts.isEmpty())
2122            return bestConflicts;
2123        if (isSatisfiedSeqCheck(assignment, assignments, conflicts)) {
2124            if (bestConflicts == null) {
2125                return new HashSet<Placement>(newConflicts);
2126            } else {
2127                int b = 0, n = 0;
2128                for (Placement value: assignments.values()) {
2129                    if (value != null && bestConflicts.contains(value)) b++;
2130                    if (value != null && newConflicts.contains(value)) n++;
2131                }
2132                if (n < b || (n == b && newConflicts.size() < bestConflicts.size()))
2133                    return new HashSet<Placement>(newConflicts);
2134            }
2135            return bestConflicts;
2136        }
2137        if (idx == variables().size())
2138            return bestConflicts;
2139        bestConflicts = isSatisfiedRecursive(assignment, idx + 1, assignments, conflicts, newConflicts,
2140                bestConflicts);
2141        Lecture lecture = variables().get(idx);
2142        //if (assignments != null && assignments.containsKey(lecture))
2143        //    return bestConflicts;
2144        Placement placement = null;
2145        if (assignments != null && assignments.containsKey(lecture))
2146            placement = assignments.get(lecture);
2147        else if (assignment != null)
2148            placement = assignment.getValue(lecture);
2149        if (placement == null)
2150            return bestConflicts;
2151        if (conflicts != null && conflicts.contains(placement))
2152            return bestConflicts;
2153        conflicts.add(placement);
2154        newConflicts.add(placement);
2155        bestConflicts = isSatisfiedRecursive(assignment, idx + 1, assignments, conflicts, newConflicts, bestConflicts);
2156        newConflicts.remove(placement);
2157        conflicts.remove(placement);
2158        return bestConflicts;
2159    }
2160
2161    private boolean isSatisfiedSeqCheck(Assignment<Lecture, Placement> assignment, HashMap<Lecture, Placement> assignments, Set<Placement> conflicts) {
2162        if (!getType().is(Flag.BACK_TO_BACK)) return true;
2163        int gapMin = getType().getMin();
2164        int gapMax = getType().getMax();
2165
2166        List<Integer> lengths = new ArrayList<Integer>();
2167
2168        Placement[] res = new Placement[Constants.SLOTS_PER_DAY];
2169        for (int i = 0; i < Constants.SLOTS_PER_DAY; i++)
2170            res[i] = null;
2171
2172        int nrLectures = 0;
2173
2174        for (Lecture lecture : variables()) {
2175            Placement placement = null;
2176            if (assignments != null && assignments.containsKey(lecture))
2177                placement = assignments.get(lecture);
2178            else if (assignment != null)
2179                placement = assignment.getValue(lecture);
2180            if (placement == null) {
2181                if (!lecture.timeLocations().isEmpty())
2182                        lengths.add(lecture.timeLocations().get(0).getLength());
2183            } else if (conflicts != null && conflicts.contains(placement)) {
2184                if (!lecture.timeLocations().isEmpty())
2185                        lengths.add(lecture.timeLocations().get(0).getLength());
2186            } else {
2187                int pos = placement.getTimeLocation().getStartSlot();
2188                int length = placement.getTimeLocation().getLength();
2189                for (int j = pos; j < pos + length; j++) {
2190                    if (res[j] != null) {
2191                        if (conflicts == null)
2192                            return false;
2193                        if (!assignments.containsKey(lecture))
2194                            conflicts.add(placement);
2195                        else if (!assignments.containsKey(res[j].variable()))
2196                            conflicts.add(res[j]);
2197                    }
2198                }
2199                for (int j = pos; j < pos + length; j++)
2200                    res[j] = placement;
2201                nrLectures++;
2202            }
2203        }
2204        if (nrLectures <= 1)
2205            return true;
2206
2207        if (iIsRequired || (!iIsProhibited && iPreference < 0)) {
2208            int i = 0;
2209            Placement p = res[i];
2210            while (p == null)
2211                p = res[++i];
2212            i = res[i].getTimeLocation().getStartSlot() + res[i].getTimeLocation().getLength();
2213            nrLectures--;
2214            while (nrLectures > 0) {
2215                int gap = 0;
2216                while (i < Constants.SLOTS_PER_DAY && res[i] == null) {
2217                    gap++;
2218                    i++;
2219                }
2220                if (i == Constants.SLOTS_PER_DAY)
2221                    break;
2222                if (!canFill(gap, gapMin, gapMax, lengths))
2223                    return false;
2224                p = res[i];
2225                i = res[i].getTimeLocation().getStartSlot() + res[i].getTimeLocation().getLength();
2226                nrLectures--;
2227            }
2228        } else if (iIsProhibited || (!iIsRequired && iPreference > 0)) {
2229            int i = 0;
2230            Placement p = res[i];
2231            while (p == null)
2232                p = res[++i];
2233            i = res[i].getTimeLocation().getStartSlot() + res[i].getTimeLocation().getLength();
2234            nrLectures--;
2235            while (nrLectures > 0) {
2236                int gap = 0;
2237                while (i < Constants.SLOTS_PER_DAY && res[i] == null) {
2238                    gap++;
2239                    i++;
2240                }
2241                if (i == Constants.SLOTS_PER_DAY)
2242                    break;
2243                if ((gapMin == 0 || !canFill(gap, 0, gapMin - 1, lengths))
2244                        && (gapMax >= Constants.SLOTS_PER_DAY || !canFill(gap, gapMax + 1, Constants.SLOTS_PER_DAY,
2245                                lengths))) {
2246                    return false;
2247                }
2248                p = res[i];
2249                i = res[i].getTimeLocation().getStartSlot() + res[i].getTimeLocation().getLength();
2250                nrLectures--;
2251            }
2252        }
2253        return true;
2254    }
2255
2256    public boolean isSatisfied(Assignment<Lecture, Placement> assignment) {
2257        if (isHard()) return true;
2258        if (countAssignedVariables(assignment) < 2) return true;
2259        if (getPreference() == 0) return true;
2260        return isHard() || countAssignedVariables(assignment) < 2 || getPreference() == 0 || getCurrentPreference(assignment) < 0;
2261    }
2262
2263    public boolean isChildrenNotOverlap(Assignment<Lecture, Placement> assignment, Lecture lec1, Placement plc1, Lecture lec2, Placement plc2) {
2264        if (lec1.getSchedulingSubpartId().equals(lec2.getSchedulingSubpartId())) {
2265            // same subpart
2266            boolean overlap = plc1.getTimeLocation().hasIntersection(plc2.getTimeLocation());
2267
2268            if (overlap && lec1.getParent() != null && variables().contains(lec1.getParent())
2269                    && lec2.getParent() != null && variables().contains(lec2.getParent())) {
2270                // children overlaps
2271                Placement p1 = assignment.getValue(lec1.getParent());
2272                Placement p2 = assignment.getValue(lec2.getParent());
2273                // parents not overlap, but children do
2274                if (p1 != null && p2 != null && !p1.getTimeLocation().hasIntersection(p2.getTimeLocation()))
2275                    return false;
2276            }
2277
2278            if (!overlap && lec1.getChildrenSubpartIds() != null && lec2.getChildrenSubpartIds() != null) {
2279                // parents not overlap
2280                for (Long subpartId: lec1.getChildrenSubpartIds()) {
2281                    for (Lecture c1 : lec1.getChildren(subpartId)) {
2282                        Placement p1 = assignment.getValue(c1);
2283                        if (p1 == null) continue;
2284                        for (Lecture c2 : lec2.getChildren(subpartId)) {
2285                            Placement p2 = assignment.getValue(c2);
2286                            if (p2 == null) continue;
2287                            if (!c1.getSchedulingSubpartId().equals(c2.getSchedulingSubpartId())) continue;
2288                            // parents not overlap, but children do
2289                            if (p1.getTimeLocation().hasIntersection(p2.getTimeLocation()))
2290                                return false;
2291                        }
2292                    }
2293                }
2294            }
2295        } else {
2296            // different subpart
2297        }
2298        return true;
2299    }
2300
2301    public boolean isSatisfiedPair(Assignment<Lecture, Placement> assignment, Placement plc1, Placement plc2) {
2302        if (iIsRequired || (!iIsProhibited && iPreference <= 0))
2303            return getType().isSatisfied(assignment, this, plc1, plc2);
2304        else if (iIsProhibited || (!iIsRequired && iPreference > 0))
2305            return getType().isViolated(assignment, this, plc1, plc2);
2306        return true;
2307    }
2308    
2309    public boolean canShareRoom() {
2310        return getType().is(Flag.CAN_SHARE_ROOM);
2311    }
2312    
2313    protected int nrSlotsADay(Assignment<Lecture, Placement> assignment, int dayCode, BitSet week, HashMap<Lecture, Placement> assignments, Set<Placement> conflicts) {
2314        Set<Integer> slots = new HashSet<Integer>();
2315        for (Lecture lecture: variables()) {
2316            Placement placement = null;
2317            if (assignments != null && assignments.containsKey(lecture))
2318                placement = assignments.get(lecture);
2319            else if (assignment != null)
2320                placement = assignment.getValue(lecture);
2321            if (placement == null || placement.getTimeLocation() == null) continue;
2322            if (conflicts != null && conflicts.contains(placement)) continue;
2323            TimeLocation t = placement.getTimeLocation();
2324            if (t == null || (t.getDayCode() & dayCode) == 0 || (week != null && !t.shareWeeks(week))) continue;
2325            for (int i = 0; i < t.getLength(); i++)
2326                slots.add(i + t.getStartSlot());
2327        }
2328        return slots.size();
2329    }
2330    
2331    protected int nrSlotsADay(Assignment<Lecture, Placement> assignment, int date, HashMap<Lecture, Placement> assignments, Set<Placement> conflicts) {
2332        Set<Integer> slots = new HashSet<Integer>();
2333        for (Lecture lecture: variables()) {
2334            Placement placement = null;
2335            if (assignments != null && assignments.containsKey(lecture))
2336                placement = assignments.get(lecture);
2337            else if (assignment != null)
2338                placement = assignment.getValue(lecture);
2339            if (placement == null || placement.getTimeLocation() == null) continue;
2340            if (conflicts != null && conflicts.contains(placement)) continue;
2341            TimeLocation t = placement.getTimeLocation();
2342            if (t == null || !t.hasDate(date, iDayOfWeekOffset)) continue;
2343            for (int i = 0; i < t.getLength(); i++)
2344                slots.add(i + t.getStartSlot());
2345        }
2346        return slots.size();
2347    }
2348
2349    @Override
2350    public boolean equals(Object o) {
2351        if (o == null || !(o instanceof GroupConstraint)) return false;
2352        return getGeneratedId() == ((GroupConstraint) o).getGeneratedId();
2353    }
2354    
2355    @Override
2356    public GroupConstraintContext createAssignmentContext(Assignment<Lecture, Placement> assignment) {
2357        return new GroupConstraintContext(assignment);
2358    }
2359
2360    public class GroupConstraintContext implements AssignmentConstraintContext<Lecture, Placement> {
2361        protected int iLastPreference = 0;
2362        
2363        public GroupConstraintContext(Assignment<Lecture, Placement> assignment) {
2364            updateCriterion(assignment);
2365        }
2366
2367        @Override
2368        public void assigned(Assignment<Lecture, Placement> assignment, Placement value) {
2369            updateCriterion(assignment);
2370        }
2371
2372        @Override
2373        public void unassigned(Assignment<Lecture, Placement> assignment, Placement value) {
2374            updateCriterion(assignment);
2375        }
2376        
2377        protected void updateCriterion(Assignment<Lecture, Placement> assignment) {
2378            if (!isHard()) {
2379                getModel().getCriterion(DistributionPreferences.class).inc(assignment, -iLastPreference);
2380                iLastPreference = getCurrentPreference(assignment) + Math.abs(iPreference);
2381                getModel().getCriterion(DistributionPreferences.class).inc(assignment, iLastPreference);
2382            }
2383        }
2384        
2385        public int getPreference() { return iLastPreference; }
2386    }
2387    
2388    private boolean isBackToBackWeeks(TimeLocation t1, TimeLocation t2) {
2389        if (t1.shareWeeks(t2)) return false;
2390        int f1 = t1.getWeekCode().nextSetBit(0);
2391        int e1 = t1.getWeekCode().previousSetBit(t1.getWeekCode().size());
2392        int f2 = t2.getWeekCode().nextSetBit(0);
2393        int e2 = t2.getWeekCode().previousSetBit(t2.getWeekCode().size());
2394        if (e1 < f2) {
2395            return (f2 - e1) < 7;
2396        } else if (e2 < f1) {
2397            return (f1 - e2) < 7;
2398        }
2399        return false;
2400    }
2401    
2402    private boolean isMaxWeekSpan(TimeLocation t1, TimeLocation t2, int nrWeeks) {
2403        if (t1.shareWeeks(t2)) return false;
2404        if (isBackToBackWeeks(t1, t2)) return true;
2405        
2406        int f1 = t1.getWeekCode().nextSetBit(0);
2407        int e1 = t1.getWeekCode().previousSetBit(t1.getWeekCode().size());
2408        int f2 = t2.getWeekCode().nextSetBit(0);
2409        int e2 = t2.getWeekCode().previousSetBit(t2.getWeekCode().size());
2410        if (e1 < f2) {
2411            return (3 + e2 - f1) / 7 <= nrWeeks;
2412        } else if (e2 < f1) {
2413            return (3 + e1 - f2) / 7 <= nrWeeks;
2414        }
2415        return false;
2416    }
2417    
2418    private boolean isNotBackToBackWeeks(TimeLocation t1, TimeLocation t2) {
2419        if (t1.shareWeeks(t2)) return false;
2420        int f1 = t1.getWeekCode().nextSetBit(0);
2421        int e1 = t1.getWeekCode().previousSetBit(t1.getWeekCode().size());
2422        int f2 = t2.getWeekCode().nextSetBit(0);
2423        int e2 = t2.getWeekCode().previousSetBit(t2.getWeekCode().size());
2424        if (e1 < f2) {
2425            return (f2 - e1) >= 7;
2426        } else if (e2 < f1) {
2427            return (f1 - e2) >= 7;
2428        }
2429        return false;
2430    }
2431    
2432    private boolean isFollowingWeeksBTB(Placement p1, Placement p2, boolean btb) {
2433        int ord1 = variables().indexOf(p1.variable());
2434        int ord2 = variables().indexOf(p2.variable());
2435        TimeLocation t1, t2;
2436        boolean following = false;
2437        if (ord1 < ord2) {
2438            t1 = p1.getTimeLocation();
2439            t2 = p2.getTimeLocation();
2440            if (ord1 + 1 == ord2) following = true;
2441        } else {
2442            t2 = p1.getTimeLocation();
2443            t1 = p2.getTimeLocation();
2444            if (ord2 + 1 == ord1) following = true;
2445        }
2446        if (t1.shareWeeks(t2)) return false;
2447        int e1 = t1.getWeekCode().previousSetBit(t1.getWeekCode().size());
2448        int s2 = t2.getWeekCode().nextSetBit(0);
2449        if (e1 >= s2) return false;
2450        if (!btb) // not back-to-back: any two classes must be at least a week apart
2451            return (s2 - e1) >= 7;
2452        else if (following) // back-to-back and following classes: must be within a week
2453            return (s2 - e1) < 7;
2454        else // back-to-back and not following: just the order
2455            return true;
2456    }
2457    
2458    private boolean isDifferentDates(TimeLocation t1, TimeLocation t2) {
2459        if (!t1.shareDays(t2) || !t1.shareWeeks(t2)) return true;
2460        for (Enumeration<Integer> e = t1.getDates(iDayOfWeekOffset); e.hasMoreElements(); ) {
2461            Integer date = e.nextElement();
2462            if (t2.hasDate(date, iDayOfWeekOffset)) return false;
2463        }
2464        return true;
2465    }
2466    
2467    private boolean isSameDates(TimeLocation t1, TimeLocation t2) {
2468        if (!t1.shareDays(t2) || !t1.shareWeeks(t2)) return false;
2469        // t1 is meets less often
2470        if (t1.countDates(iDayOfWeekOffset) > t2.countDates(iDayOfWeekOffset)) {
2471            TimeLocation t = t1; t1 = t2; t2 = t;
2472        }
2473        for (Enumeration<Integer> e = t1.getDates(iDayOfWeekOffset); e.hasMoreElements(); ) {
2474            Integer date = e.nextElement();
2475            if (!t2.hasDate(date, iDayOfWeekOffset)) return false;
2476        }
2477        return true;
2478    }
2479    
2480    protected boolean isOnline(Placement p) {
2481        // no room -- StudentConflict.OnlineRoom must allow for a blank string
2482        if (p.getNrRooms() == 0)
2483            return "".matches(iOnlineRoom);
2484        // one room -- room name must match StudentConflict.OnlineRoom
2485        if (p.getNrRooms() == 1)
2486            return (p.getRoomLocation().getName() != null && p.getRoomLocation().getName().matches(iOnlineRoom));
2487        // multiple rooms -- all rooms must match StudentConflict.OnlineRoom
2488        for (RoomLocation r: p.getRoomLocations())
2489            if (r.getName() == null || !r.getName().matches(iOnlineRoom)) return false;
2490        return true;
2491    }
2492}