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