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