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