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 Map<Integer, Integer> penalty2count = new HashMap<Integer, Integer>(); 450 for (Exam exam: variables()) { 451 ExamPlacement placement = assignment.getValue(exam); 452 if (placement == null) continue; 453 Integer preference = placement.getPeriodPlacement().getExamPenalty(); 454 Integer count = penalty2count.get(preference); 455 penalty2count.put(preference, 1 + (count == null ? 0 : count.intValue())); 456 } 457 if (!penalty2count.isEmpty()) { 458 String value = null; 459 for (Integer penalty: new TreeSet<Integer>(penalty2count.keySet())) { 460 if (penalty == 0) continue; 461 value = (value == null ? "" : value + ", ") + penalty2count.get(penalty) + "× " + penalty; 462 } 463 if (value != null) 464 info.put("Period Preferences", value); 465 } 466 return info; 467 } 468 469 /** 470 * Problem properties 471 * @return solver configuration 472 */ 473 public DataProperties getProperties() { 474 return iProperties; 475 } 476 477 /** 478 * Problem rooms 479 * 480 * @return list of {@link ExamRoom} 481 */ 482 public List<ExamRoom> getRooms() { 483 return iRooms; 484 } 485 486 /** 487 * Problem students 488 * 489 * @return list of {@link ExamStudent} 490 */ 491 public List<ExamStudent> getStudents() { 492 return iStudents; 493 } 494 495 /** 496 * Problem instructors 497 * 498 * @return list of {@link ExamInstructor} 499 */ 500 public List<ExamInstructor> getInstructors() { 501 return iInstructors; 502 } 503 504 /** 505 * Distribution constraints 506 * 507 * @return list of {@link ExamDistributionConstraint} 508 */ 509 public List<ExamDistributionConstraint> getDistributionConstraints() { 510 return iDistributionConstraints; 511 } 512 513 private String getId(boolean anonymize, String type, String id) { 514 return (anonymize ? IdConvertor.getInstance().convert(type, id) : id); 515 } 516 517 /** 518 * Save model (including its solution) into XML. 519 * @param assignment current assignment 520 * @return created XML document 521 */ 522 public Document save(Assignment<Exam, ExamPlacement> assignment) { 523 boolean saveInitial = getProperties().getPropertyBoolean("Xml.SaveInitial", true); 524 boolean saveBest = getProperties().getPropertyBoolean("Xml.SaveBest", true); 525 boolean saveSolution = getProperties().getPropertyBoolean("Xml.SaveSolution", true); 526 boolean saveConflictTable = getProperties().getPropertyBoolean("Xml.SaveConflictTable", false); 527 boolean saveParams = getProperties().getPropertyBoolean("Xml.SaveParameters", true); 528 boolean anonymize = getProperties().getPropertyBoolean("Xml.Anonymize", false); 529 boolean idconv = getProperties().getPropertyBoolean("Xml.ConvertIds", anonymize); 530 Document document = DocumentHelper.createDocument(); 531 document.addComment("Examination Timetable"); 532 if (assignment != null && assignment.nrAssignedVariables() > 0) { 533 StringBuffer comments = new StringBuffer("Solution Info:\n"); 534 Map<String, String> solutionInfo = (getProperties().getPropertyBoolean("Xml.ExtendedInfo", false) ? getExtendedInfo(assignment) : getInfo(assignment)); 535 for (String key : new TreeSet<String>(solutionInfo.keySet())) { 536 String value = solutionInfo.get(key); 537 comments.append(" " + key + ": " + value + "\n"); 538 } 539 document.addComment(comments.toString()); 540 } 541 Element root = document.addElement("examtt"); 542 root.addAttribute("version", "1.0"); 543 root.addAttribute("campus", getProperties().getProperty("Data.Initiative")); 544 root.addAttribute("term", getProperties().getProperty("Data.Term")); 545 root.addAttribute("year", getProperties().getProperty("Data.Year")); 546 root.addAttribute("created", String.valueOf(new Date())); 547 if (saveParams) { 548 Map<String, String> params = new HashMap<String, String>(); 549 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 550 if (criterion instanceof ExamCriterion) 551 ((ExamCriterion)criterion).getXmlParameters(params); 552 } 553 params.put("maxRooms", String.valueOf(getMaxRooms())); 554 Element parameters = root.addElement("parameters"); 555 for (String key: new TreeSet<String>(params.keySet())) { 556 parameters.addElement("property").addAttribute("name", key).addAttribute("value", params.get(key)); 557 } 558 } 559 Element periods = root.addElement("periods"); 560 for (ExamPeriod period : getPeriods()) { 561 periods.addElement("period").addAttribute("id", getId(idconv, "period", String.valueOf(period.getId()))) 562 .addAttribute("length", String.valueOf(period.getLength())).addAttribute("day", period.getDayStr()) 563 .addAttribute("time", period.getTimeStr()).addAttribute("penalty", 564 String.valueOf(period.getPenalty())); 565 } 566 Element rooms = root.addElement("rooms"); 567 for (ExamRoom room : getRooms()) { 568 Element r = rooms.addElement("room"); 569 r.addAttribute("id", getId(idconv, "room", String.valueOf(room.getId()))); 570 if (!anonymize && room.hasName()) 571 r.addAttribute("name", room.getName()); 572 r.addAttribute("size", String.valueOf(room.getSize())); 573 r.addAttribute("alt", String.valueOf(room.getAltSize())); 574 if (room.getCoordX() != null && room.getCoordY() != null) 575 r.addAttribute("coordinates", room.getCoordX() + "," + room.getCoordY()); 576 for (ExamPeriod period : getPeriods()) { 577 if (!room.isAvailable(period)) 578 r.addElement("period").addAttribute("id", 579 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 580 "false"); 581 else if (room.getPenalty(period) != 0) 582 r.addElement("period").addAttribute("id", 583 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("penalty", 584 String.valueOf(room.getPenalty(period))); 585 } 586 Map<Long, Integer> travelTimes = getDistanceMetric().getTravelTimes().get(room.getId()); 587 if (travelTimes != null) 588 for (Map.Entry<Long, Integer> time: travelTimes.entrySet()) 589 r.addElement("travel-time").addAttribute("id", getId(idconv, "room", time.getKey().toString())).addAttribute("minutes", time.getValue().toString()); 590 } 591 Element exams = root.addElement("exams"); 592 for (Exam exam : variables()) { 593 Element ex = exams.addElement("exam"); 594 ex.addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 595 if (!anonymize && exam.hasName()) 596 ex.addAttribute("name", exam.getName()); 597 ex.addAttribute("length", String.valueOf(exam.getLength())); 598 if (exam.getSizeOverride() != null) 599 ex.addAttribute("size", exam.getSizeOverride().toString()); 600 if (exam.getMinSize() != 0) 601 ex.addAttribute("minSize", String.valueOf(exam.getMinSize())); 602 ex.addAttribute("alt", (exam.hasAltSeating() ? "true" : "false")); 603 if (exam.getMaxRooms() != getMaxRooms()) 604 ex.addAttribute("maxRooms", String.valueOf(exam.getMaxRooms())); 605 if (exam.getPrintOffset() != null && !anonymize) 606 ex.addAttribute("printOffset", exam.getPrintOffset().toString()); 607 if (!anonymize) 608 ex.addAttribute("enrl", String.valueOf(exam.getStudents().size())); 609 if (!anonymize) 610 for (ExamOwner owner : exam.getOwners()) { 611 Element o = ex.addElement("owner"); 612 o.addAttribute("id", getId(idconv, "owner", String.valueOf(owner.getId()))); 613 o.addAttribute("name", owner.getName()); 614 } 615 for (ExamPeriodPlacement period : exam.getPeriodPlacements()) { 616 Element pe = ex.addElement("period").addAttribute("id", 617 getId(idconv, "period", String.valueOf(period.getId()))); 618 int penalty = period.getExamPenalty(); 619 if (penalty != 0) 620 pe.addAttribute("penalty", String.valueOf(penalty)); 621 } 622 for (ExamRoomPlacement room : exam.getRoomPlacements()) { 623 Element re = ex.addElement("room").addAttribute("id", 624 getId(idconv, "room", String.valueOf(room.getId()))); 625 if (room.getPenalty() != 0) 626 re.addAttribute("penalty", String.valueOf(room.getPenalty())); 627 if (room.getMaxPenalty() != 100) 628 re.addAttribute("maxPenalty", String.valueOf(room.getMaxPenalty())); 629 } 630 if (exam.hasAveragePeriod()) 631 ex.addAttribute("average", String.valueOf(exam.getAveragePeriod())); 632 ExamPlacement p = (assignment == null ? null : assignment.getValue(exam)); 633 if (p != null && saveSolution) { 634 Element asg = ex.addElement("assignment"); 635 asg.addElement("period").addAttribute("id", 636 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 637 for (ExamRoomPlacement r : p.getRoomPlacements()) { 638 asg.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 639 } 640 } 641 p = exam.getInitialAssignment(); 642 if (p != null && saveInitial) { 643 Element ini = ex.addElement("initial"); 644 ini.addElement("period").addAttribute("id", 645 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 646 for (ExamRoomPlacement r : p.getRoomPlacements()) { 647 ini.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 648 } 649 } 650 p = exam.getBestAssignment(); 651 if (p != null && saveBest) { 652 Element ini = ex.addElement("best"); 653 ini.addElement("period").addAttribute("id", 654 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 655 for (ExamRoomPlacement r : p.getRoomPlacements()) { 656 ini.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 657 } 658 } 659 if (iRoomSharing != null) 660 iRoomSharing.save(exam, ex, anonymize ? IdConvertor.getInstance() : null); 661 } 662 Element students = root.addElement("students"); 663 for (ExamStudent student : getStudents()) { 664 Element s = students.addElement("student"); 665 s.addAttribute("id", getId(idconv, "student", String.valueOf(student.getId()))); 666 for (Exam ex : student.variables()) { 667 Element x = s.addElement("exam").addAttribute("id", 668 getId(idconv, "exam", String.valueOf(ex.getId()))); 669 if (!anonymize) 670 for (ExamOwner owner : ex.getOwners(student)) { 671 x.addElement("owner").addAttribute("id", 672 getId(idconv, "owner", String.valueOf(owner.getId()))); 673 } 674 } 675 for (ExamPeriod period : getPeriods()) { 676 if (!student.isAvailable(period)) 677 s.addElement("period").addAttribute("id", 678 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 679 "false"); 680 } 681 } 682 Element instructors = root.addElement("instructors"); 683 for (ExamInstructor instructor : getInstructors()) { 684 Element i = instructors.addElement("instructor"); 685 i.addAttribute("id", getId(idconv, "instructor", String.valueOf(instructor.getId()))); 686 if (!anonymize && instructor.hasName()) 687 i.addAttribute("name", instructor.getName()); 688 for (Exam ex : instructor.variables()) { 689 Element x = i.addElement("exam").addAttribute("id", 690 getId(idconv, "exam", String.valueOf(ex.getId()))); 691 if (!anonymize) 692 for (ExamOwner owner : ex.getOwners(instructor)) { 693 x.addElement("owner").addAttribute("id", 694 getId(idconv, "owner", String.valueOf(owner.getId()))); 695 } 696 } 697 for (ExamPeriod period : getPeriods()) { 698 if (!instructor.isAvailable(period)) 699 i.addElement("period").addAttribute("id", 700 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 701 "false"); 702 } 703 } 704 Element distConstraints = root.addElement("constraints"); 705 for (ExamDistributionConstraint distConstraint : getDistributionConstraints()) { 706 Element dc = distConstraints.addElement(distConstraint.getTypeString()); 707 dc.addAttribute("id", getId(idconv, "constraint", String.valueOf(distConstraint.getId()))); 708 if (!distConstraint.isHard()) { 709 dc.addAttribute("hard", "false"); 710 dc.addAttribute("weight", String.valueOf(distConstraint.getWeight())); 711 } 712 for (Exam exam : distConstraint.variables()) { 713 dc.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 714 } 715 } 716 if (saveConflictTable && assignment != null) { 717 Element conflicts = root.addElement("conflicts"); 718 Map<ExamStudent, Set<Exam>> studentsOfPreviousPeriod = null; 719 for (ExamPeriod period : getPeriods()) { 720 Map<ExamStudent, Set<Exam>> studentsOfPeriod = getStudentsOfPeriod(assignment, period); 721 for (Map.Entry<ExamStudent, Set<Exam>> entry : studentsOfPeriod.entrySet()) { 722 ExamStudent student = entry.getKey(); 723 Set<Exam> examsOfStudent = entry.getValue(); 724 if (examsOfStudent.size() > 1) { 725 Element dir = conflicts.addElement("direct").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 726 for (Exam exam : examsOfStudent) { 727 dir.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 728 } 729 } 730 if (examsOfStudent.size() > 0 && studentsOfPreviousPeriod != null && (isDayBreakBackToBack() || period.prev().getDay() == period.getDay())) { 731 Set<Exam> previousExamsOfStudent = studentsOfPreviousPeriod.get(student); 732 if (previousExamsOfStudent != null) { 733 for (Exam ex1 : previousExamsOfStudent) 734 for (Exam ex2 : examsOfStudent) { 735 Element btb = conflicts.addElement("back-to-back").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 736 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex1.getId()))); 737 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex2.getId()))); 738 if (getBackToBackDistance() >= 0 && period.prev().getDay() == period.getDay()) { 739 double dist = (assignment.getValue(ex1)).getDistanceInMeters(assignment.getValue(ex2)); 740 if (dist > 0) 741 btb.addAttribute("distance", String.valueOf(dist)); 742 } 743 } 744 } 745 } 746 } 747 if (period.next() == null || period.next().getDay() != period.getDay()) { 748 Map<ExamStudent, Set<Exam>> studentsOfDay = getStudentsOfDay(assignment, period); 749 for (Map.Entry<ExamStudent, Set<Exam>> entry : studentsOfDay.entrySet()) { 750 ExamStudent student = entry.getKey(); 751 Set<Exam> examsOfStudent = entry.getValue(); 752 if (examsOfStudent.size() > 2) { 753 Element mt2 = conflicts.addElement("more-2-day").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 754 for (Exam exam : examsOfStudent) { 755 mt2.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 756 } 757 } 758 } 759 } 760 studentsOfPreviousPeriod = studentsOfPeriod; 761 } 762 /* 763 Element conflicts = root.addElement("conflicts"); 764 for (ExamStudent student : getStudents()) { 765 for (ExamPeriod period : getPeriods()) { 766 int nrExams = student.getExams(assignment, period).size(); 767 if (nrExams > 1) { 768 Element dir = conflicts.addElement("direct").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 769 for (Exam exam : student.getExams(assignment, period)) { 770 dir.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 771 } 772 } 773 if (nrExams > 0) { 774 if (period.next() != null && !student.getExams(assignment, period.next()).isEmpty() 775 && (!isDayBreakBackToBack() || period.next().getDay() == period.getDay())) { 776 for (Exam ex1 : student.getExams(assignment, period)) { 777 for (Exam ex2 : student.getExams(assignment, period.next())) { 778 Element btb = conflicts.addElement("back-to-back").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 779 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex1.getId()))); 780 btb.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(ex2.getId()))); 781 if (getBackToBackDistance() >= 0) { 782 double dist = (assignment.getValue(ex1)).getDistanceInMeters(assignment.getValue(ex2)); 783 if (dist > 0) 784 btb.addAttribute("distance", String.valueOf(dist)); 785 } 786 } 787 } 788 } 789 } 790 if (period.next() == null || period.next().getDay() != period.getDay()) { 791 int nrExamsADay = student.getExamsADay(assignment, period.getDay()).size(); 792 if (nrExamsADay > 2) { 793 Element mt2 = conflicts.addElement("more-2-day").addAttribute("student", getId(idconv, "student", String.valueOf(student.getId()))); 794 for (Exam exam : student.getExamsADay(assignment, period.getDay())) { 795 mt2.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 796 } 797 } 798 } 799 } 800 } 801 */ 802 } 803 return document; 804 } 805 806 /** 807 * Load model (including its solution) from XML. 808 * @param document XML document 809 * @param assignment assignment to be loaded 810 * @return true if successfully loaded 811 */ 812 public boolean load(Document document, Assignment<Exam, ExamPlacement> assignment) { 813 return load(document, assignment, null); 814 } 815 816 /** 817 * Load model (including its solution) from XML. 818 * @param document XML document 819 * @param assignment assignment to be loaded 820 * @param saveBest callback executed once the best assignment is loaded and assigned 821 * @return true if successfully loaded 822 */ 823 public boolean load(Document document, Assignment<Exam, ExamPlacement> assignment, Callback saveBest) { 824 boolean loadInitial = getProperties().getPropertyBoolean("Xml.LoadInitial", true); 825 boolean loadBest = getProperties().getPropertyBoolean("Xml.LoadBest", true); 826 boolean loadSolution = getProperties().getPropertyBoolean("Xml.LoadSolution", true); 827 boolean loadParams = getProperties().getPropertyBoolean("Xml.LoadParameters", false); 828 Integer softPeriods = getProperties().getPropertyInteger("Exam.SoftPeriods", null); 829 Integer softRooms = getProperties().getPropertyInteger("Exam.SoftRooms", null); 830 Integer softDistributions = getProperties().getPropertyInteger("Exam.SoftDistributions", null); 831 Element root = document.getRootElement(); 832 if (!"examtt".equals(root.getName())) 833 return false; 834 if (root.attribute("campus") != null) 835 getProperties().setProperty("Data.Campus", root.attributeValue("campus")); 836 else if (root.attribute("initiative") != null) 837 getProperties().setProperty("Data.Initiative", root.attributeValue("initiative")); 838 if (root.attribute("term") != null) 839 getProperties().setProperty("Data.Term", root.attributeValue("term")); 840 if (root.attribute("year") != null) 841 getProperties().setProperty("Data.Year", root.attributeValue("year")); 842 if (loadParams && root.element("parameters") != null) { 843 Map<String,String> params = new HashMap<String, String>(); 844 for (Iterator<?> i = root.element("parameters").elementIterator("property"); i.hasNext();) { 845 Element e = (Element) i.next(); 846 params.put(e.attributeValue("name"), e.attributeValue("value")); 847 } 848 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 849 if (criterion instanceof ExamCriterion) 850 ((ExamCriterion)criterion).setXmlParameters(params); 851 } 852 try { 853 setMaxRooms(Integer.valueOf(params.get("maxRooms"))); 854 } catch (NumberFormatException e) {} catch (NullPointerException e) {} 855 } 856 for (Iterator<?> i = root.element("periods").elementIterator("period"); i.hasNext();) { 857 Element e = (Element) i.next(); 858 addPeriod(Long.valueOf(e.attributeValue("id")), e.attributeValue("day"), e.attributeValue("time"), Integer 859 .parseInt(e.attributeValue("length")), Integer.parseInt(e.attributeValue("penalty") == null ? e 860 .attributeValue("weight", "0") : e.attributeValue("penalty"))); 861 } 862 HashMap<Long, ExamRoom> rooms = new HashMap<Long, ExamRoom>(); 863 HashMap<String, ArrayList<ExamRoom>> roomGroups = new HashMap<String, ArrayList<ExamRoom>>(); 864 for (Iterator<?> i = root.element("rooms").elementIterator("room"); i.hasNext();) { 865 Element e = (Element) i.next(); 866 String coords = e.attributeValue("coordinates"); 867 ExamRoom room = new ExamRoom(this, Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), 868 Integer.parseInt(e.attributeValue("size")), Integer.parseInt(e.attributeValue("alt")), 869 (coords == null ? null : Double.valueOf(coords.substring(0, coords.indexOf(',')))), 870 (coords == null ? null : Double.valueOf(coords.substring(coords.indexOf(',') + 1)))); 871 addConstraint(room); 872 getRooms().add(room); 873 rooms.put(new Long(room.getId()), room); 874 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 875 Element pe = (Element) j.next(); 876 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 877 if (period == null) continue; 878 if ("false".equals(pe.attributeValue("available"))) { 879 if (softRooms == null) 880 room.setAvailable(period, false); 881 else 882 room.setPenalty(period, softRooms); 883 } else 884 room.setPenalty(period, Integer.parseInt(pe.attributeValue("penalty"))); 885 } 886 String av = e.attributeValue("available"); 887 if (av != null) { 888 for (int j = 0; j < getPeriods().size(); j++) 889 if ('0' == av.charAt(j)) 890 room.setAvailable(getPeriods().get(j), false); 891 } 892 String g = e.attributeValue("groups"); 893 if (g != null) { 894 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 895 String gr = s.nextToken(); 896 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 897 if (roomsThisGrop == null) { 898 roomsThisGrop = new ArrayList<ExamRoom>(); 899 roomGroups.put(gr, roomsThisGrop); 900 } 901 roomsThisGrop.add(room); 902 } 903 } 904 for (Iterator<?> j = e.elementIterator("travel-time"); j.hasNext();) { 905 Element travelTimeEl = (Element)j.next(); 906 getDistanceMetric().addTravelTime(room.getId(), 907 Long.valueOf(travelTimeEl.attributeValue("id")), 908 Integer.valueOf(travelTimeEl.attributeValue("minutes"))); 909 } 910 } 911 ArrayList<ExamPlacement> assignments = new ArrayList<ExamPlacement>(); 912 HashMap<Long, Exam> exams = new HashMap<Long, Exam>(); 913 HashMap<Long, ExamOwner> courseSections = new HashMap<Long, ExamOwner>(); 914 for (Iterator<?> i = root.element("exams").elementIterator("exam"); i.hasNext();) { 915 Element e = (Element) i.next(); 916 ArrayList<ExamPeriodPlacement> periodPlacements = new ArrayList<ExamPeriodPlacement>(); 917 if (softPeriods != null) { 918 for (ExamPeriod period: getPeriods()) { 919 int penalty = softPeriods; 920 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 921 Element pe = (Element) j.next(); 922 if (period.getId().equals(Long.valueOf(pe.attributeValue("id")))) { 923 penalty = Integer.parseInt(pe.attributeValue("penalty", "0")); 924 break; 925 } 926 } 927 periodPlacements.add(new ExamPeriodPlacement(period, penalty)); 928 } 929 } else { 930 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 931 Element pe = (Element) j.next(); 932 ExamPeriod p = getPeriod(Long.valueOf(pe.attributeValue("id"))); 933 if (p != null) 934 periodPlacements.add(new ExamPeriodPlacement(p, Integer.parseInt(pe.attributeValue("penalty", "0")))); 935 } 936 } 937 ArrayList<ExamRoomPlacement> roomPlacements = new ArrayList<ExamRoomPlacement>(); 938 if (softRooms != null) { 939 for (ExamRoom room: getRooms()) { 940 boolean av = false; 941 for (ExamPeriodPlacement p: periodPlacements) { 942 if (room.isAvailable(p.getPeriod()) && room.getPenalty(p.getPeriod()) != softRooms) { av = true; break; } 943 } 944 if (!av) continue; 945 int penalty = softRooms, maxPenalty = softRooms; 946 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 947 Element re = (Element) j.next(); 948 if (room.getId() == Long.parseLong(re.attributeValue("id"))) { 949 penalty = Integer.parseInt(re.attributeValue("penalty", "0")); 950 maxPenalty = Integer.parseInt(re.attributeValue("maxPenalty", softRooms.toString())); 951 } 952 } 953 roomPlacements.add(new ExamRoomPlacement(room, penalty, maxPenalty)); 954 } 955 } else { 956 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 957 Element re = (Element) j.next(); 958 ExamRoomPlacement room = new ExamRoomPlacement(rooms.get(Long.valueOf(re.attributeValue("id"))), 959 Integer.parseInt(re.attributeValue("penalty", "0")), 960 Integer.parseInt(re.attributeValue("maxPenalty", "100"))); 961 if (room.getRoom().isAvailable()) 962 roomPlacements.add(room); 963 } 964 } 965 String g = e.attributeValue("groups"); 966 if (g != null) { 967 HashMap<ExamRoom, Integer> allRooms = new HashMap<ExamRoom, Integer>(); 968 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 969 String gr = s.nextToken(); 970 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 971 if (roomsThisGrop != null) 972 for (ExamRoom r : roomsThisGrop) 973 allRooms.put(r, 0); 974 } 975 for (Iterator<?> j = e.elementIterator("original-room"); j.hasNext();) { 976 allRooms.put((rooms.get(Long.valueOf(((Element) j.next()).attributeValue("id")))), new Integer(-1)); 977 } 978 for (Map.Entry<ExamRoom, Integer> entry : allRooms.entrySet()) { 979 ExamRoomPlacement room = new ExamRoomPlacement(entry.getKey(), entry.getValue(), 100); 980 roomPlacements.add(room); 981 } 982 if (periodPlacements.isEmpty()) { 983 for (ExamPeriod p : getPeriods()) { 984 periodPlacements.add(new ExamPeriodPlacement(p, 0)); 985 } 986 } 987 } 988 Exam exam = new Exam(Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), Integer.parseInt(e 989 .attributeValue("length")), "true".equals(e.attributeValue("alt")), 990 (e.attribute("maxRooms") == null ? getMaxRooms() : Integer.parseInt(e.attributeValue("maxRooms"))), 991 Integer.parseInt(e.attributeValue("minSize", "0")), periodPlacements, roomPlacements); 992 if (e.attributeValue("size") != null) 993 exam.setSizeOverride(Integer.valueOf(e.attributeValue("size"))); 994 if (e.attributeValue("printOffset") != null) 995 exam.setPrintOffset(Integer.valueOf(e.attributeValue("printOffset"))); 996 exams.put(new Long(exam.getId()), exam); 997 addVariable(exam); 998 if (e.attribute("average") != null) 999 exam.setAveragePeriod(Integer.parseInt(e.attributeValue("average"))); 1000 Element asg = e.element("assignment"); 1001 if (asg != null && loadSolution) { 1002 Element per = asg.element("period"); 1003 if (per != null) { 1004 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 1005 for (Iterator<?> j = asg.elementIterator("room"); j.hasNext();) 1006 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 1007 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 1008 if (pp != null) 1009 assignments.add(new ExamPlacement(exam, pp, rp)); 1010 } 1011 } 1012 Element ini = e.element("initial"); 1013 if (ini != null && loadInitial) { 1014 Element per = ini.element("period"); 1015 if (per != null) { 1016 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 1017 for (Iterator<?> j = ini.elementIterator("room"); j.hasNext();) 1018 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 1019 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 1020 if (pp != null) 1021 exam.setInitialAssignment(new ExamPlacement(exam, pp, rp)); 1022 } 1023 } 1024 Element best = e.element("best"); 1025 if (best != null && loadBest) { 1026 Element per = best.element("period"); 1027 if (per != null) { 1028 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 1029 for (Iterator<?> j = best.elementIterator("room"); j.hasNext();) 1030 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 1031 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 1032 if (pp != null) 1033 exam.setBestAssignment(new ExamPlacement(exam, pp, rp), 0); 1034 } 1035 } 1036 for (Iterator<?> j = e.elementIterator("owner"); j.hasNext();) { 1037 Element f = (Element) j.next(); 1038 ExamOwner owner = new ExamOwner(exam, Long.parseLong(f.attributeValue("id")), f.attributeValue("name")); 1039 exam.getOwners().add(owner); 1040 courseSections.put(new Long(owner.getId()), owner); 1041 } 1042 if (iRoomSharing != null) 1043 iRoomSharing.load(exam, e); 1044 } 1045 for (Iterator<?> i = root.element("students").elementIterator("student"); i.hasNext();) { 1046 Element e = (Element) i.next(); 1047 ExamStudent student = new ExamStudent(this, Long.parseLong(e.attributeValue("id"))); 1048 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1049 Element x = (Element) j.next(); 1050 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 1051 student.addVariable(ex); 1052 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 1053 Element f = (Element) k.next(); 1054 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 1055 student.getOwners().add(owner); 1056 owner.getStudents().add(student); 1057 } 1058 } 1059 String available = e.attributeValue("available"); 1060 if (available != null) 1061 for (ExamPeriod period : getPeriods()) { 1062 if (available.charAt(period.getIndex()) == '0') 1063 student.setAvailable(period.getIndex(), false); 1064 } 1065 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 1066 Element pe = (Element) j.next(); 1067 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 1068 if (period == null) continue; 1069 if ("false".equals(pe.attributeValue("available"))) 1070 student.setAvailable(period.getIndex(), false); 1071 } 1072 addConstraint(student); 1073 getStudents().add(student); 1074 } 1075 if (root.element("instructors") != null) 1076 for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) { 1077 Element e = (Element) i.next(); 1078 ExamInstructor instructor = new ExamInstructor(this, Long.parseLong(e.attributeValue("id")), e 1079 .attributeValue("name")); 1080 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1081 Element x = (Element) j.next(); 1082 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 1083 instructor.addVariable(ex); 1084 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 1085 Element f = (Element) k.next(); 1086 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 1087 instructor.getOwners().add(owner); 1088 owner.getIntructors().add(instructor); 1089 } 1090 } 1091 String available = e.attributeValue("available"); 1092 if (available != null) 1093 for (ExamPeriod period : getPeriods()) { 1094 if (available.charAt(period.getIndex()) == '0') 1095 instructor.setAvailable(period.getIndex(), false); 1096 } 1097 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 1098 Element pe = (Element) j.next(); 1099 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 1100 if (period == null) continue; 1101 if ("false".equals(pe.attributeValue("available"))) 1102 instructor.setAvailable(period.getIndex(), false); 1103 } 1104 addConstraint(instructor); 1105 getInstructors().add(instructor); 1106 } 1107 if (root.element("constraints") != null) 1108 for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) { 1109 Element e = (Element) i.next(); 1110 ExamDistributionConstraint dc = new ExamDistributionConstraint(Long.parseLong(e.attributeValue("id")), 1111 e.getName(), 1112 softDistributions != null ? false : "true".equals(e.attributeValue("hard", "true")), 1113 (softDistributions != null && "true".equals(e.attributeValue("hard", "true")) ? softDistributions : Integer.parseInt(e.attributeValue("weight", "0")))); 1114 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1115 dc.addVariable(exams.get(Long.valueOf(((Element) j.next()).attributeValue("id")))); 1116 } 1117 addConstraint(dc); 1118 getDistributionConstraints().add(dc); 1119 } 1120 init(); 1121 if (loadBest && saveBest != null && assignment != null) { 1122 for (Exam exam : variables()) { 1123 ExamPlacement placement = exam.getBestAssignment(); 1124 if (placement == null) 1125 continue; 1126 assignment.assign(0, placement); 1127 } 1128 saveBest.execute(); 1129 for (Exam exam : variables()) { 1130 if (assignment.getValue(exam) != null) 1131 assignment.unassign(0, exam); 1132 } 1133 } 1134 if (assignment != null) { 1135 for (ExamPlacement placement : assignments) { 1136 Exam exam = placement.variable(); 1137 Set<ExamPlacement> conf = conflictValues(assignment, placement); 1138 if (!conf.isEmpty()) { 1139 for (Map.Entry<Constraint<Exam, ExamPlacement>, Set<ExamPlacement>> entry : conflictConstraints(assignment, placement).entrySet()) { 1140 Constraint<Exam, ExamPlacement> constraint = entry.getKey(); 1141 Set<ExamPlacement> values = entry.getValue(); 1142 if (constraint instanceof ExamStudent) { 1143 ((ExamStudent) constraint).setAllowDirectConflicts(true); 1144 exam.setAllowDirectConflicts(true); 1145 for (ExamPlacement p : values) 1146 p.variable().setAllowDirectConflicts(true); 1147 } 1148 } 1149 conf = conflictValues(assignment, placement); 1150 } 1151 if (conf.isEmpty()) { 1152 assignment.assign(0, placement); 1153 } else { 1154 sLog.error("Unable to assign " + exam.getInitialAssignment().getName() + " to exam " + exam.getName()); 1155 sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(assignment, exam.getInitialAssignment()), 2)); 1156 } 1157 } 1158 } 1159 return true; 1160 } 1161 1162 @Override 1163 public ExamContext createAssignmentContext(Assignment<Exam, ExamPlacement> assignment) { 1164 return new ExamContext(this, assignment); 1165 } 1166 1167 public Map<ExamStudent, Set<Exam>> getStudentsOfPeriod(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1168 return getContext(assignment).getStudentsOfPeriod(period.getIndex()); 1169 } 1170 1171 public Map<ExamStudent, Set<Exam>> getStudentsOfDay(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1172 return getContext(assignment).getStudentsOfDay(period.getDay()); 1173 } 1174 1175 public Map<ExamStudent, Set<Exam>> getStudentsOfDay(Assignment<Exam, ExamPlacement> assignment, int day) { 1176 return getContext(assignment).getStudentsOfDay(day); 1177 } 1178 1179 public Map<ExamInstructor, Set<Exam>> getInstructorsOfPeriod(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1180 return getContext(assignment).getInstructorsOfPeriod(period.getIndex()); 1181 } 1182 1183 public Map<ExamInstructor, Set<Exam>> getInstructorsOfDay(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) { 1184 return getContext(assignment).getInstructorsOfDay(period.getDay()); 1185 } 1186 1187 public Map<ExamInstructor, Set<Exam>> getInstructorsOfDay(Assignment<Exam, ExamPlacement> assignment, int day) { 1188 return getContext(assignment).getInstructorsOfDay(day); 1189 } 1190 1191}