001package net.sf.cpsolver.coursett.constraint; 002 003import java.util.ArrayList; 004import java.util.HashSet; 005import java.util.HashMap; 006import java.util.Iterator; 007import java.util.List; 008import java.util.Set; 009 010import net.sf.cpsolver.coursett.Constants; 011import net.sf.cpsolver.coursett.criteria.DistributionPreferences; 012import net.sf.cpsolver.coursett.model.Lecture; 013import net.sf.cpsolver.coursett.model.Placement; 014import net.sf.cpsolver.coursett.model.TimeLocation; 015import net.sf.cpsolver.coursett.model.TimetableModel; 016import net.sf.cpsolver.ifs.model.Constraint; 017import net.sf.cpsolver.ifs.model.GlobalConstraint; 018import net.sf.cpsolver.ifs.model.Model; 019import net.sf.cpsolver.ifs.model.WeakeningConstraint; 020import net.sf.cpsolver.ifs.util.DataProperties; 021import net.sf.cpsolver.ifs.util.DistanceMetric; 022import net.sf.cpsolver.ifs.util.ToolBox; 023 024/** 025 * Group constraint. <br> 026 * This constraint expresses relations between several classes, e.g., that two 027 * sections of the same lecture can not be taught at the same time, or that some 028 * classes have to be taught one immediately after another. It can be either 029 * hard or soft. <br> 030 * <br> 031 * Following constraints are now supported: 032 * <table border='1'> 033 * <tr> 034 * <th>Constraint</th> 035 * <th>Comment</th> 036 * </tr> 037 * <tr> 038 * <td>SAME_TIME</td> 039 * <td>Same time: given classes have to be taught in the same hours. If the 040 * classes are of different length, the smaller one cannot start before the 041 * longer one and it cannot end after the longer one.</td> 042 * </tr> 043 * <tr> 044 * <td>SAME_DAYS</td> 045 * <td>Same days: given classes have to be taught in the same day. If the 046 * classes are of different time patterns, the days of one class have to form a 047 * subset of the days of the other class.</td> 048 * </tr> 049 * <tr> 050 * <td>BTB</td> 051 * <td>Back-to-back constraint: given classes have to be taught in the same room 052 * and they have to follow one strictly after another.</td> 053 * </tr> 054 * <tr> 055 * <td>BTB_TIME</td> 056 * <td>Back-to-back constraint: given classes have to follow one strictly after 057 * another, but they can be taught in different rooms.</td> 058 * </tr> 059 * <tr> 060 * <td>DIFF_TIME</td> 061 * <td>Different time: given classes cannot overlap in time.</td> 062 * </tr> 063 * <tr> 064 * <td>NHB(1), NHB(1.5), NHB(2), ... NHB(8)</td> 065 * <td>Number of hours between: between the given classes, the exact number of 066 * hours have to be kept.</td> 067 * </tr> 068 * <tr> 069 * <td>SAME_START</td> 070 * <td>Same starting hour: given classes have to start in the same hour.</td> 071 * </tr> 072 * <tr> 073 * <td>SAME_ROOM</td> 074 * <td>Same room: given classes have to be placed in the same room.</td> 075 * </tr> 076 * <tr> 077 * <td>NHB_GTE(1)</td> 078 * <td>Greater than or equal to 1 hour between: between the given classes, the 079 * number of hours have to be one or more.</td> 080 * </tr> 081 * <tr> 082 * <td>NHB_LT(6)</td> 083 * <td>Less than 6 hours between: between the given classes, the number of hours 084 * have to be less than six.</td> 085 * </tr> 086 * </table> 087 * 088 * @version CourseTT 1.2 (University Course Timetabling)<br> 089 * Copyright (C) 2006 - 2010 Tomáš Müller<br> 090 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 091 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 092 * <br> 093 * This library is free software; you can redistribute it and/or modify 094 * it under the terms of the GNU Lesser General Public License as 095 * published by the Free Software Foundation; either version 3 of the 096 * License, or (at your option) any later version. <br> 097 * <br> 098 * This library is distributed in the hope that it will be useful, but 099 * WITHOUT ANY WARRANTY; without even the implied warranty of 100 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 101 * Lesser General Public License for more details. <br> 102 * <br> 103 * You should have received a copy of the GNU Lesser General Public 104 * License along with this library; if not see 105 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 106 */ 107 108public class GroupConstraint extends Constraint<Lecture, Placement> { 109 private Long iConstraintId; 110 private int iPreference; 111 private ConstraintType iType; 112 private boolean iIsRequired; 113 private boolean iIsProhibited; 114 private int iLastPreference = 0; 115 private int iDayOfWeekOffset = 0; 116 private boolean iPrecedenceConsiderDatePatterns = true; 117 private int iForwardCheckMaxDepth = 2; 118 private int iForwardCheckMaxDomainSize = 1000; 119 120 /** 121 * Group constraints that can be checked on pairs of classes (e.g., same room means any two classes are in the same room), 122 * only need to implement this interface. 123 */ 124 public static interface PairCheck { 125 /** 126 * Check whether the constraint is satisfied for the given two assignments (required / preferred case) 127 * @param gc Calling group constraint 128 * @param plc1 First placement 129 * @param plc2 Second placement 130 * @return true if constraint is satisfied 131 */ 132 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2); 133 /** 134 * Check whether the constraint is satisfied for the given two assignments (prohibited / discouraged case) 135 * @param gc Calling group constraint 136 * @param plc1 First placement 137 * @param plc2 Second placement 138 * @return true if constraint is satisfied 139 */ 140 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2); 141 } 142 143 /** 144 * Group constraint building blocks (individual constraints that need more than {@link PairCheck}) 145 */ 146 public static enum Flag { 147 /** Back-to-back constraint (sequence check) */ 148 BACK_TO_BACK, 149 /** Can share room flag */ 150 CAN_SHARE_ROOM, 151 /** Maximum hours a day (number of slots a day check) */ 152 MAX_HRS_DAY, 153 /** Children cannot overlap */ 154 CH_NOTOVERLAP; 155 /** Bit number (to combine flags) */ 156 int flag() { return 1 << ordinal(); } 157 } 158 159 /** 160 * Group constraint type. 161 */ 162 public static enum ConstraintType { 163 /** 164 * Same Time: Given classes must be taught at the same time of day (independent of the actual day the classes meet). 165 * For the classes of the same length, this is the same constraint as same start. For classes of different length, 166 * the shorter one cannot start before, nor end after, the longer one.<BR> 167 * When prohibited or (strongly) discouraged: one class may not meet on any day at a time of day that overlaps with 168 * that of the other. For example, one class can not meet M 7:30 while the other meets F 7:30. Note the difference 169 * here from the different time constraint that only prohibits the actual class meetings from overlapping. 170 */ 171 SAME_TIME("SAME_TIME", "Same Time", new PairCheck() { 172 @Override 173 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 174 return sameHours(plc1.getTimeLocation().getStartSlot(), plc1.getTimeLocation().getLength(), 175 plc2.getTimeLocation().getStartSlot(), plc2.getTimeLocation().getLength()); 176 } 177 @Override 178 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 179 return !(plc1.getTimeLocation().shareHours(plc2.getTimeLocation())); 180 }}), 181 /** 182 * Same Days: Given classes must be taught on the same days. In case of classes of different time patterns, a class 183 * with fewer meetings must meet on a subset of the days used by the class with more meetings. For example, if one 184 * class pattern is 3x50, all others given in the constraint can only be taught on Monday, Wednesday, or Friday. 185 * For a 2x100 class MW, MF, WF is allowed but TTh is prohibited.<BR> 186 * When prohibited or (strongly) discouraged: any pair of classes classes cannot be taught on the same days (cannot 187 * overlap in days). For instance, if one class is MFW, the second has to be TTh. 188 */ 189 SAME_DAYS("SAME_DAYS", "Same Days", new PairCheck() { 190 @Override 191 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 192 return sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()); 193 } 194 @Override 195 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 196 return !plc1.getTimeLocation().shareDays(plc2.getTimeLocation()); 197 }}), 198 /** 199 * Back-To-Back & Same Room: Classes must be offered in adjacent time segments and must be placed in the same room. 200 * Given classes must also be taught on the same days.<BR> 201 * When prohibited or (strongly) discouraged: classes cannot be back-to-back. There must be at least half-hour 202 * between these classes, and they must be taught on the same days and in the same room. 203 */ 204 BTB("BTB", "Back-To-Back & Same Room", new PairCheck() { 205 @Override 206 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 207 return 208 plc1.sameRooms(plc2) && 209 sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()); 210 } 211 @Override 212 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 213 return 214 plc1.sameRooms(plc2) && 215 sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()); 216 }}, Flag.BACK_TO_BACK), 217 /** 218 * Back-To-Back: Classes must be offered in adjacent time segments but may be placed in different rooms. Given classes 219 * must also be taught on the same days.<BR> 220 * When prohibited or (strongly) discouraged: no pair of classes can be taught back-to-back. They may not overlap in time, 221 * but must be taught on the same days. This means that there must be at least half-hour between these classes. 222 */ 223 BTB_TIME("BTB_TIME", "Back-To-Back", new PairCheck() { 224 @Override 225 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 226 return sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()); 227 } 228 @Override 229 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 230 return sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()); 231 }}, Flag.BACK_TO_BACK), 232 /** 233 * Different Time: Given classes cannot overlap in time. They may be taught at the same time of day if they are on 234 * different days. For instance, MF 7:30 is compatible with TTh 7:30.<BR> 235 * When prohibited or (strongly) discouraged: every pair of classes in the constraint must overlap in time. 236 */ 237 DIFF_TIME("DIFF_TIME", "Different Time", new PairCheck() { 238 @Override 239 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 240 return !plc1.getTimeLocation().hasIntersection(plc2.getTimeLocation()); 241 } 242 @Override 243 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 244 return plc1.getTimeLocation().hasIntersection(plc2.getTimeLocation()); 245 }}), 246 /** 247 * 1 Hour Between: Given classes must have exactly 1 hour in between the end of one and the beginning of another. 248 * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR> 249 * When prohibited or (strongly) discouraged: classes can not have 1 hour in between. They may not overlap in time 250 * but must be taught on the same days. 251 */ 252 NHB_1("NHB(1)", "1 Hour Between", 10, 12, BTB_TIME.check(), Flag.BACK_TO_BACK), 253 /** 254 * 2 Hours Between: Given classes must have exactly 2 hours in between the end of one and the beginning of another. 255 * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR> 256 * When prohibited or (strongly) discouraged: classes can not have 2 hours in between. They may not overlap in time 257 * but must be taught on the same days. 258 */ 259 NHB_2("NHB(2)", "2 Hours Between", 20, 24, BTB_TIME.check(), Flag.BACK_TO_BACK), 260 /** 261 * 3 Hours Between: Given classes must have exactly 3 hours in between the end of one and the beginning of another. 262 * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR> 263 * When prohibited or (strongly) discouraged: classes can not have 3 hours in between. They may not overlap in time 264 * but must be taught on the same days. 265 */ 266 NHB_3("NHB(3)", "3 Hours Between", 30, 36, BTB_TIME.check(), Flag.BACK_TO_BACK), 267 /** 268 * 4 Hours Between: Given classes must have exactly 4 hours in between the end of one and the beginning of another. 269 * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR> 270 * When prohibited or (strongly) discouraged: classes can not have 4 hours in between. They may not overlap in time 271 * but must be taught on the same days. 272 */ 273 NHB_4("NHB(4)", "4 Hours Between", 40, 48, BTB_TIME.check(), Flag.BACK_TO_BACK), 274 /** 275 * 5 Hours Between: Given classes must have exactly 5 hours in between the end of one and the beginning of another. 276 * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR> 277 * When prohibited or (strongly) discouraged: classes can not have 5 hours in between. They may not overlap in time 278 * but must be taught on the same days. 279 */ 280 NHB_5("NHB(5)", "5 Hours Between", 50, 60, BTB_TIME.check(), Flag.BACK_TO_BACK), 281 /** 282 * 6 Hours Between: Given classes must have exactly 6 hours in between the end of one and the beginning of another. 283 * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR> 284 * When prohibited or (strongly) discouraged: classes can not have 6 hours in between. They may not overlap in time 285 * but must be taught on the same days. 286 */ 287 NHB_6("NHB(6)", "6 Hours Between", 60, 72, BTB_TIME.check(), Flag.BACK_TO_BACK), 288 /** 289 * 7 Hours Between: Given classes must have exactly 7 hours in between the end of one and the beginning of another. 290 * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR> 291 * When prohibited or (strongly) discouraged: classes can not have 7 hours in between. They may not overlap in time 292 * but must be taught on the same days. 293 */ 294 NHB_7("NHB(7)", "7 Hours Between", 70, 84, BTB_TIME.check(), Flag.BACK_TO_BACK), 295 /** 296 * 8 Hours Between: Given classes must have exactly 8 hours in between the end of one and the beginning of another. 297 * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR> 298 * When prohibited or (strongly) discouraged: classes can not have 8 hours in between. They may not overlap in time 299 * but must be taught on the same days. 300 */ 301 NHB_8("NHB(8)", "8 Hours Between", 80, 96, BTB_TIME.check(), Flag.BACK_TO_BACK), 302 /** 303 * Same Start Time: Given classes must start during the same half-hour period of a day (independent of the actual 304 * day the classes meet). For instance, MW 7:30 is compatible with TTh 7:30 but not with MWF 8:00.<BR> 305 * When prohibited or (strongly) discouraged: any pair of classes in the given constraint cannot start during the 306 * same half-hour period of any day of the week. 307 */ 308 SAME_START("SAME_START", "Same Start Time", new PairCheck() { 309 @Override 310 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 311 return 312 (plc1.getTimeLocation().getStartSlot() % Constants.SLOTS_PER_DAY) == 313 (plc2.getTimeLocation().getStartSlot() % Constants.SLOTS_PER_DAY); 314 } 315 @Override 316 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 317 return 318 (plc1.getTimeLocation().getStartSlot() % Constants.SLOTS_PER_DAY) != 319 (plc2.getTimeLocation().getStartSlot() % Constants.SLOTS_PER_DAY); 320 }}), 321 /** 322 * Same Room: Given classes must be taught in the same room.<BR> 323 * When prohibited or (strongly) discouraged: any pair of classes in the constraint cannot be taught in the same room. 324 */ 325 SAME_ROOM("SAME_ROOM", "Same Room", new PairCheck() { 326 @Override 327 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 328 return plc1.sameRooms(plc2); 329 } 330 @Override 331 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 332 return !plc1.sameRooms(plc2); 333 }}), 334 /** 335 * At Least 1 Hour Between: Given classes have to have 1 hour or more in between.<BR> 336 * When prohibited or (strongly) discouraged: given classes have to have less than 1 hour in between. 337 */ 338 NHB_GTE_1("NHB_GTE(1)", "At Least 1 Hour Between", 6, 288, BTB_TIME.check(), Flag.BACK_TO_BACK), 339 /** 340 * Less Than 6 Hours Between: Given classes must have less than 6 hours from end of first class to the beginning of 341 * the next. Given classes must also be taught on the same days.<BR> 342 * When prohibited or (strongly) discouraged: given classes must have 6 or more hours between. This constraint does 343 * not carry over from classes taught at the end of one day to the beginning of the next. 344 */ 345 NHB_LT_6("NHB_LT(6)", "Less Than 6 Hours Between", 0, 72, BTB_TIME.check(), Flag.BACK_TO_BACK), 346 /** 347 * 1.5 Hour Between: Given classes must have exactly 90 minutes in between the end of one and the beginning of another. 348 * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR> 349 * When prohibited or (strongly) discouraged: classes can not have 90 minutes in between. They may not overlap in time 350 * but must be taught on the same days. 351 */ 352 NHB_1_5("NHB(1.5)", "1.5 Hour Between", 15, 18, BTB_TIME.check(), Flag.BACK_TO_BACK), 353 /** 354 * 4.5 Hours Between: Given classes must have exactly 4.5 hours in between the end of one and the beginning of another. 355 * As with the <i>back-to-back time</i> constraint, given classes must be taught on the same days.<BR> 356 * When prohibited or (strongly) discouraged: classes can not have 4.5 hours in between. They may not overlap in time 357 * but must be taught on the same days. 358 */ 359 NHB_4_5("NHB(4.5)", "4.5 Hours Between", 45, 54, BTB_TIME.check(), Flag.BACK_TO_BACK), 360 /** 361 * Same Students: Given classes are treated as they are attended by the same students, i.e., they cannot overlap in time 362 * and if they are back-to-back the assigned rooms cannot be too far (student limit is used). 363 */ 364 SAME_STUDENTS("SAME_STUDENTS", "Same Students", new PairCheck() { 365 @Override 366 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 367 return !JenrlConstraint.isInConflict(plc1, plc2, ((TimetableModel)gc.getModel()).getDistanceMetric()); 368 } 369 @Override 370 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 371 return true; 372 }}), 373 /** 374 * Same Instructor: Given classes are treated as they are taught by the same instructor, i.e., they cannot overlap in time 375 * and if they are back-to-back the assigned rooms cannot be too far (instructor limit is used).<BR> 376 * If the constraint is required and the classes are back-to-back, discouraged and strongly discouraged distances between 377 * assigned rooms are also considered. 378 */ 379 SAME_INSTR("SAME_INSTR", "Same Instructor", new PairCheck() { 380 @Override 381 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 382 TimeLocation t1 = plc1.getTimeLocation(), t2 = plc2.getTimeLocation(); 383 if (t1.shareDays(t2) && t1.shareWeeks(t2)) { 384 if (t1.shareHours(t2)) return false; // overlap 385 DistanceMetric m = ((TimetableModel)gc.getModel()).getDistanceMetric(); 386 if ((t1.getStartSlot() + t1.getLength() == t2.getStartSlot() || t2.getStartSlot() + t2.getLength() == t1.getStartSlot())) { 387 if (Placement.getDistanceInMeters(m, plc1, plc2) > m.getInstructorProhibitedLimit()) 388 return false; 389 } else if (m.doComputeDistanceConflictsBetweenNonBTBClasses()) { 390 if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot() && 391 Placement.getDistanceInMinutes(m, plc1, plc2) > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) 392 return false; 393 if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot() && 394 Placement.getDistanceInMinutes(m, plc1, plc2) > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) 395 return false; 396 } 397 } 398 return true; 399 } 400 @Override 401 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 402 return true; 403 }}), 404 /** 405 * Can Share Room: Given classes can share the room (use the room in the same time) if the room is big enough. 406 */ 407 CAN_SHARE_ROOM("CAN_SHARE_ROOM", "Can Share Room", null, Flag.CAN_SHARE_ROOM), 408 /** 409 * Precedence: Given classes have to be taught in the given order (the first meeting of the first class has to end before 410 * the first meeting of the second class etc.)<BR> 411 * When prohibited or (strongly) discouraged: classes have to be taught in the order reverse to the given one. 412 */ 413 PRECEDENCE("PRECEDENCE", "Precedence", new PairCheck() { 414 @Override 415 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 416 return gc.isPrecedence(plc1, plc2, true, true); 417 } 418 @Override 419 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 420 return gc.isPrecedence(plc1, plc2, false, true); 421 }}), 422 /** 423 * Back-To-Back Day: Classes must be offered on adjacent days and may be placed in different rooms.<BR> 424 * When prohibited or (strongly) discouraged: classes can not be taught on adjacent days. They also can not be taught 425 * on the same days. This means that there must be at least one day between these classes. 426 */ 427 BTB_DAY("BTB_DAY", "Back-To-Back Day", new PairCheck() { 428 @Override 429 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 430 return 431 !sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) && 432 isBackToBackDays(plc1.getTimeLocation(), plc2.getTimeLocation()); 433 } 434 @Override 435 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 436 return 437 !sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) && 438 !isBackToBackDays(plc1.getTimeLocation(), plc2.getTimeLocation()); 439 }}), 440 /** 441 * Meet Together: Given classes are meeting together (same as if the given classes require constraints Can Share Room, 442 * Same Room, Same Time and Same Days all together). 443 */ 444 MEET_WITH("MEET_WITH", "Meet Together", new PairCheck() { 445 @Override 446 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 447 return 448 plc1.sameRooms(plc2) && 449 sameHours(plc1.getTimeLocation().getStartSlot(), plc1.getTimeLocation().getLength(), 450 plc2.getTimeLocation().getStartSlot(), plc2.getTimeLocation().getLength()) && 451 sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()); 452 453 } 454 @Override 455 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 456 return true; 457 }}, Flag.CAN_SHARE_ROOM), 458 /** 459 * More Than 1 Day Between: Given classes must have two or more days in between.<br> 460 * When prohibited or (strongly) discouraged: given classes must be offered on adjacent days or with at most one day in between. 461 */ 462 NDB_GT_1("NDB_GT_1", "More Than 1 Day Between", new PairCheck() { 463 @Override 464 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 465 return 466 !sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) && 467 isNrDaysBetweenGreaterThanOne(plc1.getTimeLocation(), plc2.getTimeLocation()); 468 } 469 @Override 470 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 471 return 472 !sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) && 473 !isNrDaysBetweenGreaterThanOne(plc1.getTimeLocation(), plc2.getTimeLocation()); 474 }}), 475 /** 476 * Children Cannot Overlap: If parent classes do not overlap in time, children classes can not overlap in time as well.<BR> 477 * Note: This constraint only needs to be put on the parent classes. Preferred configurations are Required All Classes 478 * or Pairwise (Strongly) Preferred. 479 */ 480 CH_NOTOVERLAP("CH_NOTOVERLAP", "Children Cannot Overlap", new PairCheck() { 481 @Override 482 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 483 return gc.isChildrenNotOverlap(plc1.variable(), plc1, plc2.variable(), plc2); 484 } 485 @Override 486 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 487 return true; 488 }}), 489 /** 490 * Next Day: The second class has to be placed on the following day of the first class (if the first class is on Friday, 491 * second class have to be on Monday).<br> 492 * When prohibited or (strongly) discouraged: The second class has to be placed on the previous day of the first class 493 * (if the first class is on Monday, second class have to be on Friday).<br> 494 * Note: This constraint works only between pairs of classes. 495 */ 496 FOLLOWING_DAY("FOLLOWING_DAY", "Next Day", new PairCheck() { 497 @Override 498 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 499 return gc.isFollowingDay(plc1, plc2, true); 500 } 501 @Override 502 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 503 return gc.isFollowingDay(plc1, plc2, false); 504 }}), 505 /** 506 * Two Days After: The second class has to be placed two days after the first class (Monday → Wednesday, Tuesday → 507 * Thurday, Wednesday → Friday, Thursday → Monday, Friday → Tuesday).<br> 508 * When prohibited or (strongly) discouraged: The second class has to be placed two days before the first class (Monday → 509 * Thursday, Tuesday → Friday, Wednesday → Monday, Thursday → Tuesday, Friday → Wednesday).<br> 510 * Note: This constraint works only between pairs of classes. 511 */ 512 EVERY_OTHER_DAY("EVERY_OTHER_DAY", "Two Days After", new PairCheck() { 513 @Override 514 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 515 return gc.isEveryOtherDay(plc1, plc2, true); 516 } 517 @Override 518 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 519 return gc.isEveryOtherDay(plc1, plc2, false); 520 }}), 521 /** 522 * 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. 523 */ 524 MAX_HRS_DAY_5("MAX_HRS_DAY(5)", "At Most 5 Hours A Day", 60, null, Flag.MAX_HRS_DAY), 525 /** 526 * 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. 527 */ 528 MAX_HRS_DAY_6("MAX_HRS_DAY(6)", "At Most 6 Hours A Day", 72, null, Flag.MAX_HRS_DAY), 529 /** 530 * 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. 531 */ 532 MAX_HRS_DAY_7("MAX_HRS_DAY(7)", "At Most 7 Hours A Day", 84, null, Flag.MAX_HRS_DAY), 533 /** 534 * 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. 535 */ 536 MAX_HRS_DAY_8("MAX_HRS_DAY(8)", "At Most 8 Hours A Day", 96, null, Flag.MAX_HRS_DAY), 537 /** 538 * Given classes must be taught during the same weeks (i.e., must have the same date pattern).<br> 539 * When prohibited or (strongly) discouraged: any two classes must have non overlapping date patterns. 540 */ 541 SAME_WEEKS("SAME_WEEKS", "Same Weeks", new PairCheck() { 542 @Override 543 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 544 return plc1.getTimeLocation().getWeekCode().equals(plc2.getTimeLocation().getWeekCode()); 545 } 546 @Override 547 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 548 return !plc1.getTimeLocation().shareWeeks(plc2.getTimeLocation()); 549 }}), 550 /** 551 * Classes (of different courses) are to be attended by the same students. For instance, 552 * if class A1 (of a course A) and class B1 (of a course B) are linked, a student requesting 553 * both courses must attend A1 if and only if he also attends B1. This is a student sectioning 554 * constraint that is interpreted as Same Students constraint during course timetabling. 555 */ 556 LINKED_SECTIONS("LINKED_SECTIONS", "Linked Classes", SAME_STUDENTS.check()), 557 /** 558 * Back-To-Back Precedence: Given classes have to be taught in the given order, on the same days, 559 * and in adjacent time segments. 560 * When prohibited or (strongly) discouraged: Given classes have to be taught in the given order, 561 * on the same days, but cannot be back-to-back. 562 */ 563 BTB_PRECEDENCE("BTB_PRECEDENCE", "Back-To-Back Precedence", new PairCheck() { 564 @Override 565 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 566 return gc.isPrecedence(plc1, plc2, true, false) && sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()); 567 } 568 @Override 569 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 570 return gc.isPrecedence(plc1, plc2, true, false) && sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()); 571 }}, Flag.BACK_TO_BACK), 572 573 /** 574 * Same Days-Time: Given classes must be taught at the same time of day and on the same days. 575 * It is the combination of Same Days and Same Time distribution preferences. 576 * When prohibited or (strongly) discouraged: Any pair of classes classes cannot be taught on the same days 577 * during the same time. 578 */ 579 SAME_DAYS_TIME("SAME_D_T", "Same Days-Time", new PairCheck() { 580 @Override 581 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 582 return sameHours(plc1.getTimeLocation().getStartSlot(), plc1.getTimeLocation().getLength(), 583 plc2.getTimeLocation().getStartSlot(), plc2.getTimeLocation().getLength()) && 584 sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()); 585 } 586 @Override 587 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 588 return !plc1.getTimeLocation().shareHours(plc2.getTimeLocation()) || 589 !plc1.getTimeLocation().shareDays(plc2.getTimeLocation()); 590 }}), 591 /** 592 * Same Days-Room-Time: Given classes must be taught at the same time of day, on the same days and in the same room. 593 * It is the combination of Same Days, Same Time and Same Room distribution preferences. 594 * Note that this constraint is the same as Meet Together constraint, except it does not allow room sharing. In other words, 595 * it is only useful when these classes are taught during non-overlapping date patterns. 596 * When prohibited or (strongly) discouraged: Any pair of classes classes cannot be taught on the same days 597 * during the same time in the same room. 598 */ 599 SAME_DAYS_ROOM_TIME("SAME_D_R_T", "Same Days-Room-Time", new PairCheck() { 600 @Override 601 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { 602 return sameHours(plc1.getTimeLocation().getStartSlot(), plc1.getTimeLocation().getLength(), 603 plc2.getTimeLocation().getStartSlot(), plc2.getTimeLocation().getLength()) && 604 sameDays(plc1.getTimeLocation().getDaysArray(), plc2.getTimeLocation().getDaysArray()) && 605 plc1.sameRooms(plc2); 606 } 607 @Override 608 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { 609 return !plc1.getTimeLocation().shareHours(plc2.getTimeLocation()) || 610 !plc1.getTimeLocation().shareDays(plc2.getTimeLocation()) || 611 !plc1.sameRooms(plc2); 612 }}), 613 ; 614 615 String iReference, iName; 616 int iFlag = 0; 617 Flag[] iFlags = null; 618 int iMin = 0, iMax = 0; 619 PairCheck iCheck = null; 620 ConstraintType(String reference, String name, PairCheck check, Flag... flags) { 621 iReference = reference; 622 iName = name; 623 iCheck = check; 624 iFlags = flags; 625 for (Flag f: flags) 626 iFlag |= f.flag(); 627 } 628 ConstraintType(String reference, String name, int limit, PairCheck check, Flag... flags) { 629 this(reference, name, check, flags); 630 iMin = iMax = limit; 631 } 632 ConstraintType(String reference, String name, int min, int max, PairCheck check, Flag... flags) { 633 this(reference, name, check, flags); 634 iMin = min; 635 iMax = max; 636 } 637 638 /** Constraint reference */ 639 public String reference() { return iReference; } 640 /** Constraint name */ 641 public String getName() { return iName; } 642 /** Minimum (gap) parameter */ 643 public int getMin() { return iMin; } 644 /** Maximum (gap, hours a day) parameter */ 645 public int getMax() { return iMax; } 646 647 /** Flag check (true if contains given flag) */ 648 public boolean is(Flag f) { return (iFlag & f.flag()) != 0; } 649 650 /** Constraint type from reference */ 651 public static ConstraintType get(String reference) { 652 for (ConstraintType t: ConstraintType.values()) 653 if (t.reference().equals(reference)) return t; 654 return null; 655 } 656 657 /** True if a required or preferred constraint is satisfied between a pair of placements */ 658 public boolean isSatisfied(GroupConstraint gc, Placement plc1, Placement plc2) { return (iCheck == null ? true : iCheck.isSatisfied(gc, plc1, plc2)); } 659 /** True if a prohibited or discouraged constraint is satisfied between a pair of placements */ 660 public boolean isViolated(GroupConstraint gc, Placement plc1, Placement plc2) { return (iCheck == null ? true : iCheck.isViolated(gc, plc1, plc2)); } 661 /** Pair check */ 662 private PairCheck check() { return iCheck; } 663 } 664 665 public GroupConstraint() { 666 } 667 668 @Override 669 public void setModel(Model<Lecture, Placement> model) { 670 super.setModel(model); 671 if (model != null) { 672 DataProperties config = ((TimetableModel)model).getProperties(); 673 iDayOfWeekOffset = config.getPropertyInt("DatePattern.DayOfWeekOffset", 0); 674 iPrecedenceConsiderDatePatterns = config.getPropertyBoolean("Precedence.ConsiderDatePatterns", true); 675 iForwardCheckMaxDepth = config.getPropertyInt("ForwardCheck.MaxDepth", iForwardCheckMaxDepth); 676 iForwardCheckMaxDomainSize = config.getPropertyInt("ForwardCheck.MaxDomainSize", iForwardCheckMaxDomainSize); 677 } 678 if (!isHard()) { 679 iLastPreference = getCurrentPreference(); 680 if (model != null) 681 model.getCriterion(DistributionPreferences.class).inc(iLastPreference); 682 } 683 } 684 685 @Override 686 public void addVariable(Lecture lecture) { 687 if (!variables().contains(lecture)) 688 super.addVariable(lecture); 689 if (getType().is(Flag.CH_NOTOVERLAP)) { 690 if (lecture.getChildrenSubpartIds() != null) { 691 for (Long subpartId: lecture.getChildrenSubpartIds()) { 692 for (Lecture ch : lecture.getChildren(subpartId)) { 693 if (!variables().contains(ch)) 694 super.addVariable(ch); 695 } 696 } 697 } 698 } 699 } 700 701 @Override 702 public void removeVariable(Lecture lecture) { 703 if (variables().contains(lecture)) 704 super.removeVariable(lecture); 705 if (getType().is(Flag.CH_NOTOVERLAP)) { 706 if (lecture.getChildrenSubpartIds() != null) { 707 for (Long subpartId: lecture.getChildrenSubpartIds()) { 708 for (Lecture ch : lecture.getChildren(subpartId)) { 709 if (variables().contains(ch)) 710 super.removeVariable(ch); 711 } 712 } 713 } 714 } 715 } 716 717 /** 718 * Constructor 719 * 720 * @param id 721 * constraint id 722 * @param type 723 * constraString type (e.g, {@link ConstraintType#SAME_TIME}) 724 * @param preference 725 * time preference ("R" for required, "P" for prohibited, "-2", 726 * "-1", "1", "2" for soft preference) 727 */ 728 public GroupConstraint(Long id, ConstraintType type, String preference) { 729 iConstraintId = id; 730 iType = type; 731 iIsRequired = preference.equals(Constants.sPreferenceRequired); 732 iIsProhibited = preference.equals(Constants.sPreferenceProhibited); 733 iPreference = Constants.preference2preferenceLevel(preference); 734 } 735 736 /** Constraint id */ 737 public Long getConstraintId() { 738 return iConstraintId; 739 } 740 741 @Override 742 public long getId() { 743 return (iConstraintId == null ? -1 : iConstraintId.longValue()); 744 } 745 746 /** Generated unique id */ 747 protected long getGeneratedId() { 748 return iId; 749 } 750 751 /** ConstraString type (e.g, {@link ConstraintType#SAME_TIME}) */ 752 public ConstraintType getType() { 753 return iType; 754 } 755 756 public void setType(ConstraintType type) { 757 iType = type; 758 } 759 760 /** Is constraint required */ 761 public boolean isRequired() { 762 return iIsRequired; 763 } 764 765 /** Is constraint prohibited */ 766 public boolean isProhibited() { 767 return iIsProhibited; 768 } 769 770 /** 771 * Prolog reference: "R" for required, "P" for prohibited", "-2",.."2" for 772 * preference 773 */ 774 public String getPrologPreference() { 775 return Constants.preferenceLevel2preference(iPreference); 776 } 777 778 @Override 779 public boolean isConsistent(Placement value1, Placement value2) { 780 if (!isHard()) 781 return true; 782 if (!isSatisfiedPair(value1, value2)) 783 return false; 784 if (getType().is(Flag.BACK_TO_BACK)) { 785 HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>(); 786 assignments.put(value1.variable(), value1); 787 assignments.put(value2.variable(), value2); 788 if (!isSatisfiedSeq(assignments, false, null)) 789 return false; 790 } 791 if (getType().is(Flag.MAX_HRS_DAY)) { 792 HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>(); 793 assignments.put(value1.variable(), value1); 794 assignments.put(value2.variable(), value2); 795 for (int dayCode: Constants.DAY_CODES) { 796 if (nrSlotsADay(dayCode, assignments, null) > getType().getMax()) 797 return false; 798 } 799 } 800 return true; 801 } 802 803 @Override 804 public void computeConflicts(Placement value, Set<Placement> conflicts) { 805 if (!isHard()) 806 return; 807 for (Lecture v : variables()) { 808 if (v.equals(value.variable())) 809 continue; // ignore this variable 810 if (v.getAssignment() == null) 811 continue; // there is an unassigned variable -- great, still a 812 // chance to get violated 813 if (!isSatisfiedPair(v.getAssignment(), value)) 814 conflicts.add(v.getAssignment()); 815 } 816 if (getType().is(Flag.BACK_TO_BACK)) { 817 HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>(); 818 assignments.put(value.variable(), value); 819 if (!isSatisfiedSeq(assignments, true, conflicts)) 820 conflicts.add(value); 821 } 822 if (getType().is(Flag.MAX_HRS_DAY)) { 823 HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>(); 824 assignments.put(value.variable(), value); 825 for (int dayCode: Constants.DAY_CODES) { 826 if (nrSlotsADay(dayCode, assignments, conflicts) > getType().getMax()) { 827 List<Placement> adepts = new ArrayList<Placement>(); 828 for (Lecture lecture: assignedVariables()) { 829 if (conflicts.contains(lecture.getAssignment()) || lecture.equals(value.variable())) continue; 830 if (lecture.getAssignment().getTimeLocation() == null) continue; 831 if ((lecture.getAssignment().getTimeLocation().getDayCode() & dayCode) == 0) continue; 832 adepts.add(lecture.getAssignment()); 833 } 834 do { 835 Placement conflict = ToolBox.random(adepts); 836 adepts.remove(conflict); 837 conflicts.add(conflict); 838 } while (!adepts.isEmpty() && nrSlotsADay(dayCode, assignments, conflicts) > getType().getMax()); 839 } 840 } 841 } 842 843 // Forward checking 844 forwardCheck(value, conflicts, new HashSet<GroupConstraint>(), iForwardCheckMaxDepth - 1); 845 } 846 847 public void forwardCheck(Placement value, Set<Placement> conflicts, Set<GroupConstraint> ignore, int depth) { 848 try { 849 if (depth < 0) return; 850 ignore.add(this); 851 852 int neededSize = value.variable().maxRoomUse(); 853 854 for (Lecture lecture: variables()) { 855 if (conflicts.contains(value)) break; // already conflicting 856 857 if (lecture.equals(value.variable())) continue; // Skip this lecture 858 if (lecture.getAssignment() != null) { // Has assignment, check whether it is conflicting 859 if (isSatisfiedPair(value, lecture.getAssignment())) { 860 // Increase needed size if the assignment is of the same room and overlapping in time 861 if (canShareRoom() && sameRoomAndOverlaps(value, lecture.getAssignment())) { 862 neededSize += lecture.maxRoomUse(); 863 } 864 continue; 865 } 866 conflicts.add(lecture.getAssignment()); 867 } 868 869 // Look for supporting assignments assignment 870 boolean shareRoomAndOverlaps = canShareRoom(); 871 Placement support = null; 872 int nrSupports = 0; 873 if (lecture.nrValues() >= iForwardCheckMaxDomainSize) { 874 // ignore variables with large domains 875 return; 876 } 877 if (lecture.values().isEmpty()) { 878 // ignore variables with empty domain 879 return; 880 } 881 for (Placement other: lecture.values()) { 882 if (nrSupports < 2) { 883 if (isSatisfiedPair(value, other)) { 884 if (support == null) support = other; 885 nrSupports ++; 886 if (shareRoomAndOverlaps && !sameRoomAndOverlaps(value, other)) 887 shareRoomAndOverlaps = false; 888 } 889 } else if (shareRoomAndOverlaps && !sameRoomAndOverlaps(value, other) && isSatisfiedPair(value, other)) { 890 shareRoomAndOverlaps = false; 891 } 892 if (nrSupports > 1 && !shareRoomAndOverlaps) 893 break; 894 } 895 896 // No supporting assignment -> fail 897 if (nrSupports == 0) { 898 conflicts.add(value); // other class cannot be assigned with this value 899 return; 900 } 901 // Increase needed size if all supporters are of the same room and in overlapping times 902 if (shareRoomAndOverlaps) { 903 neededSize += lecture.maxRoomUse(); 904 } 905 906 // Only one supporter -> propagate the new assignment over other hard constraints of the lecture 907 if (nrSupports == 1) { 908 for (Constraint<Lecture, Placement> other: lecture.hardConstraints()) { 909 if (other instanceof WeakeningConstraint) continue; 910 if (other instanceof GroupConstraint) { 911 GroupConstraint gc = (GroupConstraint)other; 912 if (depth > 0 && !ignore.contains(gc)) 913 gc.forwardCheck(support, conflicts, ignore, depth - 1); 914 } else { 915 other.computeConflicts(support, conflicts); 916 } 917 } 918 for (GlobalConstraint<Lecture, Placement> other: getModel().globalConstraints()) { 919 if (other instanceof WeakeningConstraint) continue; 920 other.computeConflicts(support, conflicts); 921 } 922 923 if (conflicts.contains(support)) 924 conflicts.add(value); 925 } 926 } 927 928 if (canShareRoom() && neededSize > value.getRoomSize()) { 929 // room is too small to fit all meet with classes 930 conflicts.add(value); 931 } 932 933 } finally { 934 ignore.remove(this); 935 } 936 } 937 938 @Override 939 public boolean inConflict(Placement value) { 940 if (!isHard()) 941 return false; 942 for (Lecture v : variables()) { 943 if (v.equals(value.variable())) 944 continue; // ignore this variable 945 if (v.getAssignment() == null) 946 continue; // there is an unassigned variable -- great, still a 947 // chance to get violated 948 if (!isSatisfiedPair(v.getAssignment(), value)) 949 return true; 950 } 951 if (getType().is(Flag.BACK_TO_BACK)) { 952 HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>(); 953 assignments.put(value.variable(), value); 954 if (!isSatisfiedSeq(assignments, true, null)) 955 return true; 956 } 957 if (getType().is(Flag.MAX_HRS_DAY)) { 958 HashMap<Lecture, Placement> assignments = new HashMap<Lecture, Placement>(); 959 assignments.put(value.variable(), value); 960 for (int dayCode: Constants.DAY_CODES) { 961 if (nrSlotsADay(dayCode, assignments, null) > getType().getMax()) 962 return true; 963 } 964 } 965 966 if (!forwardCheck(value, new HashSet<GroupConstraint>(), iForwardCheckMaxDepth - 1)) return true; 967 968 return false; 969 } 970 971 public boolean forwardCheck(Placement value, Set<GroupConstraint> ignore, int depth) { 972 try { 973 if (depth < 0) return true; 974 ignore.add(this); 975 976 int neededSize = value.variable().maxRoomUse(); 977 978 for (Lecture lecture: variables()) { 979 if (lecture.equals(value.variable())) continue; // Skip this lecture 980 if (lecture.getAssignment() != null) { // Has assignment, check whether it is conflicting 981 if (isSatisfiedPair(value, lecture.getAssignment())) { 982 // Increase needed size if the assignment is of the same room and overlapping in time 983 if (canShareRoom() && sameRoomAndOverlaps(value, lecture.getAssignment())) { 984 neededSize += lecture.maxRoomUse(); 985 } 986 continue; 987 } 988 return false; 989 } 990 991 // Look for supporting assignments assignment 992 boolean shareRoomAndOverlaps = canShareRoom(); 993 Placement support = null; 994 int nrSupports = 0; 995 if (lecture.nrValues() >= iForwardCheckMaxDomainSize) { 996 // ignore variables with large domains 997 return true; 998 } 999 if (lecture.values().isEmpty()) { 1000 // ignore variables with empty domain 1001 return true; 1002 } 1003 for (Placement other: lecture.values()) { 1004 if (nrSupports < 2) { 1005 if (isSatisfiedPair(value, other)) { 1006 if (support == null) support = other; 1007 nrSupports ++; 1008 if (shareRoomAndOverlaps && !sameRoomAndOverlaps(value, other)) 1009 shareRoomAndOverlaps = false; 1010 } 1011 } else if (shareRoomAndOverlaps && !sameRoomAndOverlaps(value, other) && isSatisfiedPair(value, other)) { 1012 shareRoomAndOverlaps = false; 1013 } 1014 if (nrSupports > 1 && !shareRoomAndOverlaps) 1015 break; 1016 } 1017 1018 // No supporting assignment -> fail 1019 if (nrSupports == 0) { 1020 return false; // other class cannot be assigned with this value 1021 } 1022 // Increase needed size if all supporters are of the same room and in overlapping times 1023 if (shareRoomAndOverlaps) { 1024 neededSize += lecture.maxRoomUse(); 1025 } 1026 1027 // Only one supporter -> propagate the new assignment over other hard constraints of the lecture 1028 if (nrSupports == 1) { 1029 for (Constraint<Lecture, Placement> other: lecture.hardConstraints()) { 1030 if (other instanceof WeakeningConstraint) continue; 1031 if (other instanceof GroupConstraint) { 1032 GroupConstraint gc = (GroupConstraint)other; 1033 if (depth > 0 && !ignore.contains(gc) && !gc.forwardCheck(support, ignore, depth - 1)) return false; 1034 } else { 1035 if (other.inConflict(support)) return false; 1036 } 1037 } 1038 for (GlobalConstraint<Lecture, Placement> other: getModel().globalConstraints()) { 1039 if (other instanceof WeakeningConstraint) continue; 1040 if (other.inConflict(support)) return false; 1041 } 1042 } 1043 } 1044 1045 if (canShareRoom() && neededSize > value.getRoomSize()) { 1046 // room is too small to fit all meet with classes 1047 return false; 1048 } 1049 1050 return true; 1051 } finally { 1052 ignore.remove(this); 1053 } 1054 } 1055 1056 /** Constraint preference (0 if prohibited or reqired) */ 1057 public int getPreference() { 1058 return iPreference; 1059 } 1060 1061 /** 1062 * Current constraint preference (0 if prohibited or reqired, depends on 1063 * current satisfaction of the constraint) 1064 */ 1065 public int getCurrentPreference() { 1066 if (isHard()) return 0; // no preference 1067 if (countAssignedVariables() < 2) return - Math.abs(iPreference); // not enough variable 1068 if (getType().is(Flag.MAX_HRS_DAY)) { // max hours a day 1069 int over = 0; 1070 for (int dayCode: Constants.DAY_CODES) 1071 over += Math.max(0, nrSlotsADay(dayCode, null, null) - getType().getMax()); 1072 return (over > 0 ? Math.abs(iPreference) * over / 12 : - Math.abs(iPreference)); 1073 } 1074 int nrViolatedPairs = 0; 1075 for (Lecture v1 : variables()) { 1076 if (v1.getAssignment() == null) continue; 1077 for (Lecture v2 : variables()) { 1078 if (v2.getAssignment() == null || v1.getId() >= v2.getId()) continue; 1079 if (!isSatisfiedPair(v1.getAssignment(), v2.getAssignment())) nrViolatedPairs++; 1080 } 1081 } 1082 if (getType().is(Flag.BACK_TO_BACK)) { 1083 Set<Placement> conflicts = new HashSet<Placement>(); 1084 if (isSatisfiedSeq(new HashMap<Lecture, Placement>(), true, conflicts)) 1085 nrViolatedPairs += conflicts.size(); 1086 else 1087 nrViolatedPairs = variables().size(); 1088 } 1089 return (nrViolatedPairs > 0 ? Math.abs(iPreference) * nrViolatedPairs : - Math.abs(iPreference)); 1090 } 1091 1092 /** Current constraint preference change (if given placement is assigned) */ 1093 public int getCurrentPreference(Placement placement) { 1094 if (isHard()) return 0; // no preference 1095 if (countAssignedVariables() + (placement.variable().getAssignment() == null ? 1 : 0) < 2) return 0; // not enough variable 1096 if (getType().is(Flag.MAX_HRS_DAY)) { 1097 HashMap<Lecture, Placement> assignment = new HashMap<Lecture, Placement>(); 1098 assignment.put(placement.variable(), placement); 1099 HashMap<Lecture, Placement> unassignment = new HashMap<Lecture, Placement>(); 1100 unassignment.put(placement.variable(), null); 1101 int after = 0; 1102 int before = 0; 1103 for (int dayCode: Constants.DAY_CODES) { 1104 after += Math.max(0, nrSlotsADay(dayCode, assignment, null) - getType().getMax()); 1105 before += Math.max(0, nrSlotsADay(dayCode, unassignment, null) - getType().getMax()); 1106 } 1107 return (after > 0 ? Math.abs(iPreference) * after / 12 : - Math.abs(iPreference)) - (before > 0 ? Math.abs(iPreference) * before / 12 : - Math.abs(iPreference)); 1108 } 1109 1110 int nrViolatedPairsAfter = 0; 1111 int nrViolatedPairsBefore = 0; 1112 for (Lecture v1 : variables()) { 1113 for (Lecture v2 : variables()) { 1114 if (v1.getId() >= v2.getId()) continue; 1115 Placement p1 = (v1.equals(placement.variable()) ? null : v1.getAssignment()); 1116 Placement p2 = (v2.equals(placement.variable()) ? null : v2.getAssignment()); 1117 if (p1 != null && p2 != null && !isSatisfiedPair(p1, p2)) 1118 nrViolatedPairsBefore ++; 1119 if (v1.equals(placement.variable())) p1 = placement; 1120 if (v2.equals(placement.variable())) p2 = placement; 1121 if (p1 != null && p2 != null && !isSatisfiedPair(p1, p2)) 1122 nrViolatedPairsAfter ++; 1123 } 1124 } 1125 1126 if (getType().is(Flag.BACK_TO_BACK)) { 1127 HashMap<Lecture, Placement> assignment = new HashMap<Lecture, Placement>(); 1128 assignment.put(placement.variable(), placement); 1129 Set<Placement> conflicts = new HashSet<Placement>(); 1130 if (isSatisfiedSeq(assignment, true, conflicts)) 1131 nrViolatedPairsAfter += conflicts.size(); 1132 else 1133 nrViolatedPairsAfter = variables().size(); 1134 1135 HashMap<Lecture, Placement> unassignment = new HashMap<Lecture, Placement>(); 1136 unassignment.put(placement.variable(), null); 1137 Set<Placement> previous = new HashSet<Placement>(); 1138 if (isSatisfiedSeq(unassignment, true, previous)) 1139 nrViolatedPairsBefore += previous.size(); 1140 else 1141 nrViolatedPairsBefore = variables().size(); 1142 } 1143 1144 return (nrViolatedPairsAfter > 0 ? Math.abs(iPreference) * nrViolatedPairsAfter : - Math.abs(iPreference)) - 1145 (nrViolatedPairsBefore > 0 ? Math.abs(iPreference) * nrViolatedPairsBefore : - Math.abs(iPreference)); 1146 } 1147 1148 @Override 1149 public void unassigned(long iteration, Placement value) { 1150 super.unassigned(iteration, value); 1151 if (iIsRequired || iIsProhibited) 1152 return; 1153 getModel().getCriterion(DistributionPreferences.class).inc(-iLastPreference); 1154 iLastPreference = getCurrentPreference(); 1155 getModel().getCriterion(DistributionPreferences.class).inc(iLastPreference); 1156 } 1157 1158 @Override 1159 public void assigned(long iteration, Placement value) { 1160 super.assigned(iteration, value); 1161 if (iIsRequired || iIsProhibited) 1162 return; 1163 getModel().getCriterion(DistributionPreferences.class).inc(-iLastPreference); 1164 iLastPreference = getCurrentPreference(); 1165 getModel().getCriterion(DistributionPreferences.class).inc(iLastPreference); 1166 } 1167 1168 @Override 1169 public String toString() { 1170 StringBuffer sb = new StringBuffer(); 1171 sb.append(getName()); 1172 sb.append(" between "); 1173 for (Iterator<Lecture> e = variables().iterator(); e.hasNext();) { 1174 Lecture v = e.next(); 1175 sb.append(v.getName() + " " + (v.getAssignment() == null ? "" : v.getAssignment().getName())); 1176 if (e.hasNext()) 1177 sb.append(", "); 1178 } 1179 return sb.toString(); 1180 } 1181 1182 @Override 1183 public boolean isHard() { 1184 return iIsRequired || iIsProhibited; 1185 } 1186 1187 @Override 1188 public String getName() { 1189 return getType().getName(); 1190 } 1191 1192 1193 private boolean isPrecedence(Placement p1, Placement p2, boolean firstGoesFirst, boolean considerDatePatterns) { 1194 int ord1 = variables().indexOf(p1.variable()); 1195 int ord2 = variables().indexOf(p2.variable()); 1196 TimeLocation t1 = null, t2 = null; 1197 if (ord1 < ord2) { 1198 if (firstGoesFirst) { 1199 t1 = p1.getTimeLocation(); 1200 t2 = p2.getTimeLocation(); 1201 } else { 1202 t2 = p1.getTimeLocation(); 1203 t1 = p2.getTimeLocation(); 1204 } 1205 } else { 1206 if (!firstGoesFirst) { 1207 t1 = p1.getTimeLocation(); 1208 t2 = p2.getTimeLocation(); 1209 } else { 1210 t2 = p1.getTimeLocation(); 1211 t1 = p2.getTimeLocation(); 1212 } 1213 } 1214 if (considerDatePatterns && iPrecedenceConsiderDatePatterns) { 1215 boolean sameDatePattern = (t1.getDatePatternId() != null ? t1.getDatePatternId().equals(t2.getDatePatternId()) : t1.getWeekCode().equals(t2.getWeekCode())); 1216 if (!sameDatePattern) { 1217 int m1 = t1.getFirstMeeting(iDayOfWeekOffset), m2 = t2.getFirstMeeting(iDayOfWeekOffset); 1218 if (m1 != m2) return m1 < m2; 1219 } 1220 } 1221 return t1.getStartSlots().nextElement() + t1.getLength() <= t2.getStartSlots().nextElement(); 1222 } 1223 1224 private static boolean isBackToBackDays(TimeLocation t1, TimeLocation t2) { 1225 int f1 = -1, f2 = -1, e1 = -1, e2 = -1; 1226 for (int i = 0; i < Constants.DAY_CODES.length; i++) { 1227 if ((t1.getDayCode() & Constants.DAY_CODES[i]) != 0) { 1228 if (f1 < 0) 1229 f1 = i; 1230 e1 = i; 1231 } 1232 if ((t2.getDayCode() & Constants.DAY_CODES[i]) != 0) { 1233 if (f2 < 0) 1234 f2 = i; 1235 e2 = i; 1236 } 1237 } 1238 return (e1 + 1 == f2) || (e2 + 1 == f1); 1239 } 1240 1241 private static boolean isNrDaysBetweenGreaterThanOne(TimeLocation t1, TimeLocation t2) { 1242 int f1 = -1, f2 = -1, e1 = -1, e2 = -1; 1243 for (int i = 0; i < Constants.DAY_CODES.length; i++) { 1244 if ((t1.getDayCode() & Constants.DAY_CODES[i]) != 0) { 1245 if (f1 < 0) 1246 f1 = i; 1247 e1 = i; 1248 } 1249 if ((t2.getDayCode() & Constants.DAY_CODES[i]) != 0) { 1250 if (f2 < 0) 1251 f2 = i; 1252 e2 = i; 1253 } 1254 } 1255 return (e1 - f2 > 2) || (e2 - f1 > 2); 1256 } 1257 1258 private boolean isFollowingDay(Placement p1, Placement p2, boolean firstGoesFirst) { 1259 int ord1 = variables().indexOf(p1.variable()); 1260 int ord2 = variables().indexOf(p2.variable()); 1261 TimeLocation t1 = null, t2 = null; 1262 if (ord1 < ord2) { 1263 if (firstGoesFirst) { 1264 t1 = p1.getTimeLocation(); 1265 t2 = p2.getTimeLocation(); 1266 } else { 1267 t2 = p1.getTimeLocation(); 1268 t1 = p2.getTimeLocation(); 1269 } 1270 } else { 1271 if (!firstGoesFirst) { 1272 t1 = p1.getTimeLocation(); 1273 t2 = p2.getTimeLocation(); 1274 } else { 1275 t2 = p1.getTimeLocation(); 1276 t1 = p2.getTimeLocation(); 1277 } 1278 } 1279 int f1 = -1, f2 = -1, e1 = -1; 1280 for (int i = 0; i < Constants.DAY_CODES.length; i++) { 1281 if ((t1.getDayCode() & Constants.DAY_CODES[i]) != 0) { 1282 if (f1 < 0) 1283 f1 = i; 1284 e1 = i; 1285 } 1286 if ((t2.getDayCode() & Constants.DAY_CODES[i]) != 0) { 1287 if (f2 < 0) 1288 f2 = i; 1289 } 1290 } 1291 return ((e1 + 1) % Constants.NR_DAYS_WEEK == f2); 1292 } 1293 1294 private boolean isEveryOtherDay(Placement p1, Placement p2, boolean firstGoesFirst) { 1295 int ord1 = variables().indexOf(p1.variable()); 1296 int ord2 = variables().indexOf(p2.variable()); 1297 TimeLocation t1 = null, t2 = null; 1298 if (ord1 < ord2) { 1299 if (firstGoesFirst) { 1300 t1 = p1.getTimeLocation(); 1301 t2 = p2.getTimeLocation(); 1302 } else { 1303 t2 = p1.getTimeLocation(); 1304 t1 = p2.getTimeLocation(); 1305 } 1306 } else { 1307 if (!firstGoesFirst) { 1308 t1 = p1.getTimeLocation(); 1309 t2 = p2.getTimeLocation(); 1310 } else { 1311 t2 = p1.getTimeLocation(); 1312 t1 = p2.getTimeLocation(); 1313 } 1314 } 1315 int f1 = -1, f2 = -1, e1 = -1; 1316 for (int i = 0; i < Constants.DAY_CODES.length; i++) { 1317 if ((t1.getDayCode() & Constants.DAY_CODES[i]) != 0) { 1318 if (f1 < 0) 1319 f1 = i; 1320 e1 = i; 1321 } 1322 if ((t2.getDayCode() & Constants.DAY_CODES[i]) != 0) { 1323 if (f2 < 0) 1324 f2 = i; 1325 } 1326 } 1327 return ((e1 + 2) % Constants.NR_DAYS_WEEK == f2); 1328 } 1329 1330 private static boolean sameDays(int[] days1, int[] days2) { 1331 if (days2.length < days1.length) 1332 return sameDays(days2, days1); 1333 int i2 = 0; 1334 for (int i1 = 0; i1 < days1.length; i1++) { 1335 int d1 = days1[i1]; 1336 while (true) { 1337 if (i2 == days2.length) 1338 return false; 1339 int d2 = days2[i2]; 1340 if (d1 == d2) 1341 break; 1342 i2++; 1343 if (i2 == days2.length) 1344 return false; 1345 } 1346 i2++; 1347 } 1348 return true; 1349 } 1350 1351 private static boolean sameRoomAndOverlaps(Placement p1, Placement p2) { 1352 return p1.shareRooms(p2) && p1.getTimeLocation() != null && p2.getTimeLocation() != null && p1.getTimeLocation().hasIntersection(p2.getTimeLocation()); 1353 } 1354 1355 private static boolean sameHours(int start1, int len1, int start2, int len2) { 1356 if (len1 > len2) 1357 return sameHours(start2, len2, start1, len1); 1358 start1 %= Constants.SLOTS_PER_DAY; 1359 start2 %= Constants.SLOTS_PER_DAY; 1360 return (start1 >= start2 && start1 + len1 <= start2 + len2); 1361 } 1362 1363 private static boolean canFill(int totalGap, int gapMin, int gapMax, List<Integer> lengths) { 1364 if (gapMin <= totalGap && totalGap <= gapMax) 1365 return true; 1366 if (totalGap < 2 * gapMin) 1367 return false; 1368 for (int i = 0; i < lengths.size(); i++) { 1369 int length = lengths.get(i); 1370 lengths.remove(i); 1371 for (int gap = gapMin; gap <= gapMax; gap++) 1372 if (canFill(totalGap - gap - length, gapMin, gapMax, lengths)) 1373 return true; 1374 lengths.add(i, length); 1375 } 1376 return false; 1377 } 1378 1379 private boolean isSatisfiedSeq(HashMap<Lecture, Placement> assignments, boolean considerCurrentAssignments, 1380 Set<Placement> conflicts) { 1381 if (conflicts == null) 1382 return isSatisfiedSeqCheck(assignments, considerCurrentAssignments, conflicts); 1383 else { 1384 Set<Placement> bestConflicts = isSatisfiedRecursive(0, assignments, considerCurrentAssignments, conflicts, 1385 new HashSet<Placement>(), null); 1386 if (bestConflicts == null) 1387 return false; 1388 conflicts.addAll(bestConflicts); 1389 return true; 1390 } 1391 } 1392 1393 private Set<Placement> isSatisfiedRecursive(int idx, HashMap<Lecture, Placement> assignments, 1394 boolean considerCurrentAssignments, Set<Placement> conflicts, Set<Placement> newConflicts, 1395 Set<Placement> bestConflicts) { 1396 if (idx == variables().size() && newConflicts.isEmpty()) 1397 return bestConflicts; 1398 if (isSatisfiedSeqCheck(assignments, considerCurrentAssignments, conflicts)) { 1399 if (bestConflicts == null) { 1400 return new HashSet<Placement>(newConflicts); 1401 } else { 1402 int b = 0, n = 0; 1403 for (Placement value: assignments.values()) { 1404 if (value != null && bestConflicts.contains(value)) b++; 1405 if (value != null && newConflicts.contains(value)) n++; 1406 } 1407 if (n < b || (n == b && newConflicts.size() < bestConflicts.size())) 1408 return new HashSet<Placement>(newConflicts); 1409 } 1410 return bestConflicts; 1411 } 1412 if (idx == variables().size()) 1413 return bestConflicts; 1414 bestConflicts = isSatisfiedRecursive(idx + 1, assignments, considerCurrentAssignments, conflicts, newConflicts, 1415 bestConflicts); 1416 Lecture lecture = variables().get(idx); 1417 //if (assignments != null && assignments.containsKey(lecture)) 1418 // return bestConflicts; 1419 Placement placement = null; 1420 if (assignments != null && assignments.containsKey(lecture)) 1421 placement = assignments.get(lecture); 1422 else if (considerCurrentAssignments) 1423 placement = lecture.getAssignment(); 1424 if (placement == null) 1425 return bestConflicts; 1426 if (conflicts != null && conflicts.contains(placement)) 1427 return bestConflicts; 1428 conflicts.add(placement); 1429 newConflicts.add(placement); 1430 bestConflicts = isSatisfiedRecursive(idx + 1, assignments, considerCurrentAssignments, conflicts, newConflicts, 1431 bestConflicts); 1432 newConflicts.remove(placement); 1433 conflicts.remove(placement); 1434 return bestConflicts; 1435 } 1436 1437 private boolean isSatisfiedSeqCheck(HashMap<Lecture, Placement> assignments, boolean considerCurrentAssignments, 1438 Set<Placement> conflicts) { 1439 if (!getType().is(Flag.BACK_TO_BACK)) return true; 1440 int gapMin = getType().getMin(); 1441 int gapMax = getType().getMax(); 1442 1443 List<Integer> lengths = new ArrayList<Integer>(); 1444 1445 Placement[] res = new Placement[Constants.SLOTS_PER_DAY]; 1446 for (int i = 0; i < Constants.SLOTS_PER_DAY; i++) 1447 res[i] = null; 1448 1449 int nrLectures = 0; 1450 1451 for (Lecture lecture : variables()) { 1452 Placement placement = null; 1453 if (assignments != null && assignments.containsKey(lecture)) 1454 placement = assignments.get(lecture); 1455 else if (considerCurrentAssignments) 1456 placement = lecture.getAssignment(); 1457 if (placement == null) { 1458 lengths.add(lecture.timeLocations().get(0).getLength()); 1459 } else if (conflicts != null && conflicts.contains(placement)) { 1460 lengths.add(lecture.timeLocations().get(0).getLength()); 1461 } else { 1462 int pos = placement.getTimeLocation().getStartSlot(); 1463 int length = placement.getTimeLocation().getLength(); 1464 for (int j = pos; j < pos + length; j++) { 1465 if (res[j] != null) { 1466 if (conflicts == null) 1467 return false; 1468 if (!assignments.containsKey(lecture)) 1469 conflicts.add(placement); 1470 else if (!assignments.containsKey(res[j].variable())) 1471 conflicts.add(res[j]); 1472 } 1473 } 1474 for (int j = pos; j < pos + length; j++) 1475 res[j] = placement; 1476 nrLectures++; 1477 } 1478 } 1479 if (nrLectures <= 1) 1480 return true; 1481 1482 if (iIsRequired || (!iIsProhibited && iPreference < 0)) { 1483 int i = 0; 1484 Placement p = res[i]; 1485 while (p == null) 1486 p = res[++i]; 1487 i += res[i].getTimeLocation().getLength(); 1488 nrLectures--; 1489 while (nrLectures > 0) { 1490 int gap = 0; 1491 while (i < Constants.SLOTS_PER_DAY && res[i] == null) { 1492 gap++; 1493 i++; 1494 } 1495 if (i == Constants.SLOTS_PER_DAY) 1496 break; 1497 if (!canFill(gap, gapMin, gapMax, lengths)) 1498 return false; 1499 p = res[i]; 1500 i += res[i].getTimeLocation().getLength(); 1501 nrLectures--; 1502 } 1503 } else if (iIsProhibited || (!iIsRequired && iPreference > 0)) { 1504 int i = 0; 1505 Placement p = res[i]; 1506 while (p == null) 1507 p = res[++i]; 1508 i += res[i].getTimeLocation().getLength(); 1509 nrLectures--; 1510 while (nrLectures > 0) { 1511 int gap = 0; 1512 while (i < Constants.SLOTS_PER_DAY && res[i] == null) { 1513 gap++; 1514 i++; 1515 } 1516 if (i == Constants.SLOTS_PER_DAY) 1517 break; 1518 if ((gapMin == 0 || !canFill(gap, 0, gapMin - 1, lengths)) 1519 && (gapMax >= Constants.SLOTS_PER_DAY || !canFill(gap, gapMax + 1, Constants.SLOTS_PER_DAY, 1520 lengths))) { 1521 return false; 1522 } 1523 p = res[i]; 1524 i += res[i].getTimeLocation().getLength(); 1525 nrLectures--; 1526 } 1527 } 1528 return true; 1529 } 1530 1531 public boolean isSatisfied() { 1532 if (isHard()) return true; 1533 if (countAssignedVariables() < 2) return true; 1534 if (getPreference() == 0) return true; 1535 return isHard() || countAssignedVariables() < 2 || getPreference() == 0 || getCurrentPreference() < 0; 1536 } 1537 1538 public boolean isChildrenNotOverlap(Lecture lec1, Placement plc1, Lecture lec2, Placement plc2) { 1539 if (lec1.getSchedulingSubpartId().equals(lec2.getSchedulingSubpartId())) { 1540 // same subpart 1541 boolean overlap = plc1.getTimeLocation().hasIntersection(plc2.getTimeLocation()); 1542 1543 if (overlap && lec1.getParent() != null && variables().contains(lec1.getParent()) 1544 && lec2.getParent() != null && variables().contains(lec2.getParent())) { 1545 // children overlaps 1546 Placement p1 = lec1.getParent().getAssignment(); 1547 Placement p2 = lec2.getParent().getAssignment(); 1548 // parents not overlap, but children do 1549 if (p1 != null && p2 != null && !p1.getTimeLocation().hasIntersection(p2.getTimeLocation())) 1550 return false; 1551 } 1552 1553 if (!overlap && lec1.getChildrenSubpartIds() != null && lec2.getChildrenSubpartIds() != null) { 1554 // parents not overlap 1555 for (Long subpartId: lec1.getChildrenSubpartIds()) { 1556 for (Lecture c1 : lec1.getChildren(subpartId)) { 1557 if (c1.getAssignment() == null) 1558 continue; 1559 for (Lecture c2 : lec2.getChildren(subpartId)) { 1560 if (c2.getAssignment() == null) 1561 continue; 1562 if (!c1.getSchedulingSubpartId().equals(c2.getSchedulingSubpartId())) 1563 continue; 1564 Placement p1 = c1.getAssignment(); 1565 Placement p2 = c2.getAssignment(); 1566 // parents not overlap, but children do 1567 if (p1.getTimeLocation().hasIntersection(p2.getTimeLocation())) 1568 return false; 1569 } 1570 } 1571 } 1572 } 1573 } else { 1574 // different subpart 1575 } 1576 return true; 1577 } 1578 1579 public boolean isSatisfiedPair(Placement plc1, Placement plc2) { 1580 if (iIsRequired || (!iIsProhibited && iPreference <= 0)) 1581 return getType().isSatisfied(this, plc1, plc2); 1582 else if (iIsProhibited || (!iIsRequired && iPreference > 0)) 1583 return getType().isViolated(this, plc1, plc2); 1584 return true; 1585 } 1586 1587 public boolean canShareRoom() { 1588 return getType().is(Flag.CAN_SHARE_ROOM); 1589 } 1590 1591 private int nrSlotsADay(int dayCode, HashMap<Lecture, Placement> assignments, Set<Placement> conflicts) { 1592 Set<Integer> slots = new HashSet<Integer>(); 1593 for (Lecture lecture: variables()) { 1594 Placement placement = null; 1595 if (assignments != null && assignments.containsKey(lecture)) 1596 placement = assignments.get(lecture); 1597 else 1598 placement = lecture.getAssignment(); 1599 if (placement == null || placement.getTimeLocation() == null) continue; 1600 if (conflicts != null && conflicts.contains(placement)) continue; 1601 TimeLocation t = placement.getTimeLocation(); 1602 if (t == null || (t.getDayCode() & dayCode) == 0) continue; 1603 for (int i = 0; i < t.getLength(); i++) 1604 slots.add(i + t.getStartSlot()); 1605 } 1606 return slots.size(); 1607 } 1608 1609 @Override 1610 public boolean equals(Object o) { 1611 if (o == null || !(o instanceof GroupConstraint)) return false; 1612 return getGeneratedId() == ((GroupConstraint) o).getGeneratedId(); 1613 } 1614}