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