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 if (room.getParentRoom() != null) 597 r.addAttribute("parentId", getId(idconv, "room", String.valueOf(room.getParentRoom().getId()))); 598 for (ExamPeriod period : getPeriods()) { 599 if (!room.isAvailable(period)) 600 r.addElement("period").addAttribute("id", 601 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 602 "false"); 603 else if (room.getPenalty(period) != 0) 604 r.addElement("period").addAttribute("id", 605 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("penalty", 606 String.valueOf(room.getPenalty(period))); 607 } 608 Map<Long, Integer> travelTimes = getDistanceMetric().getTravelTimes().get(room.getId()); 609 if (travelTimes != null) 610 for (Map.Entry<Long, Integer> time: travelTimes.entrySet()) 611 r.addElement("travel-time").addAttribute("id", getId(idconv, "room", time.getKey().toString())).addAttribute("minutes", time.getValue().toString()); 612 } 613 Element exams = root.addElement("exams"); 614 for (Exam exam : variables()) { 615 Element ex = exams.addElement("exam"); 616 ex.addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 617 if (!anonymize && exam.hasName()) 618 ex.addAttribute("name", exam.getName()); 619 ex.addAttribute("length", String.valueOf(exam.getLength())); 620 if (exam.getSizeOverride() != null) 621 ex.addAttribute("size", exam.getSizeOverride().toString()); 622 if (exam.getMinSize() != 0) 623 ex.addAttribute("minSize", String.valueOf(exam.getMinSize())); 624 ex.addAttribute("alt", (exam.hasAltSeating() ? "true" : "false")); 625 if (exam.getMaxRooms() != getMaxRooms()) 626 ex.addAttribute("maxRooms", String.valueOf(exam.getMaxRooms())); 627 if (exam.getPrintOffset() != null && !anonymize) 628 ex.addAttribute("printOffset", exam.getPrintOffset().toString()); 629 if (!anonymize) 630 ex.addAttribute("enrl", String.valueOf(exam.getStudents().size())); 631 if (!anonymize) 632 for (ExamOwner owner : exam.getOwners()) { 633 Element o = ex.addElement("owner"); 634 o.addAttribute("id", getId(idconv, "owner", String.valueOf(owner.getId()))); 635 o.addAttribute("name", owner.getName()); 636 } 637 for (ExamPeriodPlacement period : exam.getPeriodPlacements()) { 638 Element pe = ex.addElement("period").addAttribute("id", 639 getId(idconv, "period", String.valueOf(period.getId()))); 640 int penalty = period.getExamPenalty(); 641 if (penalty != 0) 642 pe.addAttribute("penalty", String.valueOf(penalty)); 643 } 644 for (ExamRoomPlacement room : exam.getRoomPlacements()) { 645 Element re = ex.addElement("room").addAttribute("id", 646 getId(idconv, "room", String.valueOf(room.getId()))); 647 if (room.getPenalty() != 0) 648 re.addAttribute("penalty", String.valueOf(room.getPenalty())); 649 if (room.getMaxPenalty() != 100) 650 re.addAttribute("maxPenalty", String.valueOf(room.getMaxPenalty())); 651 } 652 if (exam.hasAveragePeriod()) 653 ex.addAttribute("average", String.valueOf(exam.getAveragePeriod())); 654 ExamPlacement p = (assignment == null ? null : assignment.getValue(exam)); 655 if (p != null && saveSolution) { 656 Element asg = ex.addElement("assignment"); 657 asg.addElement("period").addAttribute("id", 658 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 659 for (ExamRoomPlacement r : p.getRoomPlacements()) { 660 asg.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 661 } 662 } 663 p = exam.getInitialAssignment(); 664 if (p != null && saveInitial) { 665 Element ini = ex.addElement("initial"); 666 ini.addElement("period").addAttribute("id", 667 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 668 for (ExamRoomPlacement r : p.getRoomPlacements()) { 669 ini.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 670 } 671 } 672 p = exam.getBestAssignment(); 673 if (p != null && saveBest) { 674 Element ini = ex.addElement("best"); 675 ini.addElement("period").addAttribute("id", 676 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 677 for (ExamRoomPlacement r : p.getRoomPlacements()) { 678 ini.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 679 } 680 } 681 if (iRoomSharing != null) 682 iRoomSharing.save(exam, ex, anonymize ? IdConvertor.getInstance() : null); 683 } 684 Element students = root.addElement("students"); 685 for (ExamStudent student : getStudents()) { 686 Element s = students.addElement("student"); 687 s.addAttribute("id", getId(idconv, "student", String.valueOf(student.getId()))); 688 for (Exam ex : student.variables()) { 689 Element x = s.addElement("exam").addAttribute("id", 690 getId(idconv, "exam", String.valueOf(ex.getId()))); 691 if (!anonymize) 692 for (ExamOwner owner : ex.getOwners(student)) { 693 x.addElement("owner").addAttribute("id", 694 getId(idconv, "owner", String.valueOf(owner.getId()))); 695 } 696 } 697 for (ExamPeriod period : getPeriods()) { 698 if (!student.isAvailable(period)) 699 s.addElement("period").addAttribute("id", 700 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 701 "false"); 702 } 703 } 704 Element instructors = root.addElement("instructors"); 705 for (ExamInstructor instructor : getInstructors()) { 706 Element i = instructors.addElement("instructor"); 707 i.addAttribute("id", getId(idconv, "instructor", String.valueOf(instructor.getId()))); 708 if (!anonymize && instructor.hasName()) 709 i.addAttribute("name", instructor.getName()); 710 for (Exam ex : instructor.variables()) { 711 Element x = i.addElement("exam").addAttribute("id", 712 getId(idconv, "exam", String.valueOf(ex.getId()))); 713 if (!anonymize) 714 for (ExamOwner owner : ex.getOwners(instructor)) { 715 x.addElement("owner").addAttribute("id", 716 getId(idconv, "owner", String.valueOf(owner.getId()))); 717 } 718 } 719 for (ExamPeriod period : getPeriods()) { 720 if (!instructor.isAvailable(period)) 721 i.addElement("period").addAttribute("id", 722 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 723 "false"); 724 } 725 } 726 Element distConstraints = root.addElement("constraints"); 727 for (ExamDistributionConstraint distConstraint : getDistributionConstraints()) { 728 Element dc = distConstraints.addElement(distConstraint.getTypeString()); 729 dc.addAttribute("id", getId(idconv, "constraint", String.valueOf(distConstraint.getId()))); 730 if (!distConstraint.isHard()) { 731 dc.addAttribute("hard", "false"); 732 dc.addAttribute("weight", String.valueOf(distConstraint.getWeight())); 733 } 734 for (Exam exam : distConstraint.variables()) { 735 dc.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 736 } 737 } 738 if (saveConflictTable && assignment != null) { 739 Element conflicts = root.addElement("conflicts"); 740 Map<ExamStudent, Set<Exam>> studentsOfPreviousPeriod = null; 741 for (ExamPeriod period : getPeriods()) { 742 Map<ExamStudent, Set<Exam>> studentsOfPeriod = getStudentsOfPeriod(assignment, period); 743 for (Map.Entry<ExamStudent, Set<Exam>> entry : studentsOfPeriod.entrySet()) { 744 ExamStudent student = entry.getKey(); 745 Set<Exam> examsOfStudent = entry.getValue(); 746 if (examsOfStudent.size() > 1) { 747 Element dir = conflicts.addElement("direct").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 748 for (Exam exam : examsOfStudent) { 749 dir.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 750 } 751 } 752 if (examsOfStudent.size() > 0 && studentsOfPreviousPeriod != null && (isDayBreakBackToBack() || period.prev().getDay() == period.getDay())) { 753 Set<Exam> previousExamsOfStudent = studentsOfPreviousPeriod.get(student); 754 if (previousExamsOfStudent != null) { 755 for (Exam ex1 : previousExamsOfStudent) 756 for (Exam ex2 : examsOfStudent) { 757 Element btb = conflicts.addElement("back-to-back").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 758 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex1.getId()))); 759 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex2.getId()))); 760 if (getBackToBackDistance() >= 0 && period.prev().getDay() == period.getDay()) { 761 double dist = (assignment.getValue(ex1)).getDistanceInMeters(assignment.getValue(ex2)); 762 if (dist > 0) 763 btb.addAttribute("distance", String.valueOf(dist)); 764 } 765 } 766 } 767 } 768 } 769 if (period.next() == null || period.next().getDay() != period.getDay()) { 770 Map<ExamStudent, Set<Exam>> studentsOfDay = getStudentsOfDay(assignment, period); 771 for (Map.Entry<ExamStudent, Set<Exam>> entry : studentsOfDay.entrySet()) { 772 ExamStudent student = entry.getKey(); 773 Set<Exam> examsOfStudent = entry.getValue(); 774 if (examsOfStudent.size() > 2) { 775 Element mt2 = conflicts.addElement("more-2-day").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 776 for (Exam exam : examsOfStudent) { 777 mt2.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 778 } 779 } 780 } 781 } 782 studentsOfPreviousPeriod = studentsOfPeriod; 783 } 784 /* 785 Element conflicts = root.addElement("conflicts"); 786 for (ExamStudent student : getStudents()) { 787 for (ExamPeriod period : getPeriods()) { 788 int nrExams = student.getExams(assignment, period).size(); 789 if (nrExams > 1) { 790 Element dir = conflicts.addElement("direct").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 791 for (Exam exam : student.getExams(assignment, period)) { 792 dir.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 793 } 794 } 795 if (nrExams > 0) { 796 if (period.next() != null && !student.getExams(assignment, period.next()).isEmpty() 797 && (!isDayBreakBackToBack() || period.next().getDay() == period.getDay())) { 798 for (Exam ex1 : student.getExams(assignment, period)) { 799 for (Exam ex2 : student.getExams(assignment, period.next())) { 800 Element btb = conflicts.addElement("back-to-back").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 801 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex1.getId()))); 802 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex2.getId()))); 803 if (getBackToBackDistance() >= 0) { 804 double dist = (assignment.getValue(ex1)).getDistanceInMeters(assignment.getValue(ex2)); 805 if (dist > 0) 806 btb.addAttribute("distance", String.valueOf(dist)); 807 } 808 } 809 } 810 } 811 } 812 if (period.next() == null || period.next().getDay() != period.getDay()) { 813 int nrExamsADay = student.getExamsADay(assignment, period.getDay()).size(); 814 if (nrExamsADay > 2) { 815 Element mt2 = conflicts.addElement("more-2-day").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 816 for (Exam exam : student.getExamsADay(assignment, period.getDay())) { 817 mt2.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 818 } 819 } 820 } 821 } 822 } 823 */ 824 } 825 return document; 826 } 827 828 /** 829 * Load model (including its solution) from XML. 830 * @param document XML document 831 * @param assignment assignment to be loaded 832 * @return true if successfully loaded 833 */ 834 public boolean load(Document document, Assignment<Exam, ExamPlacement> assignment) { 835 return load(document, assignment, null); 836 } 837 838 /** 839 * Load model (including its solution) from XML. 840 * @param document XML document 841 * @param assignment assignment to be loaded 842 * @param saveBest callback executed once the best assignment is loaded and assigned 843 * @return true if successfully loaded 844 */ 845 public boolean load(Document document, Assignment<Exam, ExamPlacement> assignment, Callback saveBest) { 846 boolean loadInitial = getProperties().getPropertyBoolean("Xml.LoadInitial", true); 847 boolean loadBest = getProperties().getPropertyBoolean("Xml.LoadBest", true); 848 boolean loadSolution = getProperties().getPropertyBoolean("Xml.LoadSolution", true); 849 boolean loadParams = getProperties().getPropertyBoolean("Xml.LoadParameters", false); 850 Integer softPeriods = getProperties().getPropertyInteger("Exam.SoftPeriods", null); 851 Integer softRooms = getProperties().getPropertyInteger("Exam.SoftRooms", null); 852 Integer softDistributions = getProperties().getPropertyInteger("Exam.SoftDistributions", null); 853 Element root = document.getRootElement(); 854 if (!"examtt".equals(root.getName())) 855 return false; 856 if (root.attribute("campus") != null) 857 getProperties().setProperty("Data.Campus", root.attributeValue("campus")); 858 else if (root.attribute("initiative") != null) 859 getProperties().setProperty("Data.Initiative", root.attributeValue("initiative")); 860 if (root.attribute("term") != null) 861 getProperties().setProperty("Data.Term", root.attributeValue("term")); 862 if (root.attribute("year") != null) 863 getProperties().setProperty("Data.Year", root.attributeValue("year")); 864 if (loadParams && root.element("parameters") != null) { 865 Map<String,String> params = new HashMap<String, String>(); 866 for (Iterator<?> i = root.element("parameters").elementIterator("property"); i.hasNext();) { 867 Element e = (Element) i.next(); 868 params.put(e.attributeValue("name"), e.attributeValue("value")); 869 } 870 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 871 if (criterion instanceof ExamCriterion) 872 ((ExamCriterion)criterion).setXmlParameters(params); 873 } 874 try { 875 setMaxRooms(Integer.valueOf(params.get("maxRooms"))); 876 } catch (NumberFormatException e) {} catch (NullPointerException e) {} 877 setCheckForPeriodOverlaps("true".equalsIgnoreCase(params.get("checkForPeriodOverlaps"))); 878 } 879 for (Iterator<?> i = root.element("periods").elementIterator("period"); i.hasNext();) { 880 Element e = (Element) i.next(); 881 ExamPeriod p = addPeriod(Long.valueOf(e.attributeValue("id")), e.attributeValue("day"), e.attributeValue("time"), Integer 882 .parseInt(e.attributeValue("length")), Integer.parseInt(e.attributeValue("penalty") == null ? e 883 .attributeValue("weight", "0") : e.attributeValue("penalty"))); 884 if (e.attributeValue("start") != null) 885 p.setStartTime(Integer.valueOf(e.attributeValue("start"))); 886 } 887 HashMap<Long, ExamRoom> rooms = new HashMap<Long, ExamRoom>(); 888 HashMap<String, ArrayList<ExamRoom>> roomGroups = new HashMap<String, ArrayList<ExamRoom>>(); 889 HashMap<ExamRoom, Long> roomPartitions = new HashMap<ExamRoom, Long>(); 890 for (Iterator<?> i = root.element("rooms").elementIterator("room"); i.hasNext();) { 891 Element e = (Element) i.next(); 892 String coords = e.attributeValue("coordinates"); 893 ExamRoom room = new ExamRoom(this, Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), 894 Integer.parseInt(e.attributeValue("size")), Integer.parseInt(e.attributeValue("alt")), 895 (coords == null ? null : Double.valueOf(coords.substring(0, coords.indexOf(',')))), 896 (coords == null ? null : Double.valueOf(coords.substring(coords.indexOf(',') + 1)))); 897 room.setHard("true".equalsIgnoreCase(e.attributeValue("hard", "true"))); 898 addConstraint(room); 899 getRooms().add(room); 900 rooms.put(Long.valueOf(room.getId()), room); 901 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 902 Element pe = (Element) j.next(); 903 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 904 if (period == null) continue; 905 if ("false".equals(pe.attributeValue("available"))) { 906 if (softRooms == null) 907 room.setAvailable(period, false); 908 else 909 room.setPenalty(period, softRooms); 910 } else 911 room.setPenalty(period, Integer.parseInt(pe.attributeValue("penalty"))); 912 } 913 String av = e.attributeValue("available"); 914 if (av != null) { 915 for (int j = 0; j < getPeriods().size(); j++) 916 if ('0' == av.charAt(j)) 917 room.setAvailable(getPeriods().get(j), false); 918 } 919 String g = e.attributeValue("groups"); 920 if (g != null) { 921 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 922 String gr = s.nextToken(); 923 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 924 if (roomsThisGrop == null) { 925 roomsThisGrop = new ArrayList<ExamRoom>(); 926 roomGroups.put(gr, roomsThisGrop); 927 } 928 roomsThisGrop.add(room); 929 } 930 } 931 if (e.attributeValue("parentId") != null) 932 roomPartitions.put(room, Long.valueOf(e.attributeValue("parentId"))); 933 for (Iterator<?> j = e.elementIterator("travel-time"); j.hasNext();) { 934 Element travelTimeEl = (Element)j.next(); 935 getDistanceMetric().addTravelTime(room.getId(), 936 Long.valueOf(travelTimeEl.attributeValue("id")), 937 Integer.valueOf(travelTimeEl.attributeValue("minutes"))); 938 } 939 } 940 for (Map.Entry<ExamRoom, Long> partition: roomPartitions.entrySet()) { 941 ExamRoom parent = rooms.get(partition.getValue()); 942 if (parent != null) 943 parent.addPartition(partition.getKey()); 944 } 945 ArrayList<ExamPlacement> assignments = new ArrayList<ExamPlacement>(); 946 HashMap<Long, Exam> exams = new HashMap<Long, Exam>(); 947 HashMap<Long, ExamOwner> courseSections = new HashMap<Long, ExamOwner>(); 948 for (Iterator<?> i = root.element("exams").elementIterator("exam"); i.hasNext();) { 949 Element e = (Element) i.next(); 950 ArrayList<ExamPeriodPlacement> periodPlacements = new ArrayList<ExamPeriodPlacement>(); 951 if (softPeriods != null) { 952 for (ExamPeriod period: getPeriods()) { 953 int penalty = softPeriods; 954 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 955 Element pe = (Element) j.next(); 956 if (period.getId().equals(Long.valueOf(pe.attributeValue("id")))) { 957 penalty = Integer.parseInt(pe.attributeValue("penalty", "0")); 958 break; 959 } 960 } 961 periodPlacements.add(new ExamPeriodPlacement(period, penalty)); 962 } 963 } else { 964 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 965 Element pe = (Element) j.next(); 966 ExamPeriod p = getPeriod(Long.valueOf(pe.attributeValue("id"))); 967 if (p != null) 968 periodPlacements.add(new ExamPeriodPlacement(p, Integer.parseInt(pe.attributeValue("penalty", "0")))); 969 } 970 } 971 ArrayList<ExamRoomPlacement> roomPlacements = new ArrayList<ExamRoomPlacement>(); 972 if (softRooms != null) { 973 for (ExamRoom room: getRooms()) { 974 boolean av = false; 975 for (ExamPeriodPlacement p: periodPlacements) { 976 if (room.isAvailable(p.getPeriod()) && room.getPenalty(p.getPeriod()) != softRooms) { av = true; break; } 977 } 978 if (!av) continue; 979 int penalty = softRooms, maxPenalty = softRooms; 980 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 981 Element re = (Element) j.next(); 982 if (room.getId() == Long.parseLong(re.attributeValue("id"))) { 983 penalty = Integer.parseInt(re.attributeValue("penalty", "0")); 984 maxPenalty = Integer.parseInt(re.attributeValue("maxPenalty", softRooms.toString())); 985 } 986 } 987 roomPlacements.add(new ExamRoomPlacement(room, penalty, maxPenalty)); 988 } 989 } else { 990 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 991 Element re = (Element) j.next(); 992 ExamRoomPlacement room = new ExamRoomPlacement(rooms.get(Long.valueOf(re.attributeValue("id"))), 993 Integer.parseInt(re.attributeValue("penalty", "0")), 994 Integer.parseInt(re.attributeValue("maxPenalty", "100"))); 995 if (room.getRoom().isAvailable()) 996 roomPlacements.add(room); 997 } 998 } 999 String g = e.attributeValue("groups"); 1000 if (g != null) { 1001 HashMap<ExamRoom, Integer> allRooms = new HashMap<ExamRoom, Integer>(); 1002 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 1003 String gr = s.nextToken(); 1004 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 1005 if (roomsThisGrop != null) 1006 for (ExamRoom r : roomsThisGrop) 1007 allRooms.put(r, 0); 1008 } 1009 for (Iterator<?> j = e.elementIterator("original-room"); j.hasNext();) { 1010 allRooms.put((rooms.get(Long.valueOf(((Element) j.next()).attributeValue("id")))), Integer.valueOf(-1)); 1011 } 1012 for (Map.Entry<ExamRoom, Integer> entry : allRooms.entrySet()) { 1013 ExamRoomPlacement room = new ExamRoomPlacement(entry.getKey(), entry.getValue(), 100); 1014 roomPlacements.add(room); 1015 } 1016 if (periodPlacements.isEmpty()) { 1017 for (ExamPeriod p : getPeriods()) { 1018 periodPlacements.add(new ExamPeriodPlacement(p, 0)); 1019 } 1020 } 1021 } 1022 Exam exam = new Exam(Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), Integer.parseInt(e 1023 .attributeValue("length")), "true".equals(e.attributeValue("alt")), 1024 (e.attribute("maxRooms") == null ? getMaxRooms() : Integer.parseInt(e.attributeValue("maxRooms"))), 1025 Integer.parseInt(e.attributeValue("minSize", "0")), periodPlacements, roomPlacements); 1026 if (e.attributeValue("size") != null) 1027 exam.setSizeOverride(Integer.valueOf(e.attributeValue("size"))); 1028 if (e.attributeValue("printOffset") != null) 1029 exam.setPrintOffset(Integer.valueOf(e.attributeValue("printOffset"))); 1030 exams.put(Long.valueOf(exam.getId()), exam); 1031 addVariable(exam); 1032 if (e.attribute("average") != null) 1033 exam.setAveragePeriod(Integer.parseInt(e.attributeValue("average"))); 1034 Element asg = e.element("assignment"); 1035 if (asg != null && loadSolution) { 1036 Element per = asg.element("period"); 1037 if (per != null) { 1038 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 1039 for (Iterator<?> j = asg.elementIterator("room"); j.hasNext();) 1040 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 1041 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 1042 if (pp != null) 1043 assignments.add(new ExamPlacement(exam, pp, rp)); 1044 } 1045 } 1046 Element ini = e.element("initial"); 1047 if (ini != null && loadInitial) { 1048 Element per = ini.element("period"); 1049 if (per != null) { 1050 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 1051 for (Iterator<?> j = ini.elementIterator("room"); j.hasNext();) 1052 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 1053 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 1054 if (pp != null) 1055 exam.setInitialAssignment(new ExamPlacement(exam, pp, rp)); 1056 } 1057 } 1058 Element best = e.element("best"); 1059 if (best != null && loadBest) { 1060 Element per = best.element("period"); 1061 if (per != null) { 1062 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 1063 for (Iterator<?> j = best.elementIterator("room"); j.hasNext();) 1064 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 1065 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 1066 if (pp != null) 1067 exam.setBestAssignment(new ExamPlacement(exam, pp, rp), 0); 1068 } 1069 } 1070 for (Iterator<?> j = e.elementIterator("owner"); j.hasNext();) { 1071 Element f = (Element) j.next(); 1072 ExamOwner owner = new ExamOwner(exam, Long.parseLong(f.attributeValue("id")), f.attributeValue("name")); 1073 exam.getOwners().add(owner); 1074 courseSections.put(Long.valueOf(owner.getId()), owner); 1075 } 1076 if (iRoomSharing != null) 1077 iRoomSharing.load(exam, e); 1078 } 1079 for (Iterator<?> i = root.element("students").elementIterator("student"); i.hasNext();) { 1080 Element e = (Element) i.next(); 1081 ExamStudent student = new ExamStudent(this, Long.parseLong(e.attributeValue("id"))); 1082 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1083 Element x = (Element) j.next(); 1084 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 1085 student.addVariable(ex); 1086 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 1087 Element f = (Element) k.next(); 1088 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 1089 student.getOwners().add(owner); 1090 owner.getStudents().add(student); 1091 } 1092 } 1093 String available = e.attributeValue("available"); 1094 if (available != null) 1095 for (ExamPeriod period : getPeriods()) { 1096 if (available.charAt(period.getIndex()) == '0') 1097 student.setAvailable(period.getIndex(), false); 1098 } 1099 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 1100 Element pe = (Element) j.next(); 1101 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 1102 if (period == null) continue; 1103 if ("false".equals(pe.attributeValue("available"))) 1104 student.setAvailable(period.getIndex(), false); 1105 } 1106 addConstraint(student); 1107 getStudents().add(student); 1108 } 1109 if (root.element("instructors") != null) 1110 for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) { 1111 Element e = (Element) i.next(); 1112 ExamInstructor instructor = new ExamInstructor(this, Long.parseLong(e.attributeValue("id")), e 1113 .attributeValue("name")); 1114 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1115 Element x = (Element) j.next(); 1116 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 1117 instructor.addVariable(ex); 1118 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 1119 Element f = (Element) k.next(); 1120 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 1121 instructor.getOwners().add(owner); 1122 owner.getIntructors().add(instructor); 1123 } 1124 } 1125 String available = e.attributeValue("available"); 1126 if (available != null) 1127 for (ExamPeriod period : getPeriods()) { 1128 if (available.charAt(period.getIndex()) == '0') 1129 instructor.setAvailable(period.getIndex(), false); 1130 } 1131 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 1132 Element pe = (Element) j.next(); 1133 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 1134 if (period == null) continue; 1135 if ("false".equals(pe.attributeValue("available"))) 1136 instructor.setAvailable(period.getIndex(), false); 1137 } 1138 addConstraint(instructor); 1139 getInstructors().add(instructor); 1140 } 1141 if (root.element("constraints") != null) 1142 for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) { 1143 Element e = (Element) i.next(); 1144 ExamDistributionConstraint dc = new ExamDistributionConstraint(Long.parseLong(e.attributeValue("id")), 1145 e.getName(), 1146 softDistributions != null ? false : "true".equals(e.attributeValue("hard", "true")), 1147 (softDistributions != null && "true".equals(e.attributeValue("hard", "true")) ? softDistributions : Integer.parseInt(e.attributeValue("weight", "0")))); 1148 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1149 dc.addVariable(exams.get(Long.valueOf(((Element) j.next()).attributeValue("id")))); 1150 } 1151 addConstraint(dc); 1152 getDistributionConstraints().add(dc); 1153 } 1154 init(); 1155 if (loadBest && saveBest != null && assignment != null) { 1156 for (Exam exam : variables()) { 1157 ExamPlacement placement = exam.getBestAssignment(); 1158 if (placement == null) 1159 continue; 1160 assignment.assign(0, placement); 1161 } 1162 saveBest.execute(); 1163 for (Exam exam : variables()) { 1164 if (assignment.getValue(exam) != null) 1165 assignment.unassign(0, exam); 1166 } 1167 } 1168 if (assignment != null) { 1169 for (ExamPlacement placement : assignments) { 1170 Exam exam = placement.variable(); 1171 Set<ExamPlacement> conf = conflictValues(assignment, placement); 1172 if (!conf.isEmpty()) { 1173 for (Map.Entry<Constraint<Exam, ExamPlacement>, Set<ExamPlacement>> entry : conflictConstraints(assignment, placement).entrySet()) { 1174 Constraint<Exam, ExamPlacement> constraint = entry.getKey(); 1175 Set<ExamPlacement> values = entry.getValue(); 1176 if (constraint instanceof ExamStudent) { 1177 ((ExamStudent) constraint).setAllowDirectConflicts(true); 1178 exam.setAllowDirectConflicts(true); 1179 for (ExamPlacement p : values) 1180 p.variable().setAllowDirectConflicts(true); 1181 } 1182 } 1183 conf = conflictValues(assignment, placement); 1184 } 1185 if (conf.isEmpty()) { 1186 assignment.assign(0, placement); 1187 } else { 1188 sLog.error("Unable to assign " + exam.getInitialAssignment().getName() + " to exam " + exam.getName()); 1189 sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(assignment, exam.getInitialAssignment()), 2)); 1190 } 1191 } 1192 } 1193 return true; 1194 } 1195 1196 @Override 1197 public ExamContext createAssignmentContext(Assignment<Exam, ExamPlacement> assignment) { 1198 return new ExamContext(this, assignment); 1199 } 1200 1201 public Map<ExamStudent, Set<Exam>> getStudentsOfPeriod(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1202 return getContext(assignment).getStudentsOfPeriod(period.getIndex()); 1203 } 1204 1205 public Map<ExamStudent, Set<Exam>> getStudentsOfDay(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1206 return getContext(assignment).getStudentsOfDay(period.getDay()); 1207 } 1208 1209 public Map<ExamStudent, Set<Exam>> getStudentsOfDay(Assignment<Exam, ExamPlacement> assignment, int day) { 1210 return getContext(assignment).getStudentsOfDay(day); 1211 } 1212 1213 public Map<ExamInstructor, Set<Exam>> getInstructorsOfPeriod(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1214 return getContext(assignment).getInstructorsOfPeriod(period.getIndex()); 1215 } 1216 1217 public Map<ExamInstructor, Set<Exam>> getInstructorsOfDay(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1218 return getContext(assignment).getInstructorsOfDay(period.getDay()); 1219 } 1220 1221 public Map<ExamInstructor, Set<Exam>> getInstructorsOfDay(Assignment<Exam, ExamPlacement> assignment, int day) { 1222 return getContext(assignment).getInstructorsOfDay(day); 1223 } 1224 1225}