001package net.sf.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 014import net.sf.cpsolver.coursett.IdConvertor; 015import net.sf.cpsolver.exam.criteria.DistributionPenalty; 016import net.sf.cpsolver.exam.criteria.ExamCriterion; 017import net.sf.cpsolver.exam.criteria.ExamRotationPenalty; 018import net.sf.cpsolver.exam.criteria.InstructorBackToBackConflicts; 019import net.sf.cpsolver.exam.criteria.InstructorDirectConflicts; 020import net.sf.cpsolver.exam.criteria.InstructorDistanceBackToBackConflicts; 021import net.sf.cpsolver.exam.criteria.InstructorMoreThan2ADayConflicts; 022import net.sf.cpsolver.exam.criteria.InstructorNotAvailableConflicts; 023import net.sf.cpsolver.exam.criteria.LargeExamsPenalty; 024import net.sf.cpsolver.exam.criteria.PeriodIndexPenalty; 025import net.sf.cpsolver.exam.criteria.PeriodPenalty; 026import net.sf.cpsolver.exam.criteria.PeriodSizePenalty; 027import net.sf.cpsolver.exam.criteria.PerturbationPenalty; 028import net.sf.cpsolver.exam.criteria.RoomPenalty; 029import net.sf.cpsolver.exam.criteria.RoomPerturbationPenalty; 030import net.sf.cpsolver.exam.criteria.RoomSizePenalty; 031import net.sf.cpsolver.exam.criteria.RoomSplitDistancePenalty; 032import net.sf.cpsolver.exam.criteria.RoomSplitPenalty; 033import net.sf.cpsolver.exam.criteria.StudentBackToBackConflicts; 034import net.sf.cpsolver.exam.criteria.StudentDirectConflicts; 035import net.sf.cpsolver.exam.criteria.StudentMoreThan2ADayConflicts; 036import net.sf.cpsolver.exam.criteria.StudentDistanceBackToBackConflicts; 037import net.sf.cpsolver.exam.criteria.StudentNotAvailableConflicts; 038import net.sf.cpsolver.ifs.criteria.Criterion; 039import net.sf.cpsolver.ifs.model.Constraint; 040import net.sf.cpsolver.ifs.model.Model; 041import net.sf.cpsolver.ifs.util.Callback; 042import net.sf.cpsolver.ifs.util.DataProperties; 043import net.sf.cpsolver.ifs.util.DistanceMetric; 044import net.sf.cpsolver.ifs.util.ToolBox; 045 046import org.apache.log4j.Logger; 047import org.dom4j.Document; 048import org.dom4j.DocumentHelper; 049import org.dom4j.Element; 050 051/** 052 * Examination timetabling model. Exams {@link Exam} are modeled as variables, 053 * rooms {@link ExamRoom} and students {@link ExamStudent} as constraints. 054 * Assignment of an exam to time (modeled as non-overlapping periods 055 * {@link ExamPeriod}) and space (set of rooms) is modeled using values 056 * {@link ExamPlacement}. In order to be able to model individual period and 057 * room preferences, period and room assignments are wrapped with 058 * {@link ExamPeriodPlacement} and {@link ExamRoomPlacement} classes 059 * respectively. Moreover, additional distribution constraint 060 * {@link ExamDistributionConstraint} can be defined in the model. <br> 061 * <br> 062 * The objective function consists of the following criteria: 063 * <ul> 064 * <li>Direct student conflicts (a student is enrolled in two exams that are 065 * scheduled at the same period, weighted by Exams.DirectConflictWeight) 066 * <li>Back-to-Back student conflicts (a student is enrolled in two exams that 067 * are scheduled in consecutive periods, weighted by 068 * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false, 069 * there is no conflict between the last period and the first period of 070 * consecutive days. 071 * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student 072 * conflict, but the maximum distance between rooms in which both exam take 073 * place is greater than Exams.BackToBackDistance, weighted by 074 * Exams.DistanceBackToBackConflictWeight). 075 * <li>More than two exams a day (a student is enrolled in three exams that are 076 * scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight). 077 * <li>Period penalty (total of period penalties 078 * {@link PeriodPenalty} of all assigned exams, weighted by 079 * Exams.PeriodWeight). 080 * <li>Room size penalty (total of room size penalties 081 * {@link RoomSizePenalty} of all assigned exams, weighted by 082 * Exams.RoomSizeWeight). 083 * <li>Room split penalty (total of room split penalties 084 * {@link RoomSplitPenalty} of all assigned exams, weighted 085 * by Exams.RoomSplitWeight). 086 * <li>Room penalty (total of room penalties 087 * {@link RoomPenalty} of all assigned exams, weighted by 088 * Exams.RoomWeight). 089 * <li>Distribution penalty (total of distribution constraint weights 090 * {@link ExamDistributionConstraint#getWeight()} of all soft distribution 091 * constraints that are not satisfied, i.e., 092 * {@link ExamDistributionConstraint#isSatisfied()} = false; weighted by 093 * Exams.DistributionWeight). 094 * <li>Direct instructor conflicts (an instructor is enrolled in two exams that 095 * are scheduled at the same period, weighted by 096 * Exams.InstructorDirectConflictWeight) 097 * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two exams 098 * that are scheduled in consecutive periods, weighted by 099 * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack is 100 * false, there is no conflict between the last period and the first period of 101 * consecutive days. 102 * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back 103 * instructor conflict, but the maximum distance between rooms in which both 104 * exam take place is greater than Exams.BackToBackDistance, weighted by 105 * Exams.InstructorDistanceBackToBackConflictWeight). 106 * <li>Room split distance penalty (if an examination is assigned between two or 107 * three rooms, distance between these rooms can be minimized using this 108 * criterion) 109 * <li>Front load penalty (large exams can be penalized if assigned on or after 110 * a certain period) 111 * </ul> 112 * 113 * @version ExamTT 1.2 (Examination Timetabling)<br> 114 * Copyright (C) 2008 - 2010 Tomáš Müller<br> 115 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 116 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 117 * <br> 118 * This library is free software; you can redistribute it and/or modify 119 * it under the terms of the GNU Lesser General Public License as 120 * published by the Free Software Foundation; either version 3 of the 121 * License, or (at your option) any later version. <br> 122 * <br> 123 * This library is distributed in the hope that it will be useful, but 124 * WITHOUT ANY WARRANTY; without even the implied warranty of 125 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 126 * Lesser General Public License for more details. <br> 127 * <br> 128 * You should have received a copy of the GNU Lesser General Public 129 * License along with this library; if not see 130 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 131 */ 132public class ExamModel extends Model<Exam, ExamPlacement> { 133 private static Logger sLog = Logger.getLogger(ExamModel.class); 134 private DataProperties iProperties = null; 135 private int iMaxRooms = 4; 136 private List<ExamPeriod> iPeriods = new ArrayList<ExamPeriod>(); 137 private List<ExamRoom> iRooms = new ArrayList<ExamRoom>(); 138 private List<ExamStudent> iStudents = new ArrayList<ExamStudent>(); 139 private List<ExamDistributionConstraint> iDistributionConstraints = new ArrayList<ExamDistributionConstraint>(); 140 private List<ExamInstructor> iInstructors = new ArrayList<ExamInstructor>(); 141 private ExamRoomSharing iRoomSharing = null; 142 143 private DistanceMetric iDistanceMetric = null; 144 145 /** 146 * Constructor 147 * 148 * @param properties 149 * problem properties 150 */ 151 public ExamModel(DataProperties properties) { 152 iAssignedVariables = null; 153 iUnassignedVariables = null; 154 iPerturbVariables = null; 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 */ 212 public boolean hasRoomSharing() { return iRoomSharing != null; } 213 214 /** 215 * Return examination room sharing model 216 */ 217 public ExamRoomSharing getRoomSharing() { return iRoomSharing; } 218 219 /** 220 * Set examination sharing model 221 */ 222 public void setRoomSharing(ExamRoomSharing sharing) { 223 iRoomSharing = sharing; 224 } 225 226 /** 227 * Initialization of the model 228 */ 229 public void init() { 230 for (Exam exam : variables()) { 231 for (ExamRoomPlacement room : exam.getRoomPlacements()) { 232 room.getRoom().addVariable(exam); 233 } 234 } 235 } 236 237 /** 238 * Default maximum number of rooms (can be set by problem property 239 * Exams.MaxRooms, or in the input xml file, property maxRooms) 240 */ 241 public int getMaxRooms() { 242 return iMaxRooms; 243 } 244 245 /** 246 * Default maximum number of rooms (can be set by problem property 247 * Exams.MaxRooms, or in the input xml file, property maxRooms) 248 */ 249 public void setMaxRooms(int maxRooms) { 250 iMaxRooms = maxRooms; 251 } 252 253 /** 254 * Add a period 255 * 256 * @param id 257 * period unique identifier 258 * @param day 259 * day (e.g., 07/12/10) 260 * @param time 261 * (e.g., 8:00am-10:00am) 262 * @param length 263 * length of period in minutes 264 * @param penalty 265 * period penalty 266 */ 267 public ExamPeriod addPeriod(Long id, String day, String time, int length, int penalty) { 268 ExamPeriod lastPeriod = (iPeriods.isEmpty() ? null : (ExamPeriod) iPeriods.get(iPeriods.size() - 1)); 269 ExamPeriod p = new ExamPeriod(id, day, time, length, penalty); 270 if (lastPeriod == null) 271 p.setIndex(iPeriods.size(), 0, 0); 272 else if (lastPeriod.getDayStr().equals(day)) { 273 p.setIndex(iPeriods.size(), lastPeriod.getDay(), lastPeriod.getTime() + 1); 274 } else 275 p.setIndex(iPeriods.size(), lastPeriod.getDay() + 1, 0); 276 if (lastPeriod != null) { 277 lastPeriod.setNext(p); 278 p.setPrev(lastPeriod); 279 } 280 iPeriods.add(p); 281 return p; 282 } 283 284 /** 285 * Number of days 286 */ 287 public int getNrDays() { 288 return (iPeriods.get(iPeriods.size() - 1)).getDay() + 1; 289 } 290 291 /** 292 * Number of periods 293 */ 294 public int getNrPeriods() { 295 return iPeriods.size(); 296 } 297 298 /** 299 * List of periods, use 300 * {@link ExamModel#addPeriod(Long, String, String, int, int)} to add a 301 * period 302 * 303 * @return list of {@link ExamPeriod} 304 */ 305 public List<ExamPeriod> getPeriods() { 306 return iPeriods; 307 } 308 309 /** Period of given unique id */ 310 public ExamPeriod getPeriod(Long id) { 311 for (ExamPeriod period : iPeriods) { 312 if (period.getId().equals(id)) 313 return period; 314 } 315 return null; 316 } 317 318 /** 319 * True when back-to-back student conflict is to be encountered when a 320 * student is enrolled into an exam that is on the last period of one day 321 * and another exam that is on the first period of the consecutive day. It 322 * can be set by problem property Exams.IsDayBreakBackToBack, or in the 323 * input xml file, property isDayBreakBackToBack) 324 * 325 */ 326 public boolean isDayBreakBackToBack() { 327 return ((StudentBackToBackConflicts)getCriterion(StudentBackToBackConflicts.class)).isDayBreakBackToBack(); 328 } 329 330 /** 331 * Back-to-back distance, can be set by 332 * problem property Exams.BackToBackDistance, or in the input xml file, 333 * property backToBackDistance) 334 */ 335 public double getBackToBackDistance() { 336 return ((StudentDistanceBackToBackConflicts)getCriterion(StudentDistanceBackToBackConflicts.class)).getBackToBackDistance(); 337 } 338 339 /** 340 * Called before a value is unassigned from its variable, optimization 341 * criteria are updated 342 */ 343 @Override 344 public void beforeUnassigned(long iteration, ExamPlacement placement) { 345 super.beforeUnassigned(iteration, placement); 346 Exam exam = placement.variable(); 347 for (ExamStudent s : exam.getStudents()) 348 s.afterUnassigned(iteration, placement); 349 for (ExamInstructor i : exam.getInstructors()) 350 i.afterUnassigned(iteration, placement); 351 for (ExamRoomPlacement r : placement.getRoomPlacements()) 352 r.getRoom().afterUnassigned(iteration, placement); 353 } 354 355 /** 356 * Called after a value is assigned to its variable, optimization criteria 357 * are updated 358 */ 359 @Override 360 public void afterAssigned(long iteration, ExamPlacement placement) { 361 super.afterAssigned(iteration, placement); 362 Exam exam = placement.variable(); 363 for (ExamStudent s : exam.getStudents()) 364 s.afterAssigned(iteration, placement); 365 for (ExamInstructor i : exam.getInstructors()) 366 i.afterAssigned(iteration, placement); 367 for (ExamRoomPlacement r : placement.getRoomPlacements()) 368 r.getRoom().afterAssigned(iteration, placement); 369 } 370 371 /** 372 * Objective function. 373 * @return weighted sum of objective criteria 374 */ 375 @Override 376 public double getTotalValue() { 377 double total = 0; 378 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) 379 total += criterion.getWeightedValue(); 380 return total; 381 } 382 383 /** 384 * Return weighted individual objective criteria. 385 * @return an array of weighted objective criteria 386 */ 387 public double[] getTotalMultiValue() { 388 double[] total = new double[getCriteria().size()]; 389 int i = 0; 390 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) 391 total[i++] = criterion.getWeightedValue(); 392 return total; 393 } 394 395 /** 396 * String representation -- returns a list of values of objective criteria 397 */ 398 @Override 399 public String toString() { 400 Set<String> props = new TreeSet<String>(); 401 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 402 String val = criterion.toString(); 403 if (!val.isEmpty()) 404 props.add(val); 405 } 406 return props.toString(); 407 } 408 409 /** 410 * Extended info table 411 */ 412 @Override 413 public Map<String, String> getExtendedInfo() { 414 Map<String, String> info = super.getExtendedInfo(); 415 /* 416 info.put("Direct Conflicts [p]", String.valueOf(getNrDirectConflicts(true))); 417 info.put("More Than 2 A Day Conflicts [p]", String.valueOf(getNrMoreThanTwoADayConflicts(true))); 418 info.put("Back-To-Back Conflicts [p]", String.valueOf(getNrBackToBackConflicts(true))); 419 info.put("Distance Back-To-Back Conflicts [p]", String.valueOf(getNrDistanceBackToBackConflicts(true))); 420 info.put("Instructor Direct Conflicts [p]", String.valueOf(getNrInstructorDirectConflicts(true))); 421 info.put("Instructor More Than 2 A Day Conflicts [p]", String.valueOf(getNrInstructorMoreThanTwoADayConflicts(true))); 422 info.put("Instructor Back-To-Back Conflicts [p]", String.valueOf(getNrInstructorBackToBackConflicts(true))); 423 info.put("Instructor Distance Back-To-Back Conflicts [p]", String.valueOf(getNrInstructorDistanceBackToBackConflicts(true))); 424 info.put("Room Size Penalty [p]", String.valueOf(getRoomSizePenalty(true))); 425 info.put("Room Split Penalty [p]", String.valueOf(getRoomSplitPenalty(true))); 426 info.put("Period Penalty [p]", String.valueOf(getPeriodPenalty(true))); 427 info.put("Period Size Penalty [p]", String.valueOf(getPeriodSizePenalty(true))); 428 info.put("Period Index Penalty [p]", String.valueOf(getPeriodIndexPenalty(true))); 429 info.put("Room Penalty [p]", String.valueOf(getRoomPenalty(true))); 430 info.put("Distribution Penalty [p]", String.valueOf(getDistributionPenalty(true))); 431 info.put("Perturbation Penalty [p]", String.valueOf(getPerturbationPenalty(true))); 432 info.put("Room Perturbation Penalty [p]", String.valueOf(getRoomPerturbationPenalty(true))); 433 info.put("Room Split Distance Penalty [p]", sDoubleFormat.format(getRoomSplitDistancePenalty(true)) + " / " + getNrRoomSplits(true)); 434 */ 435 info.put("Number of Periods", String.valueOf(getPeriods().size())); 436 info.put("Number of Exams", String.valueOf(variables().size())); 437 info.put("Number of Rooms", String.valueOf(getRooms().size())); 438 info.put("Number of Students", String.valueOf(getStudents().size())); 439 int nrStudentExams = 0; 440 for (ExamStudent student : getStudents()) { 441 nrStudentExams += student.getOwners().size(); 442 } 443 info.put("Number of Student Exams", String.valueOf(nrStudentExams)); 444 int nrAltExams = 0, nrSmallExams = 0; 445 for (Exam exam : variables()) { 446 if (exam.hasAltSeating()) 447 nrAltExams++; 448 if (exam.getMaxRooms() == 0) 449 nrSmallExams++; 450 } 451 info.put("Number of Exams Requiring Alt Seating", String.valueOf(nrAltExams)); 452 info.put("Number of Small Exams (Exams W/O Room)", String.valueOf(nrSmallExams)); 453 int[] nbrMtgs = new int[11]; 454 for (int i = 0; i <= 10; i++) 455 nbrMtgs[i] = 0; 456 for (ExamStudent student : getStudents()) { 457 nbrMtgs[Math.min(10, student.variables().size())]++; 458 } 459 for (int i = 0; i <= 10; i++) { 460 if (nbrMtgs[i] == 0) 461 continue; 462 info.put("Number of Students with " + (i == 0 ? "no" : String.valueOf(i)) + (i == 10 ? " or more" : "") 463 + " meeting" + (i != 1 ? "s" : ""), String.valueOf(nbrMtgs[i])); 464 } 465 return info; 466 } 467 468 /** 469 * Problem properties 470 */ 471 public DataProperties getProperties() { 472 return iProperties; 473 } 474 475 /** 476 * Problem rooms 477 * 478 * @return list of {@link ExamRoom} 479 */ 480 public List<ExamRoom> getRooms() { 481 return iRooms; 482 } 483 484 /** 485 * Problem students 486 * 487 * @return list of {@link ExamStudent} 488 */ 489 public List<ExamStudent> getStudents() { 490 return iStudents; 491 } 492 493 /** 494 * Problem instructors 495 * 496 * @return list of {@link ExamInstructor} 497 */ 498 public List<ExamInstructor> getInstructors() { 499 return iInstructors; 500 } 501 502 /** 503 * Distribution constraints 504 * 505 * @return list of {@link ExamDistributionConstraint} 506 */ 507 public List<ExamDistributionConstraint> getDistributionConstraints() { 508 return iDistributionConstraints; 509 } 510 511 private String getId(boolean anonymize, String type, String id) { 512 return (anonymize ? IdConvertor.getInstance().convert(type, id) : id); 513 } 514 515 /** 516 * Save model (including its solution) into XML. 517 */ 518 public Document save() { 519 boolean saveInitial = getProperties().getPropertyBoolean("Xml.SaveInitial", true); 520 boolean saveBest = getProperties().getPropertyBoolean("Xml.SaveBest", true); 521 boolean saveSolution = getProperties().getPropertyBoolean("Xml.SaveSolution", true); 522 boolean saveConflictTable = getProperties().getPropertyBoolean("Xml.SaveConflictTable", false); 523 boolean saveParams = getProperties().getPropertyBoolean("Xml.SaveParameters", true); 524 boolean anonymize = getProperties().getPropertyBoolean("Xml.Anonymize", false); 525 boolean idconv = getProperties().getPropertyBoolean("Xml.ConvertIds", anonymize); 526 Document document = DocumentHelper.createDocument(); 527 document.addComment("Examination Timetable"); 528 if (nrAssignedVariables() > 0) { 529 StringBuffer comments = new StringBuffer("Solution Info:\n"); 530 Map<String, String> solutionInfo = (getProperties().getPropertyBoolean("Xml.ExtendedInfo", false) ? getExtendedInfo() 531 : getInfo()); 532 for (String key : new TreeSet<String>(solutionInfo.keySet())) { 533 String value = solutionInfo.get(key); 534 comments.append(" " + key + ": " + value + "\n"); 535 } 536 document.addComment(comments.toString()); 537 } 538 Element root = document.addElement("examtt"); 539 root.addAttribute("version", "1.0"); 540 root.addAttribute("campus", getProperties().getProperty("Data.Initiative")); 541 root.addAttribute("term", getProperties().getProperty("Data.Term")); 542 root.addAttribute("year", getProperties().getProperty("Data.Year")); 543 root.addAttribute("created", String.valueOf(new Date())); 544 if (saveParams) { 545 Map<String, String> params = new HashMap<String, String>(); 546 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 547 if (criterion instanceof ExamCriterion) 548 ((ExamCriterion)criterion).getXmlParameters(params); 549 } 550 params.put("maxRooms", String.valueOf(getMaxRooms())); 551 Element parameters = root.addElement("parameters"); 552 for (String key: new TreeSet<String>(params.keySet())) { 553 parameters.addElement("property").addAttribute("name", key).addAttribute("value", params.get(key)); 554 } 555 } 556 Element periods = root.addElement("periods"); 557 for (ExamPeriod period : getPeriods()) { 558 periods.addElement("period").addAttribute("id", getId(idconv, "period", String.valueOf(period.getId()))) 559 .addAttribute("length", String.valueOf(period.getLength())).addAttribute("day", period.getDayStr()) 560 .addAttribute("time", period.getTimeStr()).addAttribute("penalty", 561 String.valueOf(period.getPenalty())); 562 } 563 Element rooms = root.addElement("rooms"); 564 for (ExamRoom room : getRooms()) { 565 Element r = rooms.addElement("room"); 566 r.addAttribute("id", getId(idconv, "room", String.valueOf(room.getId()))); 567 if (!anonymize && room.hasName()) 568 r.addAttribute("name", room.getName()); 569 r.addAttribute("size", String.valueOf(room.getSize())); 570 r.addAttribute("alt", String.valueOf(room.getAltSize())); 571 if (room.getCoordX() != null && room.getCoordY() != null) 572 r.addAttribute("coordinates", room.getCoordX() + "," + room.getCoordY()); 573 for (ExamPeriod period : getPeriods()) { 574 if (!room.isAvailable(period)) 575 r.addElement("period").addAttribute("id", 576 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 577 "false"); 578 else if (room.getPenalty(period) != 0) 579 r.addElement("period").addAttribute("id", 580 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("penalty", 581 String.valueOf(room.getPenalty(period))); 582 } 583 Map<Long, Integer> travelTimes = getDistanceMetric().getTravelTimes().get(room.getId()); 584 if (travelTimes != null) 585 for (Map.Entry<Long, Integer> time: travelTimes.entrySet()) 586 r.addElement("travel-time").addAttribute("id", getId(idconv, "room", time.getKey().toString())).addAttribute("minutes", time.getValue().toString()); 587 } 588 Element exams = root.addElement("exams"); 589 for (Exam exam : variables()) { 590 Element ex = exams.addElement("exam"); 591 ex.addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 592 if (!anonymize && exam.hasName()) 593 ex.addAttribute("name", exam.getName()); 594 ex.addAttribute("length", String.valueOf(exam.getLength())); 595 if (exam.getSizeOverride() != null) 596 ex.addAttribute("size", exam.getSizeOverride().toString()); 597 if (exam.getMinSize() != 0) 598 ex.addAttribute("minSize", String.valueOf(exam.getMinSize())); 599 ex.addAttribute("alt", (exam.hasAltSeating() ? "true" : "false")); 600 if (exam.getMaxRooms() != getMaxRooms()) 601 ex.addAttribute("maxRooms", String.valueOf(exam.getMaxRooms())); 602 if (exam.getPrintOffset() != null && !anonymize) 603 ex.addAttribute("printOffset", exam.getPrintOffset().toString()); 604 if (!anonymize) 605 ex.addAttribute("enrl", String.valueOf(exam.getStudents().size())); 606 if (!anonymize) 607 for (ExamOwner owner : exam.getOwners()) { 608 Element o = ex.addElement("owner"); 609 o.addAttribute("id", getId(idconv, "owner", String.valueOf(owner.getId()))); 610 o.addAttribute("name", owner.getName()); 611 } 612 for (ExamPeriodPlacement period : exam.getPeriodPlacements()) { 613 Element pe = ex.addElement("period").addAttribute("id", 614 getId(idconv, "period", String.valueOf(period.getId()))); 615 int penalty = period.getPenalty() - period.getPeriod().getPenalty(); 616 if (penalty != 0) 617 pe.addAttribute("penalty", String.valueOf(penalty)); 618 } 619 for (ExamRoomPlacement room : exam.getRoomPlacements()) { 620 Element re = ex.addElement("room").addAttribute("id", 621 getId(idconv, "room", String.valueOf(room.getId()))); 622 if (room.getPenalty() != 0) 623 re.addAttribute("penalty", String.valueOf(room.getPenalty())); 624 if (room.getMaxPenalty() != 100) 625 re.addAttribute("maxPenalty", String.valueOf(room.getMaxPenalty())); 626 } 627 if (exam.hasAveragePeriod()) 628 ex.addAttribute("average", String.valueOf(exam.getAveragePeriod())); 629 ExamPlacement p = exam.getAssignment(); 630 if (p != null && saveSolution) { 631 Element asg = ex.addElement("assignment"); 632 asg.addElement("period").addAttribute("id", 633 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 634 for (ExamRoomPlacement r : p.getRoomPlacements()) { 635 asg.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 636 } 637 } 638 p = exam.getInitialAssignment(); 639 if (p != null && saveInitial) { 640 Element ini = ex.addElement("initial"); 641 ini.addElement("period").addAttribute("id", 642 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 643 for (ExamRoomPlacement r : p.getRoomPlacements()) { 644 ini.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 645 } 646 } 647 p = exam.getBestAssignment(); 648 if (p != null && saveBest) { 649 Element ini = ex.addElement("best"); 650 ini.addElement("period").addAttribute("id", 651 getId(idconv, "period", String.valueOf(p.getPeriod().getId()))); 652 for (ExamRoomPlacement r : p.getRoomPlacements()) { 653 ini.addElement("room").addAttribute("id", getId(idconv, "room", String.valueOf(r.getId()))); 654 } 655 } 656 if (iRoomSharing != null) 657 iRoomSharing.save(exam, ex, anonymize ? IdConvertor.getInstance() : null); 658 } 659 Element students = root.addElement("students"); 660 for (ExamStudent student : getStudents()) { 661 Element s = students.addElement("student"); 662 s.addAttribute("id", getId(idconv, "student", String.valueOf(student.getId()))); 663 for (Exam ex : student.variables()) { 664 Element x = s.addElement("exam").addAttribute("id", 665 getId(idconv, "exam", String.valueOf(ex.getId()))); 666 if (!anonymize) 667 for (ExamOwner owner : ex.getOwners(student)) { 668 x.addElement("owner").addAttribute("id", 669 getId(idconv, "owner", String.valueOf(owner.getId()))); 670 } 671 } 672 for (ExamPeriod period : getPeriods()) { 673 if (!student.isAvailable(period)) 674 s.addElement("period").addAttribute("id", 675 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 676 "false"); 677 } 678 } 679 Element instructors = root.addElement("instructors"); 680 for (ExamInstructor instructor : getInstructors()) { 681 Element i = instructors.addElement("instructor"); 682 i.addAttribute("id", getId(idconv, "instructor", String.valueOf(instructor.getId()))); 683 if (!anonymize && instructor.hasName()) 684 i.addAttribute("name", instructor.getName()); 685 for (Exam ex : instructor.variables()) { 686 Element x = i.addElement("exam").addAttribute("id", 687 getId(idconv, "exam", String.valueOf(ex.getId()))); 688 if (!anonymize) 689 for (ExamOwner owner : ex.getOwners(instructor)) { 690 x.addElement("owner").addAttribute("id", 691 getId(idconv, "owner", String.valueOf(owner.getId()))); 692 } 693 } 694 for (ExamPeriod period : getPeriods()) { 695 if (!instructor.isAvailable(period)) 696 i.addElement("period").addAttribute("id", 697 getId(idconv, "period", String.valueOf(period.getId()))).addAttribute("available", 698 "false"); 699 } 700 } 701 Element distConstraints = root.addElement("constraints"); 702 for (ExamDistributionConstraint distConstraint : getDistributionConstraints()) { 703 Element dc = distConstraints.addElement(distConstraint.getTypeString()); 704 dc.addAttribute("id", getId(idconv, "constraint", String.valueOf(distConstraint.getId()))); 705 if (!distConstraint.isHard()) { 706 dc.addAttribute("hard", "false"); 707 dc.addAttribute("weight", String.valueOf(distConstraint.getWeight())); 708 } 709 for (Exam exam : distConstraint.variables()) { 710 dc.addElement("exam").addAttribute("id", getId(idconv, "exam", String.valueOf(exam.getId()))); 711 } 712 } 713 if (saveConflictTable) { 714 Element conflicts = root.addElement("conflicts"); 715 for (ExamStudent student : getStudents()) { 716 for (ExamPeriod period : getPeriods()) { 717 int nrExams = student.getExams(period).size(); 718 if (nrExams > 1) { 719 Element dir = conflicts.addElement("direct").addAttribute("student", 720 getId(idconv, "student", String.valueOf(student.getId()))); 721 for (Exam exam : student.getExams(period)) { 722 dir.addElement("exam").addAttribute("id", 723 getId(idconv, "exam", String.valueOf(exam.getId()))); 724 } 725 } 726 if (nrExams > 0) { 727 if (period.next() != null && !student.getExams(period.next()).isEmpty() 728 && (!isDayBreakBackToBack() || period.next().getDay() == period.getDay())) { 729 for (Exam ex1 : student.getExams(period)) { 730 for (Exam ex2 : student.getExams(period.next())) { 731 Element btb = conflicts.addElement("back-to-back").addAttribute("student", 732 getId(idconv, "student", String.valueOf(student.getId()))); 733 btb.addElement("exam").addAttribute("id", 734 getId(idconv, "exam", String.valueOf(ex1.getId()))); 735 btb.addElement("exam").addAttribute("id", 736 getId(idconv, "exam", String.valueOf(ex2.getId()))); 737 if (getBackToBackDistance() >= 0) { 738 double dist = (ex1.getAssignment()).getDistanceInMeters(ex2.getAssignment()); 739 if (dist > 0) 740 btb.addAttribute("distance", String.valueOf(dist)); 741 } 742 } 743 } 744 } 745 } 746 if (period.next() == null || period.next().getDay() != period.getDay()) { 747 int nrExamsADay = student.getExamsADay(period.getDay()).size(); 748 if (nrExamsADay > 2) { 749 Element mt2 = conflicts.addElement("more-2-day").addAttribute("student", 750 getId(idconv, "student", String.valueOf(student.getId()))); 751 for (Exam exam : student.getExamsADay(period.getDay())) { 752 mt2.addElement("exam").addAttribute("id", 753 getId(idconv, "exam", String.valueOf(exam.getId()))); 754 } 755 } 756 } 757 } 758 } 759 760 } 761 return document; 762 } 763 764 /** 765 * Load model (including its solution) from XML. 766 */ 767 public boolean load(Document document) { 768 return load(document, null); 769 } 770 771 /** 772 * Load model (including its solution) from XML. 773 */ 774 public boolean load(Document document, Callback saveBest) { 775 boolean loadInitial = getProperties().getPropertyBoolean("Xml.LoadInitial", true); 776 boolean loadBest = getProperties().getPropertyBoolean("Xml.LoadBest", true); 777 boolean loadSolution = getProperties().getPropertyBoolean("Xml.LoadSolution", true); 778 boolean loadParams = getProperties().getPropertyBoolean("Xml.LoadParameters", false); 779 Integer softPeriods = getProperties().getPropertyInteger("Exam.SoftPeriods", null); 780 Integer softRooms = getProperties().getPropertyInteger("Exam.SoftRooms", null); 781 Integer softDistributions = getProperties().getPropertyInteger("Exam.SoftDistributions", null); 782 Element root = document.getRootElement(); 783 if (!"examtt".equals(root.getName())) 784 return false; 785 if (root.attribute("campus") != null) 786 getProperties().setProperty("Data.Campus", root.attributeValue("campus")); 787 else if (root.attribute("initiative") != null) 788 getProperties().setProperty("Data.Initiative", root.attributeValue("initiative")); 789 if (root.attribute("term") != null) 790 getProperties().setProperty("Data.Term", root.attributeValue("term")); 791 if (root.attribute("year") != null) 792 getProperties().setProperty("Data.Year", root.attributeValue("year")); 793 if (loadParams && root.element("parameters") != null) { 794 Map<String,String> params = new HashMap<String, String>(); 795 for (Iterator<?> i = root.element("parameters").elementIterator("property"); i.hasNext();) { 796 Element e = (Element) i.next(); 797 params.put(e.attributeValue("name"), e.attributeValue("value")); 798 } 799 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 800 if (criterion instanceof ExamCriterion) 801 ((ExamCriterion)criterion).setXmlParameters(params); 802 } 803 try { 804 setMaxRooms(Integer.valueOf(params.get("maxRooms"))); 805 } catch (NumberFormatException e) {} catch (NullPointerException e) {} 806 } 807 for (Iterator<?> i = root.element("periods").elementIterator("period"); i.hasNext();) { 808 Element e = (Element) i.next(); 809 addPeriod(Long.valueOf(e.attributeValue("id")), e.attributeValue("day"), e.attributeValue("time"), Integer 810 .parseInt(e.attributeValue("length")), Integer.parseInt(e.attributeValue("penalty") == null ? e 811 .attributeValue("weight", "0") : e.attributeValue("penalty"))); 812 } 813 HashMap<Long, ExamRoom> rooms = new HashMap<Long, ExamRoom>(); 814 HashMap<String, ArrayList<ExamRoom>> roomGroups = new HashMap<String, ArrayList<ExamRoom>>(); 815 for (Iterator<?> i = root.element("rooms").elementIterator("room"); i.hasNext();) { 816 Element e = (Element) i.next(); 817 String coords = e.attributeValue("coordinates"); 818 ExamRoom room = new ExamRoom(this, Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), 819 Integer.parseInt(e.attributeValue("size")), Integer.parseInt(e.attributeValue("alt")), 820 (coords == null ? null : Double.valueOf(coords.substring(0, coords.indexOf(',')))), 821 (coords == null ? null : Double.valueOf(coords.substring(coords.indexOf(',') + 1)))); 822 addConstraint(room); 823 getRooms().add(room); 824 rooms.put(new Long(room.getId()), room); 825 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 826 Element pe = (Element) j.next(); 827 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 828 if (period == null) continue; 829 if ("false".equals(pe.attributeValue("available"))) { 830 if (softRooms == null) 831 room.setAvailable(period, false); 832 else 833 room.setPenalty(period, softRooms); 834 } else 835 room.setPenalty(period, Integer.parseInt(pe.attributeValue("penalty"))); 836 } 837 String av = e.attributeValue("available"); 838 if (av != null) { 839 for (int j = 0; j < getPeriods().size(); j++) 840 if ('0' == av.charAt(j)) 841 room.setAvailable(getPeriods().get(j), false); 842 } 843 String g = e.attributeValue("groups"); 844 if (g != null) { 845 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 846 String gr = s.nextToken(); 847 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 848 if (roomsThisGrop == null) { 849 roomsThisGrop = new ArrayList<ExamRoom>(); 850 roomGroups.put(gr, roomsThisGrop); 851 } 852 roomsThisGrop.add(room); 853 } 854 } 855 for (Iterator<?> j = e.elementIterator("travel-time"); j.hasNext();) { 856 Element travelTimeEl = (Element)j.next(); 857 getDistanceMetric().addTravelTime(room.getId(), 858 Long.valueOf(travelTimeEl.attributeValue("id")), 859 Integer.valueOf(travelTimeEl.attributeValue("minutes"))); 860 } 861 } 862 ArrayList<ExamPlacement> assignments = new ArrayList<ExamPlacement>(); 863 HashMap<Long, Exam> exams = new HashMap<Long, Exam>(); 864 HashMap<Long, ExamOwner> courseSections = new HashMap<Long, ExamOwner>(); 865 for (Iterator<?> i = root.element("exams").elementIterator("exam"); i.hasNext();) { 866 Element e = (Element) i.next(); 867 ArrayList<ExamPeriodPlacement> periodPlacements = new ArrayList<ExamPeriodPlacement>(); 868 if (softPeriods != null) { 869 for (ExamPeriod period: getPeriods()) { 870 int penalty = softPeriods; 871 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 872 Element pe = (Element) j.next(); 873 if (period.getId().equals(Long.valueOf(pe.attributeValue("id")))) { 874 penalty = Integer.parseInt(pe.attributeValue("penalty", "0")); 875 break; 876 } 877 } 878 periodPlacements.add(new ExamPeriodPlacement(period, penalty)); 879 } 880 } else { 881 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 882 Element pe = (Element) j.next(); 883 ExamPeriod p = getPeriod(Long.valueOf(pe.attributeValue("id"))); 884 if (p != null) 885 periodPlacements.add(new ExamPeriodPlacement(p, Integer.parseInt(pe.attributeValue("penalty", "0")))); 886 } 887 } 888 ArrayList<ExamRoomPlacement> roomPlacements = new ArrayList<ExamRoomPlacement>(); 889 if (softRooms != null) { 890 for (ExamRoom room: getRooms()) { 891 boolean av = false; 892 for (ExamPeriodPlacement p: periodPlacements) { 893 if (room.isAvailable(p.getPeriod()) && room.getPenalty(p.getPeriod()) != softRooms) { av = true; break; } 894 } 895 if (!av) continue; 896 int penalty = softRooms, maxPenalty = softRooms; 897 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 898 Element re = (Element) j.next(); 899 if (room.getId() == Long.parseLong(re.attributeValue("id"))) { 900 penalty = Integer.parseInt(re.attributeValue("penalty", "0")); 901 maxPenalty = Integer.parseInt(re.attributeValue("maxPenalty", softRooms.toString())); 902 } 903 } 904 roomPlacements.add(new ExamRoomPlacement(room, penalty, maxPenalty)); 905 } 906 } else { 907 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 908 Element re = (Element) j.next(); 909 ExamRoomPlacement room = new ExamRoomPlacement(rooms.get(Long.valueOf(re.attributeValue("id"))), 910 Integer.parseInt(re.attributeValue("penalty", "0")), 911 Integer.parseInt(re.attributeValue("maxPenalty", "100"))); 912 if (room.getRoom().isAvailable()) 913 roomPlacements.add(room); 914 } 915 } 916 String g = e.attributeValue("groups"); 917 if (g != null) { 918 HashMap<ExamRoom, Integer> allRooms = new HashMap<ExamRoom, Integer>(); 919 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 920 String gr = s.nextToken(); 921 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 922 if (roomsThisGrop != null) 923 for (ExamRoom r : roomsThisGrop) 924 allRooms.put(r, 0); 925 } 926 for (Iterator<?> j = e.elementIterator("original-room"); j.hasNext();) { 927 allRooms.put((rooms.get(Long.valueOf(((Element) j.next()).attributeValue("id")))), new Integer(-1)); 928 } 929 for (Map.Entry<ExamRoom, Integer> entry : allRooms.entrySet()) { 930 ExamRoomPlacement room = new ExamRoomPlacement(entry.getKey(), entry.getValue(), 100); 931 roomPlacements.add(room); 932 } 933 if (periodPlacements.isEmpty()) { 934 for (ExamPeriod p : getPeriods()) { 935 periodPlacements.add(new ExamPeriodPlacement(p, 0)); 936 } 937 } 938 } 939 Exam exam = new Exam(Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), Integer.parseInt(e 940 .attributeValue("length")), "true".equals(e.attributeValue("alt")), 941 (e.attribute("maxRooms") == null ? getMaxRooms() : Integer.parseInt(e.attributeValue("maxRooms"))), 942 Integer.parseInt(e.attributeValue("minSize", "0")), periodPlacements, roomPlacements); 943 if (e.attributeValue("size") != null) 944 exam.setSizeOverride(Integer.valueOf(e.attributeValue("size"))); 945 if (e.attributeValue("printOffset") != null) 946 exam.setPrintOffset(Integer.valueOf(e.attributeValue("printOffset"))); 947 exams.put(new Long(exam.getId()), exam); 948 addVariable(exam); 949 if (e.attribute("average") != null) 950 exam.setAveragePeriod(Integer.parseInt(e.attributeValue("average"))); 951 Element asg = e.element("assignment"); 952 if (asg != null && loadSolution) { 953 Element per = asg.element("period"); 954 if (per != null) { 955 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 956 for (Iterator<?> j = asg.elementIterator("room"); j.hasNext();) 957 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 958 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 959 if (pp != null) 960 assignments.add(new ExamPlacement(exam, pp, rp)); 961 } 962 } 963 Element ini = e.element("initial"); 964 if (ini != null && loadInitial) { 965 Element per = ini.element("period"); 966 if (per != null) { 967 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 968 for (Iterator<?> j = ini.elementIterator("room"); j.hasNext();) 969 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 970 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 971 if (pp != null) 972 exam.setInitialAssignment(new ExamPlacement(exam, pp, rp)); 973 } 974 } 975 Element best = e.element("best"); 976 if (best != null && loadBest) { 977 Element per = best.element("period"); 978 if (per != null) { 979 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 980 for (Iterator<?> j = best.elementIterator("room"); j.hasNext();) 981 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 982 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 983 if (pp != null) 984 exam.setBestAssignment(new ExamPlacement(exam, pp, rp)); 985 } 986 } 987 for (Iterator<?> j = e.elementIterator("owner"); j.hasNext();) { 988 Element f = (Element) j.next(); 989 ExamOwner owner = new ExamOwner(exam, Long.parseLong(f.attributeValue("id")), f.attributeValue("name")); 990 exam.getOwners().add(owner); 991 courseSections.put(new Long(owner.getId()), owner); 992 } 993 if (iRoomSharing != null) 994 iRoomSharing.load(exam, e); 995 } 996 for (Iterator<?> i = root.element("students").elementIterator("student"); i.hasNext();) { 997 Element e = (Element) i.next(); 998 ExamStudent student = new ExamStudent(this, Long.parseLong(e.attributeValue("id"))); 999 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1000 Element x = (Element) j.next(); 1001 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 1002 student.addVariable(ex); 1003 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 1004 Element f = (Element) k.next(); 1005 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 1006 student.getOwners().add(owner); 1007 owner.getStudents().add(student); 1008 } 1009 } 1010 String available = e.attributeValue("available"); 1011 if (available != null) 1012 for (ExamPeriod period : getPeriods()) { 1013 if (available.charAt(period.getIndex()) == '0') 1014 student.setAvailable(period.getIndex(), false); 1015 } 1016 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 1017 Element pe = (Element) j.next(); 1018 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 1019 if (period == null) continue; 1020 if ("false".equals(pe.attributeValue("available"))) 1021 student.setAvailable(period.getIndex(), false); 1022 } 1023 addConstraint(student); 1024 getStudents().add(student); 1025 } 1026 if (root.element("instructors") != null) 1027 for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) { 1028 Element e = (Element) i.next(); 1029 ExamInstructor instructor = new ExamInstructor(this, Long.parseLong(e.attributeValue("id")), e 1030 .attributeValue("name")); 1031 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1032 Element x = (Element) j.next(); 1033 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 1034 instructor.addVariable(ex); 1035 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 1036 Element f = (Element) k.next(); 1037 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 1038 instructor.getOwners().add(owner); 1039 owner.getIntructors().add(instructor); 1040 } 1041 } 1042 String available = e.attributeValue("available"); 1043 if (available != null) 1044 for (ExamPeriod period : getPeriods()) { 1045 if (available.charAt(period.getIndex()) == '0') 1046 instructor.setAvailable(period.getIndex(), false); 1047 } 1048 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 1049 Element pe = (Element) j.next(); 1050 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 1051 if (period == null) continue; 1052 if ("false".equals(pe.attributeValue("available"))) 1053 instructor.setAvailable(period.getIndex(), false); 1054 } 1055 addConstraint(instructor); 1056 getInstructors().add(instructor); 1057 } 1058 if (root.element("constraints") != null) 1059 for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) { 1060 Element e = (Element) i.next(); 1061 ExamDistributionConstraint dc = new ExamDistributionConstraint(Long.parseLong(e.attributeValue("id")), 1062 e.getName(), 1063 softDistributions != null ? false : "true".equals(e.attributeValue("hard", "true")), 1064 (softDistributions != null && "true".equals(e.attributeValue("hard", "true")) ? softDistributions : Integer.parseInt(e.attributeValue("weight", "0")))); 1065 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1066 dc.addVariable(exams.get(Long.valueOf(((Element) j.next()).attributeValue("id")))); 1067 } 1068 addConstraint(dc); 1069 getDistributionConstraints().add(dc); 1070 } 1071 init(); 1072 if (loadBest && saveBest != null) { 1073 for (Exam exam : variables()) { 1074 ExamPlacement placement = exam.getBestAssignment(); 1075 if (placement == null) 1076 continue; 1077 exam.assign(0, placement); 1078 } 1079 saveBest.execute(); 1080 for (Exam exam : variables()) { 1081 if (exam.getAssignment() != null) 1082 exam.unassign(0); 1083 } 1084 } 1085 for (ExamPlacement placement : assignments) { 1086 Exam exam = placement.variable(); 1087 Set<ExamPlacement> conf = conflictValues(placement); 1088 if (!conf.isEmpty()) { 1089 for (Map.Entry<Constraint<Exam, ExamPlacement>, Set<ExamPlacement>> entry : conflictConstraints( 1090 placement).entrySet()) { 1091 Constraint<Exam, ExamPlacement> constraint = entry.getKey(); 1092 Set<ExamPlacement> values = entry.getValue(); 1093 if (constraint instanceof ExamStudent) { 1094 ((ExamStudent) constraint).setAllowDirectConflicts(true); 1095 exam.setAllowDirectConflicts(true); 1096 for (ExamPlacement p : values) 1097 p.variable().setAllowDirectConflicts(true); 1098 } 1099 } 1100 conf = conflictValues(placement); 1101 } 1102 if (conf.isEmpty()) { 1103 exam.assign(0, placement); 1104 } else { 1105 sLog.error("Unable to assign " + exam.getInitialAssignment().getName() + " to exam " + exam.getName()); 1106 sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(exam.getInitialAssignment()), 2)); 1107 } 1108 } 1109 return true; 1110 } 1111}