001package org.cpsolver.exam.model; 002 003import java.util.Iterator; 004import java.util.Set; 005 006import org.cpsolver.exam.criteria.DistributionPenalty; 007import org.cpsolver.ifs.assignment.Assignment; 008import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext; 009import org.cpsolver.ifs.assignment.context.ConstraintWithContext; 010 011 012/** 013 * Distribution binary constraint. <br> 014 * <br> 015 * The following binary distribution constraints are implemented 016 * <ul> 017 * <li>Same room 018 * <li>Different room 019 * <li>Same period 020 * <li>Different period 021 * <li>Precedence 022 * <li>Same day 023 * </ul> 024 * <br> 025 * <br> 026 * 027 * @author Tomáš Müller 028 * @version ExamTT 1.3 (Examination Timetabling)<br> 029 * Copyright (C) 2008 - 2014 Tomáš Müller<br> 030 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 031 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 032 * <br> 033 * This library is free software; you can redistribute it and/or modify 034 * it under the terms of the GNU Lesser General Public License as 035 * published by the Free Software Foundation; either version 3 of the 036 * License, or (at your option) any later version. <br> 037 * <br> 038 * This library is distributed in the hope that it will be useful, but 039 * WITHOUT ANY WARRANTY; without even the implied warranty of 040 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 041 * Lesser General Public License for more details. <br> 042 * <br> 043 * You should have received a copy of the GNU Lesser General Public 044 * License along with this library; if not see 045 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 046 */ 047public class ExamDistributionConstraint extends ConstraintWithContext<Exam, ExamPlacement, ExamDistributionConstraint.Context> { 048 @Deprecated 049 public static int sDistSameRoom = DistributionType.SameRoom.ordinal(); 050 private DistributionType iType = null; 051 private boolean iHard = true; 052 private int iWeight = 0; 053 054 public static enum DistributionType { 055 /** Same room constraint type */ 056 SameRoom("same-room", "EX_SAME_ROOM", false, new RoomCheck() { 057 @Override 058 public boolean isSatisfied(ExamPlacement first, ExamPlacement second) { 059 return first.getRoomPlacements().containsAll(second.getRoomPlacements()) || second.getRoomPlacements().containsAll(first.getRoomPlacements()); 060 } 061 @Override 062 public boolean isSatisfied(ExamPlacement first, ExamRoomPlacement second) { 063 return first.getRoomPlacements().contains(second); 064 }}), 065 /** Different room constraint type */ 066 DifferentRoom("different-room", "EX_SAME_ROOM", true, new RoomCheck() { 067 @Override 068 public boolean isSatisfied(ExamPlacement first, ExamPlacement second) { 069 for (Iterator<ExamRoomPlacement> i = first.getRoomPlacements().iterator(); i.hasNext();) 070 if (second.getRoomPlacements().contains(i.next())) 071 return false; 072 return true; 073 } 074 @Override 075 public boolean isSatisfied(ExamPlacement first, ExamRoomPlacement second) { 076 return !first.getRoomPlacements().contains(second); 077 }}), 078 /** Same period constraint type */ 079 SamePeriod("same-period", "EX_SAME_PER", false, new PeriodCheck() { 080 @Override 081 public boolean isSatisfied(ExamPlacement first, ExamPlacement second) { 082 return first.getPeriod().getIndex() == second.getPeriod().getIndex(); 083 } 084 @Override 085 public boolean isSatisfied(ExamPeriod first, ExamPeriod second) { 086 return first.getIndex() == second.getIndex(); 087 }}), 088 /** Different period constraint type */ 089 DifferentPeriod("different-period", "EX_SAME_PER", true, new PeriodCheck() { 090 @Override 091 public boolean isSatisfied(ExamPlacement first, ExamPlacement second) { 092 return first.getPeriod().getIndex() != second.getPeriod().getIndex(); 093 } 094 @Override 095 public boolean isSatisfied(ExamPeriod first, ExamPeriod second) { 096 return first.getIndex() != second.getIndex(); 097 }}), 098 /** Precedence constraint type */ 099 Precedence("precedence", "EX_PRECEDENCE", false, new PeriodCheck() { 100 @Override 101 public boolean isSatisfied(ExamPlacement first, ExamPlacement second) { 102 return first.getPeriod().getIndex() < second.getPeriod().getIndex(); 103 } 104 @Override 105 public boolean isSatisfied(ExamPeriod first, ExamPeriod second) { 106 return first.getIndex() < second.getIndex(); 107 }}), 108 /** Precedence constraint type (reverse order) */ 109 PrecedenceRev("precedence-rev", "EX_PRECEDENCE", true, new PeriodCheck() { 110 @Override 111 public boolean isSatisfied(ExamPlacement first, ExamPlacement second) { 112 return first.getPeriod().getIndex() > second.getPeriod().getIndex(); 113 } 114 @Override 115 public boolean isSatisfied(ExamPeriod first, ExamPeriod second) { 116 return first.getIndex() > second.getIndex(); 117 }}), 118 /** Same day constraint type */ 119 SameDay("same-day", "EX_SAME_DAY", false, new PeriodCheck() { 120 @Override 121 public boolean isSatisfied(ExamPlacement first, ExamPlacement second) { 122 return first.getPeriod().getDay() == second.getPeriod().getDay(); 123 } 124 @Override 125 public boolean isSatisfied(ExamPeriod first, ExamPeriod second) { 126 return first.getDay() == second.getDay(); 127 }}), 128 /** Different day constraint type */ 129 DifferentDay("different-day", "EX_SAME_DAY", true, new PeriodCheck() { 130 @Override 131 public boolean isSatisfied(ExamPlacement first, ExamPlacement second) { 132 return first.getPeriod().getDay() != second.getPeriod().getDay(); 133 } 134 @Override 135 public boolean isSatisfied(ExamPeriod first, ExamPeriod second) { 136 return first.getDay() != second.getDay(); 137 }}), 138 ; 139 private String iReference; 140 private String iUniTimeReference; 141 private boolean iUniTimeNegative; 142 private PairCheck iCheck; 143 private DistributionType(String reference, String utReference, boolean utNegative, PairCheck check) { 144 iReference = reference; 145 iUniTimeReference = utReference; 146 iUniTimeNegative = utNegative; 147 iCheck = check; 148 } 149 150 public String getReference() { return iReference; } 151 public boolean isSatisfied(ExamPlacement first, ExamPlacement second) { 152 return iCheck.isSatisfied(first, second); 153 } 154 public boolean isRoomRelated() { return iCheck instanceof RoomCheck; } 155 public boolean isPeriodRelated() { return iCheck instanceof PeriodCheck; } 156 public boolean isSatisfied(ExamPeriod first, ExamPeriod second) { 157 if (iCheck instanceof PeriodCheck) 158 return ((PeriodCheck)iCheck).isSatisfied(first, second); 159 else 160 return true; 161 } 162 public boolean isSatisfied(ExamPlacement first, ExamRoomPlacement second) { 163 if (iCheck instanceof RoomCheck) 164 return ((RoomCheck)iCheck).isSatisfied(first, second); 165 else 166 return true; 167 } 168 public String getUniTimeReference() { return iUniTimeReference; } 169 public boolean isUniTimeNegative() { return iUniTimeNegative; } 170 } 171 172 public static interface PairCheck { 173 public boolean isSatisfied(ExamPlacement first, ExamPlacement second); 174 } 175 176 public static interface PeriodCheck extends PairCheck { 177 public boolean isSatisfied(ExamPeriod first, ExamPeriod second); 178 } 179 180 public static interface RoomCheck extends PairCheck { 181 public boolean isSatisfied(ExamPlacement first, ExamRoomPlacement second); 182 } 183 184 /** 185 * Constructor 186 * 187 * @param id 188 * constraint unique id 189 * @param type 190 * constraint type 191 * @param hard 192 * true if the constraint is hard (cannot be violated) 193 * @param weight 194 * if not hard, penalty for violation 195 */ 196 public ExamDistributionConstraint(long id, DistributionType type, boolean hard, int weight) { 197 iId = id; 198 iType = type; 199 iHard = hard; 200 iWeight = weight; 201 } 202 203 /** 204 * Constructor 205 * 206 * @param id 207 * constraint unique id 208 * @param type 209 * constraint type 210 * @param hard 211 * true if the constraint is hard (cannot be violated) 212 * @param weight 213 * if not hard, penalty for violation 214 * @deprecated use {@link ExamDistributionConstraint#ExamDistributionConstraint(long, DistributionType, boolean, int)} 215 */ 216 @Deprecated 217 public ExamDistributionConstraint(long id, int type, boolean hard, int weight) { 218 iId = id; 219 iType = DistributionType.values()[type]; 220 iHard = hard; 221 iWeight = weight; 222 } 223 224 /** 225 * Constructor 226 * 227 * @param id 228 * constraint unique id 229 * @param type 230 * constraint type (EX_SAME_PREF, EX_SAME_ROOM, or EX_PRECEDENCE) 231 * @param pref 232 * preference (R/P for required/prohibited, or -2, -1, 0, 1, 2 233 * for preference (from preferred to discouraged)) 234 */ 235 public ExamDistributionConstraint(long id, String type, String pref) { 236 iId = id; 237 boolean neg = "P".equals(pref) || "2".equals(pref) || "1".equals(pref); 238 for (DistributionType t: DistributionType.values()) { 239 if (t.getUniTimeReference().equals(type) && t.isUniTimeNegative() == neg) { 240 iType = t; 241 break; 242 } 243 } 244 if (iType == null) 245 throw new RuntimeException("Unkown type " + type); 246 if ("P".equals(pref) || "R".equals(pref)) 247 iHard = true; 248 else { 249 iHard = false; 250 iWeight = Integer.parseInt(pref) * Integer.parseInt(pref); 251 } 252 } 253 254 /** 255 * Constructor 256 * 257 * @param id 258 * constraint unique id 259 * @param type 260 * constraint type name 261 * @param hard true if the constraint is hard 262 * @param weight constraint penalty if violated (for soft constraint) 263 */ 264 public ExamDistributionConstraint(long id, String type, boolean hard, int weight) { 265 iId = id; 266 for (DistributionType t: DistributionType.values()) { 267 if (t.getReference().equals(type)) { 268 iType = t; break; 269 } 270 } 271 if (iType == null) 272 throw new RuntimeException("Unknown type '" + type + "'."); 273 iHard = hard; 274 iWeight = weight; 275 } 276 277 /** 278 * True if hard (must be satisfied), false for soft (should be satisfied) 279 */ 280 @Override 281 public boolean isHard() { 282 return iHard; 283 } 284 285 /** 286 * If not hard, penalty for violation 287 * @return constraint penalty if violated 288 */ 289 public int getWeight() { 290 return iWeight; 291 } 292 293 /** 294 * Constraint type 295 * @return constraint type 296 */ 297 public int getType() { 298 return iType.ordinal() - 1; 299 } 300 301 public DistributionType getDistributionType() { 302 return iType; 303 } 304 305 /** 306 * Constraint type name 307 * @return constraint type name (one of {@link DistributionType#getReference()}) 308 */ 309 public String getTypeString() { 310 return iType.getReference(); 311 } 312 313 /** 314 * String representation -- constraint type name (exam 1, exam 2) 315 */ 316 @Override 317 public String toString() { 318 return getTypeString() + " (" + variables() + ")"; 319 } 320 321 /** 322 * Compute conflicts -- there is a conflict if the other variable is 323 * assigned and 324 * {@link ExamDistributionConstraint#check(ExamPlacement, ExamPlacement)} is 325 * false 326 */ 327 @Override 328 public void computeConflicts(Assignment<Exam, ExamPlacement> assignment, ExamPlacement givenPlacement, Set<ExamPlacement> conflicts) { 329 boolean before = true; 330 for (Exam exam : variables()) { 331 if (exam.equals(givenPlacement.variable())) { 332 before = false; 333 continue; 334 } 335 ExamPlacement placement = assignment.getValue(exam); 336 if (placement == null) 337 continue; 338 if (!iType.isSatisfied(before ? placement : givenPlacement, before ? givenPlacement : placement)) 339 conflicts.add(placement); 340 } 341 } 342 343 /** 344 * Check for conflict -- there is a conflict if the other variable is 345 * assigned and 346 * {@link ExamDistributionConstraint#check(ExamPlacement, ExamPlacement)} is 347 * false 348 */ 349 @Override 350 public boolean inConflict(Assignment<Exam, ExamPlacement> assignment, ExamPlacement givenPlacement) { 351 boolean before = true; 352 for (Exam exam : variables()) { 353 if (exam.equals(givenPlacement.variable())) { 354 before = false; 355 continue; 356 } 357 ExamPlacement placement = assignment.getValue(exam); 358 if (placement == null) 359 continue; 360 if (!iType.isSatisfied(before ? placement : givenPlacement, before ? givenPlacement : placement)) 361 return true; 362 } 363 return false; 364 } 365 366 /** 367 * Consistency check -- 368 * {@link ExamDistributionConstraint#check(ExamPlacement, ExamPlacement)} is 369 * called 370 */ 371 @Override 372 public boolean isConsistent(ExamPlacement first, ExamPlacement second) { 373 boolean before = (variables().indexOf(first.variable()) < variables().indexOf(second.variable())); 374 return iType.isSatisfied(before ? first : second, before ? second : first); 375 } 376 377 /** 378 * Check assignments of the given exams 379 * 380 * @param first 381 * assignment of the first exam 382 * @param second 383 * assignment of the second exam 384 * @return true, if the constraint is satisfied 385 * @deprecated use {@link DistributionType#isSatisfied(ExamPlacement, ExamPlacement)} 386 */ 387 @Deprecated 388 public boolean check(ExamPlacement first, ExamPlacement second) { 389 return iType.isSatisfied(first, second); 390 } 391 392 /** 393 * Compare with other constraint for equality 394 */ 395 @Override 396 public boolean equals(Object o) { 397 if (o == null || !(o instanceof ExamDistributionConstraint)) 398 return false; 399 ExamDistributionConstraint c = (ExamDistributionConstraint) o; 400 return getType() == c.getType() && getId() == c.getId(); 401 } 402 403 /** 404 * Return true if this is hard constraint or this is a soft constraint 405 * without any violation 406 * @param assignment current assignment 407 * @return true if the constraint is satisfied 408 */ 409 public boolean isSatisfied(Assignment<Exam, ExamPlacement> assignment) { 410 return isSatisfied(assignment, null); 411 } 412 413 /** 414 * Return true if this is hard constraint or this is a soft constraint 415 * without any violation 416 * 417 * @param assignment current assignment 418 * @param p 419 * exam assignment to be made 420 * @return true if the constraint is satisfied 421 */ 422 public boolean isSatisfied(Assignment<Exam, ExamPlacement> assignment, ExamPlacement p) { 423 return countViolations(assignment, p) == 0; 424 } 425 426 /** 427 * Return number of violated pairs for a soft constraint and the given placement 428 * 429 * @param assignment current assignment 430 * @param p 431 * exam assignment to be made 432 * @return number of examination pairs violated 433 */ 434 public int countViolations(Assignment<Exam, ExamPlacement> assignment, ExamPlacement p) { 435 if (isHard()) return 0; 436 if (p == null) return countViolations(assignment); 437 if (countAssignedVariables(assignment) + (assignment.getValue(p.variable()) == null ? 1 : 0) < 2) return 0; // not enough variables 438 439 int nrViolatedPairs = 0; 440 boolean before = true; 441 for (Exam other: variables()) { 442 if (other.equals(p.variable())) { 443 before = false; 444 continue; 445 } 446 ExamPlacement otherPlacement = assignment.getValue(other); 447 if (otherPlacement == null) continue; 448 if (before) { 449 if (!iType.isSatisfied(otherPlacement, p)) nrViolatedPairs ++; 450 } else { 451 if (!iType.isSatisfied(p, otherPlacement)) nrViolatedPairs ++; 452 } 453 } 454 return nrViolatedPairs; 455 } 456 457 /** 458 * Return number of all violated pairs for a soft constraint 459 * 460 * @param assignment current assignment 461 * @return number of examination pairs violated 462 */ 463 public int countViolations(Assignment<Exam, ExamPlacement> assignment) { 464 if (isHard()) return 0; 465 int violations = 0; 466 for (int i = 0; i < variables().size() - 1; i++) { 467 ExamPlacement first = assignment.getValue(variables().get(i)); 468 if (first == null) continue; 469 for (int j = i + 1; j < variables().size(); j++) { 470 ExamPlacement second = assignment.getValue(variables().get(j)); 471 if (second == null) continue; 472 if (!iType.isSatisfied(first, second)) violations ++; 473 } 474 } 475 return violations; 476 } 477 478 /** True if the constraint is related to rooms 479 * @return true if the constraint is related to room placement 480 **/ 481 public boolean isRoomRelated() { 482 return iType.isRoomRelated(); 483 } 484 485 /** True if the constraint is related to periods 486 * @return true if the constraint is related to period placement 487 **/ 488 public boolean isPeriodRelated() { 489 return iType.isPeriodRelated(); 490 } 491 492 @Override 493 public Context createAssignmentContext(Assignment<Exam, ExamPlacement> assignment) { 494 return new Context(assignment); 495 } 496 497 public class Context implements AssignmentConstraintContext<Exam, ExamPlacement> { 498 private int iViolations = 0; 499 500 public Context(Assignment<Exam, ExamPlacement> assignment) { 501 updateCriterion(assignment); 502 } 503 504 @Override 505 public void assigned(Assignment<Exam, ExamPlacement> assignment, ExamPlacement placement) { 506 updateCriterion(assignment); 507 } 508 509 @Override 510 public void unassigned(Assignment<Exam, ExamPlacement> assignment, ExamPlacement placement) { 511 updateCriterion(assignment); 512 } 513 514 protected void updateCriterion(Assignment<Exam, ExamPlacement> assignment) { 515 if (!isHard()) { 516 ((DistributionPenalty)getModel().getCriterion(DistributionPenalty.class)).inc(assignment, -iViolations * getWeight(), ExamDistributionConstraint.this); 517 iViolations = countViolations(assignment); 518 ((DistributionPenalty)getModel().getCriterion(DistributionPenalty.class)).inc(assignment, +iViolations * getWeight(), ExamDistributionConstraint.this); 519 } 520 } 521 522 public int getViolations() { return iViolations; } 523 } 524}