001 package net.sf.cpsolver.exam.model; 002 003 import java.util.ArrayList; 004 import java.util.Date; 005 import java.util.HashSet; 006 import java.util.HashMap; 007 import java.util.Iterator; 008 import java.util.List; 009 import java.util.Map; 010 import java.util.Set; 011 import java.util.StringTokenizer; 012 import java.util.TreeSet; 013 014 import net.sf.cpsolver.coursett.IdConvertor; 015 import net.sf.cpsolver.ifs.model.Constraint; 016 import net.sf.cpsolver.ifs.model.Model; 017 import net.sf.cpsolver.ifs.util.Callback; 018 import net.sf.cpsolver.ifs.util.DataProperties; 019 import net.sf.cpsolver.ifs.util.DistanceMetric; 020 import net.sf.cpsolver.ifs.util.ToolBox; 021 022 import org.apache.log4j.Logger; 023 import org.dom4j.Document; 024 import org.dom4j.DocumentHelper; 025 import org.dom4j.Element; 026 027 /** 028 * Examination timetabling model. Exams {@link Exam} are modeled as variables, 029 * rooms {@link ExamRoom} and students {@link ExamStudent} as constraints. 030 * Assignment of an exam to time (modeled as non-overlapping periods 031 * {@link ExamPeriod}) and space (set of rooms) is modeled using values 032 * {@link ExamPlacement}. In order to be able to model individual period and 033 * room preferences, period and room assignments are wrapped with 034 * {@link ExamPeriodPlacement} and {@link ExamRoomPlacement} classes 035 * respectively. Moreover, additional distribution constraint 036 * {@link ExamDistributionConstraint} can be defined in the model. <br> 037 * <br> 038 * The objective function consists of the following criteria: 039 * <ul> 040 * <li>Direct student conflicts (a student is enrolled in two exams that are 041 * scheduled at the same period, weighted by Exams.DirectConflictWeight) 042 * <li>Back-to-Back student conflicts (a student is enrolled in two exams that 043 * are scheduled in consecutive periods, weighted by 044 * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false, 045 * there is no conflict between the last period and the first period of 046 * consecutive days. 047 * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student 048 * conflict, but the maximum distance between rooms in which both exam take 049 * place is greater than Exams.BackToBackDistance, weighted by 050 * Exams.DistanceBackToBackConflictWeight). 051 * <li>More than two exams a day (a student is enrolled in three exams that are 052 * scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight). 053 * <li>Period penalty (total of period penalties 054 * {@link ExamPlacement#getPeriodPenalty()} of all assigned exams, weighted by 055 * Exams.PeriodWeight). 056 * <li>Room size penalty (total of room size penalties 057 * {@link ExamPlacement#getRoomSizePenalty()} of all assigned exams, weighted by 058 * Exams.RoomSizeWeight). 059 * <li>Room split penalty (total of room split penalties 060 * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned exams, weighted 061 * by Exams.RoomSplitWeight). 062 * <li>Room penalty (total of room penalties 063 * {@link ExamPlacement#getRoomPenalty()} of all assigned exams, weighted by 064 * Exams.RoomWeight). 065 * <li>Distribution penalty (total of distribution constraint weights 066 * {@link ExamDistributionConstraint#getWeight()} of all soft distribution 067 * constraints that are not satisfied, i.e., 068 * {@link ExamDistributionConstraint#isSatisfied()} = false; weighted by 069 * Exams.DistributionWeight). 070 * <li>Direct instructor conflicts (an instructor is enrolled in two exams that 071 * are scheduled at the same period, weighted by 072 * Exams.InstructorDirectConflictWeight) 073 * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two exams 074 * that are scheduled in consecutive periods, weighted by 075 * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack is 076 * false, there is no conflict between the last period and the first period of 077 * consecutive days. 078 * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back 079 * instructor conflict, but the maximum distance between rooms in which both 080 * exam take place is greater than Exams.BackToBackDistance, weighted by 081 * Exams.InstructorDistanceBackToBackConflictWeight). 082 * <li>Room split distance penalty (if an examination is assigned between two or 083 * three rooms, distance between these rooms can be minimized using this 084 * criterion) 085 * <li>Front load penalty (large exams can be penalized if assigned on or after 086 * a certain period) 087 * </ul> 088 * 089 * @version ExamTT 1.2 (Examination Timetabling)<br> 090 * Copyright (C) 2008 - 2010 Tomas Muller<br> 091 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 092 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 093 * <br> 094 * This library is free software; you can redistribute it and/or modify 095 * it under the terms of the GNU Lesser General Public License as 096 * published by the Free Software Foundation; either version 3 of the 097 * License, or (at your option) any later version. <br> 098 * <br> 099 * This library is distributed in the hope that it will be useful, but 100 * WITHOUT ANY WARRANTY; without even the implied warranty of 101 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 102 * Lesser General Public License for more details. <br> 103 * <br> 104 * You should have received a copy of the GNU Lesser General Public 105 * License along with this library; if not see 106 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 107 */ 108 public class ExamModel extends Model<Exam, ExamPlacement> { 109 private static Logger sLog = Logger.getLogger(ExamModel.class); 110 private DataProperties iProperties = null; 111 private int iMaxRooms = 4; 112 private List<ExamPeriod> iPeriods = new ArrayList<ExamPeriod>(); 113 private List<ExamRoom> iRooms = new ArrayList<ExamRoom>(); 114 private List<ExamStudent> iStudents = new ArrayList<ExamStudent>(); 115 private List<ExamDistributionConstraint> iDistributionConstraints = new ArrayList<ExamDistributionConstraint>(); 116 private List<ExamInstructor> iInstructors = new ArrayList<ExamInstructor>(); 117 118 private boolean iDayBreakBackToBack = false; 119 private double iDirectConflictWeight = 1000.0; 120 private double iMoreThanTwoADayWeight = 100.0; 121 private double iBackToBackConflictWeight = 10.0; 122 private double iDistanceBackToBackConflictWeight = 25.0; 123 private double iPeriodWeight = 1.0; 124 private double iPeriodSizeWeight = 1.0; 125 private double iPeriodIndexWeight = 0.0000001; 126 private double iExamRotationWeight = 0.001; 127 private double iRoomSizeWeight = 0.0001; 128 private double iRoomSplitWeight = 10.0; 129 private double iRoomWeight = 0.1; 130 private double iDistributionWeight = 1.0; 131 private double iBackToBackDistance = -1; // 67 132 private double iInstructorDirectConflictWeight = 1000.0; 133 private double iInstructorMoreThanTwoADayWeight = 100.0; 134 private double iInstructorBackToBackConflictWeight = 10.0; 135 private double iInstructorDistanceBackToBackConflictWeight = 25.0; 136 private boolean iMPP = false; 137 private double iPerturbationWeight = 0.01; 138 private double iRoomPerturbationWeight = 0.01; 139 private double iRoomSplitDistanceWeight = 0.01; 140 private int iLargeSize = -1; 141 private double iLargePeriod = 0.67; 142 private double iLargeWeight = 1.0; 143 144 private int iNrDirectConflicts = 0; 145 private int iNrNADirectConflicts = 0; 146 private int iNrBackToBackConflicts = 0; 147 private int iNrDistanceBackToBackConflicts = 0; 148 private int iNrMoreThanTwoADayConflicts = 0; 149 private int iRoomSizePenalty = 0; 150 private int iRoomSplitPenalty = 0; 151 private int iRoomSplits = 0; 152 private int iRoomSplitPenalties[] = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 153 private int iRoomPenalty = 0; 154 private int iDistributionPenalty = 0; 155 private int iPeriodPenalty = 0; 156 private int iPeriodSizePenalty = 0; 157 private int iPeriodIndexPenalty = 0; 158 private int iExamRotationPenalty = 0; 159 private int iPerturbationPenalty = 0; 160 private int iRoomPerturbationPenalty = 0; 161 private int iNrInstructorDirectConflicts = 0; 162 private int iNrNAInstructorDirectConflicts = 0; 163 private int iNrInstructorBackToBackConflicts = 0; 164 private int iNrInstructorDistanceBackToBackConflicts = 0; 165 private int iNrInstructorMoreThanTwoADayConflicts = 0; 166 private int iLargePenalty = 0; 167 private double iRoomSplitDistancePenalty = 0; 168 private int iNrLargeExams; 169 170 private DistanceMetric iDistanceMetric = null; 171 172 /** 173 * Constructor 174 * 175 * @param properties 176 * problem properties 177 */ 178 public ExamModel(DataProperties properties) { 179 iAssignedVariables = null; 180 iUnassignedVariables = null; 181 iPerturbVariables = null; 182 iProperties = properties; 183 iMaxRooms = properties.getPropertyInt("Exams.MaxRooms", iMaxRooms); 184 iDayBreakBackToBack = properties.getPropertyBoolean("Exams.IsDayBreakBackToBack", iDayBreakBackToBack); 185 iDirectConflictWeight = properties.getPropertyDouble("Exams.DirectConflictWeight", iDirectConflictWeight); 186 iBackToBackConflictWeight = properties.getPropertyDouble("Exams.BackToBackConflictWeight", 187 iBackToBackConflictWeight); 188 iDistanceBackToBackConflictWeight = properties.getPropertyDouble("Exams.DistanceBackToBackConflictWeight", 189 iDistanceBackToBackConflictWeight); 190 iMoreThanTwoADayWeight = properties.getPropertyDouble("Exams.MoreThanTwoADayWeight", iMoreThanTwoADayWeight); 191 iPeriodWeight = properties.getPropertyDouble("Exams.PeriodWeight", iPeriodWeight); 192 iPeriodIndexWeight = properties.getPropertyDouble("Exams.PeriodIndexWeight", iPeriodIndexWeight); 193 iPeriodSizeWeight = properties.getPropertyDouble("Exams.PeriodSizeWeight", iPeriodSizeWeight); 194 iExamRotationWeight = properties.getPropertyDouble("Exams.RotationWeight", iExamRotationWeight); 195 iRoomSizeWeight = properties.getPropertyDouble("Exams.RoomSizeWeight", iRoomSizeWeight); 196 iRoomWeight = properties.getPropertyDouble("Exams.RoomWeight", iRoomWeight); 197 iRoomSplitWeight = properties.getPropertyDouble("Exams.RoomSplitWeight", iRoomSplitWeight); 198 iBackToBackDistance = properties.getPropertyDouble("Exams.BackToBackDistance", iBackToBackDistance); 199 iDistributionWeight = properties.getPropertyDouble("Exams.DistributionWeight", iDistributionWeight); 200 iInstructorDirectConflictWeight = properties.getPropertyDouble("Exams.InstructorDirectConflictWeight", 201 iInstructorDirectConflictWeight); 202 iInstructorBackToBackConflictWeight = properties.getPropertyDouble("Exams.InstructorBackToBackConflictWeight", 203 iInstructorBackToBackConflictWeight); 204 iInstructorDistanceBackToBackConflictWeight = properties.getPropertyDouble( 205 "Exams.InstructorDistanceBackToBackConflictWeight", iInstructorDistanceBackToBackConflictWeight); 206 iInstructorMoreThanTwoADayWeight = properties.getPropertyDouble("Exams.InstructorMoreThanTwoADayWeight", 207 iInstructorMoreThanTwoADayWeight); 208 iMPP = properties.getPropertyBoolean("General.MPP", iMPP); 209 iPerturbationWeight = properties.getPropertyDouble("Exams.PerturbationWeight", iPerturbationWeight); 210 iRoomPerturbationWeight = properties.getPropertyDouble("Exams.RoomPerturbationWeight", iRoomPerturbationWeight); 211 iRoomSplitDistanceWeight = properties.getPropertyDouble("Exams.RoomSplitDistanceWeight", 212 iRoomSplitDistanceWeight); 213 iLargeSize = properties.getPropertyInt("Exams.LargeSize", iLargeSize); 214 iLargePeriod = properties.getPropertyDouble("Exams.LargePeriod", iLargePeriod); 215 iLargeWeight = properties.getPropertyDouble("Exams.LargeWeight", iLargeWeight); 216 iDistanceMetric = new DistanceMetric(properties); 217 } 218 219 public DistanceMetric getDistanceMetric() { 220 return iDistanceMetric; 221 } 222 223 /** 224 * Initialization of the model 225 */ 226 public void init() { 227 iNrLargeExams = 0; 228 for (Exam exam : variables()) { 229 if (getLargeSize() >= 0 && exam.getSize() >= getLargeSize()) 230 iNrLargeExams++; 231 for (ExamRoomPlacement room : exam.getRoomPlacements()) { 232 room.getRoom().addVariable(exam); 233 } 234 } 235 iLimits = null; 236 iMaxDistributionPenalty = null; 237 } 238 239 /** 240 * Default maximum number of rooms (can be set by problem property 241 * Exams.MaxRooms, or in the input xml file, property maxRooms) 242 */ 243 public int getMaxRooms() { 244 return iMaxRooms; 245 } 246 247 /** 248 * Default maximum number of rooms (can be set by problem property 249 * Exams.MaxRooms, or in the input xml file, property maxRooms) 250 */ 251 public void setMaxRooms(int maxRooms) { 252 iMaxRooms = maxRooms; 253 } 254 255 /** 256 * Add a period 257 * 258 * @param id 259 * period unique identifier 260 * @param day 261 * day (e.g., 07/12/10) 262 * @param time 263 * (e.g., 8:00am-10:00am) 264 * @param length 265 * length of period in minutes 266 * @param penalty 267 * period penalty 268 */ 269 public ExamPeriod addPeriod(Long id, String day, String time, int length, int penalty) { 270 ExamPeriod lastPeriod = (iPeriods.isEmpty() ? null : (ExamPeriod) iPeriods.get(iPeriods.size() - 1)); 271 ExamPeriod p = new ExamPeriod(id, day, time, length, penalty); 272 if (lastPeriod == null) 273 p.setIndex(iPeriods.size(), 0, 0); 274 else if (lastPeriod.getDayStr().equals(day)) { 275 p.setIndex(iPeriods.size(), lastPeriod.getDay(), lastPeriod.getTime() + 1); 276 } else 277 p.setIndex(iPeriods.size(), lastPeriod.getDay() + 1, 0); 278 if (lastPeriod != null) { 279 lastPeriod.setNext(p); 280 p.setPrev(lastPeriod); 281 } 282 iPeriods.add(p); 283 return p; 284 } 285 286 /** 287 * Number of days 288 */ 289 public int getNrDays() { 290 return (iPeriods.get(iPeriods.size() - 1)).getDay() + 1; 291 } 292 293 /** 294 * Number of periods 295 */ 296 public int getNrPeriods() { 297 return iPeriods.size(); 298 } 299 300 /** 301 * List of periods, use 302 * {@link ExamModel#addPeriod(Long, String, String, int, int)} to add a 303 * period 304 * 305 * @return list of {@link ExamPeriod} 306 */ 307 public List<ExamPeriod> getPeriods() { 308 return iPeriods; 309 } 310 311 /** Period of given unique id */ 312 public ExamPeriod getPeriod(Long id) { 313 for (ExamPeriod period : iPeriods) { 314 if (period.getId().equals(id)) 315 return period; 316 } 317 return null; 318 } 319 320 /** 321 * Direct student conflict weight (can be set by problem property 322 * Exams.DirectConflictWeight, or in the input xml file, property 323 * directConflictWeight) 324 */ 325 public double getDirectConflictWeight() { 326 return iDirectConflictWeight; 327 } 328 329 /** 330 * Direct student conflict weight (can be set by problem property 331 * Exams.DirectConflictWeight, or in the input xml file, property 332 * directConflictWeight) 333 */ 334 public void setDirectConflictWeight(double directConflictWeight) { 335 iDirectConflictWeight = directConflictWeight; 336 } 337 338 /** 339 * Back-to-back student conflict weight (can be set by problem property 340 * Exams.BackToBackConflictWeight, or in the input xml file, property 341 * backToBackConflictWeight) 342 */ 343 public double getBackToBackConflictWeight() { 344 return iBackToBackConflictWeight; 345 } 346 347 /** 348 * Back-to-back student conflict weight (can be set by problem property 349 * Exams.BackToBackConflictWeight, or in the input xml file, property 350 * backToBackConflictWeight) 351 */ 352 public void setBackToBackConflictWeight(double backToBackConflictWeight) { 353 iBackToBackConflictWeight = backToBackConflictWeight; 354 } 355 356 /** 357 * Distance back-to-back student conflict weight (can be set by problem 358 * property Exams.DistanceBackToBackConflictWeight, or in the input xml 359 * file, property distanceBackToBackConflictWeight) 360 */ 361 public double getDistanceBackToBackConflictWeight() { 362 return iDistanceBackToBackConflictWeight; 363 } 364 365 /** 366 * Distance back-to-back student conflict weight (can be set by problem 367 * property Exams.DistanceBackToBackConflictWeight, or in the input xml 368 * file, property distanceBackToBackConflictWeight) 369 */ 370 public void setDistanceBackToBackConflictWeight(double distanceBackToBackConflictWeight) { 371 iDistanceBackToBackConflictWeight = distanceBackToBackConflictWeight; 372 } 373 374 /** 375 * More than two exams a day student conflict weight (can be set by problem 376 * property Exams.MoreThanTwoADayWeight, or in the input xml file, property 377 * moreThanTwoADayWeight) 378 */ 379 public double getMoreThanTwoADayWeight() { 380 return iMoreThanTwoADayWeight; 381 } 382 383 /** 384 * More than two exams a day student conflict weight (can be set by problem 385 * property Exams.MoreThanTwoADayWeight, or in the input xml file, property 386 * moreThanTwoADayWeight) 387 */ 388 public void setMoreThanTwoADayWeight(double moreThanTwoADayWeight) { 389 iMoreThanTwoADayWeight = moreThanTwoADayWeight; 390 } 391 392 /** 393 * Direct instructor conflict weight (can be set by problem property 394 * Exams.InstructorDirectConflictWeight, or in the input xml file, property 395 * instructorDirectConflictWeight) 396 */ 397 public double getInstructorDirectConflictWeight() { 398 return iInstructorDirectConflictWeight; 399 } 400 401 /** 402 * Direct instructor conflict weight (can be set by problem property 403 * Exams.InstructorDirectConflictWeight, or in the input xml file, property 404 * instructorDirectConflictWeight) 405 */ 406 public void setInstructorDirectConflictWeight(double directConflictWeight) { 407 iInstructorDirectConflictWeight = directConflictWeight; 408 } 409 410 /** 411 * Back-to-back instructor conflict weight (can be set by problem property 412 * Exams.InstructorBackToBackConflictWeight, or in the input xml file, 413 * property instructorBackToBackConflictWeight) 414 */ 415 public double getInstructorBackToBackConflictWeight() { 416 return iInstructorBackToBackConflictWeight; 417 } 418 419 /** 420 * Back-to-back instructor conflict weight (can be set by problem property 421 * Exams.InstructorBackToBackConflictWeight, or in the input xml file, 422 * property instructorBackToBackConflictWeight) 423 */ 424 public void setInstructorBackToBackConflictWeight(double backToBackConflictWeight) { 425 iInstructorBackToBackConflictWeight = backToBackConflictWeight; 426 } 427 428 /** 429 * Distance back-to-back instructor conflict weight (can be set by problem 430 * property Exams.InstructorDistanceBackToBackConflictWeight, or in the 431 * input xml file, property instructorDistanceBackToBackConflictWeight) 432 */ 433 public double getInstructorDistanceBackToBackConflictWeight() { 434 return iInstructorDistanceBackToBackConflictWeight; 435 } 436 437 /** 438 * Distance back-to-back instructor conflict weight (can be set by problem 439 * property Exams.InstructorDistanceBackToBackConflictWeight, or in the 440 * input xml file, property instructorDistanceBackToBackConflictWeight) 441 */ 442 public void setInstructorDistanceBackToBackConflictWeight(double distanceBackToBackConflictWeight) { 443 iInstructorDistanceBackToBackConflictWeight = distanceBackToBackConflictWeight; 444 } 445 446 /** 447 * More than two exams a day instructor conflict weight (can be set by 448 * problem property Exams.InstructorMoreThanTwoADayWeight, or in the input 449 * xml file, property instructorMoreThanTwoADayWeight) 450 */ 451 public double getInstructorMoreThanTwoADayWeight() { 452 return iInstructorMoreThanTwoADayWeight; 453 } 454 455 /** 456 * More than two exams a day instructor conflict weight (can be set by 457 * problem property Exams.InstructorMoreThanTwoADayWeight, or in the input 458 * xml file, property instructorMoreThanTwoADayWeight) 459 */ 460 public void setInstructorMoreThanTwoADayWeight(double moreThanTwoADayWeight) { 461 iInstructorMoreThanTwoADayWeight = moreThanTwoADayWeight; 462 } 463 464 /** 465 * True when back-to-back student conflict is to be encountered when a 466 * student is enrolled into an exam that is on the last period of one day 467 * and another exam that is on the first period of the consecutive day. It 468 * can be set by problem property Exams.IsDayBreakBackToBack, or in the 469 * input xml file, property isDayBreakBackToBack) 470 * 471 */ 472 public boolean isDayBreakBackToBack() { 473 return iDayBreakBackToBack; 474 } 475 476 /** 477 * True when back-to-back student conflict is to be encountered when a 478 * student is enrolled into an exam that is on the last period of one day 479 * and another exam that is on the first period of the consecutive day. It 480 * can be set by problem property Exams.IsDayBreakBackToBack, or in the 481 * input xml file, property isDayBreakBackToBack) 482 * 483 */ 484 public void setDayBreakBackToBack(boolean dayBreakBackToBack) { 485 iDayBreakBackToBack = dayBreakBackToBack; 486 } 487 488 /** 489 * A weight for period penalty (used in 490 * {@link ExamPlacement#getPeriodPenalty()}, can be set by problem property 491 * Exams.PeriodWeight, or in the input xml file, property periodWeight) 492 * 493 */ 494 public double getPeriodWeight() { 495 return iPeriodWeight; 496 } 497 498 /** 499 * A weight for period penalty (used in 500 * {@link ExamPlacement#getPeriodPenalty()}, can be set by problem property 501 * Exams.PeriodWeight, or in the input xml file, property periodWeight) 502 * 503 */ 504 public void setPeriodWeight(double periodWeight) { 505 iPeriodWeight = periodWeight; 506 } 507 508 /** 509 * A weight for period penalty (used in 510 * {@link ExamPlacement#getPeriodPenalty()} multiplied by examination size 511 * {@link Exam#getSize()}, can be set by problem property 512 * Exams.PeriodSizeWeight, or in the input xml file, property periodWeight) 513 * 514 */ 515 public double getPeriodSizeWeight() { 516 return iPeriodSizeWeight; 517 } 518 519 /** 520 * A weight for period penalty (used in 521 * {@link ExamPlacement#getPeriodPenalty()} multiplied by examination size 522 * {@link Exam#getSize()}, can be set by problem property 523 * Exams.PeriodSizeWeight, or in the input xml file, property periodWeight) 524 * 525 */ 526 public void setPeriodSizeWeight(double periodSizeWeight) { 527 iPeriodSizeWeight = periodSizeWeight; 528 } 529 530 /** 531 * A weight for period index, can be set by problem property 532 * Exams.PeriodIndexWeight, or in the input xml file, property periodWeight) 533 * 534 */ 535 public double getPeriodIndexWeight() { 536 return iPeriodIndexWeight; 537 } 538 539 /** 540 * A weight for period index, can be set by problem property 541 * Exams.PeriodIndexWeight, or in the input xml file, property periodWeight) 542 * 543 */ 544 public void setPeriodIndexWeight(double periodIndexWeight) { 545 iPeriodIndexWeight = periodIndexWeight; 546 } 547 548 /** 549 * A weight for exam rotation penalty (used in 550 * {@link ExamPlacement#getRotationPenalty()} can be set by problem property 551 * Exams.RotationWeight, or in the input xml file, property 552 * examRotationWeight) 553 * 554 */ 555 public double getExamRotationWeight() { 556 return iExamRotationWeight; 557 } 558 559 /** 560 * A weight for period penalty (used in 561 * {@link ExamPlacement#getRotationPenalty()}, can be set by problem 562 * property Exams.RotationWeight, or in the input xml file, property 563 * examRotationWeight) 564 * 565 */ 566 public void setExamRotationWeight(double examRotationWeight) { 567 iExamRotationWeight = examRotationWeight; 568 } 569 570 /** 571 * A weight for room size penalty (used in 572 * {@link ExamPlacement#getRoomSizePenalty()}, can be set by problem 573 * property Exams.RoomSizeWeight, or in the input xml file, property 574 * roomSizeWeight) 575 * 576 */ 577 public double getRoomSizeWeight() { 578 return iRoomSizeWeight; 579 } 580 581 /** 582 * A weight for room size penalty (used in 583 * {@link ExamPlacement#getRoomSizePenalty()}, can be set by problem 584 * property Exams.RoomSizeWeight, or in the input xml file, property 585 * roomSizeWeight) 586 * 587 */ 588 public void setRoomSizeWeight(double roomSizeWeight) { 589 iRoomSizeWeight = roomSizeWeight; 590 } 591 592 /** 593 * A weight for room penalty weight (used in 594 * {@link ExamPlacement#getRoomPenalty()}, can be set by problem property 595 * Exams.RoomPreferenceWeight, or in the input xml file, property 596 * roomPreferenceWeight) 597 * 598 */ 599 public double getRoomWeight() { 600 return iRoomWeight; 601 } 602 603 /** 604 * A weight for room penalty weight (used in 605 * {@link ExamPlacement#getRoomPenalty()}, can be set by problem property 606 * Exams.RoomWeight, or in the input xml file, property roomWeight) 607 * 608 */ 609 public void setRoomWeight(double roomWeight) { 610 iRoomWeight = roomWeight; 611 } 612 613 /** 614 * A weight for room split penalty (used in 615 * {@link ExamPlacement#getRoomSplitPenalty()}, can be set by problem 616 * property Exams.RoomSplitWeight, or in the input xml file, property 617 * roomSplitWeight) 618 * 619 */ 620 public double getRoomSplitWeight() { 621 return iRoomSplitWeight; 622 } 623 624 /** 625 * A weight for room split penalty (used in 626 * {@link ExamPlacement#getRoomSplitPenalty()}, can be set by problem 627 * property Exams.RoomSplitWeight, or in the input xml file, property 628 * roomSplitWeight) 629 * 630 */ 631 public void setRoomSplitWeight(double roomSplitWeight) { 632 iRoomSplitWeight = roomSplitWeight; 633 } 634 635 /** 636 * Back-to-back distance (used in 637 * {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, can be set by 638 * problem property Exams.BackToBackDistance, or in the input xml file, 639 * property backToBackDistance) 640 */ 641 public double getBackToBackDistance() { 642 return iBackToBackDistance; 643 } 644 645 /** 646 * Back-to-back distance (used in 647 * {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, can be set by 648 * problem property Exams.BackToBackDistance, or in the input xml file, 649 * property backToBackDistance) 650 */ 651 public void setBackToBackDistance(double backToBackDistance) { 652 iBackToBackDistance = backToBackDistance; 653 } 654 655 /** 656 * A weight of violated distribution soft constraints (see 657 * {@link ExamDistributionConstraint}, can be set by problem property 658 * Exams.RoomDistributionWeight, or in the input xml file, property 659 * roomDistributionWeight) 660 */ 661 public double getDistributionWeight() { 662 return iDistributionWeight; 663 } 664 665 /** 666 * A weight of violated distribution soft constraints (see 667 * {@link ExamDistributionConstraint}, can be set by problem property 668 * Exams.RoomDistributionWeight, or in the input xml file, property 669 * roomDistributionWeight) 670 * 671 */ 672 public void setDistributionWeight(double distributionWeight) { 673 iDistributionWeight = distributionWeight; 674 } 675 676 /** 677 * A weight of perturbations (see 678 * {@link ExamPlacement#getPerturbationPenalty()}), i.e., a penalty for an 679 * assignment of an exam to a place different from the initial one. Can by 680 * set by problem property Exams.PerturbationWeight, or in the input xml 681 * file, property perturbationWeight) 682 */ 683 public double getPerturbationWeight() { 684 return iPerturbationWeight; 685 } 686 687 /** 688 * A weight of perturbations (see 689 * {@link ExamPlacement#getPerturbationPenalty()}), i.e., a penalty for an 690 * assignment of an exam to a place different from the initial one. Can by 691 * set by problem property Exams.PerturbationWeight, or in the input xml 692 * file, property perturbationWeight) 693 */ 694 public void setPerturbationWeight(double perturbationWeight) { 695 iPerturbationWeight = perturbationWeight; 696 } 697 698 /** 699 * A weight of room perturbations (see 700 * {@link ExamPlacement#getRoomPerturbationPenalty()}), i.e., a penalty for 701 * an assignment of an exam to a room different from the initial one. Can by 702 * set by problem property Exams.RoomPerturbationWeight, or in the input xml 703 * file, property perturbationWeight) 704 */ 705 public double getRoomPerturbationWeight() { 706 return iRoomPerturbationWeight; 707 } 708 709 /** 710 * A weight of room perturbations (see 711 * {@link ExamPlacement#getRoomPerturbationPenalty()}), i.e., a penalty for 712 * an assignment of an exam to a room different from the initial one. Can by 713 * set by problem property Exams.RoomPerturbationWeight, or in the input xml 714 * file, property perturbationWeight) 715 */ 716 public void setRoomPerturbationWeight(double perturbationWeight) { 717 iRoomPerturbationWeight = perturbationWeight; 718 } 719 720 /** 721 * A weight for distance between two or more rooms into which an exam is 722 * split. Can by set by problem property Exams.RoomSplitDistanceWeight, or 723 * in the input xml file, property roomSplitDistanceWeight) 724 **/ 725 public double getRoomSplitDistanceWeight() { 726 return iRoomSplitDistanceWeight; 727 } 728 729 /** 730 * A weight for distance between two or more rooms into which an exam is 731 * split. Can by set by problem property Exams.RoomSplitDistanceWeight, or 732 * in the input xml file, property roomSplitDistanceWeight) 733 **/ 734 public void setRoomSplitDistanceWeight(double roomSplitDistanceWeight) { 735 iRoomSplitDistanceWeight = roomSplitDistanceWeight; 736 } 737 738 /** 739 * An exam is considered large, if its size is greater or equal to this 740 * large size. Value -1 means all exams are small. Can by set by problem 741 * property Exams.LargeSize, or in the input xml file, property largeSize) 742 **/ 743 public int getLargeSize() { 744 return iLargeSize; 745 } 746 747 /** 748 * An exam is considered large, if its size is greater or equal to this 749 * large size. Value -1 means all exams are small. Can by set by problem 750 * property Exams.LargeSize, or in the input xml file, property largeSize) 751 **/ 752 public void setLargeSize(int largeSize) { 753 iLargeSize = largeSize; 754 } 755 756 /** 757 * Period index (number of periods multiplied by this number) for front load 758 * criteria for large exams Can by set by problem property 759 * Exams.LargePeriod, or in the input xml file, property largePeriod) 760 **/ 761 public double getLargePeriod() { 762 return iLargePeriod; 763 } 764 765 /** 766 * Period index (number of periods multiplied by this number) for front load 767 * criteria for large exams Can by set by problem property 768 * Exams.LargePeriod, or in the input xml file, property largePeriod) 769 **/ 770 public void setLargePeriod(double largePeriod) { 771 iLargePeriod = largePeriod; 772 } 773 774 /** 775 * Weight of front load criteria, i.e., a weight for assigning a large exam 776 * after large period Can by set by problem property Exams.LargeWeight, or 777 * in the input xml file, property largeWeight) 778 **/ 779 public double getLargeWeight() { 780 return iLargeWeight; 781 } 782 783 /** 784 * Weight of front load criteria, i.e., a weight for assigning a large exam 785 * after large period Can by set by problem property Exams.LargeWeight, or 786 * in the input xml file, property largeWeight) 787 **/ 788 public void setLargeWeight(double largeWeight) { 789 iLargeWeight = largeWeight; 790 } 791 792 /** 793 * Called before a value is unassigned from its variable, optimization 794 * criteria are updated 795 */ 796 @Override 797 public void beforeUnassigned(long iteration, ExamPlacement placement) { 798 super.beforeUnassigned(iteration, placement); 799 Exam exam = placement.variable(); 800 iNrDirectConflicts -= placement.getNrDirectConflicts(); 801 iNrNADirectConflicts -= placement.getNrNotAvailableConflicts(); 802 iNrBackToBackConflicts -= placement.getNrBackToBackConflicts(); 803 iNrMoreThanTwoADayConflicts -= placement.getNrMoreThanTwoADayConflicts(); 804 iRoomSizePenalty -= placement.getRoomSizePenalty(); 805 iNrDistanceBackToBackConflicts -= placement.getNrDistanceBackToBackConflicts(); 806 iRoomSplitPenalty -= placement.getRoomSplitPenalty(); 807 iRoomSplitPenalties[placement.getRoomPlacements().size()]--; 808 iPeriodPenalty -= placement.getPeriodPenalty(); 809 iPeriodIndexPenalty -= placement.getPeriod().getIndex(); 810 iPeriodSizePenalty -= placement.getPeriodPenalty() * (exam.getSize() + 1); 811 iExamRotationPenalty -= placement.getRotationPenalty(); 812 iRoomPenalty -= placement.getRoomPenalty(); 813 iNrInstructorDirectConflicts -= placement.getNrInstructorDirectConflicts(); 814 iNrNAInstructorDirectConflicts -= placement.getNrInstructorNotAvailableConflicts(); 815 iNrInstructorBackToBackConflicts -= placement.getNrInstructorBackToBackConflicts(); 816 iNrInstructorMoreThanTwoADayConflicts -= placement.getNrInstructorMoreThanTwoADayConflicts(); 817 iNrInstructorDistanceBackToBackConflicts -= placement.getNrInstructorDistanceBackToBackConflicts(); 818 iPerturbationPenalty -= placement.getPerturbationPenalty(); 819 iRoomPerturbationPenalty -= placement.getRoomPerturbationPenalty(); 820 iRoomSplitDistancePenalty -= placement.getRoomSplitDistancePenalty(); 821 iLargePenalty -= placement.getLargePenalty(); 822 if (placement.getRoomPlacements().size() > 1) 823 iRoomSplits--; 824 for (ExamStudent s : exam.getStudents()) 825 s.afterUnassigned(iteration, placement); 826 for (ExamInstructor i : exam.getInstructors()) 827 i.afterUnassigned(iteration, placement); 828 for (ExamRoomPlacement r : placement.getRoomPlacements()) 829 r.getRoom().afterUnassigned(iteration, placement); 830 } 831 832 /** 833 * Called after a value is assigned to its variable, optimization criteria 834 * are updated 835 */ 836 @Override 837 public void afterAssigned(long iteration, ExamPlacement placement) { 838 super.afterAssigned(iteration, placement); 839 Exam exam = placement.variable(); 840 iNrDirectConflicts += placement.getNrDirectConflicts(); 841 iNrNADirectConflicts += placement.getNrNotAvailableConflicts(); 842 iNrBackToBackConflicts += placement.getNrBackToBackConflicts(); 843 iNrMoreThanTwoADayConflicts += placement.getNrMoreThanTwoADayConflicts(); 844 iRoomSizePenalty += placement.getRoomSizePenalty(); 845 iNrDistanceBackToBackConflicts += placement.getNrDistanceBackToBackConflicts(); 846 iRoomSplitPenalty += placement.getRoomSplitPenalty(); 847 iRoomSplitPenalties[placement.getRoomPlacements().size()]++; 848 iPeriodPenalty += placement.getPeriodPenalty(); 849 iPeriodIndexPenalty += placement.getPeriod().getIndex(); 850 iPeriodSizePenalty += placement.getPeriodPenalty() * (exam.getSize() + 1); 851 iExamRotationPenalty += placement.getRotationPenalty(); 852 iRoomPenalty += placement.getRoomPenalty(); 853 iNrInstructorDirectConflicts += placement.getNrInstructorDirectConflicts(); 854 iNrNAInstructorDirectConflicts += placement.getNrInstructorNotAvailableConflicts(); 855 iNrInstructorBackToBackConflicts += placement.getNrInstructorBackToBackConflicts(); 856 iNrInstructorMoreThanTwoADayConflicts += placement.getNrInstructorMoreThanTwoADayConflicts(); 857 iNrInstructorDistanceBackToBackConflicts += placement.getNrInstructorDistanceBackToBackConflicts(); 858 iPerturbationPenalty += placement.getPerturbationPenalty(); 859 iRoomPerturbationPenalty += placement.getRoomPerturbationPenalty(); 860 iRoomSplitDistancePenalty += placement.getRoomSplitDistancePenalty(); 861 iLargePenalty += placement.getLargePenalty(); 862 if (placement.getRoomPlacements().size() > 1) 863 iRoomSplits++; 864 for (ExamStudent s : exam.getStudents()) 865 s.afterAssigned(iteration, placement); 866 for (ExamInstructor i : exam.getInstructors()) 867 i.afterAssigned(iteration, placement); 868 for (ExamRoomPlacement r : placement.getRoomPlacements()) 869 r.getRoom().afterAssigned(iteration, placement); 870 } 871 872 /** 873 * Objective function. The objective function consists of the following 874 * criteria: 875 * <ul> 876 * <li>Direct student conflicts (a student is enrolled in two exams that are 877 * scheduled at the same period, weighted by Exams.DirectConflictWeight) 878 * <li>Back-to-Back student conflicts (a student is enrolled in two exams 879 * that are scheduled in consecutive periods, weighted by 880 * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false, 881 * there is no conflict between the last period and the first period of 882 * consecutive days. 883 * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student 884 * conflict, but the maximum distance between rooms in which both exam take 885 * place is greater than Exams.BackToBackDistance, weighted by 886 * Exams.DistanceBackToBackConflictWeight). 887 * <li>More than two exams a day (a student is enrolled in three exams that 888 * are scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight). 889 * <li>Period penalty (total of period penalties 890 * {@link ExamPlacement#getPeriodPenalty()} of all assigned exams, weighted 891 * by Exams.PeriodWeight). 892 * <li>Room size penalty (total of room size penalties 893 * {@link ExamPlacement#getRoomSizePenalty()} of all assigned exams, 894 * weighted by Exams.RoomSizeWeight). 895 * <li>Room split penalty (total of room split penalties 896 * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned exams, 897 * weighted by Exams.RoomSplitWeight). 898 * <li>Room split distance penalty 899 * {@link ExamPlacement#getRoomSplitDistancePenalty()}, of all assigned 900 * exams, weighted by {@link ExamModel#getRoomSplitDistanceWeight()} 901 * <li>Room penalty (total of room penalties 902 * {@link ExamPlacement#getRoomPenalty()} of all assigned exams, weighted by 903 * Exams.RoomWeight). 904 * <li>Distribution penalty (total of room split penalties 905 * {@link ExamDistributionConstraint#getWeight()} of all soft violated 906 * distribution constraints, weighted by Exams.DistributionWeight). 907 * <li>Direct instructor conflicts (an instructor is enrolled in two exams 908 * that are scheduled at the same period, weighted by 909 * Exams.InstructorDirectConflictWeight) 910 * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two 911 * exams that are scheduled in consecutive periods, weighted by 912 * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack 913 * is false, there is no conflict between the last period and the first 914 * period of consecutive days. 915 * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back 916 * instructor conflict, but the maximum distance between rooms in which both 917 * exam take place is greater than Exams.BackToBackDistance, weighted by 918 * Exams.InstructorDistanceBackToBackConflictWeight). 919 * <li>More than two exams a day (an instructor is enrolled in three exams 920 * that are scheduled at the same day, weighted by 921 * Exams.InstructorMoreThanTwoADayWeight). 922 * <li>Perturbation penalty (total of period penalties 923 * {@link ExamPlacement#getPerturbationPenalty()} of all assigned exams, 924 * weighted by Exams.PerturbationWeight). 925 * <li>Front load penalty {@link ExamPlacement#getLargePenalty()} of all 926 * assigned exams, weighted by Exam.LargeWeight 927 * </ul> 928 * 929 * @return weighted sum of objective criteria 930 */ 931 @Override 932 public double getTotalValue() { 933 return getDirectConflictWeight() * getNrDirectConflicts(false) + getMoreThanTwoADayWeight() 934 * getNrMoreThanTwoADayConflicts(false) + getBackToBackConflictWeight() 935 * getNrBackToBackConflicts(false) + getDistanceBackToBackConflictWeight() 936 * getNrDistanceBackToBackConflicts(false) + getPeriodWeight() * getPeriodPenalty(false) 937 + getPeriodIndexWeight() * getPeriodIndexPenalty(false) + getPeriodSizeWeight() 938 * getPeriodSizePenalty(false) + getPeriodIndexWeight() * getPeriodIndexPenalty(false) 939 + getRoomSizeWeight() * getRoomSizePenalty(false) + getRoomSplitWeight() * getRoomSplitPenalty(false) 940 + getRoomWeight() * getRoomPenalty(false) + getDistributionWeight() * getDistributionPenalty(false) 941 + getInstructorDirectConflictWeight() * getNrInstructorDirectConflicts(false) 942 + getInstructorMoreThanTwoADayWeight() * getNrInstructorMoreThanTwoADayConflicts(false) 943 + getInstructorBackToBackConflictWeight() * getNrInstructorBackToBackConflicts(false) 944 + getInstructorDistanceBackToBackConflictWeight() * getNrInstructorDistanceBackToBackConflicts(false) 945 + getExamRotationWeight() * getExamRotationPenalty(false) + getPerturbationWeight() 946 * getPerturbationPenalty(false) + getRoomPerturbationWeight() * getRoomPerturbationPenalty(false) 947 + getRoomSplitDistanceWeight() * getRoomSplitDistancePenalty(false) + getLargeWeight() 948 * getLargePenalty(false); 949 } 950 951 /** 952 * Return weighted individual objective criteria. The objective function 953 * consists of the following criteria: 954 * <ul> 955 * <li>Direct student conflicts (a student is enrolled in two exams that are 956 * scheduled at the same period, weighted by Exams.DirectConflictWeight) 957 * <li>Back-to-Back student conflicts (a student is enrolled in two exams 958 * that are scheduled in consecutive periods, weighted by 959 * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false, 960 * there is no conflict between the last period and the first period of 961 * consecutive days. 962 * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student 963 * conflict, but the maximum distance between rooms in which both exam take 964 * place is greater than Exams.BackToBackDistance, weighted by 965 * Exams.DistanceBackToBackConflictWeight). 966 * <li>More than two exams a day (a student is enrolled in three exams that 967 * are scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight). 968 * <li>Period penalty (total of period penalties 969 * {@link ExamPlacement#getPeriodPenalty()} of all assigned exams, weighted 970 * by Exams.PeriodWeight). 971 * <li>Room size penalty (total of room size penalties 972 * {@link ExamPlacement#getRoomSizePenalty()} of all assigned exams, 973 * weighted by Exams.RoomSizeWeight). 974 * <li>Room split penalty (total of room split penalties 975 * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned exams, 976 * weighted by Exams.RoomSplitWeight). 977 * <li>Room split distance penalty 978 * {@link ExamPlacement#getRoomSplitDistancePenalty()}, of all assigned 979 * exams, weighted by {@link ExamModel#getRoomSplitDistanceWeight()} 980 * <li>Room penalty (total of room penalties 981 * {@link ExamPlacement#getRoomPenalty()} of all assigned exams, weighted by 982 * Exams.RoomWeight). 983 * <li>Distribution penalty (total of room split penalties 984 * {@link ExamDistributionConstraint#getWeight()} of all soft violated 985 * distribution constraints, weighted by Exams.DistributionWeight). 986 * <li>Direct instructor conflicts (an instructor is enrolled in two exams 987 * that are scheduled at the same period, weighted by 988 * Exams.InstructorDirectConflictWeight) 989 * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two 990 * exams that are scheduled in consecutive periods, weighted by 991 * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack 992 * is false, there is no conflict between the last period and the first 993 * period of consecutive days. 994 * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back 995 * instructor conflict, but the maximum distance between rooms in which both 996 * exam take place is greater than Exams.BackToBackDistance, weighted by 997 * Exams.InstructorDistanceBackToBackConflictWeight). 998 * <li>More than two exams a day (an instructor is enrolled in three exams 999 * that are scheduled at the same day, weighted by 1000 * Exams.InstructorMoreThanTwoADayWeight). 1001 * <li>Perturbation penalty (total of period penalties 1002 * {@link ExamPlacement#getPerturbationPenalty()} of all assigned exams, 1003 * weighted by Exams.PerturbationWeight). 1004 * <li>Front load penalty {@link ExamPlacement#getLargePenalty()} of all 1005 * assigned exams, weighted by Exam.LargeWeight 1006 * </ul> 1007 * 1008 * @return an array of weighted objective criteria 1009 */ 1010 public double[] getTotalMultiValue() { 1011 return new double[] { getDirectConflictWeight() * getNrDirectConflicts(false), 1012 getMoreThanTwoADayWeight() * getNrMoreThanTwoADayConflicts(false), 1013 getBackToBackConflictWeight() * getNrBackToBackConflicts(false), 1014 getDistanceBackToBackConflictWeight() * getNrDistanceBackToBackConflicts(false), 1015 getPeriodWeight() * getPeriodPenalty(false), getPeriodSizeWeight() * getPeriodSizePenalty(false), 1016 getPeriodIndexWeight() * getPeriodIndexPenalty(false), getRoomSizeWeight() * getRoomSizePenalty(false), 1017 getRoomSplitWeight() * getRoomSplitPenalty(false), 1018 getRoomSplitDistanceWeight() * getRoomSplitDistancePenalty(false), 1019 getRoomWeight() * getRoomPenalty(false), getDistributionWeight() * getDistributionPenalty(false), 1020 getInstructorDirectConflictWeight() * getNrInstructorDirectConflicts(false), 1021 getInstructorMoreThanTwoADayWeight() * getNrInstructorMoreThanTwoADayConflicts(false), 1022 getInstructorBackToBackConflictWeight() * getNrInstructorBackToBackConflicts(false), 1023 getInstructorDistanceBackToBackConflictWeight() * getNrInstructorDistanceBackToBackConflicts(false), 1024 getExamRotationWeight() * getExamRotationPenalty(false), 1025 getPerturbationWeight() * getPerturbationPenalty(false), 1026 getRoomPerturbationWeight() * getRoomPerturbationPenalty(false), 1027 getLargeWeight() * getLargePenalty(false) }; 1028 } 1029 1030 /** 1031 * String representation -- returns a list of values of objective criteria 1032 */ 1033 @Override 1034 public String toString() { 1035 return "DC:" + getNrDirectConflicts(false) + "," + "M2D:" + getNrMoreThanTwoADayConflicts(false) + "," + "BTB:" 1036 + getNrBackToBackConflicts(false) + "," 1037 + (getBackToBackDistance() < 0 ? "" : "dBTB:" + getNrDistanceBackToBackConflicts(false) + ",") + "PP:" 1038 + getPeriodPenalty(false) + "," + "PSP:" + getPeriodSizePenalty(false) + "," + "PX:" 1039 + getPeriodIndexPenalty(false) + "," + "@P:" + getExamRotationPenalty(false) + "," + "RSz:" 1040 + getRoomSizePenalty(false) + "," + "RSp:" + getRoomSplitPenalty(false) + "," + "RD:" 1041 + sDoubleFormat.format(getRoomSplitDistancePenalty(false)) + "," + "RP:" + getRoomPenalty(false) + "," 1042 + "DP:" + getDistributionPenalty(false) + (getLargeSize() >= 0 ? ",LP:" + getLargePenalty(false) : "") 1043 + (isMPP() ? ",IP:" + getPerturbationPenalty(false) + ",IRP:" + getRoomPerturbationPenalty(false) : ""); 1044 } 1045 1046 /** 1047 * Return number of direct student conflicts, i.e., the total number of 1048 * cases where a student is enrolled into two exams that are scheduled at 1049 * the same period. 1050 * 1051 * @param precise 1052 * if false, the cached value is used 1053 * @return number of direct student conflicts 1054 */ 1055 public int getNrDirectConflicts(boolean precise) { 1056 if (!precise) 1057 return iNrDirectConflicts; 1058 int conflicts = 0; 1059 for (ExamStudent student : getStudents()) { 1060 for (ExamPeriod period : getPeriods()) { 1061 int nrExams = student.getExams(period).size(); 1062 if (!student.isAvailable(period)) 1063 conflicts += nrExams; 1064 else if (nrExams > 1) 1065 conflicts += nrExams - 1; 1066 } 1067 } 1068 return conflicts; 1069 } 1070 1071 /** 1072 * Return number of back-to-back student conflicts, i.e., the total number 1073 * of cases where a student is enrolled into two exams that are scheduled at 1074 * consecutive periods. If {@link ExamModel#isDayBreakBackToBack()} is 1075 * false, the last period of one day and the first period of the following 1076 * day are not considered as consecutive periods. 1077 * 1078 * @param precise 1079 * if false, the cached value is used 1080 * @return number of back-to-back student conflicts 1081 */ 1082 public int getNrBackToBackConflicts(boolean precise) { 1083 if (!precise) 1084 return iNrBackToBackConflicts; 1085 int conflicts = 0; 1086 for (ExamStudent student : getStudents()) { 1087 for (ExamPeriod period : getPeriods()) { 1088 int nrExams = student.getExams(period).size(); 1089 if (nrExams == 0) 1090 continue; 1091 if (period.next() != null && !student.getExams(period.next()).isEmpty() 1092 && (isDayBreakBackToBack() || period.next().getDay() == period.getDay())) 1093 conflicts += nrExams * student.getExams(period.next()).size(); 1094 } 1095 } 1096 return conflicts; 1097 } 1098 1099 /** 1100 * Return number of distance back-to-back student conflicts, i.e., the total 1101 * number of back-to-back student conflicts where the two exam take place in 1102 * rooms that are too far a part (i.e., 1103 * {@link ExamPlacement#getDistanceInMeters(ExamPlacement)} is greater than 1104 * {@link ExamModel#getBackToBackDistance()}). 1105 * 1106 * @param precise 1107 * if false, the cached value is used 1108 * @return number of distance back-to-back student conflicts 1109 */ 1110 public int getNrDistanceBackToBackConflicts(boolean precise) { 1111 if (getBackToBackDistance() < 0) 1112 return 0; 1113 if (!precise) 1114 return iNrDistanceBackToBackConflicts; 1115 int conflicts = 0; 1116 for (ExamStudent student : getStudents()) { 1117 for (ExamPeriod period : getPeriods()) { 1118 Set<Exam> exams = student.getExams(period); 1119 if (exams.isEmpty()) 1120 continue; 1121 if (period.next() != null && !student.getExams(period.next()).isEmpty() 1122 && period.next().getDay() == period.getDay()) { 1123 for (Exam x1 : exams) { 1124 ExamPlacement p1 = x1.getAssignment(); 1125 for (Exam x2 : student.getExams(period.next())) { 1126 ExamPlacement p2 = x2.getAssignment(); 1127 if (p1.getDistanceInMeters(p2) > getBackToBackDistance()) 1128 conflicts++; 1129 } 1130 } 1131 } 1132 } 1133 } 1134 return conflicts; 1135 } 1136 1137 /** 1138 * Return number of more than two exams a day student conflicts, i.e., the 1139 * total number of cases where a student is enrolled into three exams that 1140 * are scheduled at the same day (i.e., {@link ExamPeriod#getDay()} is the 1141 * same). 1142 * 1143 * @param precise 1144 * if false, the cached value is used 1145 * @return number of more than two exams a day student conflicts 1146 */ 1147 public int getNrMoreThanTwoADayConflicts(boolean precise) { 1148 if (!precise) 1149 return iNrMoreThanTwoADayConflicts; 1150 int conflicts = 0; 1151 for (ExamStudent student : getStudents()) { 1152 for (int d = 0; d < getNrDays(); d++) { 1153 int nrExams = student.getExamsADay(d).size(); 1154 if (nrExams > 2) 1155 conflicts += nrExams - 2; 1156 } 1157 } 1158 return conflicts; 1159 } 1160 1161 /** 1162 * Return number of direct instructor conflicts, i.e., the total number of 1163 * cases where an instructor is enrolled into two exams that are scheduled 1164 * at the same period. 1165 * 1166 * @param precise 1167 * if false, the cached value is used 1168 * @return number of direct instructor conflicts 1169 */ 1170 public int getNrInstructorDirectConflicts(boolean precise) { 1171 if (!precise) 1172 return iNrInstructorDirectConflicts; 1173 int conflicts = 0; 1174 for (ExamInstructor instructor : getInstructors()) { 1175 for (ExamPeriod period : getPeriods()) { 1176 int nrExams = instructor.getExams(period).size(); 1177 if (!instructor.isAvailable(period)) 1178 conflicts += nrExams; 1179 else if (nrExams > 1) 1180 conflicts += nrExams - 1; 1181 } 1182 } 1183 return conflicts; 1184 } 1185 1186 /** 1187 * Return number of back-to-back instructor conflicts, i.e., the total 1188 * number of cases where an instructor is enrolled into two exams that are 1189 * scheduled at consecutive periods. If 1190 * {@link ExamModel#isDayBreakBackToBack()} is false, the last period of one 1191 * day and the first period of the following day are not considered as 1192 * consecutive periods. 1193 * 1194 * @param precise 1195 * if false, the cached value is used 1196 * @return number of back-to-back instructor conflicts 1197 */ 1198 public int getNrInstructorBackToBackConflicts(boolean precise) { 1199 if (!precise) 1200 return iNrInstructorBackToBackConflicts; 1201 int conflicts = 0; 1202 for (ExamInstructor instructor : getInstructors()) { 1203 for (ExamPeriod period : getPeriods()) { 1204 int nrExams = instructor.getExams(period).size(); 1205 if (nrExams == 0) 1206 continue; 1207 if (period.next() != null && !instructor.getExams(period.next()).isEmpty() 1208 && (isDayBreakBackToBack() || period.next().getDay() == period.getDay())) 1209 conflicts += nrExams * instructor.getExams(period.next()).size(); 1210 } 1211 } 1212 return conflicts; 1213 } 1214 1215 /** 1216 * Return number of distance back-to-back instructor conflicts, i.e., the 1217 * total number of back-to-back instructor conflicts where the two exam take 1218 * place in rooms that are too far a part (i.e., 1219 * {@link ExamPlacement#getDistanceInMeters(ExamPlacement)} is greater than 1220 * {@link ExamModel#getBackToBackDistance()}). 1221 * 1222 * @param precise 1223 * if false, the cached value is used 1224 * @return number of distance back-to-back student conflicts 1225 */ 1226 public int getNrInstructorDistanceBackToBackConflicts(boolean precise) { 1227 if (getBackToBackDistance() < 0) 1228 return 0; 1229 if (!precise) 1230 return iNrInstructorDistanceBackToBackConflicts; 1231 int conflicts = 0; 1232 for (ExamInstructor instructor : getInstructors()) { 1233 for (ExamPeriod period : getPeriods()) { 1234 Set<Exam> exams = instructor.getExams(period); 1235 if (exams.isEmpty()) 1236 continue; 1237 if (period.next() != null && !instructor.getExams(period.next()).isEmpty() 1238 && period.next().getDay() == period.getDay()) { 1239 for (Exam x1 : exams) { 1240 ExamPlacement p1 = x1.getAssignment(); 1241 for (Exam x2 : instructor.getExams(period.next())) { 1242 ExamPlacement p2 = x2.getAssignment(); 1243 if (p1.getDistanceInMeters(p2) > getBackToBackDistance()) 1244 conflicts++; 1245 } 1246 } 1247 } 1248 } 1249 } 1250 return conflicts; 1251 } 1252 1253 /** 1254 * Return number of more than two exams a day instructor conflicts, i.e., 1255 * the total number of cases where an instructor is enrolled into three 1256 * exams that are scheduled at the same day (i.e., 1257 * {@link ExamPeriod#getDay()} is the same). 1258 * 1259 * @param precise 1260 * if false, the cached value is used 1261 * @return number of more than two exams a day student conflicts 1262 */ 1263 public int getNrInstructorMoreThanTwoADayConflicts(boolean precise) { 1264 if (!precise) 1265 return iNrInstructorMoreThanTwoADayConflicts; 1266 int conflicts = 0; 1267 for (ExamInstructor instructor : getInstructors()) { 1268 for (int d = 0; d < getNrDays(); d++) { 1269 int nrExams = instructor.getExamsADay(d).size(); 1270 if (nrExams > 2) 1271 conflicts += nrExams - 2; 1272 } 1273 } 1274 return conflicts; 1275 } 1276 1277 /** 1278 * Return total room size penalty, i.e., the sum of 1279 * {@link ExamPlacement#getRoomSizePenalty()} of all assigned placements. 1280 * 1281 * @param precise 1282 * if false, the cached value is used 1283 * @return total room size penalty 1284 */ 1285 public int getRoomSizePenalty(boolean precise) { 1286 if (!precise) 1287 return iRoomSizePenalty; 1288 int penalty = 0; 1289 for (Exam exam : assignedVariables()) { 1290 penalty += exam.getAssignment().getRoomSizePenalty(); 1291 } 1292 return penalty; 1293 } 1294 1295 /** 1296 * Return total room split penalty, i.e., the sum of 1297 * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned placements. 1298 * 1299 * @param precise 1300 * if false, the cached value is used 1301 * @return total room split penalty 1302 */ 1303 public int getRoomSplitPenalty(boolean precise) { 1304 if (!precise) 1305 return iRoomSplitPenalty; 1306 int penalty = 0; 1307 for (Exam exam : assignedVariables()) { 1308 penalty += exam.getAssignment().getRoomSplitPenalty(); 1309 } 1310 return penalty; 1311 } 1312 1313 /** 1314 * Return total period penalty, i.e., the sum of 1315 * {@link ExamPlacement#getPeriodPenalty()} of all assigned placements. 1316 * 1317 * @param precise 1318 * if false, the cached value is used 1319 * @return total period penalty 1320 */ 1321 public int getPeriodPenalty(boolean precise) { 1322 if (!precise) 1323 return iPeriodPenalty; 1324 int penalty = 0; 1325 for (Exam exam : assignedVariables()) { 1326 penalty += exam.getAssignment().getPeriodPenalty(); 1327 } 1328 return penalty; 1329 } 1330 1331 /** 1332 * Return total period index of all assigned placements. 1333 * 1334 * @param precise 1335 * if false, the cached value is used 1336 * @return total period penalty 1337 */ 1338 public int getPeriodIndexPenalty(boolean precise) { 1339 if (!precise) 1340 return iPeriodIndexPenalty; 1341 int penalty = 0; 1342 for (Exam exam : assignedVariables()) { 1343 penalty += (exam.getAssignment()).getPeriod().getIndex(); 1344 } 1345 return penalty; 1346 } 1347 1348 /** 1349 * Return total period size penalty, i.e., the sum of 1350 * {@link ExamPlacement#getPeriodPenalty()} multiplied by 1351 * {@link Exam#getSize()} of all assigned placements. 1352 * 1353 * @param precise 1354 * if false, the cached value is used 1355 * @return total period penalty 1356 */ 1357 public int getPeriodSizePenalty(boolean precise) { 1358 if (!precise) 1359 return iPeriodSizePenalty; 1360 int penalty = 0; 1361 for (Exam exam : assignedVariables()) { 1362 penalty += exam.getAssignment().getPeriodPenalty() * (exam.getSize() + 1); 1363 } 1364 return penalty; 1365 } 1366 1367 /** 1368 * Return total exam rotation penalty, i.e., the sum of 1369 * {@link ExamPlacement#getRotationPenalty()} of all assigned placements. 1370 * 1371 * @param precise 1372 * if false, the cached value is used 1373 * @return total period penalty 1374 */ 1375 public int getExamRotationPenalty(boolean precise) { 1376 if (!precise) 1377 return iExamRotationPenalty; 1378 int penalty = 0; 1379 for (Exam exam : assignedVariables()) { 1380 penalty += exam.getAssignment().getRotationPenalty(); 1381 } 1382 return penalty; 1383 } 1384 1385 /** 1386 * Return total room (weight) penalty, i.e., the sum of 1387 * {@link ExamPlacement#getRoomPenalty()} of all assigned placements. 1388 * 1389 * @param precise 1390 * if false, the cached value is used 1391 * @return total room penalty 1392 */ 1393 public int getRoomPenalty(boolean precise) { 1394 if (!precise) 1395 return iRoomPenalty; 1396 int penalty = 0; 1397 for (Exam exam : assignedVariables()) { 1398 penalty += exam.getAssignment().getRoomPenalty(); 1399 } 1400 return penalty; 1401 } 1402 1403 /** 1404 * Return total distribution penalty, i.e., the sum of 1405 * {@link ExamDistributionConstraint#getWeight()} of all violated soft 1406 * distribution constraints. 1407 * 1408 * @param precise 1409 * if false, the cached value is used 1410 * @return total distribution penalty 1411 */ 1412 public int getDistributionPenalty(boolean precise) { 1413 if (!precise) 1414 return iDistributionPenalty; 1415 int penalty = 0; 1416 for (ExamDistributionConstraint dc : getDistributionConstraints()) { 1417 if (!dc.isSatisfied()) 1418 penalty += dc.getWeight(); 1419 } 1420 return penalty; 1421 } 1422 1423 /** 1424 * Return total room split distance penalty, i.e., the sum of 1425 * {@link ExamPlacement#getRoomSplitDistancePenalty()} of all assigned 1426 * placements. 1427 * 1428 * @param precise 1429 * if false, the cached value is used 1430 * @return total room split distance penalty 1431 */ 1432 public double getRoomSplitDistancePenalty(boolean precise) { 1433 if (!precise) 1434 return iRoomSplitDistancePenalty; 1435 double penalty = 0; 1436 for (Exam exam : assignedVariables()) { 1437 penalty += exam.getAssignment().getRoomSplitDistancePenalty(); 1438 } 1439 return penalty; 1440 } 1441 1442 /** 1443 * Count exam placements with a room split. 1444 * 1445 * @param precise 1446 * if false, the cached value is used 1447 * @return total number of exams that are assigned into two or more rooms 1448 */ 1449 public double getNrRoomSplits(boolean precise) { 1450 if (!precise) 1451 return iRoomSplits; 1452 int penalty = 0; 1453 for (Exam exam : assignedVariables()) { 1454 penalty += (exam.getAssignment().getRoomPlacements().size() > 1 ? 1 : 0); 1455 } 1456 return penalty; 1457 } 1458 1459 /** 1460 * To be called by soft {@link ExamDistributionConstraint} when satisfaction 1461 * changes. 1462 */ 1463 protected void addDistributionPenalty(int inc) { 1464 iDistributionPenalty += inc; 1465 } 1466 1467 private Integer iMaxDistributionPenalty = null; 1468 1469 private int getMaxDistributionPenalty() { 1470 if (iMaxDistributionPenalty == null) { 1471 int maxDistributionPenalty = 0; 1472 for (ExamDistributionConstraint dc : getDistributionConstraints()) { 1473 if (dc.isHard()) 1474 continue; 1475 maxDistributionPenalty += dc.getWeight(); 1476 } 1477 iMaxDistributionPenalty = new Integer(maxDistributionPenalty); 1478 } 1479 return iMaxDistributionPenalty.intValue(); 1480 } 1481 1482 private int[] iLimits = null; 1483 1484 private int getMinPenalty(ExamRoom r) { 1485 int min = Integer.MAX_VALUE; 1486 for (ExamPeriod p : getPeriods()) { 1487 if (r.isAvailable(p)) { 1488 min = Math.min(min, r.getPenalty(p)); 1489 } 1490 } 1491 return min; 1492 } 1493 1494 private int getMaxPenalty(ExamRoom r) { 1495 int max = Integer.MIN_VALUE; 1496 for (ExamPeriod p : getPeriods()) { 1497 if (r.isAvailable(p)) { 1498 max = Math.max(max, r.getPenalty(p)); 1499 } 1500 } 1501 return max; 1502 } 1503 1504 private int[] getLimits() { 1505 if (iLimits == null) { 1506 int minPeriodPenalty = 0, maxPeriodPenalty = 0; 1507 int minPeriodSizePenalty = 0, maxPeriodSizePenalty = 0; 1508 int minRoomPenalty = 0, maxRoomPenalty = 0; 1509 for (Exam exam : variables()) { 1510 if (!exam.getPeriodPlacements().isEmpty()) { 1511 int minPenalty = Integer.MAX_VALUE, maxPenalty = Integer.MIN_VALUE; 1512 int minSizePenalty = Integer.MAX_VALUE, maxSizePenalty = Integer.MIN_VALUE; 1513 for (ExamPeriodPlacement periodPlacement : exam.getPeriodPlacements()) { 1514 minPenalty = Math.min(minPenalty, periodPlacement.getPenalty()); 1515 maxPenalty = Math.max(maxPenalty, periodPlacement.getPenalty()); 1516 minSizePenalty = Math.min(minSizePenalty, periodPlacement.getPenalty() * (exam.getSize() + 1)); 1517 maxSizePenalty = Math.max(maxSizePenalty, periodPlacement.getPenalty() * (exam.getSize() + 1)); 1518 } 1519 minPeriodPenalty += minPenalty; 1520 maxPeriodPenalty += maxPenalty; 1521 minPeriodSizePenalty += minSizePenalty; 1522 maxPeriodSizePenalty += maxSizePenalty; 1523 } 1524 if (!exam.getRoomPlacements().isEmpty()) { 1525 int minPenalty = Integer.MAX_VALUE, maxPenalty = Integer.MIN_VALUE; 1526 for (ExamRoomPlacement roomPlacement : exam.getRoomPlacements()) { 1527 minPenalty = Math.min(minPenalty, (roomPlacement.getPenalty() != 0 ? roomPlacement.getPenalty() 1528 : getMinPenalty(roomPlacement.getRoom()))); 1529 maxPenalty = Math.max(maxPenalty, (roomPlacement.getPenalty() != 0 ? roomPlacement.getPenalty() 1530 : getMaxPenalty(roomPlacement.getRoom()))); 1531 } 1532 minRoomPenalty += minPenalty; 1533 maxRoomPenalty += maxPenalty; 1534 } 1535 } 1536 iLimits = new int[] { minPeriodPenalty, maxPeriodPenalty, minRoomPenalty, maxRoomPenalty, 1537 minPeriodSizePenalty, maxPeriodSizePenalty }; 1538 } 1539 return iLimits; 1540 } 1541 1542 /** 1543 * Return total perturbation penalty, i.e., the sum of 1544 * {@link ExamPlacement#getPerturbationPenalty()} of all assigned 1545 * placements. 1546 * 1547 * @param precise 1548 * if false, the cached value is used 1549 * @return total period penalty 1550 */ 1551 public int getPerturbationPenalty(boolean precise) { 1552 if (!precise) 1553 return iPerturbationPenalty; 1554 int penalty = 0; 1555 for (Exam exam : assignedVariables()) { 1556 penalty += exam.getAssignment().getPerturbationPenalty(); 1557 } 1558 return penalty; 1559 } 1560 1561 /** 1562 * Return total room perturbation penalty, i.e., the sum of 1563 * {@link ExamPlacement#getRoomPerturbationPenalty()} of all assigned 1564 * placements. 1565 * 1566 * @param precise 1567 * if false, the cached value is used 1568 * @return total room period penalty 1569 */ 1570 public int getRoomPerturbationPenalty(boolean precise) { 1571 if (!precise) 1572 return iRoomPerturbationPenalty; 1573 int penalty = 0; 1574 for (Exam exam : assignedVariables()) { 1575 penalty += exam.getAssignment().getRoomPerturbationPenalty(); 1576 } 1577 return penalty; 1578 } 1579 1580 /** 1581 * Return total front load penalty, i.e., the sum of 1582 * {@link ExamPlacement#getLargePenalty()} of all assigned placements. 1583 * 1584 * @param precise 1585 * if false, the cached value is used 1586 * @return total period penalty 1587 */ 1588 public int getLargePenalty(boolean precise) { 1589 if (!precise) 1590 return iLargePenalty; 1591 int penalty = 0; 1592 for (Exam exam : assignedVariables()) { 1593 penalty += exam.getAssignment().getLargePenalty(); 1594 } 1595 return penalty; 1596 } 1597 1598 /** 1599 * Info table 1600 */ 1601 @Override 1602 public Map<String, String> getInfo() { 1603 Map<String, String> info = super.getInfo(); 1604 info.put("Direct Conflicts", getNrDirectConflicts(false) 1605 + (iNrNADirectConflicts > 0 ? " (" + iNrNADirectConflicts + " N/A)" : "")); 1606 info.put("More Than 2 A Day Conflicts", String.valueOf(getNrMoreThanTwoADayConflicts(false))); 1607 info.put("Back-To-Back Conflicts", String.valueOf(getNrBackToBackConflicts(false))); 1608 if (getBackToBackDistance() >= 0 && getNrDistanceBackToBackConflicts(false) > 0) 1609 info.put("Distance Back-To-Back Conflicts", String.valueOf(getNrDistanceBackToBackConflicts(false))); 1610 if (getNrInstructorDirectConflicts(false) > 0) 1611 info.put("Instructor Direct Conflicts", getNrInstructorDirectConflicts(false) 1612 + (iNrNAInstructorDirectConflicts > 0 ? " (" + iNrNAInstructorDirectConflicts + " N/A)" : "")); 1613 if (getNrInstructorMoreThanTwoADayConflicts(false) > 0) 1614 info.put("Instructor More Than 2 A Day Conflicts", String 1615 .valueOf(getNrInstructorMoreThanTwoADayConflicts(false))); 1616 if (getNrInstructorBackToBackConflicts(false) > 0) 1617 info.put("Instructor Back-To-Back Conflicts", String.valueOf(getNrInstructorBackToBackConflicts(false))); 1618 if (getBackToBackDistance() >= 0 && getNrInstructorDistanceBackToBackConflicts(false) > 0) 1619 info.put("Instructor Distance Back-To-Back Conflicts", String 1620 .valueOf(getNrInstructorDistanceBackToBackConflicts(false))); 1621 if (nrAssignedVariables() > 0 && getRoomSizePenalty(false) > 0) 1622 info.put("Room Size Penalty", sDoubleFormat.format(((double) getRoomSizePenalty(false)) 1623 / nrAssignedVariables())); 1624 if (getRoomSplitPenalty(false) > 0) { 1625 String split = ""; 1626 for (int i = 2; i < getMaxRooms(); i++) 1627 if (iRoomSplitPenalties[i] > 0) { 1628 if (split.length() > 0) 1629 split += ", "; 1630 split += iRoomSplitPenalties[i] + "×" + i; 1631 } 1632 info.put("Room Split Penalty", getRoomSplitPenalty(false) + " (" + split + ")"); 1633 } 1634 info.put("Period Penalty", getPerc(getPeriodPenalty(false), getLimits()[0], getLimits()[1]) + "% (" 1635 + getPeriodPenalty(false) + ")"); 1636 info.put("Period×Size Penalty", getPerc(getPeriodSizePenalty(false), getLimits()[4], getLimits()[5]) 1637 + "% (" + getPeriodSizePenalty(false) + ")"); 1638 info.put("Average Period", sDoubleFormat 1639 .format(((double) getPeriodIndexPenalty(false)) / nrAssignedVariables())); 1640 info.put("Room Penalty", getPerc(getRoomPenalty(false), getLimits()[2], getLimits()[3]) + "% (" 1641 + getRoomPenalty(false) + ")"); 1642 info.put("Distribution Penalty", getPerc(getDistributionPenalty(false), 0, getMaxDistributionPenalty()) + "% (" 1643 + getDistributionPenalty(false) + ")"); 1644 info.put("Room Split Distance Penalty", sDoubleFormat.format(getRoomSplitDistancePenalty(false) 1645 / getNrRoomSplits(false))); 1646 if (getExamRotationPenalty(false) > 0) 1647 info.put("Exam Rotation Penalty", String.valueOf(getExamRotationPenalty(false))); 1648 if (isMPP()) { 1649 info.put("Perturbation Penalty", sDoubleFormat.format(((double) getPerturbationPenalty(false)) 1650 / nrAssignedVariables())); 1651 info.put("Room Perturbation Penalty", sDoubleFormat.format(((double) getRoomPerturbationPenalty(false)) 1652 / nrAssignedVariables())); 1653 } 1654 if (getLargeSize() >= 0) 1655 info.put("Large Exams Penalty", getPerc(getLargePenalty(false), 0, iNrLargeExams) + "% (" 1656 + getLargePenalty(false) + ")"); 1657 return info; 1658 } 1659 1660 /** 1661 * Extended info table 1662 */ 1663 @Override 1664 public Map<String, String> getExtendedInfo() { 1665 Map<String, String> info = super.getExtendedInfo(); 1666 info.put("Direct Conflicts [p]", String.valueOf(getNrDirectConflicts(true))); 1667 info.put("More Than 2 A Day Conflicts [p]", String.valueOf(getNrMoreThanTwoADayConflicts(true))); 1668 info.put("Back-To-Back Conflicts [p]", String.valueOf(getNrBackToBackConflicts(true))); 1669 info.put("Distance Back-To-Back Conflicts [p]", String.valueOf(getNrDistanceBackToBackConflicts(true))); 1670 info.put("Instructor Direct Conflicts [p]", String.valueOf(getNrInstructorDirectConflicts(true))); 1671 info.put("Instructor More Than 2 A Day Conflicts [p]", String 1672 .valueOf(getNrInstructorMoreThanTwoADayConflicts(true))); 1673 info.put("Instructor Back-To-Back Conflicts [p]", String.valueOf(getNrInstructorBackToBackConflicts(true))); 1674 info.put("Instructor Distance Back-To-Back Conflicts [p]", String 1675 .valueOf(getNrInstructorDistanceBackToBackConflicts(true))); 1676 info.put("Room Size Penalty [p]", String.valueOf(getRoomSizePenalty(true))); 1677 info.put("Room Split Penalty [p]", String.valueOf(getRoomSplitPenalty(true))); 1678 info.put("Period Penalty [p]", String.valueOf(getPeriodPenalty(true))); 1679 info.put("Period Size Penalty [p]", String.valueOf(getPeriodSizePenalty(true))); 1680 info.put("Period Index Penalty [p]", String.valueOf(getPeriodIndexPenalty(true))); 1681 info.put("Room Penalty [p]", String.valueOf(getRoomPenalty(true))); 1682 info.put("Distribution Penalty [p]", String.valueOf(getDistributionPenalty(true))); 1683 info.put("Perturbation Penalty [p]", String.valueOf(getPerturbationPenalty(true))); 1684 info.put("Room Perturbation Penalty [p]", String.valueOf(getRoomPerturbationPenalty(true))); 1685 info.put("Room Split Distance Penalty [p]", sDoubleFormat.format(getRoomSplitDistancePenalty(true)) + " / " 1686 + getNrRoomSplits(true)); 1687 info.put("Number of Periods", String.valueOf(getPeriods().size())); 1688 info.put("Number of Exams", String.valueOf(variables().size())); 1689 info.put("Number of Rooms", String.valueOf(getRooms().size())); 1690 info.put("Number of Students", String.valueOf(getStudents().size())); 1691 int nrStudentExams = 0; 1692 for (ExamStudent student : getStudents()) { 1693 nrStudentExams += student.getOwners().size(); 1694 } 1695 info.put("Number of Student Exams", String.valueOf(nrStudentExams)); 1696 int nrAltExams = 0, nrSmallExams = 0; 1697 for (Exam exam : variables()) { 1698 if (exam.hasAltSeating()) 1699 nrAltExams++; 1700 if (exam.getMaxRooms() == 0) 1701 nrSmallExams++; 1702 } 1703 info.put("Number of Exams Requiring Alt Seating", String.valueOf(nrAltExams)); 1704 info.put("Number of Small Exams (Exams W/O Room)", String.valueOf(nrSmallExams)); 1705 int[] nbrMtgs = new int[11]; 1706 for (int i = 0; i <= 10; i++) 1707 nbrMtgs[i] = 0; 1708 for (ExamStudent student : getStudents()) { 1709 nbrMtgs[Math.min(10, student.variables().size())]++; 1710 } 1711 for (int i = 0; i <= 10; i++) { 1712 if (nbrMtgs[i] == 0) 1713 continue; 1714 info.put("Number of Students with " + (i == 0 ? "no" : String.valueOf(i)) + (i == 10 ? " or more" : "") 1715 + " meeting" + (i != 1 ? "s" : ""), String.valueOf(nbrMtgs[i])); 1716 } 1717 return info; 1718 } 1719 1720 /** 1721 * Problem properties 1722 */ 1723 public DataProperties getProperties() { 1724 return iProperties; 1725 } 1726 1727 /** 1728 * Problem rooms 1729 * 1730 * @return list of {@link ExamRoom} 1731 */ 1732 public List<ExamRoom> getRooms() { 1733 return iRooms; 1734 } 1735 1736 /** 1737 * Problem students 1738 * 1739 * @return list of {@link ExamStudent} 1740 */ 1741 public List<ExamStudent> getStudents() { 1742 return iStudents; 1743 } 1744 1745 /** 1746 * Problem instructors 1747 * 1748 * @return list of {@link ExamInstructor} 1749 */ 1750 public List<ExamInstructor> getInstructors() { 1751 return iInstructors; 1752 } 1753 1754 /** 1755 * Distribution constraints 1756 * 1757 * @return list of {@link ExamDistributionConstraint} 1758 */ 1759 public List<ExamDistributionConstraint> getDistributionConstraints() { 1760 return iDistributionConstraints; 1761 } 1762 1763 private String getId(boolean anonymize, String type, String id) { 1764 return (anonymize ? IdConvertor.getInstance().convert(type, id) : id); 1765 } 1766 1767 /** 1768 * Save model (including its solution) into XML. 1769 */ 1770 public Document save() { 1771 boolean saveInitial = getProperties().getPropertyBoolean("Xml.SaveInitial", true); 1772 boolean saveBest = getProperties().getPropertyBoolean("Xml.SaveBest", true); 1773 boolean saveSolution = getProperties().getPropertyBoolean("Xml.SaveSolution", true); 1774 boolean saveConflictTable = getProperties().getPropertyBoolean("Xml.SaveConflictTable", false); 1775 boolean saveParams = getProperties().getPropertyBoolean("Xml.SaveParameters", true); 1776 boolean anonymize = getProperties().getPropertyBoolean("Xml.Anonymize", false); 1777 Document document = DocumentHelper.createDocument(); 1778 document.addComment("Examination Timetable"); 1779 if (nrAssignedVariables() > 0) { 1780 StringBuffer comments = new StringBuffer("Solution Info:\n"); 1781 Map<String, String> solutionInfo = (getProperties().getPropertyBoolean("Xml.ExtendedInfo", false) ? getExtendedInfo() 1782 : getInfo()); 1783 for (String key : new TreeSet<String>(solutionInfo.keySet())) { 1784 String value = solutionInfo.get(key); 1785 comments.append(" " + key + ": " + value + "\n"); 1786 } 1787 document.addComment(comments.toString()); 1788 } 1789 Element root = document.addElement("examtt"); 1790 root.addAttribute("version", "1.0"); 1791 root.addAttribute("campus", getProperties().getProperty("Data.Initiative")); 1792 root.addAttribute("term", getProperties().getProperty("Data.Term")); 1793 root.addAttribute("year", getProperties().getProperty("Data.Year")); 1794 root.addAttribute("created", String.valueOf(new Date())); 1795 if (saveParams) { 1796 Element params = root.addElement("parameters"); 1797 params.addElement("property").addAttribute("name", "isDayBreakBackToBack").addAttribute("value", 1798 (isDayBreakBackToBack() ? "true" : "false")); 1799 params.addElement("property").addAttribute("name", "directConflictWeight").addAttribute("value", 1800 String.valueOf(getDirectConflictWeight())); 1801 params.addElement("property").addAttribute("name", "moreThanTwoADayWeight").addAttribute("value", 1802 String.valueOf(getMoreThanTwoADayWeight())); 1803 params.addElement("property").addAttribute("name", "backToBackConflictWeight").addAttribute("value", 1804 String.valueOf(getBackToBackConflictWeight())); 1805 params.addElement("property").addAttribute("name", "distanceBackToBackConflictWeight").addAttribute( 1806 "value", String.valueOf(getDistanceBackToBackConflictWeight())); 1807 params.addElement("property").addAttribute("name", "backToBackDistance").addAttribute("value", 1808 String.valueOf(getBackToBackDistance())); 1809 params.addElement("property").addAttribute("name", "maxRooms").addAttribute("value", 1810 String.valueOf(getMaxRooms())); 1811 params.addElement("property").addAttribute("name", "periodWeight").addAttribute("value", 1812 String.valueOf(getPeriodWeight())); 1813 params.addElement("property").addAttribute("name", "periodSizeWeight").addAttribute("value", 1814 String.valueOf(getPeriodSizeWeight())); 1815 params.addElement("property").addAttribute("name", "periodIndexWeight").addAttribute("value", 1816 String.valueOf(getPeriodIndexWeight())); 1817 params.addElement("property").addAttribute("name", "examRotationWeight").addAttribute("value", 1818 String.valueOf(getExamRotationWeight())); 1819 params.addElement("property").addAttribute("name", "roomSizeWeight").addAttribute("value", 1820 String.valueOf(getRoomSizeWeight())); 1821 params.addElement("property").addAttribute("name", "roomSplitWeight").addAttribute("value", 1822 String.valueOf(getRoomSplitWeight())); 1823 params.addElement("property").addAttribute("name", "roomWeight").addAttribute("value", 1824 String.valueOf(getRoomWeight())); 1825 params.addElement("property").addAttribute("name", "distributionWeight").addAttribute("value", 1826 String.valueOf(getDistributionWeight())); 1827 params.addElement("property").addAttribute("name", "instructorDirectConflictWeight").addAttribute("value", 1828 String.valueOf(getInstructorDirectConflictWeight())); 1829 params.addElement("property").addAttribute("name", "instructorMoreThanTwoADayWeight").addAttribute("value", 1830 String.valueOf(getInstructorMoreThanTwoADayWeight())); 1831 params.addElement("property").addAttribute("name", "instructorBackToBackConflictWeight").addAttribute( 1832 "value", String.valueOf(getInstructorBackToBackConflictWeight())); 1833 params.addElement("property").addAttribute("name", "instructorDistanceBackToBackConflictWeight") 1834 .addAttribute("value", String.valueOf(getInstructorDistanceBackToBackConflictWeight())); 1835 params.addElement("property").addAttribute("name", "perturbationWeight").addAttribute("value", 1836 String.valueOf(getPerturbationWeight())); 1837 params.addElement("property").addAttribute("name", "roomPerturbationWeight").addAttribute("value", 1838 String.valueOf(getRoomPerturbationWeight())); 1839 params.addElement("property").addAttribute("name", "roomSplitDistanceWeight").addAttribute("value", 1840 String.valueOf(getRoomSplitDistanceWeight())); 1841 params.addElement("property").addAttribute("name", "largeSize").addAttribute("value", 1842 String.valueOf(getLargeSize())); 1843 params.addElement("property").addAttribute("name", "largePeriod").addAttribute("value", 1844 String.valueOf(getLargePeriod())); 1845 params.addElement("property").addAttribute("name", "largeWeight").addAttribute("value", 1846 String.valueOf(getLargeWeight())); 1847 } 1848 Element periods = root.addElement("periods"); 1849 for (ExamPeriod period : getPeriods()) { 1850 periods.addElement("period").addAttribute("id", getId(anonymize, "period", String.valueOf(period.getId()))) 1851 .addAttribute("length", String.valueOf(period.getLength())).addAttribute("day", period.getDayStr()) 1852 .addAttribute("time", period.getTimeStr()).addAttribute("penalty", 1853 String.valueOf(period.getPenalty())); 1854 } 1855 Element rooms = root.addElement("rooms"); 1856 for (ExamRoom room : getRooms()) { 1857 Element r = rooms.addElement("room"); 1858 r.addAttribute("id", getId(anonymize, "room", String.valueOf(room.getId()))); 1859 if (!anonymize && room.hasName()) 1860 r.addAttribute("name", room.getName()); 1861 r.addAttribute("size", String.valueOf(room.getSize())); 1862 r.addAttribute("alt", String.valueOf(room.getAltSize())); 1863 if (room.getCoordX() != null && room.getCoordY() != null) 1864 r.addAttribute("coordinates", room.getCoordX() + "," + room.getCoordY()); 1865 for (ExamPeriod period : getPeriods()) { 1866 if (!room.isAvailable(period)) 1867 r.addElement("period").addAttribute("id", 1868 getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("available", 1869 "false"); 1870 else if (room.getPenalty(period) != 0) 1871 r.addElement("period").addAttribute("id", 1872 getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("penalty", 1873 String.valueOf(room.getPenalty(period))); 1874 } 1875 Map<Long, Integer> travelTimes = getDistanceMetric().getTravelTimes().get(room.getId()); 1876 if (travelTimes != null) 1877 for (Map.Entry<Long, Integer> time: travelTimes.entrySet()) 1878 r.addElement("travel-time").addAttribute("id", getId(anonymize, "room", time.getKey().toString())).addAttribute("minutes", time.getValue().toString()); 1879 } 1880 Element exams = root.addElement("exams"); 1881 for (Exam exam : variables()) { 1882 Element ex = exams.addElement("exam"); 1883 ex.addAttribute("id", getId(anonymize, "exam", String.valueOf(exam.getId()))); 1884 if (!anonymize && exam.hasName()) 1885 ex.addAttribute("name", exam.getName()); 1886 ex.addAttribute("length", String.valueOf(exam.getLength())); 1887 if (exam.getSizeOverride() != null) 1888 ex.addAttribute("size", exam.getSizeOverride().toString()); 1889 if (exam.getMinSize() != 0) 1890 ex.addAttribute("minSize", String.valueOf(exam.getMinSize())); 1891 ex.addAttribute("alt", (exam.hasAltSeating() ? "true" : "false")); 1892 if (exam.getMaxRooms() != getMaxRooms()) 1893 ex.addAttribute("maxRooms", String.valueOf(exam.getMaxRooms())); 1894 if (exam.getPrintOffset() != null) 1895 ex.addAttribute("printOffset", exam.getPrintOffset().toString()); 1896 if (!anonymize) 1897 ex.addAttribute("enrl", String.valueOf(exam.getStudents().size())); 1898 if (!anonymize) 1899 for (ExamOwner owner : exam.getOwners()) { 1900 Element o = ex.addElement("owner"); 1901 o.addAttribute("id", getId(anonymize, "owner", String.valueOf(owner.getId()))); 1902 o.addAttribute("name", owner.getName()); 1903 } 1904 for (ExamPeriodPlacement period : exam.getPeriodPlacements()) { 1905 Element pe = ex.addElement("period").addAttribute("id", 1906 getId(anonymize, "period", String.valueOf(period.getId()))); 1907 int penalty = period.getPenalty() - period.getPeriod().getPenalty(); 1908 if (penalty != 0) 1909 pe.addAttribute("penalty", String.valueOf(penalty)); 1910 } 1911 for (ExamRoomPlacement room : exam.getRoomPlacements()) { 1912 Element re = ex.addElement("room").addAttribute("id", 1913 getId(anonymize, "room", String.valueOf(room.getId()))); 1914 if (room.getPenalty() != 0) 1915 re.addAttribute("penalty", String.valueOf(room.getPenalty())); 1916 if (room.getMaxPenalty() != 100) 1917 re.addAttribute("maxPenalty", String.valueOf(room.getMaxPenalty())); 1918 } 1919 if (exam.hasAveragePeriod()) 1920 ex.addAttribute("average", String.valueOf(exam.getAveragePeriod())); 1921 ExamPlacement p = exam.getAssignment(); 1922 if (p != null && saveSolution) { 1923 Element asg = ex.addElement("assignment"); 1924 asg.addElement("period").addAttribute("id", 1925 getId(anonymize, "period", String.valueOf(p.getPeriod().getId()))); 1926 for (ExamRoomPlacement r : p.getRoomPlacements()) { 1927 asg.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId()))); 1928 } 1929 } 1930 p = exam.getInitialAssignment(); 1931 if (p != null && saveInitial) { 1932 Element ini = ex.addElement("initial"); 1933 ini.addElement("period").addAttribute("id", 1934 getId(anonymize, "period", String.valueOf(p.getPeriod().getId()))); 1935 for (ExamRoomPlacement r : p.getRoomPlacements()) { 1936 ini.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId()))); 1937 } 1938 } 1939 p = exam.getBestAssignment(); 1940 if (p != null && saveBest) { 1941 Element ini = ex.addElement("best"); 1942 ini.addElement("period").addAttribute("id", 1943 getId(anonymize, "period", String.valueOf(p.getPeriod().getId()))); 1944 for (ExamRoomPlacement r : p.getRoomPlacements()) { 1945 ini.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId()))); 1946 } 1947 } 1948 } 1949 Element students = root.addElement("students"); 1950 for (ExamStudent student : getStudents()) { 1951 Element s = students.addElement("student"); 1952 s.addAttribute("id", getId(anonymize, "student", String.valueOf(student.getId()))); 1953 for (Exam ex : student.variables()) { 1954 Element x = s.addElement("exam").addAttribute("id", 1955 getId(anonymize, "exam", String.valueOf(ex.getId()))); 1956 if (!anonymize) 1957 for (ExamOwner owner : ex.getOwners(student)) { 1958 x.addElement("owner").addAttribute("id", 1959 getId(anonymize, "owner", String.valueOf(owner.getId()))); 1960 } 1961 } 1962 for (ExamPeriod period : getPeriods()) { 1963 if (!student.isAvailable(period)) 1964 s.addElement("period").addAttribute("id", 1965 getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("available", 1966 "false"); 1967 } 1968 } 1969 Element instructors = root.addElement("instructors"); 1970 for (ExamInstructor instructor : getInstructors()) { 1971 Element i = instructors.addElement("instructor"); 1972 i.addAttribute("id", getId(anonymize, "instructor", String.valueOf(instructor.getId()))); 1973 if (!anonymize && instructor.hasName()) 1974 i.addAttribute("name", instructor.getName()); 1975 for (Exam ex : instructor.variables()) { 1976 Element x = i.addElement("exam").addAttribute("id", 1977 getId(anonymize, "exam", String.valueOf(ex.getId()))); 1978 if (!anonymize) 1979 for (ExamOwner owner : ex.getOwners(instructor)) { 1980 x.addElement("owner").addAttribute("id", 1981 getId(anonymize, "owner", String.valueOf(owner.getId()))); 1982 } 1983 } 1984 for (ExamPeriod period : getPeriods()) { 1985 if (!instructor.isAvailable(period)) 1986 i.addElement("period").addAttribute("id", 1987 getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("available", 1988 "false"); 1989 } 1990 } 1991 Element distConstraints = root.addElement("constraints"); 1992 for (ExamDistributionConstraint distConstraint : getDistributionConstraints()) { 1993 Element dc = distConstraints.addElement(distConstraint.getTypeString()); 1994 dc.addAttribute("id", getId(anonymize, "constraint", String.valueOf(distConstraint.getId()))); 1995 if (!distConstraint.isHard()) { 1996 dc.addAttribute("hard", "false"); 1997 dc.addAttribute("weight", String.valueOf(distConstraint.getWeight())); 1998 } 1999 for (Exam exam : distConstraint.variables()) { 2000 dc.addElement("exam").addAttribute("id", getId(anonymize, "exam", String.valueOf(exam.getId()))); 2001 } 2002 } 2003 if (saveConflictTable) { 2004 Element conflicts = root.addElement("conflicts"); 2005 for (ExamStudent student : getStudents()) { 2006 for (ExamPeriod period : getPeriods()) { 2007 int nrExams = student.getExams(period).size(); 2008 if (nrExams > 1) { 2009 Element dir = conflicts.addElement("direct").addAttribute("student", 2010 getId(anonymize, "student", String.valueOf(student.getId()))); 2011 for (Exam exam : student.getExams(period)) { 2012 dir.addElement("exam").addAttribute("id", 2013 getId(anonymize, "exam", String.valueOf(exam.getId()))); 2014 } 2015 } 2016 if (nrExams > 0) { 2017 if (period.next() != null && !student.getExams(period.next()).isEmpty() 2018 && (!isDayBreakBackToBack() || period.next().getDay() == period.getDay())) { 2019 for (Exam ex1 : student.getExams(period)) { 2020 for (Exam ex2 : student.getExams(period.next())) { 2021 Element btb = conflicts.addElement("back-to-back").addAttribute("student", 2022 getId(anonymize, "student", String.valueOf(student.getId()))); 2023 btb.addElement("exam").addAttribute("id", 2024 getId(anonymize, "exam", String.valueOf(ex1.getId()))); 2025 btb.addElement("exam").addAttribute("id", 2026 getId(anonymize, "exam", String.valueOf(ex2.getId()))); 2027 if (getBackToBackDistance() >= 0) { 2028 double dist = (ex1.getAssignment()).getDistanceInMeters(ex2.getAssignment()); 2029 if (dist > 0) 2030 btb.addAttribute("distance", String.valueOf(dist)); 2031 } 2032 } 2033 } 2034 } 2035 } 2036 if (period.next() == null || period.next().getDay() != period.getDay()) { 2037 int nrExamsADay = student.getExamsADay(period.getDay()).size(); 2038 if (nrExamsADay > 2) { 2039 Element mt2 = conflicts.addElement("more-2-day").addAttribute("student", 2040 getId(anonymize, "student", String.valueOf(student.getId()))); 2041 for (Exam exam : student.getExamsADay(period.getDay())) { 2042 mt2.addElement("exam").addAttribute("id", 2043 getId(anonymize, "exam", String.valueOf(exam.getId()))); 2044 } 2045 } 2046 } 2047 } 2048 } 2049 2050 } 2051 return document; 2052 } 2053 2054 /** 2055 * Load model (including its solution) from XML. 2056 */ 2057 public boolean load(Document document) { 2058 return load(document, null); 2059 } 2060 2061 /** 2062 * Load model (including its solution) from XML. 2063 */ 2064 public boolean load(Document document, Callback saveBest) { 2065 boolean loadInitial = getProperties().getPropertyBoolean("Xml.LoadInitial", true); 2066 boolean loadBest = getProperties().getPropertyBoolean("Xml.LoadBest", true); 2067 boolean loadSolution = getProperties().getPropertyBoolean("Xml.LoadSolution", true); 2068 boolean loadParams = getProperties().getPropertyBoolean("Xml.LoadParameters", false); 2069 Element root = document.getRootElement(); 2070 if (!"examtt".equals(root.getName())) 2071 return false; 2072 if (root.attribute("campus") != null) 2073 getProperties().setProperty("Data.Initiative", root.attributeValue("campus")); 2074 else if (root.attribute("initiative") != null) 2075 getProperties().setProperty("Data.Initiative", root.attributeValue("initiative")); 2076 if (root.attribute("term") != null) 2077 getProperties().setProperty("Data.Term", root.attributeValue("term")); 2078 if (root.attribute("year") != null) 2079 getProperties().setProperty("Data.Year", root.attributeValue("year")); 2080 if (loadParams && root.element("parameters") != null) 2081 for (Iterator<?> i = root.element("parameters").elementIterator("property"); i.hasNext();) { 2082 Element e = (Element) i.next(); 2083 String name = e.attributeValue("name"); 2084 String value = e.attributeValue("value"); 2085 if ("isDayBreakBackToBack".equals(name)) 2086 setDayBreakBackToBack("true".equals(value)); 2087 else if ("directConflictWeight".equals(name)) 2088 setDirectConflictWeight(Double.parseDouble(value)); 2089 else if ("moreThanTwoADayWeight".equals(name)) 2090 setMoreThanTwoADayWeight(Double.parseDouble(value)); 2091 else if ("backToBackConflictWeight".equals(name)) 2092 setBackToBackConflictWeight(Double.parseDouble(value)); 2093 else if ("distanceBackToBackConflictWeight".equals(name)) 2094 setDistanceBackToBackConflictWeight(Double.parseDouble(value)); 2095 else if ("backToBackDistance".equals(name)) 2096 setBackToBackDistance(Double.parseDouble(value)); 2097 else if ("maxRooms".equals(name)) 2098 setMaxRooms(Integer.parseInt(value)); 2099 else if ("periodWeight".equals(name)) 2100 setPeriodWeight(Double.parseDouble(value)); 2101 else if ("periodSizeWeight".equals(name)) 2102 setPeriodSizeWeight(Double.parseDouble(value)); 2103 else if ("periodIndexWeight".equals(name)) 2104 setPeriodIndexWeight(Double.parseDouble(value)); 2105 else if ("examRotationWeight".equals(name)) 2106 setExamRotationWeight(Double.parseDouble(value)); 2107 else if ("roomSizeWeight".equals(name)) 2108 setRoomSizeWeight(Double.parseDouble(value)); 2109 else if ("roomSplitWeight".equals(name)) 2110 setRoomSplitWeight(Double.parseDouble(value)); 2111 else if ("roomWeight".equals(name)) 2112 setRoomWeight(Double.parseDouble(value)); 2113 else if ("distributionWeight".equals(name)) 2114 setDistributionWeight(Double.parseDouble(value)); 2115 else if ("instructorDirectConflictWeight".equals(name)) 2116 setInstructorDirectConflictWeight(Double.parseDouble(value)); 2117 else if ("instructorMoreThanTwoADayWeight".equals(name)) 2118 setInstructorMoreThanTwoADayWeight(Double.parseDouble(value)); 2119 else if ("instructorBackToBackConflictWeight".equals(name)) 2120 setInstructorBackToBackConflictWeight(Double.parseDouble(value)); 2121 else if ("instructorDistanceBackToBackConflictWeight".equals(name)) 2122 setInstructorDistanceBackToBackConflictWeight(Double.parseDouble(value)); 2123 else if ("perturbationWeight".equals(name)) 2124 setPerturbationWeight(Double.parseDouble(value)); 2125 else if ("roomPerturbationWeight".equals(name)) 2126 setRoomPerturbationWeight(Double.parseDouble(value)); 2127 else if ("roomSplitDistanceWeight".equals(name)) 2128 setRoomSplitDistanceWeight(Double.parseDouble(value)); 2129 else if ("largeSize".equals(name)) 2130 setLargeSize(Integer.parseInt(value)); 2131 else if ("largePeriod".equals(name)) 2132 setLargePeriod(Double.parseDouble(value)); 2133 else if ("largeWeight".equals(name)) 2134 setLargeWeight(Double.parseDouble(value)); 2135 else 2136 getProperties().setProperty(name, value); 2137 } 2138 for (Iterator<?> i = root.element("periods").elementIterator("period"); i.hasNext();) { 2139 Element e = (Element) i.next(); 2140 addPeriod(Long.valueOf(e.attributeValue("id")), e.attributeValue("day"), e.attributeValue("time"), Integer 2141 .parseInt(e.attributeValue("length")), Integer.parseInt(e.attributeValue("penalty") == null ? e 2142 .attributeValue("weight", "0") : e.attributeValue("penalty"))); 2143 } 2144 HashMap<Long, ExamRoom> rooms = new HashMap<Long, ExamRoom>(); 2145 HashMap<String, ArrayList<ExamRoom>> roomGroups = new HashMap<String, ArrayList<ExamRoom>>(); 2146 for (Iterator<?> i = root.element("rooms").elementIterator("room"); i.hasNext();) { 2147 Element e = (Element) i.next(); 2148 String coords = e.attributeValue("coordinates"); 2149 ExamRoom room = new ExamRoom(this, Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), 2150 Integer.parseInt(e.attributeValue("size")), Integer.parseInt(e.attributeValue("alt")), 2151 (coords == null ? null : Double.valueOf(coords.substring(0, coords.indexOf(',')))), 2152 (coords == null ? null : Double.valueOf(coords.substring(coords.indexOf(',') + 1)))); 2153 addConstraint(room); 2154 getRooms().add(room); 2155 rooms.put(new Long(room.getId()), room); 2156 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 2157 Element pe = (Element) j.next(); 2158 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 2159 if ("false".equals(pe.attributeValue("available"))) 2160 room.setAvailable(period, false); 2161 else 2162 room.setPenalty(period, Integer.parseInt(pe.attributeValue("penalty"))); 2163 } 2164 String av = e.attributeValue("available"); 2165 if (av != null) { 2166 for (int j = 0; j < getPeriods().size(); j++) 2167 if ('0' == av.charAt(j)) 2168 room.setAvailable(getPeriods().get(j), false); 2169 } 2170 String g = e.attributeValue("groups"); 2171 if (g != null) { 2172 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 2173 String gr = s.nextToken(); 2174 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 2175 if (roomsThisGrop == null) { 2176 roomsThisGrop = new ArrayList<ExamRoom>(); 2177 roomGroups.put(gr, roomsThisGrop); 2178 } 2179 roomsThisGrop.add(room); 2180 } 2181 } 2182 for (Iterator<?> j = e.elementIterator("travel-time"); j.hasNext();) { 2183 Element travelTimeEl = (Element)j.next(); 2184 getDistanceMetric().addTravelTime(room.getId(), 2185 Long.valueOf(travelTimeEl.attributeValue("id")), 2186 Integer.valueOf(travelTimeEl.attributeValue("minutes"))); 2187 } 2188 } 2189 ArrayList<ExamPlacement> assignments = new ArrayList<ExamPlacement>(); 2190 HashMap<Long, Exam> exams = new HashMap<Long, Exam>(); 2191 HashMap<Long, ExamOwner> courseSections = new HashMap<Long, ExamOwner>(); 2192 for (Iterator<?> i = root.element("exams").elementIterator("exam"); i.hasNext();) { 2193 Element e = (Element) i.next(); 2194 ArrayList<ExamPeriodPlacement> periodPlacements = new ArrayList<ExamPeriodPlacement>(); 2195 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 2196 Element pe = (Element) j.next(); 2197 periodPlacements.add(new ExamPeriodPlacement(getPeriod(Long.valueOf(pe.attributeValue("id"))), Integer 2198 .parseInt(pe.attributeValue("penalty", "0")))); 2199 } 2200 ArrayList<ExamRoomPlacement> roomPlacements = new ArrayList<ExamRoomPlacement>(); 2201 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 2202 Element re = (Element) j.next(); 2203 ExamRoomPlacement room = new ExamRoomPlacement(rooms.get(Long.valueOf(re.attributeValue("id"))), 2204 Integer.parseInt(re.attributeValue("penalty", "0")), Integer.parseInt(re.attributeValue( 2205 "maxPenalty", "100"))); 2206 roomPlacements.add(room); 2207 } 2208 String g = e.attributeValue("groups"); 2209 if (g != null) { 2210 HashMap<ExamRoom, Integer> allRooms = new HashMap<ExamRoom, Integer>(); 2211 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 2212 String gr = s.nextToken(); 2213 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 2214 if (roomsThisGrop != null) 2215 for (ExamRoom r : roomsThisGrop) 2216 allRooms.put(r, 0); 2217 } 2218 for (Iterator<?> j = e.elementIterator("original-room"); j.hasNext();) { 2219 allRooms.put((rooms.get(Long.valueOf(((Element) j.next()).attributeValue("id")))), new Integer(-1)); 2220 } 2221 for (Map.Entry<ExamRoom, Integer> entry : allRooms.entrySet()) { 2222 ExamRoomPlacement room = new ExamRoomPlacement(entry.getKey(), entry.getValue(), 100); 2223 roomPlacements.add(room); 2224 } 2225 if (periodPlacements.isEmpty()) { 2226 for (ExamPeriod p : getPeriods()) { 2227 periodPlacements.add(new ExamPeriodPlacement(p, 0)); 2228 } 2229 } 2230 } 2231 Exam exam = new Exam(Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), Integer.parseInt(e 2232 .attributeValue("length")), "true".equals(e.attributeValue("alt")), 2233 (e.attribute("maxRooms") == null ? getMaxRooms() : Integer.parseInt(e.attributeValue("maxRooms"))), 2234 Integer.parseInt(e.attributeValue("minSize", "0")), periodPlacements, roomPlacements); 2235 if (e.attributeValue("size") != null) 2236 exam.setSizeOverride(Integer.valueOf(e.attributeValue("size"))); 2237 if (e.attributeValue("printOffset") != null) 2238 exam.setPrintOffset(Integer.valueOf(e.attributeValue("printOffset"))); 2239 exams.put(new Long(exam.getId()), exam); 2240 addVariable(exam); 2241 if (e.attribute("average") != null) 2242 exam.setAveragePeriod(Integer.parseInt(e.attributeValue("average"))); 2243 Element asg = e.element("assignment"); 2244 if (asg != null && loadSolution) { 2245 Element per = asg.element("period"); 2246 if (per != null) { 2247 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 2248 for (Iterator<?> j = asg.elementIterator("room"); j.hasNext();) 2249 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 2250 ExamPlacement p = new ExamPlacement(exam, exam.getPeriodPlacement(Long.valueOf(per 2251 .attributeValue("id"))), rp); 2252 assignments.add(p); 2253 } 2254 } 2255 Element ini = e.element("initial"); 2256 if (ini != null && loadInitial) { 2257 Element per = ini.element("period"); 2258 if (per != null) { 2259 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 2260 for (Iterator<?> j = ini.elementIterator("room"); j.hasNext();) 2261 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 2262 ExamPlacement p = new ExamPlacement(exam, exam.getPeriodPlacement(Long.valueOf(per 2263 .attributeValue("id"))), rp); 2264 exam.setInitialAssignment(p); 2265 } 2266 } 2267 Element best = e.element("best"); 2268 if (best != null && loadBest) { 2269 Element per = best.element("period"); 2270 if (per != null) { 2271 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 2272 for (Iterator<?> j = best.elementIterator("room"); j.hasNext();) 2273 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 2274 ExamPlacement p = new ExamPlacement(exam, exam.getPeriodPlacement(Long.valueOf(per 2275 .attributeValue("id"))), rp); 2276 exam.setBestAssignment(p); 2277 } 2278 } 2279 for (Iterator<?> j = e.elementIterator("owner"); j.hasNext();) { 2280 Element f = (Element) j.next(); 2281 ExamOwner owner = new ExamOwner(exam, Long.parseLong(f.attributeValue("id")), f.attributeValue("name")); 2282 exam.getOwners().add(owner); 2283 courseSections.put(new Long(owner.getId()), owner); 2284 } 2285 } 2286 for (Iterator<?> i = root.element("students").elementIterator("student"); i.hasNext();) { 2287 Element e = (Element) i.next(); 2288 ExamStudent student = new ExamStudent(this, Long.parseLong(e.attributeValue("id"))); 2289 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 2290 Element x = (Element) j.next(); 2291 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 2292 student.addVariable(ex); 2293 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 2294 Element f = (Element) k.next(); 2295 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 2296 student.getOwners().add(owner); 2297 owner.getStudents().add(student); 2298 } 2299 } 2300 String available = e.attributeValue("available"); 2301 if (available != null) 2302 for (ExamPeriod period : getPeriods()) { 2303 if (available.charAt(period.getIndex()) == '0') 2304 student.setAvailable(period.getIndex(), false); 2305 } 2306 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 2307 Element pe = (Element) j.next(); 2308 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 2309 if ("false".equals(pe.attributeValue("available"))) 2310 student.setAvailable(period.getIndex(), false); 2311 } 2312 addConstraint(student); 2313 getStudents().add(student); 2314 } 2315 if (root.element("instructors") != null) 2316 for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) { 2317 Element e = (Element) i.next(); 2318 ExamInstructor instructor = new ExamInstructor(this, Long.parseLong(e.attributeValue("id")), e 2319 .attributeValue("name")); 2320 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 2321 Element x = (Element) j.next(); 2322 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 2323 instructor.addVariable(ex); 2324 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 2325 Element f = (Element) k.next(); 2326 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 2327 instructor.getOwners().add(owner); 2328 owner.getIntructors().add(instructor); 2329 } 2330 } 2331 String available = e.attributeValue("available"); 2332 if (available != null) 2333 for (ExamPeriod period : getPeriods()) { 2334 if (available.charAt(period.getIndex()) == '0') 2335 instructor.setAvailable(period.getIndex(), false); 2336 } 2337 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 2338 Element pe = (Element) j.next(); 2339 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 2340 if ("false".equals(pe.attributeValue("available"))) 2341 instructor.setAvailable(period.getIndex(), false); 2342 } 2343 addConstraint(instructor); 2344 getInstructors().add(instructor); 2345 } 2346 if (root.element("constraints") != null) 2347 for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) { 2348 Element e = (Element) i.next(); 2349 ExamDistributionConstraint dc = new ExamDistributionConstraint(Long.parseLong(e.attributeValue("id")), 2350 e.getName(), "true".equals(e.attributeValue("hard", "true")), Integer.parseInt(e 2351 .attributeValue("weight", "0"))); 2352 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 2353 dc.addVariable(exams.get(Long.valueOf(((Element) j.next()).attributeValue("id")))); 2354 } 2355 addConstraint(dc); 2356 getDistributionConstraints().add(dc); 2357 } 2358 init(); 2359 if (loadBest && saveBest != null) { 2360 for (Exam exam : variables()) { 2361 ExamPlacement placement = exam.getBestAssignment(); 2362 if (placement == null) 2363 continue; 2364 exam.assign(0, placement); 2365 } 2366 saveBest.execute(); 2367 for (Exam exam : variables()) { 2368 if (exam.getAssignment() != null) 2369 exam.unassign(0); 2370 } 2371 } 2372 for (ExamPlacement placement : assignments) { 2373 Exam exam = placement.variable(); 2374 Set<ExamPlacement> conf = conflictValues(placement); 2375 if (!conf.isEmpty()) { 2376 for (Map.Entry<Constraint<Exam, ExamPlacement>, Set<ExamPlacement>> entry : conflictConstraints( 2377 placement).entrySet()) { 2378 Constraint<Exam, ExamPlacement> constraint = entry.getKey(); 2379 Set<ExamPlacement> values = entry.getValue(); 2380 if (constraint instanceof ExamStudent) { 2381 ((ExamStudent) constraint).setAllowDirectConflicts(true); 2382 exam.setAllowDirectConflicts(true); 2383 for (ExamPlacement p : values) 2384 p.variable().setAllowDirectConflicts(true); 2385 } 2386 } 2387 conf = conflictValues(placement); 2388 } 2389 if (conf.isEmpty()) { 2390 exam.assign(0, placement); 2391 } else { 2392 sLog.error("Unable to assign " + exam.getInitialAssignment().getName() + " to exam " + exam.getName()); 2393 sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(exam.getInitialAssignment()), 2)); 2394 } 2395 } 2396 return true; 2397 } 2398 2399 public boolean isMPP() { 2400 return iMPP; 2401 } 2402 }