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