001 package net.sf.cpsolver.exam.model; 002 003 import java.text.DecimalFormat; 004 import java.util.Enumeration; 005 import java.util.HashSet; 006 import java.util.Iterator; 007 import java.util.Set; 008 009 import net.sf.cpsolver.ifs.model.Value; 010 011 /** 012 * Representation of an exam placement (problem value), i.e., assignment of an exam to period and room(s). 013 * Each placement has defined a period and a set of rooms. The exam as well as the rooms have to be available 014 * during the given period (see {@link Exam#getPeriodPlacements()} and {@link Exam#getRoomPlacements()}). 015 * The total size of rooms have to be equal or greater than the number of students enrolled in the exam 016 * {@link Exam#getSize()}, using either 017 * {@link ExamRoom#getSize()} or {@link ExamRoom#getAltSize()}, depending on {@link Exam#hasAltSeating()}. 018 * Also, the number of rooms has to be smaller or equal to {@link Exam#getMaxRooms()}. If 019 * {@link Exam#getMaxRooms()} is zero, the exam is only to be assigned to period (the set of rooms is empty). 020 * <br><br> 021 * The cost of an assignment consists of the following criteria: 022 * <ul> 023 * <li> Direct student conflicts {@link ExamPlacement#getNrDirectConflicts()}, weighted by {@link ExamModel#getDirectConflictWeight()} 024 * <li> More than two exams a day student conflicts {@link ExamPlacement#getNrMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getMoreThanTwoADayWeight()} 025 * <li> Back-to-back student conflicts {@link ExamPlacement#getNrBackToBackConflicts()}, weighted by {@link ExamModel#getBackToBackConflictWeight()} 026 * <li> Distance back-to-back student conflicts {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getDistanceBackToBackConflictWeight()} 027 * <li> Period penalty {@link ExamPlacement#getPeriodPenalty()}, weighted by {@link ExamModel#getPeriodWeight()} 028 * <li> Room size penalty {@link ExamPlacement#getRoomSizePenalty()}, weighted by {@link ExamModel#getRoomSizeWeight()} 029 * <li> Room split penalty {@link ExamPlacement#getRoomSplitPenalty()}, weighted by {@link ExamModel#getRoomSplitWeight()} 030 * <li> Room penalty {@link ExamPlacement#getRoomPenalty()}, weighted by {@link ExamModel#getRoomWeight()} 031 * <li> Exam rotation penalty {@link ExamPlacement#getRotationPenalty()}, weighted by {@link ExamModel#getExamRotationWeight()} 032 * <li> Direct instructor conflicts {@link ExamPlacement#getNrInstructorDirectConflicts()}, weighted by {@link ExamModel#getInstructorDirectConflictWeight()} 033 * <li> More than two exams a day instructor conflicts {@link ExamPlacement#getNrInstructorMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getInstructorMoreThanTwoADayWeight()} 034 * <li> Back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorBackToBackConflictWeight()} 035 * <li> Distance back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorDistanceBackToBackConflictWeight()} 036 * </ul> 037 * <br><br> 038 * 039 * @version 040 * ExamTT 1.1 (Examination Timetabling)<br> 041 * Copyright (C) 2008 Tomáš Müller<br> 042 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 043 * Lazenska 391, 76314 Zlin, Czech Republic<br> 044 * <br> 045 * This library is free software; you can redistribute it and/or 046 * modify it under the terms of the GNU Lesser General Public 047 * License as published by the Free Software Foundation; either 048 * version 2.1 of the License, or (at your option) any later version. 049 * <br><br> 050 * This library is distributed in the hope that it will be useful, 051 * but WITHOUT ANY WARRANTY; without even the implied warranty of 052 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 053 * Lesser General Public License for more details. 054 * <br><br> 055 * You should have received a copy of the GNU Lesser General Public 056 * License along with this library; if not, write to the Free Software 057 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 058 */ 059 public class ExamPlacement extends Value { 060 private ExamPeriodPlacement iPeriodPlacement; 061 private Set iRoomPlacements; 062 private int iSize; 063 private int iRoomPenalty; 064 private double iRoomSplitDistance; 065 066 private int iHashCode; 067 068 /** 069 * Constructor 070 * @param exam an exam 071 * @param periodPlacement period placement 072 * @param roomPlacements a set of room placements {@link ExamRoomPlacement} 073 */ 074 public ExamPlacement(Exam exam, ExamPeriodPlacement periodPlacement, Set roomPlacements) { 075 super(exam); 076 iPeriodPlacement = periodPlacement; 077 if (roomPlacements==null) 078 iRoomPlacements = new HashSet(); 079 else 080 iRoomPlacements = roomPlacements; 081 iSize = 0; 082 iRoomPenalty = 0; 083 iRoomSplitDistance = 0.0; 084 for (Iterator i=iRoomPlacements.iterator();i.hasNext();) { 085 ExamRoomPlacement r = (ExamRoomPlacement)i.next(); 086 iSize += r.getSize(exam.hasAltSeating()); 087 iRoomPenalty += r.getPenalty(periodPlacement.getPeriod()); 088 if (iRoomPlacements.size()>1) { 089 for (Iterator j=iRoomPlacements.iterator();j.hasNext();) { 090 ExamRoomPlacement w = (ExamRoomPlacement)j.next(); 091 if (r.getRoom().getId()<w.getRoom().getId()) 092 iRoomSplitDistance += r.getRoom().getDistance(w.getRoom()); 093 } 094 } 095 } 096 if (iRoomPlacements.size()>2) { 097 iRoomSplitDistance /= iRoomPlacements.size()*(iRoomPlacements.size()-1)/2; 098 } 099 iHashCode = getName().hashCode(); 100 } 101 102 /** 103 * Assigned period 104 */ 105 public ExamPeriod getPeriod() { return iPeriodPlacement.getPeriod(); } 106 107 /** 108 * Assigned period placement 109 */ 110 public ExamPeriodPlacement getPeriodPlacement() { return iPeriodPlacement; } 111 112 /** 113 * Assigned rooms (it is empty when {@link Exam#getMaxRooms()} is zero) 114 * @return list of {@link ExamRoomPlacement} 115 */ 116 public Set getRoomPlacements() { return iRoomPlacements; } 117 118 /** 119 * Overall size of assigned rooms 120 */ 121 public int getSize() { return iSize; } 122 123 /** 124 * Number of direct student conflicts, i.e., number of cases when this exam 125 * is attended by a student that attends some other exam at the same period 126 */ 127 public int getNrDirectConflicts() { 128 Exam exam = (Exam)variable(); 129 //if (!exam.isAllowDirectConflicts()) return 0; 130 int penalty = 0; 131 for (Enumeration e=exam.getStudents().elements();e.hasMoreElements();) { 132 ExamStudent s = (ExamStudent)e.nextElement(); 133 Set exams = s.getExams(getPeriod()); 134 int nrExams = exams.size() + (exams.contains(exam)?0:1); 135 if (nrExams>1) penalty++; 136 else if (!s.isAvailable(getPeriod())) penalty++; 137 } 138 return penalty; 139 } 140 141 /** 142 * Number of direct student conflicts caused by the fact that a student is not available 143 */ 144 public int getNrNotAvailableConflicts() { 145 Exam exam = (Exam)variable(); 146 int penalty = 0; 147 for (Enumeration e=exam.getStudents().elements();e.hasMoreElements();) { 148 ExamStudent s = (ExamStudent)e.nextElement(); 149 if (!s.isAvailable(getPeriod())) penalty++; 150 } 151 return penalty; 152 } 153 154 /** 155 * Number of back-to-back student conflicts, i.e., number of cases when this exam 156 * is attended by a student that attends some other exam at the previous {@link ExamPeriod#prev()} 157 * or following {@link ExamPeriod#next()} period. If {@link ExamModel#isDayBreakBackToBack()} is false, 158 * back-to-back conflicts are only considered between consecutive periods that are of the 159 * same day. 160 */ 161 public int getNrBackToBackConflicts() { 162 Exam exam = (Exam)variable(); 163 ExamModel model = (ExamModel)exam.getModel(); 164 int penalty = 0; 165 for (Enumeration e=exam.getStudents().elements();e.hasMoreElements();) { 166 ExamStudent s = (ExamStudent)e.nextElement(); 167 if (getPeriod().prev()!=null) { 168 if (model.isDayBreakBackToBack() || getPeriod().prev().getDay()==getPeriod().getDay()) { 169 Set exams = s.getExams(getPeriod().prev()); 170 int nrExams = exams.size() + (exams.contains(exam)?-1:0); 171 penalty += nrExams; 172 } 173 } 174 if (getPeriod().next()!=null) { 175 if (model.isDayBreakBackToBack() || getPeriod().next().getDay()==getPeriod().getDay()) { 176 Set exams = s.getExams(getPeriod().next()); 177 int nrExams = exams.size() + (exams.contains(exam)?-1:0); 178 penalty += nrExams; 179 } 180 } 181 } 182 return penalty; 183 } 184 185 /** 186 * Distance between two placements, i.e., maximal distance between a room 187 * of this placement and a room of the given placement. 188 * Method {@link ExamRoom#getDistance(ExamRoom)} is used to get a distance between two rooms. 189 */ 190 public int getDistance(ExamPlacement other) { 191 if (getRoomPlacements().isEmpty() || other.getRoomPlacements().isEmpty()) return 0; 192 int maxDistance = 0; 193 for (Iterator i1=getRoomPlacements().iterator();i1.hasNext();) { 194 ExamRoomPlacement r1 = (ExamRoomPlacement)i1.next(); 195 for (Iterator i2=other.getRoomPlacements().iterator();i2.hasNext();) { 196 ExamRoomPlacement r2 = (ExamRoomPlacement)i2.next(); 197 maxDistance = Math.max(maxDistance, r1.getDistance(r2)); 198 } 199 } 200 return maxDistance; 201 } 202 203 /** 204 * Number of back-to-back distance student conflicts, i.e., number of cases when this exam 205 * is attended by a student that attends some other exam at the previous {@link ExamPeriod#prev()} 206 * or following {@link ExamPeriod#next()} period and the distance {@link ExamPlacement#getDistance(ExamPlacement)} 207 * between these two exams is greater than {@link ExamModel#getBackToBackDistance()}. 208 * Distance back-to-back conflicts are only 209 * considered between consecutive periods that are of the 210 * same day. 211 */ 212 public int getNrDistanceBackToBackConflicts() { 213 Exam exam = (Exam)variable(); 214 ExamModel model = (ExamModel)exam.getModel(); 215 int btbDist = model.getBackToBackDistance(); 216 if (btbDist<0) return 0; 217 int penalty = 0; 218 for (Enumeration e=exam.getStudents().elements();e.hasMoreElements();) { 219 ExamStudent s = (ExamStudent)e.nextElement(); 220 if (getPeriod().prev()!=null) { 221 if (getPeriod().prev().getDay()==getPeriod().getDay()) { 222 for (Iterator i=s.getExams(getPeriod().prev()).iterator();i.hasNext();) { 223 Exam x = (Exam)i.next(); 224 if (x.equals(exam)) continue; 225 if (getDistance((ExamPlacement)x.getAssignment())>btbDist) penalty++; 226 } 227 } 228 } 229 if (getPeriod().next()!=null) { 230 if (getPeriod().next().getDay()==getPeriod().getDay()) { 231 for (Iterator i=s.getExams(getPeriod().next()).iterator();i.hasNext();) { 232 Exam x = (Exam)i.next(); 233 if (x.equals(exam)) continue; 234 if (getDistance((ExamPlacement)x.getAssignment())>btbDist) penalty++; 235 } 236 } 237 } 238 } 239 return penalty; 240 } 241 242 /** 243 * Number of more than two exams a day student conflicts, i.e., when this exam 244 * is attended by a student that attends two or more other exams at the same day. 245 */ 246 public int getNrMoreThanTwoADayConflicts() { 247 Exam exam = (Exam)variable(); 248 int penalty = 0; 249 for (Enumeration e=exam.getStudents().elements();e.hasMoreElements();) { 250 ExamStudent s = (ExamStudent)e.nextElement(); 251 Set exams = s.getExamsADay(getPeriod()); 252 int nrExams = exams.size() + (exams.contains(exam)?0:1); 253 if (nrExams>2) penalty++; 254 } 255 return penalty; 256 } 257 258 /** 259 * Number of direct instructor conflicts, i.e., number of cases when this exam 260 * is attended by an instructor that attends some other exam at the same period 261 */ 262 public int getNrInstructorDirectConflicts() { 263 Exam exam = (Exam)variable(); 264 //if (!exam.isAllowDirectConflicts()) return 0; 265 int penalty = 0; 266 for (Enumeration e=exam.getInstructors().elements();e.hasMoreElements();) { 267 ExamInstructor s = (ExamInstructor)e.nextElement(); 268 Set exams = s.getExams(getPeriod()); 269 int nrExams = exams.size() + (exams.contains(exam)?0:1); 270 if (nrExams>1) penalty++; 271 else if (!s.isAvailable(getPeriod())) penalty++; 272 } 273 return penalty; 274 } 275 276 /** 277 * Number of direct instructor conflicts caused by the fact that a student is not available 278 */ 279 public int getNrInstructorNotAvailableConflicts() { 280 Exam exam = (Exam)variable(); 281 int penalty = 0; 282 for (Enumeration e=exam.getInstructors().elements();e.hasMoreElements();) { 283 ExamInstructor s = (ExamInstructor)e.nextElement(); 284 if (!s.isAvailable(getPeriod())) penalty++; 285 } 286 return penalty; 287 } 288 289 290 /** 291 * Number of back-to-back instructor conflicts, i.e., number of cases when this exam 292 * is attended by an instructor that attends some other exam at the previous {@link ExamPeriod#prev()} 293 * or following {@link ExamPeriod#next()} period. If {@link ExamModel#isDayBreakBackToBack()} is false, 294 * back-to-back conflicts are only considered between consecutive periods that are of the 295 * same day. 296 */ 297 public int getNrInstructorBackToBackConflicts() { 298 Exam exam = (Exam)variable(); 299 ExamModel model = (ExamModel)exam.getModel(); 300 int penalty = 0; 301 for (Enumeration e=exam.getInstructors().elements();e.hasMoreElements();) { 302 ExamInstructor s = (ExamInstructor)e.nextElement(); 303 if (getPeriod().prev()!=null) { 304 if (model.isDayBreakBackToBack() || getPeriod().prev().getDay()==getPeriod().getDay()) { 305 Set exams = s.getExams(getPeriod().prev()); 306 int nrExams = exams.size() + (exams.contains(exam)?-1:0); 307 penalty += nrExams; 308 } 309 } 310 if (getPeriod().next()!=null) { 311 if (model.isDayBreakBackToBack() || getPeriod().next().getDay()==getPeriod().getDay()) { 312 Set exams = s.getExams(getPeriod().next()); 313 int nrExams = exams.size() + (exams.contains(exam)?-1:0); 314 penalty += nrExams; 315 } 316 } 317 } 318 return penalty; 319 } 320 321 /** 322 * Number of back-to-back distance instructor conflicts, i.e., number of cases when this exam 323 * is attended by an instructor that attends some other exam at the previous {@link ExamPeriod#prev()} 324 * or following {@link ExamPeriod#next()} period and the distance {@link ExamPlacement#getDistance(ExamPlacement)} 325 * between these two exams is greater than {@link ExamModel#getBackToBackDistance()}. 326 * Distance back-to-back conflicts are only 327 * considered between consecutive periods that are of the 328 * same day. 329 */ 330 public int getNrInstructorDistanceBackToBackConflicts() { 331 Exam exam = (Exam)variable(); 332 ExamModel model = (ExamModel)exam.getModel(); 333 int btbDist = model.getBackToBackDistance(); 334 if (btbDist<0) return 0; 335 int penalty = 0; 336 for (Enumeration e=exam.getInstructors().elements();e.hasMoreElements();) { 337 ExamInstructor s = (ExamInstructor)e.nextElement(); 338 if (getPeriod().prev()!=null) { 339 if (getPeriod().prev().getDay()==getPeriod().getDay()) { 340 for (Iterator i=s.getExams(getPeriod().prev()).iterator();i.hasNext();) { 341 Exam x = (Exam)i.next(); 342 if (x.equals(exam)) continue; 343 if (getDistance((ExamPlacement)x.getAssignment())>btbDist) penalty++; 344 } 345 } 346 } 347 if (getPeriod().next()!=null) { 348 if (getPeriod().next().getDay()==getPeriod().getDay()) { 349 for (Iterator i=s.getExams(getPeriod().next()).iterator();i.hasNext();) { 350 Exam x = (Exam)i.next(); 351 if (x.equals(exam)) continue; 352 if (getDistance((ExamPlacement)x.getAssignment())>btbDist) penalty++; 353 } 354 } 355 } 356 } 357 return penalty; 358 } 359 360 /** 361 * Number of more than two exams a day instructor conflicts, i.e., when this exam 362 * is attended by an instructor student that attends two or more other exams at the same day. 363 */ 364 public int getNrInstructorMoreThanTwoADayConflicts() { 365 Exam exam = (Exam)variable(); 366 int penalty = 0; 367 for (Enumeration e=exam.getInstructors().elements();e.hasMoreElements();) { 368 ExamInstructor s = (ExamInstructor)e.nextElement(); 369 Set exams = s.getExamsADay(getPeriod()); 370 int nrExams = exams.size() + (exams.contains(exam)?0:1); 371 if (nrExams>2) penalty++; 372 } 373 return penalty; 374 } 375 376 /** 377 * Cost for using room(s) that are too big 378 * @return difference between total room size (computed using either {@link ExamRoom#getSize()} or 379 * {@link ExamRoom#getAltSize()} based on {@link Exam#hasAltSeating()}) and the number of students {@link Exam#getSize()} 380 */ 381 public int getRoomSizePenalty() { 382 Exam exam = (Exam)variable(); 383 int diff = getSize()-exam.getSize(); 384 return (diff<0?0:diff); 385 } 386 387 /** 388 * Cost for using more than one room (nrSplits^2). 389 * @return penalty (1 for 2 rooms, 4 for 3 rooms, 9 for 4 rooms, etc.) 390 */ 391 public int getRoomSplitPenalty() { 392 return (iRoomPlacements.size()<=1?0:(iRoomPlacements.size()-1)*(iRoomPlacements.size()-1)); 393 } 394 395 /** 396 * Cost for using a period, i.e., {@link ExamPeriodPlacement#getPenalty()} 397 */ 398 public int getPeriodPenalty() { 399 return iPeriodPlacement.getPenalty(); 400 } 401 402 403 /** 404 * Rotation penalty (an exam that has been in later period last times tries to be in an earlier period) 405 */ 406 public int getRotationPenalty() { 407 Exam exam = (Exam)variable(); 408 if (exam.getAveragePeriod()<0) return 0; 409 return (1+getPeriod().getIndex())*(1+exam.getAveragePeriod()); 410 } 411 412 /** 413 * Front load penalty (large exam is discouraged to be placed on or after a certain period) 414 * @return zero if not large exam or if before {@link ExamModel#getLargePeriod()}, one otherwise 415 */ 416 public int getLargePenalty() { 417 Exam exam = (Exam)variable(); 418 ExamModel model = (ExamModel)exam.getModel(); 419 if (model.getLargeSize()<0 || exam.getSize()<model.getLargeSize()) return 0; 420 int periodIdx = (int)Math.round(model.getPeriods().size() * model.getLargePeriod()); 421 return (getPeriod().getIndex()<periodIdx?0:1); 422 } 423 424 /** 425 * Room penalty (penalty for using given rooms), i.e., sum of {@link ExamRoomPlacement#getPenalty(ExamPeriod)} of assigned rooms 426 */ 427 public int getRoomPenalty() { 428 return iRoomPenalty; 429 } 430 431 /** 432 * Perturbation penalty, i.e., penalty for using a different assignment than initial. 433 * Only applicable when {@link ExamModel#isMPP()} is true (minimal perturbation problem). 434 * @return |period index - initial period index | * exam size 435 */ 436 public int getPerturbationPenalty() { 437 Exam exam = (Exam)variable(); 438 if (!((ExamModel)exam.getModel()).isMPP()) return 0; 439 ExamPlacement initial = (ExamPlacement)exam.getInitialAssignment(); 440 if (initial==null) return 0; 441 return Math.abs(initial.getPeriod().getIndex()-getPeriod().getIndex())*(1+exam.getSize()); 442 } 443 444 /** 445 * Room perturbation penalty, i.e., number of assigned rooms different from initial. 446 * Only applicable when {@link ExamModel#isMPP()} is true (minimal perturbation problem). 447 * @return |period index - initial period index | * exam size 448 */ 449 public int getRoomPerturbationPenalty() { 450 Exam exam = (Exam)variable(); 451 if (!((ExamModel)exam.getModel()).isMPP()) return 0; 452 ExamPlacement initial = (ExamPlacement)exam.getInitialAssignment(); 453 if (initial==null) return 0; 454 int penalty = 0; 455 for (Iterator i=getRoomPlacements().iterator();i.hasNext();) { 456 ExamRoomPlacement rp = (ExamRoomPlacement)i.next(); 457 if (!initial.getRoomPlacements().contains(rp)) penalty++; 458 } 459 return penalty; 460 } 461 462 /** 463 * Room split distance penalty, i.e., average distance between two rooms of this placement 464 */ 465 public double getRoomSplitDistancePenalty() { 466 return iRoomSplitDistance; 467 } 468 469 /** 470 * Distribution penalty, i.e., sum weights of violated distribution constraints 471 */ 472 public double getDistributionPenalty() { 473 int penalty = 0; 474 for (Enumeration e=((Exam)variable()).getDistributionConstraints().elements();e.hasMoreElements();) { 475 ExamDistributionConstraint dc = (ExamDistributionConstraint)e.nextElement(); 476 if (dc.isHard()) continue; 477 boolean sat = dc.isSatisfied(this); 478 if (sat!=dc.isSatisfied()) 479 penalty += (sat?-dc.getWeight():dc.getWeight()); 480 } 481 return penalty; 482 } 483 484 /** 485 * Room related distribution penalty, i.e., sum weights of violated distribution constraints 486 */ 487 public double getRoomDistributionPenalty() { 488 int penalty = 0; 489 for (Enumeration e=((Exam)variable()).getDistributionConstraints().elements();e.hasMoreElements();) { 490 ExamDistributionConstraint dc = (ExamDistributionConstraint)e.nextElement(); 491 if (dc.isHard() || !dc.isRoomRelated()) continue; 492 boolean sat = dc.isSatisfied(this); 493 if (sat!=dc.isSatisfied()) 494 penalty += (sat?-dc.getWeight():dc.getWeight()); 495 } 496 return penalty; 497 } 498 499 /** 500 * Period related distribution penalty, i.e., sum weights of violated distribution constraints 501 */ 502 public double getPeriodDistributionPenalty() { 503 int penalty = 0; 504 for (Enumeration e=((Exam)variable()).getDistributionConstraints().elements();e.hasMoreElements();) { 505 ExamDistributionConstraint dc = (ExamDistributionConstraint)e.nextElement(); 506 if (dc.isHard() || !dc.isPeriodRelated()) continue; 507 boolean sat = dc.isSatisfied(this); 508 if (sat!=dc.isSatisfied()) 509 penalty += (sat?-dc.getWeight():dc.getWeight()); 510 } 511 return penalty; 512 } 513 514 /** 515 * Overall cost of using this placement. 516 * The cost of an assignment consists of the following criteria: 517 * <ul> 518 * <li> Direct student conflicts {@link ExamPlacement#getNrDirectConflicts()}, weighted by {@link ExamModel#getDirectConflictWeight()} 519 * <li> More than two exams a day student conflicts {@link ExamPlacement#getNrMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getMoreThanTwoADayWeight()} 520 * <li> Back-to-back student conflicts {@link ExamPlacement#getNrBackToBackConflicts()}, weighted by {@link ExamModel#getBackToBackConflictWeight()} 521 * <li> Distance back-to-back student conflicts {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getDistanceBackToBackConflictWeight()} 522 * <li> Period penalty {@link ExamPlacement#getPeriodPenalty()}, weighted by {@link ExamModel#getPeriodWeight()} 523 * <li> Room size penalty {@link ExamPlacement#getRoomSizePenalty()}, weighted by {@link ExamModel#getRoomSizeWeight()} 524 * <li> Room split penalty {@link ExamPlacement#getRoomSplitPenalty()}, weighted by {@link ExamModel#getRoomSplitWeight()} 525 * <li> Room split distance penalty {@link ExamPlacement#getRoomSplitDistancePenalty()}, weighted by {@link ExamModel#getRoomSplitDistanceWeight()} 526 * <li> Room penalty {@link ExamPlacement#getRoomPenalty()}, weighted by {@link ExamModel#getRoomWeight()} 527 * <li> Exam rotation penalty {@link ExamPlacement#getRotationPenalty()}, weighted by {@link ExamModel#getExamRotationWeight()} 528 * <li> Direct instructor conflicts {@link ExamPlacement#getNrInstructorDirectConflicts()}, weighted by {@link ExamModel#getInstructorDirectConflictWeight()} 529 * <li> More than two exams a day instructor conflicts {@link ExamPlacement#getNrInstructorMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getInstructorMoreThanTwoADayWeight()} 530 * <li> Back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorBackToBackConflictWeight()} 531 * <li> Distance back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorDistanceBackToBackConflictWeight()} 532 * <li> Front load penalty {@link ExamPlacement#getLargePenalty()}, weighted by {@link ExamModel#getLargeWeight()} 533 * </ul> 534 */ 535 public double toDouble() { 536 Exam exam = (Exam)variable(); 537 ExamModel model = (ExamModel)exam.getModel(); 538 return 539 model.getDirectConflictWeight()*getNrDirectConflicts()+ 540 model.getMoreThanTwoADayWeight()*getNrMoreThanTwoADayConflicts()+ 541 model.getBackToBackConflictWeight()*getNrBackToBackConflicts()+ 542 model.getDistanceBackToBackConflictWeight()*getNrDistanceBackToBackConflicts()+ 543 model.getPeriodWeight()*getPeriodPenalty()+ 544 model.getPeriodSizeWeight()*getPeriodPenalty()*(exam.getSize()+1)+ 545 model.getPeriodIndexWeight()*getPeriod().getIndex()+ 546 model.getRoomSizeWeight()*getRoomSizePenalty()+ 547 model.getRoomSplitWeight()*getRoomSplitPenalty()+ 548 model.getExamRotationWeight()*getRotationPenalty()+ 549 model.getRoomWeight()*getRoomPenalty()+ 550 model.getInstructorDirectConflictWeight()*getNrInstructorDirectConflicts()+ 551 model.getInstructorMoreThanTwoADayWeight()*getNrInstructorMoreThanTwoADayConflicts()+ 552 model.getInstructorBackToBackConflictWeight()*getNrInstructorBackToBackConflicts()+ 553 model.getInstructorDistanceBackToBackConflictWeight()*getNrInstructorDistanceBackToBackConflicts()+ 554 model.getRoomSplitDistanceWeight()*getRoomSplitDistancePenalty()+ 555 model.getPerturbationWeight()*getPerturbationPenalty()+ 556 model.getRoomPerturbationWeight()*getRoomPerturbationPenalty()+ 557 model.getDistributionWeight()*getDistributionPenalty()+ 558 model.getLargeWeight()*getLargePenalty(); 559 } 560 561 /** 562 * Overall cost of using this period. 563 * The time cost of an assignment consists of the following criteria: 564 * <ul> 565 * <li> Direct student conflicts {@link ExamPlacement#getNrDirectConflicts()}, weighted by {@link ExamModel#getDirectConflictWeight()} 566 * <li> More than two exams a day student conflicts {@link ExamPlacement#getNrMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getMoreThanTwoADayWeight()} 567 * <li> Back-to-back student conflicts {@link ExamPlacement#getNrBackToBackConflicts()}, weighted by {@link ExamModel#getBackToBackConflictWeight()} 568 * <li> Period penalty {@link ExamPlacement#getPeriodPenalty()}, weighted by {@link ExamModel#getPeriodWeight()} 569 * <li> Exam rotation penalty {@link ExamPlacement#getRotationPenalty()}, weighted by {@link ExamModel#getExamRotationWeight()} 570 * <li> Direct instructor conflicts {@link ExamPlacement#getNrInstructorDirectConflicts()}, weighted by {@link ExamModel#getInstructorDirectConflictWeight()} 571 * <li> More than two exams a day instructor conflicts {@link ExamPlacement#getNrInstructorMoreThanTwoADayConflicts()}, weighted by {@link ExamModel#getInstructorMoreThanTwoADayWeight()} 572 * <li> Back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorBackToBackConflictWeight()} 573 * <li> Distance back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorDistanceBackToBackConflictWeight()} 574 * <li> Front load penalty {@link ExamPlacement#getLargePenalty()}, weighted by {@link ExamModel#getLargeWeight()} 575 * </ul> 576 */ 577 public double getTimeCost() { 578 Exam exam = (Exam)variable(); 579 ExamModel model = (ExamModel)exam.getModel(); 580 return 581 model.getDirectConflictWeight()*getNrDirectConflicts()+ 582 model.getBackToBackConflictWeight()*getNrBackToBackConflicts()+ 583 model.getMoreThanTwoADayWeight()*getNrMoreThanTwoADayConflicts()+ 584 model.getPeriodWeight()*getPeriodPenalty()+ 585 model.getPeriodSizeWeight()*getPeriodPenalty()*(exam.getSize()+1)+ 586 model.getPeriodIndexWeight()*getPeriod().getIndex()+ 587 model.getExamRotationWeight()*getRotationPenalty()+ 588 model.getInstructorDirectConflictWeight()*getNrInstructorDirectConflicts()+ 589 model.getInstructorMoreThanTwoADayWeight()*getNrInstructorMoreThanTwoADayConflicts()+ 590 model.getInstructorBackToBackConflictWeight()*getNrInstructorBackToBackConflicts()+ 591 model.getPerturbationWeight()*getPerturbationPenalty()+ 592 model.getDistributionWeight()*getPeriodDistributionPenalty()+ 593 model.getLargeWeight()*getLargePenalty(); 594 } 595 596 /** 597 * Overall cost of using this set or rooms. 598 * The room cost of an assignment consists of the following criteria: 599 * <ul> 600 * <li> Distance back-to-back student conflicts {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getDistanceBackToBackConflictWeight()} 601 * <li> Distance back-to-back instructor conflicts {@link ExamPlacement#getNrInstructorDistanceBackToBackConflicts()}, weighted by {@link ExamModel#getInstructorDistanceBackToBackConflictWeight()} 602 * <li> Room size penalty {@link ExamPlacement#getRoomSizePenalty()}, weighted by {@link ExamModel#getRoomSizeWeight()} 603 * <li> Room split penalty {@link ExamPlacement#getRoomSplitPenalty()}, weighted by {@link ExamModel#getRoomSplitWeight()} 604 * <li> Room split distance penalty {@link ExamPlacement#getRoomSplitDistancePenalty()}, weighted by {@link ExamModel#getRoomSplitDistanceWeight()} 605 * <li> Room penalty {@link ExamPlacement#getRoomPenalty()}, weighted by {@link ExamModel#getRoomWeight()} 606 * </ul> 607 */ 608 public double getRoomCost() { 609 Exam exam = (Exam)variable(); 610 ExamModel model = (ExamModel)exam.getModel(); 611 return 612 model.getDistanceBackToBackConflictWeight()*getNrDistanceBackToBackConflicts()+ 613 model.getRoomSizeWeight()*getRoomSizePenalty()+ 614 model.getRoomSplitWeight()*getRoomSplitPenalty()+ 615 model.getRoomWeight()*getRoomPenalty()+ 616 model.getInstructorDistanceBackToBackConflictWeight()*getNrInstructorDistanceBackToBackConflicts()+ 617 model.getRoomSplitDistanceWeight()*getRoomSizePenalty()+ 618 model.getDistributionWeight()*getRoomDistributionPenalty()+ 619 model.getRoomPerturbationWeight()*getRoomPerturbationPenalty(); 620 } 621 622 /** 623 * Room names separated with the given delimiter 624 */ 625 public String getRoomName(String delim) { 626 String roomName = ""; 627 for (Iterator i=getRoomPlacements().iterator();i.hasNext();) { 628 ExamRoomPlacement r = (ExamRoomPlacement)i.next(); 629 roomName += r.getRoom().getName(); 630 if (i.hasNext()) roomName += delim; 631 } 632 return roomName; 633 } 634 635 /** 636 * Assignment name (period / room(s)) 637 */ 638 public String getName() { 639 return getPeriod()+"/"+getRoomName(","); 640 } 641 642 /** 643 * String representation -- returns a list of assignment costs 644 */ 645 public String toString() { 646 DecimalFormat df = new DecimalFormat("0.00"); 647 Exam exam = (Exam)variable(); 648 ExamModel model = (ExamModel)exam.getModel(); 649 return variable().getName()+" = "+getName()+" ("+ 650 df.format(toDouble())+"/"+ 651 "DC:"+getNrDirectConflicts()+","+ 652 "M2D:"+getNrMoreThanTwoADayConflicts()+","+ 653 "BTB:"+getNrBackToBackConflicts()+","+ 654 (model.getBackToBackDistance()<0?"":"dBTB:"+getNrDistanceBackToBackConflicts()+",")+ 655 "PP:"+getPeriodPenalty()+","+ 656 "@P:"+getRotationPenalty()+","+ 657 "RSz:"+getRoomSizePenalty()+","+ 658 "RSp:"+getRoomSplitPenalty()+","+ 659 "RD:"+df.format(getRoomSplitDistancePenalty())+","+ 660 "RP:"+getRoomPenalty()+ 661 (model.isMPP()?",IP:"+getPerturbationPenalty()+",IRP:"+getRoomPerturbationPenalty():"")+ 662 ")"; 663 } 664 665 /** 666 * Compare two assignments for equality 667 */ 668 public boolean equals(Object o) { 669 if (o==null || !(o instanceof ExamPlacement)) return false; 670 ExamPlacement p = (ExamPlacement)o; 671 return p.variable().equals(variable()) && p.getPeriod().equals(getPeriod()) && p.getRoomPlacements().equals(getRoomPlacements()); 672 } 673 674 /** 675 * Hash code 676 */ 677 public int hashCode() { 678 return iHashCode; 679 } 680 681 /** 682 * True if given room is between {@link ExamPlacement#getRoomPlacements()} 683 */ 684 public boolean contains(ExamRoom room) { 685 return getRoomPlacements().contains(new ExamRoomPlacement(room)); 686 } 687 }