001package org.cpsolver.exam.model; 002 003import java.util.ArrayList; 004import java.util.Date; 005import java.util.HashSet; 006import java.util.HashMap; 007import java.util.Iterator; 008import java.util.List; 009import java.util.Map; 010import java.util.Set; 011import java.util.StringTokenizer; 012import java.util.TreeSet; 013 014 015import org.apache.logging.log4j.Logger; 016import org.cpsolver.coursett.IdConvertor; 017import org.cpsolver.exam.criteria.DistributionPenalty; 018import org.cpsolver.exam.criteria.ExamCriterion; 019import org.cpsolver.exam.criteria.ExamRotationPenalty; 020import org.cpsolver.exam.criteria.InstructorBackToBackConflicts; 021import org.cpsolver.exam.criteria.InstructorDirectConflicts; 022import org.cpsolver.exam.criteria.InstructorDistanceBackToBackConflicts; 023import org.cpsolver.exam.criteria.InstructorMoreThan2ADayConflicts; 024import org.cpsolver.exam.criteria.InstructorNotAvailableConflicts; 025import org.cpsolver.exam.criteria.LargeExamsPenalty; 026import org.cpsolver.exam.criteria.PeriodIndexPenalty; 027import org.cpsolver.exam.criteria.PeriodPenalty; 028import org.cpsolver.exam.criteria.PeriodSizePenalty; 029import org.cpsolver.exam.criteria.PerturbationPenalty; 030import org.cpsolver.exam.criteria.RoomPenalty; 031import org.cpsolver.exam.criteria.RoomPerturbationPenalty; 032import org.cpsolver.exam.criteria.RoomSizePenalty; 033import org.cpsolver.exam.criteria.RoomSplitDistancePenalty; 034import org.cpsolver.exam.criteria.RoomSplitPenalty; 035import org.cpsolver.exam.criteria.StudentBackToBackConflicts; 036import org.cpsolver.exam.criteria.StudentDirectConflicts; 037import org.cpsolver.exam.criteria.StudentDistanceBackToBackConflicts; 038import org.cpsolver.exam.criteria.StudentMoreThan2ADayConflicts; 039import org.cpsolver.exam.criteria.StudentNotAvailableConflicts; 040import org.cpsolver.ifs.assignment.Assignment; 041import org.cpsolver.ifs.assignment.context.ModelWithContext; 042import org.cpsolver.ifs.criteria.Criterion; 043import org.cpsolver.ifs.model.Constraint; 044import org.cpsolver.ifs.model.Model; 045import org.cpsolver.ifs.util.Callback; 046import org.cpsolver.ifs.util.DataProperties; 047import org.cpsolver.ifs.util.DistanceMetric; 048import org.cpsolver.ifs.util.ToolBox; 049import org.dom4j.Document; 050import org.dom4j.DocumentHelper; 051import org.dom4j.Element; 052 053/** 054 * Examination timetabling model. Exams {@link Exam} are modeled as variables, 055 * rooms {@link ExamRoom} and students {@link ExamStudent} as constraints. 056 * Assignment of an exam to time (modeled as non-overlapping periods 057 * {@link ExamPeriod}) and space (set of rooms) is modeled using values 058 * {@link ExamPlacement}. In order to be able to model individual period and 059 * room preferences, period and room assignments are wrapped with 060 * {@link ExamPeriodPlacement} and {@link ExamRoomPlacement} classes 061 * respectively. Moreover, additional distribution constraint 062 * {@link ExamDistributionConstraint} can be defined in the model. <br> 063 * <br> 064 * The objective function consists of the following criteria: 065 * <ul> 066 * <li>Direct student conflicts (a student is enrolled in two exams that are 067 * scheduled at the same period, weighted by Exams.DirectConflictWeight) 068 * <li>Back-to-Back student conflicts (a student is enrolled in two exams that 069 * are scheduled in consecutive periods, weighted by 070 * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false, 071 * there is no conflict between the last period and the first period of 072 * consecutive days. 073 * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student 074 * conflict, but the maximum distance between rooms in which both exam take 075 * place is greater than Exams.BackToBackDistance, weighted by 076 * Exams.DistanceBackToBackConflictWeight). 077 * <li>More than two exams a day (a student is enrolled in three exams that are 078 * scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight). 079 * <li>Period penalty (total of period penalties 080 * {@link PeriodPenalty} of all assigned exams, weighted by 081 * Exams.PeriodWeight). 082 * <li>Room size penalty (total of room size penalties 083 * {@link RoomSizePenalty} of all assigned exams, weighted by 084 * Exams.RoomSizeWeight). 085 * <li>Room split penalty (total of room split penalties 086 * {@link RoomSplitPenalty} of all assigned exams, weighted 087 * by Exams.RoomSplitWeight). 088 * <li>Room penalty (total of room penalties 089 * {@link RoomPenalty} of all assigned exams, weighted by 090 * Exams.RoomWeight). 091 * <li>Distribution penalty (total of distribution constraint weights 092 * {@link ExamDistributionConstraint#getWeight()} of all soft distribution 093 * constraints that are not satisfied, i.e., 094 * {@link ExamDistributionConstraint#isSatisfied(Assignment)} = false; weighted by 095 * Exams.DistributionWeight). 096 * <li>Direct instructor conflicts (an instructor is enrolled in two exams that 097 * are scheduled at the same period, weighted by 098 * Exams.InstructorDirectConflictWeight) 099 * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two exams 100 * that are scheduled in consecutive periods, weighted by 101 * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack is 102 * false, there is no conflict between the last period and the first period of 103 * consecutive days. 104 * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back 105 * instructor conflict, but the maximum distance between rooms in which both 106 * exam take place is greater than Exams.BackToBackDistance, weighted by 107 * Exams.InstructorDistanceBackToBackConflictWeight). 108 * <li>Room split distance penalty (if an examination is assigned between two or 109 * three rooms, distance between these rooms can be minimized using this 110 * criterion) 111 * <li>Front load penalty (large exams can be penalized if assigned on or after 112 * a certain period) 113 * </ul> 114 * 115 * @version ExamTT 1.3 (Examination Timetabling)<br> 116 * Copyright (C) 2008 - 2014 Tomáš Müller<br> 117 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 118 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 119 * <br> 120 * This library is free software; you can redistribute it and/or modify 121 * it under the terms of the GNU Lesser General Public License as 122 * published by the Free Software Foundation; either version 3 of the 123 * License, or (at your option) any later version. <br> 124 * <br> 125 * This library is distributed in the hope that it will be useful, but 126 * WITHOUT ANY WARRANTY; without even the implied warranty of 127 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 128 * Lesser General Public License for more details. <br> 129 * <br> 130 * You should have received a copy of the GNU Lesser General Public 131 * License along with this library; if not see 132 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 133 */ 134public class ExamModel extends ModelWithContext<Exam, ExamPlacement, ExamContext> { 135 private static Logger sLog = org.apache.logging.log4j.LogManager.getLogger(ExamModel.class); 136 private DataProperties iProperties = null; 137 private int iMaxRooms = 4; 138 private List<ExamPeriod> iPeriods = new ArrayList<ExamPeriod>(); 139 private List<ExamRoom> iRooms = new ArrayList<ExamRoom>(); 140 private List<ExamStudent> iStudents = new ArrayList<ExamStudent>(); 141 private List<ExamDistributionConstraint> iDistributionConstraints = new ArrayList<ExamDistributionConstraint>(); 142 private List<ExamInstructor> iInstructors = new ArrayList<ExamInstructor>(); 143 private ExamRoomSharing iRoomSharing = null; 144 private boolean iCheckForPeriodOverlaps = false; 145 146 private DistanceMetric iDistanceMetric = null; 147 148 /** 149 * Constructor 150 * 151 * @param properties 152 * problem properties 153 */ 154 public ExamModel(DataProperties properties) { 155 super(); 156 iProperties = properties; 157 iMaxRooms = properties.getPropertyInt("Exams.MaxRooms", iMaxRooms); 158 iCheckForPeriodOverlaps = properties.getPropertyBoolean("Exams.CheckForPeriodOverlaps", iCheckForPeriodOverlaps); 159 iDistanceMetric = new DistanceMetric(properties); 160 String roomSharingClass = properties.getProperty("Exams.RoomSharingClass"); 161 if (roomSharingClass != null) { 162 try { 163 iRoomSharing = (ExamRoomSharing)Class.forName(roomSharingClass).getConstructor(Model.class, DataProperties.class).newInstance(this, properties); 164 } catch (Exception e) { 165 sLog.error("Failed to instantiate room sharing class " + roomSharingClass + ", reason: " + e.getMessage()); 166 } 167 } 168 169 String criteria = properties.getProperty("Exams.Criteria", 170 StudentDirectConflicts.class.getName() + ";" + 171 StudentNotAvailableConflicts.class.getName() + ";" + 172 StudentBackToBackConflicts.class.getName() + ";" + 173 StudentDistanceBackToBackConflicts.class.getName() + ";" + 174 StudentMoreThan2ADayConflicts.class.getName() + ";" + 175 InstructorDirectConflicts.class.getName() + ";" + 176 InstructorNotAvailableConflicts.class.getName() + ";" + 177 InstructorBackToBackConflicts.class.getName() + ";" + 178 InstructorDistanceBackToBackConflicts.class.getName() + ";" + 179 InstructorMoreThan2ADayConflicts.class.getName() + ";" + 180 PeriodPenalty.class.getName() + ";" + 181 RoomPenalty.class.getName() + ";" + 182 DistributionPenalty.class.getName() + ";" + 183 RoomSplitPenalty.class.getName() + ";" + 184 RoomSplitDistancePenalty.class.getName() + ";" + 185 RoomSizePenalty.class.getName() + ";" + 186 ExamRotationPenalty.class.getName() + ";" + 187 LargeExamsPenalty.class.getName() + ";" + 188 PeriodSizePenalty.class.getName() + ";" + 189 PeriodIndexPenalty.class.getName() + ";" + 190 PerturbationPenalty.class.getName() + ";" + 191 RoomPerturbationPenalty.class.getName() + ";" 192 ); 193 // Additional (custom) criteria 194 criteria += ";" + properties.getProperty("Exams.AdditionalCriteria", ""); 195 for (String criterion: criteria.split("\\;")) { 196 if (criterion == null || criterion.isEmpty()) continue; 197 try { 198 @SuppressWarnings("unchecked") 199 Class<Criterion<Exam, ExamPlacement>> clazz = (Class<Criterion<Exam, ExamPlacement>>)Class.forName(criterion); 200 addCriterion(clazz.newInstance()); 201 } catch (Exception e) { 202 sLog.error("Unable to use " + criterion + ": " + e.getMessage()); 203 } 204 } 205 } 206 207 public DistanceMetric getDistanceMetric() { 208 return iDistanceMetric; 209 } 210 211 /** 212 * True if there is an examination sharing model 213 * @return true if there is an examination sharing model 214 */ 215 public boolean hasRoomSharing() { return iRoomSharing != null; } 216 217 /** 218 * Return examination room sharing model 219 * @return examination room sharing model, if set 220 */ 221 public ExamRoomSharing getRoomSharing() { return iRoomSharing; } 222 223 /** 224 * Set examination sharing model 225 * @param sharing examination sharing model 226 */ 227 public void setRoomSharing(ExamRoomSharing sharing) { 228 iRoomSharing = sharing; 229 } 230 231 /** 232 * Initialization of the model 233 */ 234 public void init() { 235 for (Exam exam : variables()) { 236 for (ExamRoomPlacement room : exam.getRoomPlacements()) { 237 room.getRoom().addVariable(exam); 238 } 239 } 240 } 241 242 /** 243 * Default maximum number of rooms (can be set by problem property 244 * Exams.MaxRooms, or in the input xml file, property maxRooms) 245 * @return default maximum number of rooms for an exam 246 */ 247 public int getMaxRooms() { 248 return iMaxRooms; 249 } 250 251 /** 252 * Default maximum number of rooms (can be set by problem property 253 * Exams.MaxRooms, or in the input xml file, property maxRooms) 254 * @param maxRooms default maximum number of rooms for an exam 255 */ 256 public void setMaxRooms(int maxRooms) { 257 iMaxRooms = maxRooms; 258 } 259 260 /** 261 * Check for examination periods that overlap with each other 262 * @return true if examination periods can overlap with each other 263 */ 264 public boolean isCheckForPeriodOverlaps() { return iCheckForPeriodOverlaps; } 265 266 /** 267 * Enable checking for period overlaps 268 */ 269 public void setCheckForPeriodOverlaps(boolean check) { 270 iCheckForPeriodOverlaps = check; 271 } 272 273 /** 274 * Add a period 275 * 276 * @param id 277 * period unique identifier 278 * @param day 279 * day (e.g., 07/12/10) 280 * @param time 281 * (e.g., 8:00am-10:00am) 282 * @param length 283 * length of period in minutes 284 * @param penalty 285 * period penalty 286 * @return added period 287 */ 288 public ExamPeriod addPeriod(Long id, String day, String time, int length, int penalty) { 289 ExamPeriod lastPeriod = (iPeriods.isEmpty() ? null : (ExamPeriod) iPeriods.get(iPeriods.size() - 1)); 290 ExamPeriod p = new ExamPeriod(id, day, time, length, penalty); 291 if (lastPeriod == null) 292 p.setIndex(iPeriods.size(), 0, 0); 293 else if (lastPeriod.getDayStr().equals(day)) { 294 p.setIndex(iPeriods.size(), lastPeriod.getDay(), lastPeriod.getTime() + 1); 295 } else 296 p.setIndex(iPeriods.size(), lastPeriod.getDay() + 1, 0); 297 if (lastPeriod != null) { 298 lastPeriod.setNext(p); 299 p.setPrev(lastPeriod); 300 } 301 iPeriods.add(p); 302 return p; 303 } 304 305 /** 306 * Number of days 307 * @return number of days 308 */ 309 public int getNrDays() { 310 return (iPeriods.get(iPeriods.size() - 1)).getDay() + 1; 311 } 312 313 /** 314 * Number of periods 315 * @return number of periods 316 */ 317 public int getNrPeriods() { 318 return iPeriods.size(); 319 } 320 321 /** 322 * List of periods, use 323 * {@link ExamModel#addPeriod(Long, String, String, int, int)} to add a 324 * period 325 * 326 * @return list of {@link ExamPeriod} 327 */ 328 public List<ExamPeriod> getPeriods() { 329 return iPeriods; 330 } 331 332 /** Period of given unique id 333 * @param id period unique id 334 * @return the appropriate period 335 **/ 336 public ExamPeriod getPeriod(Long id) { 337 for (ExamPeriod period : iPeriods) { 338 if (period.getId().equals(id)) 339 return period; 340 } 341 return null; 342 } 343 344 /** 345 * True when back-to-back student conflict is to be encountered when a 346 * student is enrolled into an exam that is on the last period of one day 347 * and another exam that is on the first period of the consecutive day. It 348 * can be set by problem property Exams.IsDayBreakBackToBack, or in the 349 * input xml file, property isDayBreakBackToBack) 350 * @return true if last exam on one day is back-to-back to the first exam of the following day 351 * 352 */ 353 public boolean isDayBreakBackToBack() { 354 return ((StudentBackToBackConflicts)getCriterion(StudentBackToBackConflicts.class)).isDayBreakBackToBack(); 355 } 356 357 /** 358 * Back-to-back distance, can be set by 359 * problem property Exams.BackToBackDistance, or in the input xml file, 360 * property backToBackDistance) 361 * @return back-to-back distance in meters 362 */ 363 public double getBackToBackDistance() { 364 return ((StudentDistanceBackToBackConflicts)getCriterion(StudentDistanceBackToBackConflicts.class)).getBackToBackDistance(); 365 } 366 367 /** 368 * Objective function. 369 * @return weighted sum of objective criteria 370 */ 371 @Override 372 public double getTotalValue(Assignment<Exam, ExamPlacement> assignment) { 373 double total = 0; 374 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) 375 total += criterion.getWeightedValue(assignment); 376 return total; 377 } 378 379 /** 380 * Return weighted individual objective criteria. 381 * @param assignment current assignment 382 * @return an array of weighted objective criteria 383 */ 384 public double[] getTotalMultiValue(Assignment<Exam, ExamPlacement> assignment) { 385 double[] total = new double[getCriteria().size()]; 386 int i = 0; 387 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) 388 total[i++] = criterion.getWeightedValue(assignment); 389 return total; 390 } 391 392 /** 393 * String representation -- returns a list of values of objective criteria 394 * @param assignment current assignment 395 * @return comma separated list of {@link ExamCriterion#toString(Assignment)} 396 */ 397 @Override 398 public String toString(Assignment<Exam, ExamPlacement> assignment) { 399 Set<String> props = new TreeSet<String>(); 400 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 401 String val = ((ExamCriterion)criterion).toString(assignment); 402 if (!val.isEmpty()) 403 props.add(val); 404 } 405 return props.toString(); 406 } 407 408 /** 409 * Extended info table 410 */ 411 @Override 412 public Map<String, String> getExtendedInfo(Assignment<Exam, ExamPlacement> assignment) { 413 Map<String, String> info = super.getExtendedInfo(assignment); 414 /* 415 info.put("Direct Conflicts [p]", String.valueOf(getNrDirectConflicts(true))); 416 info.put("More Than 2 A Day Conflicts [p]", String.valueOf(getNrMoreThanTwoADayConflicts(true))); 417 info.put("Back-To-Back Conflicts [p]", String.valueOf(getNrBackToBackConflicts(true))); 418 info.put("Distance Back-To-Back Conflicts [p]", String.valueOf(getNrDistanceBackToBackConflicts(true))); 419 info.put("Instructor Direct Conflicts [p]", String.valueOf(getNrInstructorDirectConflicts(true))); 420 info.put("Instructor More Than 2 A Day Conflicts [p]", String.valueOf(getNrInstructorMoreThanTwoADayConflicts(true))); 421 info.put("Instructor Back-To-Back Conflicts [p]", String.valueOf(getNrInstructorBackToBackConflicts(true))); 422 info.put("Instructor Distance Back-To-Back Conflicts [p]", String.valueOf(getNrInstructorDistanceBackToBackConflicts(true))); 423 info.put("Room Size Penalty [p]", String.valueOf(getRoomSizePenalty(true))); 424 info.put("Room Split Penalty [p]", String.valueOf(getRoomSplitPenalty(true))); 425 info.put("Period Penalty [p]", String.valueOf(getPeriodPenalty(true))); 426 info.put("Period Size Penalty [p]", String.valueOf(getPeriodSizePenalty(true))); 427 info.put("Period Index Penalty [p]", String.valueOf(getPeriodIndexPenalty(true))); 428 info.put("Room Penalty [p]", String.valueOf(getRoomPenalty(true))); 429 info.put("Distribution Penalty [p]", String.valueOf(getDistributionPenalty(true))); 430 info.put("Perturbation Penalty [p]", String.valueOf(getPerturbationPenalty(true))); 431 info.put("Room Perturbation Penalty [p]", String.valueOf(getRoomPerturbationPenalty(true))); 432 info.put("Room Split Distance Penalty [p]", sDoubleFormat.format(getRoomSplitDistancePenalty(true)) + " / " + getNrRoomSplits(true)); 433 */ 434 info.put("Number of Periods", String.valueOf(getPeriods().size())); 435 info.put("Number of Exams", String.valueOf(variables().size())); 436 info.put("Number of Rooms", String.valueOf(getRooms().size())); 437 info.put("Number of Students", String.valueOf(getStudents().size())); 438 int nrStudentExams = 0; 439 for (ExamStudent student : getStudents()) { 440 nrStudentExams += student.getOwners().size(); 441 } 442 info.put("Number of Student Exams", String.valueOf(nrStudentExams)); 443 int nrAltExams = 0, nrSmallExams = 0; 444 for (Exam exam : variables()) { 445 if (exam.hasAltSeating()) 446 nrAltExams++; 447 if (exam.getMaxRooms() == 0) 448 nrSmallExams++; 449 } 450 info.put("Number of Exams Requiring Alt Seating", String.valueOf(nrAltExams)); 451 info.put("Number of Small Exams (Exams W/O Room)", String.valueOf(nrSmallExams)); 452 int[] nbrMtgs = new int[11]; 453 for (int i = 0; i <= 10; i++) 454 nbrMtgs[i] = 0; 455 for (ExamStudent student : getStudents()) { 456 nbrMtgs[Math.min(10, student.variables().size())]++; 457 } 458 for (int i = 0; i <= 10; i++) { 459 if (nbrMtgs[i] == 0) 460 continue; 461 info.put("Number of Students with " + (i == 0 ? "no" : String.valueOf(i)) + (i == 10 ? " or more" : "") 462 + " meeting" + (i != 1 ? "s" : ""), String.valueOf(nbrMtgs[i])); 463 } 464 Map<Integer, Integer> penalty2count = new HashMap<Integer, Integer>(); 465 for (Exam exam: variables()) { 466 ExamPlacement placement = assignment.getValue(exam); 467 if (placement == null) continue; 468 Integer preference = placement.getPeriodPlacement().getExamPenalty(); 469 Integer count = penalty2count.get(preference); 470 penalty2count.put(preference, 1 + (count == null ? 0 : count.intValue())); 471 } 472 if (!penalty2count.isEmpty()) { 473 String value = null; 474 for (Integer penalty: new TreeSet<Integer>(penalty2count.keySet())) { 475 if (penalty == 0) continue; 476 value = (value == null ? "" : value + ", ") + penalty2count.get(penalty) + "× " + penalty; 477 } 478 if (value != null) 479 info.put("Period Preferences", value); 480 } 481 return info; 482 } 483 484 /** 485 * Problem properties 486 * @return solver configuration 487 */ 488 public DataProperties getProperties() { 489 return iProperties; 490 } 491 492 /** 493 * Problem rooms 494 * 495 * @return list of {@link ExamRoom} 496 */ 497 public List<ExamRoom> getRooms() { 498 return iRooms; 499 } 500 501 /** 502 * Problem students 503 * 504 * @return list of {@link ExamStudent} 505 */ 506 public List<ExamStudent> getStudents() { 507 return iStudents; 508 } 509 510 /** 511 * Problem instructors 512 * 513 * @return list of {@link ExamInstructor} 514 */ 515 public List<ExamInstructor> getInstructors() { 516 return iInstructors; 517 } 518 519 /** 520 * Distribution constraints 521 * 522 * @return list of {@link ExamDistributionConstraint} 523 */ 524 public List<ExamDistributionConstraint> getDistributionConstraints() { 525 return iDistributionConstraints; 526 } 527 528 private String getId(boolean anonymize, String type, String id) { 529 return (anonymize ? IdConvertor.getInstance().convert(type, id) : id); 530 } 531 532 /** 533 * Save model (including its solution) into XML. 534 * @param assignment current assignment 535 * @return created XML document 536 */ 537 public Document save(Assignment<Exam, ExamPlacement> assignment) { 538 boolean saveInitial = getProperties().getPropertyBoolean("Xml.SaveInitial", true); 539 boolean saveBest = getProperties().getPropertyBoolean("Xml.SaveBest", true); 540 boolean saveSolution = getProperties().getPropertyBoolean("Xml.SaveSolution", true); 541 boolean saveConflictTable = getProperties().getPropertyBoolean("Xml.SaveConflictTable", false); 542 boolean saveParams = getProperties().getPropertyBoolean("Xml.SaveParameters", true); 543 boolean anonymize = getProperties().getPropertyBoolean("Xml.Anonymize", false); 544 boolean idconv = getProperties().getPropertyBoolean("Xml.ConvertIds", anonymize); 545 Document document = DocumentHelper.createDocument(); 546 document.addComment("Examination Timetable"); 547 if (assignment != null && assignment.nrAssignedVariables() > 0) { 548 StringBuffer comments = new StringBuffer("Solution Info:\n"); 549 Map<String, String> solutionInfo = (getProperties().getPropertyBoolean("Xml.ExtendedInfo", false) ? getExtendedInfo(assignment) : getInfo(assignment)); 550 for (String key : new TreeSet<String>(solutionInfo.keySet())) { 551 String value = solutionInfo.get(key); 552 comments.append(" " + key + ": " + value + "\n"); 553 } 554 document.addComment(comments.toString()); 555 } 556 Element root = document.addElement("examtt"); 557 root.addAttribute("version", "1.0"); 558 root.addAttribute("campus", getProperties().getProperty("Data.Initiative")); 559 root.addAttribute("term", getProperties().getProperty("Data.Term")); 560 root.addAttribute("year", getProperties().getProperty("Data.Year")); 561 root.addAttribute("created", String.valueOf(new Date())); 562 if (saveParams) { 563 Map<String, String> params = new HashMap<String, String>(); 564 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 565 if (criterion instanceof ExamCriterion) 566 ((ExamCriterion)criterion).getXmlParameters(params); 567 } 568 params.put("maxRooms", String.valueOf(getMaxRooms())); 569 params.put("checkForPeriodOverlaps", isCheckForPeriodOverlaps() ? "true" : "false"); 570 Element parameters = root.addElement("parameters"); 571 for (String key: new TreeSet<String>(params.keySet())) { 572 parameters.addElement("property").addAttribute("name", key).addAttribute("value", params.get(key)); 573 } 574 } 575 Element periods = root.addElement("periods"); 576 for (ExamPeriod period : getPeriods()) { 577 Element periodEl = periods.addElement("period").addAttribute("id", getId(idconv, "period", String.valueOf(period.getId()))) 578 .addAttribute("length", String.valueOf(period.getLength())).addAttribute("day", period.getDayStr()) 579 .addAttribute("time", period.getTimeStr()).addAttribute("penalty", 580 String.valueOf(period.getPenalty())); 581 if (period.getStartTime() != null) 582 periodEl.addAttribute("start", period.getStartTime().toString()); 583 } 584 Element rooms = root.addElement("rooms"); 585 for (ExamRoom room : getRooms()) { 586 Element r = rooms.addElement("room"); 587 r.addAttribute("id", getId(idconv, "room", String.valueOf(room.getId()))); 588 if (!anonymize && room.hasName()) 589 r.addAttribute("name", room.getName()); 590 r.addAttribute("size", String.valueOf(room.getSize())); 591 r.addAttribute("alt", String.valueOf(room.getAltSize())); 592 if (!room.isHard()) 593 r.addAttribute("hard", "false"); 594 if (room.getCoordX() != null && room.getCoordY() != null) 595 r.addAttribute("coordinates", room.getCoordX() + "," + room.getCoordY()); 596 for (ExamPeriod period : getPeriods()) { 597 if (!room.isAvailable(period)) 598 r.addElement("period").addAttribute("id", 599 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 600 "false"); 601 else if (room.getPenalty(period) != 0) 602 r.addElement("period").addAttribute("id", 603 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("penalty", 604 String.valueOf(room.getPenalty(period))); 605 } 606 Map<Long, Integer> travelTimes = getDistanceMetric().getTravelTimes().get(room.getId()); 607 if (travelTimes != null) 608 for (Map.Entry<Long, Integer> time: travelTimes.entrySet()) 609 r.addElement("travel-time").addAttribute("id", getId(idconv, "room", time.getKey().toString())).addAttribute("minutes", time.getValue().toString()); 610 } 611 Element exams = root.addElement("exams"); 612 for (Exam exam : variables()) { 613 Element ex = exams.addElement("exam"); 614 ex.addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 615 if (!anonymize && exam.hasName()) 616 ex.addAttribute("name", exam.getName()); 617 ex.addAttribute("length", String.valueOf(exam.getLength())); 618 if (exam.getSizeOverride() != null) 619 ex.addAttribute("size", exam.getSizeOverride().toString()); 620 if (exam.getMinSize() != 0) 621 ex.addAttribute("minSize", String.valueOf(exam.getMinSize())); 622 ex.addAttribute("alt", (exam.hasAltSeating() ? "true" : "false")); 623 if (exam.getMaxRooms() != getMaxRooms()) 624 ex.addAttribute("maxRooms", String.valueOf(exam.getMaxRooms())); 625 if (exam.getPrintOffset() != null && !anonymize) 626 ex.addAttribute("printOffset", exam.getPrintOffset().toString()); 627 if (!anonymize) 628 ex.addAttribute("enrl", String.valueOf(exam.getStudents().size())); 629 if (!anonymize) 630 for (ExamOwner owner : exam.getOwners()) { 631 Element o = ex.addElement("owner"); 632 o.addAttribute("id", getId(idconv, "owner", String.valueOf(owner.getId()))); 633 o.addAttribute("name", owner.getName()); 634 } 635 for (ExamPeriodPlacement period : exam.getPeriodPlacements()) { 636 Element pe = ex.addElement("period").addAttribute("id", 637 getId(idconv, "period", String.valueOf(period.getId()))); 638 int penalty = period.getExamPenalty(); 639 if (penalty != 0) 640 pe.addAttribute("penalty", String.valueOf(penalty)); 641 } 642 for (ExamRoomPlacement room : exam.getRoomPlacements()) { 643 Element re = ex.addElement("room").addAttribute("id", 644 getId(idconv, "room", String.valueOf(room.getId()))); 645 if (room.getPenalty() != 0) 646 re.addAttribute("penalty", String.valueOf(room.getPenalty())); 647 if (room.getMaxPenalty() != 100) 648 re.addAttribute("maxPenalty", String.valueOf(room.getMaxPenalty())); 649 } 650 if (exam.hasAveragePeriod()) 651 ex.addAttribute("average", String.valueOf(exam.getAveragePeriod())); 652 ExamPlacement p = (assignment == null ? null : assignment.getValue(exam)); 653 if (p != null && saveSolution) { 654 Element asg = ex.addElement("assignment"); 655 asg.addElement("period").addAttribute("id", 656 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 657 for (ExamRoomPlacement r : p.getRoomPlacements()) { 658 asg.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 659 } 660 } 661 p = exam.getInitialAssignment(); 662 if (p != null && saveInitial) { 663 Element ini = ex.addElement("initial"); 664 ini.addElement("period").addAttribute("id", 665 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 666 for (ExamRoomPlacement r : p.getRoomPlacements()) { 667 ini.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 668 } 669 } 670 p = exam.getBestAssignment(); 671 if (p != null && saveBest) { 672 Element ini = ex.addElement("best"); 673 ini.addElement("period").addAttribute("id", 674 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 675 for (ExamRoomPlacement r : p.getRoomPlacements()) { 676 ini.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 677 } 678 } 679 if (iRoomSharing != null) 680 iRoomSharing.save(exam, ex, anonymize ? IdConvertor.getInstance() : null); 681 } 682 Element students = root.addElement("students"); 683 for (ExamStudent student : getStudents()) { 684 Element s = students.addElement("student"); 685 s.addAttribute("id", getId(idconv, "student", String.valueOf(student.getId()))); 686 for (Exam ex : student.variables()) { 687 Element x = s.addElement("exam").addAttribute("id", 688 getId(idconv, "exam", String.valueOf(ex.getId()))); 689 if (!anonymize) 690 for (ExamOwner owner : ex.getOwners(student)) { 691 x.addElement("owner").addAttribute("id", 692 getId(idconv, "owner", String.valueOf(owner.getId()))); 693 } 694 } 695 for (ExamPeriod period : getPeriods()) { 696 if (!student.isAvailable(period)) 697 s.addElement("period").addAttribute("id", 698 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 699 "false"); 700 } 701 } 702 Element instructors = root.addElement("instructors"); 703 for (ExamInstructor instructor : getInstructors()) { 704 Element i = instructors.addElement("instructor"); 705 i.addAttribute("id", getId(idconv, "instructor", String.valueOf(instructor.getId()))); 706 if (!anonymize && instructor.hasName()) 707 i.addAttribute("name", instructor.getName()); 708 for (Exam ex : instructor.variables()) { 709 Element x = i.addElement("exam").addAttribute("id", 710 getId(idconv, "exam", String.valueOf(ex.getId()))); 711 if (!anonymize) 712 for (ExamOwner owner : ex.getOwners(instructor)) { 713 x.addElement("owner").addAttribute("id", 714 getId(idconv, "owner", String.valueOf(owner.getId()))); 715 } 716 } 717 for (ExamPeriod period : getPeriods()) { 718 if (!instructor.isAvailable(period)) 719 i.addElement("period").addAttribute("id", 720 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 721 "false"); 722 } 723 } 724 Element distConstraints = root.addElement("constraints"); 725 for (ExamDistributionConstraint distConstraint : getDistributionConstraints()) { 726 Element dc = distConstraints.addElement(distConstraint.getTypeString()); 727 dc.addAttribute("id", getId(idconv, "constraint", String.valueOf(distConstraint.getId()))); 728 if (!distConstraint.isHard()) { 729 dc.addAttribute("hard", "false"); 730 dc.addAttribute("weight", String.valueOf(distConstraint.getWeight())); 731 } 732 for (Exam exam : distConstraint.variables()) { 733 dc.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 734 } 735 } 736 if (saveConflictTable && assignment != null) { 737 Element conflicts = root.addElement("conflicts"); 738 Map<ExamStudent, Set<Exam>> studentsOfPreviousPeriod = null; 739 for (ExamPeriod period : getPeriods()) { 740 Map<ExamStudent, Set<Exam>> studentsOfPeriod = getStudentsOfPeriod(assignment, period); 741 for (Map.Entry<ExamStudent, Set<Exam>> entry : studentsOfPeriod.entrySet()) { 742 ExamStudent student = entry.getKey(); 743 Set<Exam> examsOfStudent = entry.getValue(); 744 if (examsOfStudent.size() > 1) { 745 Element dir = conflicts.addElement("direct").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 746 for (Exam exam : examsOfStudent) { 747 dir.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 748 } 749 } 750 if (examsOfStudent.size() > 0 && studentsOfPreviousPeriod != null && (isDayBreakBackToBack() || period.prev().getDay() == period.getDay())) { 751 Set<Exam> previousExamsOfStudent = studentsOfPreviousPeriod.get(student); 752 if (previousExamsOfStudent != null) { 753 for (Exam ex1 : previousExamsOfStudent) 754 for (Exam ex2 : examsOfStudent) { 755 Element btb = conflicts.addElement("back-to-back").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 756 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex1.getId()))); 757 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex2.getId()))); 758 if (getBackToBackDistance() >= 0 && period.prev().getDay() == period.getDay()) { 759 double dist = (assignment.getValue(ex1)).getDistanceInMeters(assignment.getValue(ex2)); 760 if (dist > 0) 761 btb.addAttribute("distance", String.valueOf(dist)); 762 } 763 } 764 } 765 } 766 } 767 if (period.next() == null || period.next().getDay() != period.getDay()) { 768 Map<ExamStudent, Set<Exam>> studentsOfDay = getStudentsOfDay(assignment, period); 769 for (Map.Entry<ExamStudent, Set<Exam>> entry : studentsOfDay.entrySet()) { 770 ExamStudent student = entry.getKey(); 771 Set<Exam> examsOfStudent = entry.getValue(); 772 if (examsOfStudent.size() > 2) { 773 Element mt2 = conflicts.addElement("more-2-day").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 774 for (Exam exam : examsOfStudent) { 775 mt2.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 776 } 777 } 778 } 779 } 780 studentsOfPreviousPeriod = studentsOfPeriod; 781 } 782 /* 783 Element conflicts = root.addElement("conflicts"); 784 for (ExamStudent student : getStudents()) { 785 for (ExamPeriod period : getPeriods()) { 786 int nrExams = student.getExams(assignment, period).size(); 787 if (nrExams > 1) { 788 Element dir = conflicts.addElement("direct").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 789 for (Exam exam : student.getExams(assignment, period)) { 790 dir.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 791 } 792 } 793 if (nrExams > 0) { 794 if (period.next() != null && !student.getExams(assignment, period.next()).isEmpty() 795 && (!isDayBreakBackToBack() || period.next().getDay() == period.getDay())) { 796 for (Exam ex1 : student.getExams(assignment, period)) { 797 for (Exam ex2 : student.getExams(assignment, period.next())) { 798 Element btb = conflicts.addElement("back-to-back").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 799 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex1.getId()))); 800 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex2.getId()))); 801 if (getBackToBackDistance() >= 0) { 802 double dist = (assignment.getValue(ex1)).getDistanceInMeters(assignment.getValue(ex2)); 803 if (dist > 0) 804 btb.addAttribute("distance", String.valueOf(dist)); 805 } 806 } 807 } 808 } 809 } 810 if (period.next() == null || period.next().getDay() != period.getDay()) { 811 int nrExamsADay = student.getExamsADay(assignment, period.getDay()).size(); 812 if (nrExamsADay > 2) { 813 Element mt2 = conflicts.addElement("more-2-day").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 814 for (Exam exam : student.getExamsADay(assignment, period.getDay())) { 815 mt2.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 816 } 817 } 818 } 819 } 820 } 821 */ 822 } 823 return document; 824 } 825 826 /** 827 * Load model (including its solution) from XML. 828 * @param document XML document 829 * @param assignment assignment to be loaded 830 * @return true if successfully loaded 831 */ 832 public boolean load(Document document, Assignment<Exam, ExamPlacement> assignment) { 833 return load(document, assignment, null); 834 } 835 836 /** 837 * Load model (including its solution) from XML. 838 * @param document XML document 839 * @param assignment assignment to be loaded 840 * @param saveBest callback executed once the best assignment is loaded and assigned 841 * @return true if successfully loaded 842 */ 843 public boolean load(Document document, Assignment<Exam, ExamPlacement> assignment, Callback saveBest) { 844 boolean loadInitial = getProperties().getPropertyBoolean("Xml.LoadInitial", true); 845 boolean loadBest = getProperties().getPropertyBoolean("Xml.LoadBest", true); 846 boolean loadSolution = getProperties().getPropertyBoolean("Xml.LoadSolution", true); 847 boolean loadParams = getProperties().getPropertyBoolean("Xml.LoadParameters", false); 848 Integer softPeriods = getProperties().getPropertyInteger("Exam.SoftPeriods", null); 849 Integer softRooms = getProperties().getPropertyInteger("Exam.SoftRooms", null); 850 Integer softDistributions = getProperties().getPropertyInteger("Exam.SoftDistributions", null); 851 Element root = document.getRootElement(); 852 if (!"examtt".equals(root.getName())) 853 return false; 854 if (root.attribute("campus") != null) 855 getProperties().setProperty("Data.Campus", root.attributeValue("campus")); 856 else if (root.attribute("initiative") != null) 857 getProperties().setProperty("Data.Initiative", root.attributeValue("initiative")); 858 if (root.attribute("term") != null) 859 getProperties().setProperty("Data.Term", root.attributeValue("term")); 860 if (root.attribute("year") != null) 861 getProperties().setProperty("Data.Year", root.attributeValue("year")); 862 if (loadParams && root.element("parameters") != null) { 863 Map<String,String> params = new HashMap<String, String>(); 864 for (Iterator<?> i = root.element("parameters").elementIterator("property"); i.hasNext();) { 865 Element e = (Element) i.next(); 866 params.put(e.attributeValue("name"), e.attributeValue("value")); 867 } 868 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 869 if (criterion instanceof ExamCriterion) 870 ((ExamCriterion)criterion).setXmlParameters(params); 871 } 872 try { 873 setMaxRooms(Integer.valueOf(params.get("maxRooms"))); 874 } catch (NumberFormatException e) {} catch (NullPointerException e) {} 875 setCheckForPeriodOverlaps("true".equalsIgnoreCase(params.get("checkForPeriodOverlaps"))); 876 } 877 for (Iterator<?> i = root.element("periods").elementIterator("period"); i.hasNext();) { 878 Element e = (Element) i.next(); 879 ExamPeriod p = addPeriod(Long.valueOf(e.attributeValue("id")), e.attributeValue("day"), e.attributeValue("time"), Integer 880 .parseInt(e.attributeValue("length")), Integer.parseInt(e.attributeValue("penalty") == null ? e 881 .attributeValue("weight", "0") : e.attributeValue("penalty"))); 882 if (e.attributeValue("start") != null) 883 p.setStartTime(Integer.valueOf(e.attributeValue("start"))); 884 } 885 HashMap<Long, ExamRoom> rooms = new HashMap<Long, ExamRoom>(); 886 HashMap<String, ArrayList<ExamRoom>> roomGroups = new HashMap<String, ArrayList<ExamRoom>>(); 887 for (Iterator<?> i = root.element("rooms").elementIterator("room"); i.hasNext();) { 888 Element e = (Element) i.next(); 889 String coords = e.attributeValue("coordinates"); 890 ExamRoom room = new ExamRoom(this, Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), 891 Integer.parseInt(e.attributeValue("size")), Integer.parseInt(e.attributeValue("alt")), 892 (coords == null ? null : Double.valueOf(coords.substring(0, coords.indexOf(',')))), 893 (coords == null ? null : Double.valueOf(coords.substring(coords.indexOf(',') + 1)))); 894 room.setHard("true".equalsIgnoreCase(e.attributeValue("hard", "true"))); 895 addConstraint(room); 896 getRooms().add(room); 897 rooms.put(Long.valueOf(room.getId()), room); 898 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 899 Element pe = (Element) j.next(); 900 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 901 if (period == null) continue; 902 if ("false".equals(pe.attributeValue("available"))) { 903 if (softRooms == null) 904 room.setAvailable(period, false); 905 else 906 room.setPenalty(period, softRooms); 907 } else 908 room.setPenalty(period, Integer.parseInt(pe.attributeValue("penalty"))); 909 } 910 String av = e.attributeValue("available"); 911 if (av != null) { 912 for (int j = 0; j < getPeriods().size(); j++) 913 if ('0' == av.charAt(j)) 914 room.setAvailable(getPeriods().get(j), false); 915 } 916 String g = e.attributeValue("groups"); 917 if (g != null) { 918 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 919 String gr = s.nextToken(); 920 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 921 if (roomsThisGrop == null) { 922 roomsThisGrop = new ArrayList<ExamRoom>(); 923 roomGroups.put(gr, roomsThisGrop); 924 } 925 roomsThisGrop.add(room); 926 } 927 } 928 for (Iterator<?> j = e.elementIterator("travel-time"); j.hasNext();) { 929 Element travelTimeEl = (Element)j.next(); 930 getDistanceMetric().addTravelTime(room.getId(), 931 Long.valueOf(travelTimeEl.attributeValue("id")), 932 Integer.valueOf(travelTimeEl.attributeValue("minutes"))); 933 } 934 } 935 ArrayList<ExamPlacement> assignments = new ArrayList<ExamPlacement>(); 936 HashMap<Long, Exam> exams = new HashMap<Long, Exam>(); 937 HashMap<Long, ExamOwner> courseSections = new HashMap<Long, ExamOwner>(); 938 for (Iterator<?> i = root.element("exams").elementIterator("exam"); i.hasNext();) { 939 Element e = (Element) i.next(); 940 ArrayList<ExamPeriodPlacement> periodPlacements = new ArrayList<ExamPeriodPlacement>(); 941 if (softPeriods != null) { 942 for (ExamPeriod period: getPeriods()) { 943 int penalty = softPeriods; 944 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 945 Element pe = (Element) j.next(); 946 if (period.getId().equals(Long.valueOf(pe.attributeValue("id")))) { 947 penalty = Integer.parseInt(pe.attributeValue("penalty", "0")); 948 break; 949 } 950 } 951 periodPlacements.add(new ExamPeriodPlacement(period, penalty)); 952 } 953 } else { 954 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 955 Element pe = (Element) j.next(); 956 ExamPeriod p = getPeriod(Long.valueOf(pe.attributeValue("id"))); 957 if (p != null) 958 periodPlacements.add(new ExamPeriodPlacement(p, Integer.parseInt(pe.attributeValue("penalty", "0")))); 959 } 960 } 961 ArrayList<ExamRoomPlacement> roomPlacements = new ArrayList<ExamRoomPlacement>(); 962 if (softRooms != null) { 963 for (ExamRoom room: getRooms()) { 964 boolean av = false; 965 for (ExamPeriodPlacement p: periodPlacements) { 966 if (room.isAvailable(p.getPeriod()) && room.getPenalty(p.getPeriod()) != softRooms) { av = true; break; } 967 } 968 if (!av) continue; 969 int penalty = softRooms, maxPenalty = softRooms; 970 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 971 Element re = (Element) j.next(); 972 if (room.getId() == Long.parseLong(re.attributeValue("id"))) { 973 penalty = Integer.parseInt(re.attributeValue("penalty", "0")); 974 maxPenalty = Integer.parseInt(re.attributeValue("maxPenalty", softRooms.toString())); 975 } 976 } 977 roomPlacements.add(new ExamRoomPlacement(room, penalty, maxPenalty)); 978 } 979 } else { 980 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 981 Element re = (Element) j.next(); 982 ExamRoomPlacement room = new ExamRoomPlacement(rooms.get(Long.valueOf(re.attributeValue("id"))), 983 Integer.parseInt(re.attributeValue("penalty", "0")), 984 Integer.parseInt(re.attributeValue("maxPenalty", "100"))); 985 if (room.getRoom().isAvailable()) 986 roomPlacements.add(room); 987 } 988 } 989 String g = e.attributeValue("groups"); 990 if (g != null) { 991 HashMap<ExamRoom, Integer> allRooms = new HashMap<ExamRoom, Integer>(); 992 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 993 String gr = s.nextToken(); 994 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 995 if (roomsThisGrop != null) 996 for (ExamRoom r : roomsThisGrop) 997 allRooms.put(r, 0); 998 } 999 for (Iterator<?> j = e.elementIterator("original-room"); j.hasNext();) { 1000 allRooms.put((rooms.get(Long.valueOf(((Element) j.next()).attributeValue("id")))), Integer.valueOf(-1)); 1001 } 1002 for (Map.Entry<ExamRoom, Integer> entry : allRooms.entrySet()) { 1003 ExamRoomPlacement room = new ExamRoomPlacement(entry.getKey(), entry.getValue(), 100); 1004 roomPlacements.add(room); 1005 } 1006 if (periodPlacements.isEmpty()) { 1007 for (ExamPeriod p : getPeriods()) { 1008 periodPlacements.add(new ExamPeriodPlacement(p, 0)); 1009 } 1010 } 1011 } 1012 Exam exam = new Exam(Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), Integer.parseInt(e 1013 .attributeValue("length")), "true".equals(e.attributeValue("alt")), 1014 (e.attribute("maxRooms") == null ? getMaxRooms() : Integer.parseInt(e.attributeValue("maxRooms"))), 1015 Integer.parseInt(e.attributeValue("minSize", "0")), periodPlacements, roomPlacements); 1016 if (e.attributeValue("size") != null) 1017 exam.setSizeOverride(Integer.valueOf(e.attributeValue("size"))); 1018 if (e.attributeValue("printOffset") != null) 1019 exam.setPrintOffset(Integer.valueOf(e.attributeValue("printOffset"))); 1020 exams.put(Long.valueOf(exam.getId()), exam); 1021 addVariable(exam); 1022 if (e.attribute("average") != null) 1023 exam.setAveragePeriod(Integer.parseInt(e.attributeValue("average"))); 1024 Element asg = e.element("assignment"); 1025 if (asg != null && loadSolution) { 1026 Element per = asg.element("period"); 1027 if (per != null) { 1028 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 1029 for (Iterator<?> j = asg.elementIterator("room"); j.hasNext();) 1030 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 1031 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 1032 if (pp != null) 1033 assignments.add(new ExamPlacement(exam, pp, rp)); 1034 } 1035 } 1036 Element ini = e.element("initial"); 1037 if (ini != null && loadInitial) { 1038 Element per = ini.element("period"); 1039 if (per != null) { 1040 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 1041 for (Iterator<?> j = ini.elementIterator("room"); j.hasNext();) 1042 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 1043 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 1044 if (pp != null) 1045 exam.setInitialAssignment(new ExamPlacement(exam, pp, rp)); 1046 } 1047 } 1048 Element best = e.element("best"); 1049 if (best != null && loadBest) { 1050 Element per = best.element("period"); 1051 if (per != null) { 1052 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 1053 for (Iterator<?> j = best.elementIterator("room"); j.hasNext();) 1054 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 1055 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 1056 if (pp != null) 1057 exam.setBestAssignment(new ExamPlacement(exam, pp, rp), 0); 1058 } 1059 } 1060 for (Iterator<?> j = e.elementIterator("owner"); j.hasNext();) { 1061 Element f = (Element) j.next(); 1062 ExamOwner owner = new ExamOwner(exam, Long.parseLong(f.attributeValue("id")), f.attributeValue("name")); 1063 exam.getOwners().add(owner); 1064 courseSections.put(Long.valueOf(owner.getId()), owner); 1065 } 1066 if (iRoomSharing != null) 1067 iRoomSharing.load(exam, e); 1068 } 1069 for (Iterator<?> i = root.element("students").elementIterator("student"); i.hasNext();) { 1070 Element e = (Element) i.next(); 1071 ExamStudent student = new ExamStudent(this, Long.parseLong(e.attributeValue("id"))); 1072 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1073 Element x = (Element) j.next(); 1074 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 1075 student.addVariable(ex); 1076 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 1077 Element f = (Element) k.next(); 1078 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 1079 student.getOwners().add(owner); 1080 owner.getStudents().add(student); 1081 } 1082 } 1083 String available = e.attributeValue("available"); 1084 if (available != null) 1085 for (ExamPeriod period : getPeriods()) { 1086 if (available.charAt(period.getIndex()) == '0') 1087 student.setAvailable(period.getIndex(), false); 1088 } 1089 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 1090 Element pe = (Element) j.next(); 1091 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 1092 if (period == null) continue; 1093 if ("false".equals(pe.attributeValue("available"))) 1094 student.setAvailable(period.getIndex(), false); 1095 } 1096 addConstraint(student); 1097 getStudents().add(student); 1098 } 1099 if (root.element("instructors") != null) 1100 for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) { 1101 Element e = (Element) i.next(); 1102 ExamInstructor instructor = new ExamInstructor(this, Long.parseLong(e.attributeValue("id")), e 1103 .attributeValue("name")); 1104 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1105 Element x = (Element) j.next(); 1106 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 1107 instructor.addVariable(ex); 1108 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 1109 Element f = (Element) k.next(); 1110 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 1111 instructor.getOwners().add(owner); 1112 owner.getIntructors().add(instructor); 1113 } 1114 } 1115 String available = e.attributeValue("available"); 1116 if (available != null) 1117 for (ExamPeriod period : getPeriods()) { 1118 if (available.charAt(period.getIndex()) == '0') 1119 instructor.setAvailable(period.getIndex(), false); 1120 } 1121 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 1122 Element pe = (Element) j.next(); 1123 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 1124 if (period == null) continue; 1125 if ("false".equals(pe.attributeValue("available"))) 1126 instructor.setAvailable(period.getIndex(), false); 1127 } 1128 addConstraint(instructor); 1129 getInstructors().add(instructor); 1130 } 1131 if (root.element("constraints") != null) 1132 for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) { 1133 Element e = (Element) i.next(); 1134 ExamDistributionConstraint dc = new ExamDistributionConstraint(Long.parseLong(e.attributeValue("id")), 1135 e.getName(), 1136 softDistributions != null ? false : "true".equals(e.attributeValue("hard", "true")), 1137 (softDistributions != null && "true".equals(e.attributeValue("hard", "true")) ? softDistributions : Integer.parseInt(e.attributeValue("weight", "0")))); 1138 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1139 dc.addVariable(exams.get(Long.valueOf(((Element) j.next()).attributeValue("id")))); 1140 } 1141 addConstraint(dc); 1142 getDistributionConstraints().add(dc); 1143 } 1144 init(); 1145 if (loadBest && saveBest != null && assignment != null) { 1146 for (Exam exam : variables()) { 1147 ExamPlacement placement = exam.getBestAssignment(); 1148 if (placement == null) 1149 continue; 1150 assignment.assign(0, placement); 1151 } 1152 saveBest.execute(); 1153 for (Exam exam : variables()) { 1154 if (assignment.getValue(exam) != null) 1155 assignment.unassign(0, exam); 1156 } 1157 } 1158 if (assignment != null) { 1159 for (ExamPlacement placement : assignments) { 1160 Exam exam = placement.variable(); 1161 Set<ExamPlacement> conf = conflictValues(assignment, placement); 1162 if (!conf.isEmpty()) { 1163 for (Map.Entry<Constraint<Exam, ExamPlacement>, Set<ExamPlacement>> entry : conflictConstraints(assignment, placement).entrySet()) { 1164 Constraint<Exam, ExamPlacement> constraint = entry.getKey(); 1165 Set<ExamPlacement> values = entry.getValue(); 1166 if (constraint instanceof ExamStudent) { 1167 ((ExamStudent) constraint).setAllowDirectConflicts(true); 1168 exam.setAllowDirectConflicts(true); 1169 for (ExamPlacement p : values) 1170 p.variable().setAllowDirectConflicts(true); 1171 } 1172 } 1173 conf = conflictValues(assignment, placement); 1174 } 1175 if (conf.isEmpty()) { 1176 assignment.assign(0, placement); 1177 } else { 1178 sLog.error("Unable to assign " + exam.getInitialAssignment().getName() + " to exam " + exam.getName()); 1179 sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(assignment, exam.getInitialAssignment()), 2)); 1180 } 1181 } 1182 } 1183 return true; 1184 } 1185 1186 @Override 1187 public ExamContext createAssignmentContext(Assignment<Exam, ExamPlacement> assignment) { 1188 return new ExamContext(this, assignment); 1189 } 1190 1191 public Map<ExamStudent, Set<Exam>> getStudentsOfPeriod(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1192 return getContext(assignment).getStudentsOfPeriod(period.getIndex()); 1193 } 1194 1195 public Map<ExamStudent, Set<Exam>> getStudentsOfDay(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1196 return getContext(assignment).getStudentsOfDay(period.getDay()); 1197 } 1198 1199 public Map<ExamStudent, Set<Exam>> getStudentsOfDay(Assignment<Exam, ExamPlacement> assignment, int day) { 1200 return getContext(assignment).getStudentsOfDay(day); 1201 } 1202 1203 public Map<ExamInstructor, Set<Exam>> getInstructorsOfPeriod(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1204 return getContext(assignment).getInstructorsOfPeriod(period.getIndex()); 1205 } 1206 1207 public Map<ExamInstructor, Set<Exam>> getInstructorsOfDay(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1208 return getContext(assignment).getInstructorsOfDay(period.getDay()); 1209 } 1210 1211 public Map<ExamInstructor, Set<Exam>> getInstructorsOfDay(Assignment<Exam, ExamPlacement> assignment, int day) { 1212 return getContext(assignment).getInstructorsOfDay(day); 1213 } 1214 1215}