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