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