001 package net.sf.cpsolver.exam.model; 002 003 import java.util.ArrayList; 004 import java.util.Date; 005 import java.util.HashSet; 006 import java.util.HashMap; 007 import java.util.Iterator; 008 import java.util.List; 009 import java.util.Map; 010 import java.util.Set; 011 import java.util.StringTokenizer; 012 import java.util.TreeSet; 013 014 import net.sf.cpsolver.coursett.IdConvertor; 015 import net.sf.cpsolver.exam.criteria.DistributionPenalty; 016 import net.sf.cpsolver.exam.criteria.ExamCriterion; 017 import net.sf.cpsolver.exam.criteria.ExamRotationPenalty; 018 import net.sf.cpsolver.exam.criteria.InstructorBackToBackConflicts; 019 import net.sf.cpsolver.exam.criteria.InstructorDirectConflicts; 020 import net.sf.cpsolver.exam.criteria.InstructorDistanceBackToBackConflicts; 021 import net.sf.cpsolver.exam.criteria.InstructorMoreThan2ADayConflicts; 022 import net.sf.cpsolver.exam.criteria.InstructorNotAvailableConflicts; 023 import net.sf.cpsolver.exam.criteria.LargeExamsPenalty; 024 import net.sf.cpsolver.exam.criteria.PeriodIndexPenalty; 025 import net.sf.cpsolver.exam.criteria.PeriodPenalty; 026 import net.sf.cpsolver.exam.criteria.PeriodSizePenalty; 027 import net.sf.cpsolver.exam.criteria.PerturbationPenalty; 028 import net.sf.cpsolver.exam.criteria.RoomPenalty; 029 import net.sf.cpsolver.exam.criteria.RoomPerturbationPenalty; 030 import net.sf.cpsolver.exam.criteria.RoomSizePenalty; 031 import net.sf.cpsolver.exam.criteria.RoomSplitDistancePenalty; 032 import net.sf.cpsolver.exam.criteria.RoomSplitPenalty; 033 import net.sf.cpsolver.exam.criteria.StudentBackToBackConflicts; 034 import net.sf.cpsolver.exam.criteria.StudentDirectConflicts; 035 import net.sf.cpsolver.exam.criteria.StudentMoreThan2ADayConflicts; 036 import net.sf.cpsolver.exam.criteria.StudentDistanceBackToBackConflicts; 037 import net.sf.cpsolver.exam.criteria.StudentNotAvailableConflicts; 038 import net.sf.cpsolver.ifs.criteria.Criterion; 039 import net.sf.cpsolver.ifs.model.Constraint; 040 import net.sf.cpsolver.ifs.model.Model; 041 import net.sf.cpsolver.ifs.util.Callback; 042 import net.sf.cpsolver.ifs.util.DataProperties; 043 import net.sf.cpsolver.ifs.util.DistanceMetric; 044 import net.sf.cpsolver.ifs.util.ToolBox; 045 046 import org.apache.log4j.Logger; 047 import org.dom4j.Document; 048 import org.dom4j.DocumentHelper; 049 import 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 Tomas Muller<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 */ 132 public 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 Document document = DocumentHelper.createDocument(); 526 document.addComment("Examination Timetable"); 527 if (nrAssignedVariables() > 0) { 528 StringBuffer comments = new StringBuffer("Solution Info:\n"); 529 Map<String, String> solutionInfo = (getProperties().getPropertyBoolean("Xml.ExtendedInfo", false) ? getExtendedInfo() 530 : getInfo()); 531 for (String key : new TreeSet<String>(solutionInfo.keySet())) { 532 String value = solutionInfo.get(key); 533 comments.append(" " + key + ": " + value + "\n"); 534 } 535 document.addComment(comments.toString()); 536 } 537 Element root = document.addElement("examtt"); 538 root.addAttribute("version", "1.0"); 539 root.addAttribute("campus", getProperties().getProperty("Data.Initiative")); 540 root.addAttribute("term", getProperties().getProperty("Data.Term")); 541 root.addAttribute("year", getProperties().getProperty("Data.Year")); 542 root.addAttribute("created", String.valueOf(new Date())); 543 if (saveParams) { 544 Map<String, String> params = new HashMap<String, String>(); 545 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 546 if (criterion instanceof ExamCriterion) 547 ((ExamCriterion)criterion).getXmlParameters(params); 548 } 549 params.put("maxRooms", String.valueOf(getMaxRooms())); 550 Element parameters = root.addElement("parameters"); 551 for (String key: new TreeSet<String>(params.keySet())) { 552 parameters.addElement("property").addAttribute("name", key).addAttribute("value", params.get(key)); 553 } 554 } 555 Element periods = root.addElement("periods"); 556 for (ExamPeriod period : getPeriods()) { 557 periods.addElement("period").addAttribute("id", getId(anonymize, "period", String.valueOf(period.getId()))) 558 .addAttribute("length", String.valueOf(period.getLength())).addAttribute("day", period.getDayStr()) 559 .addAttribute("time", period.getTimeStr()).addAttribute("penalty", 560 String.valueOf(period.getPenalty())); 561 } 562 Element rooms = root.addElement("rooms"); 563 for (ExamRoom room : getRooms()) { 564 Element r = rooms.addElement("room"); 565 r.addAttribute("id", getId(anonymize, "room", String.valueOf(room.getId()))); 566 if (!anonymize && room.hasName()) 567 r.addAttribute("name", room.getName()); 568 r.addAttribute("size", String.valueOf(room.getSize())); 569 r.addAttribute("alt", String.valueOf(room.getAltSize())); 570 if (room.getCoordX() != null && room.getCoordY() != null) 571 r.addAttribute("coordinates", room.getCoordX() + "," + room.getCoordY()); 572 for (ExamPeriod period : getPeriods()) { 573 if (!room.isAvailable(period)) 574 r.addElement("period").addAttribute("id", 575 getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("available", 576 "false"); 577 else if (room.getPenalty(period) != 0) 578 r.addElement("period").addAttribute("id", 579 getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("penalty", 580 String.valueOf(room.getPenalty(period))); 581 } 582 Map<Long, Integer> travelTimes = getDistanceMetric().getTravelTimes().get(room.getId()); 583 if (travelTimes != null) 584 for (Map.Entry<Long, Integer> time: travelTimes.entrySet()) 585 r.addElement("travel-time").addAttribute("id", getId(anonymize, "room", time.getKey().toString())).addAttribute("minutes", time.getValue().toString()); 586 } 587 Element exams = root.addElement("exams"); 588 for (Exam exam : variables()) { 589 Element ex = exams.addElement("exam"); 590 ex.addAttribute("id", getId(anonymize, "exam", String.valueOf(exam.getId()))); 591 if (!anonymize && exam.hasName()) 592 ex.addAttribute("name", exam.getName()); 593 ex.addAttribute("length", String.valueOf(exam.getLength())); 594 if (exam.getSizeOverride() != null) 595 ex.addAttribute("size", exam.getSizeOverride().toString()); 596 if (exam.getMinSize() != 0) 597 ex.addAttribute("minSize", String.valueOf(exam.getMinSize())); 598 ex.addAttribute("alt", (exam.hasAltSeating() ? "true" : "false")); 599 if (exam.getMaxRooms() != getMaxRooms()) 600 ex.addAttribute("maxRooms", String.valueOf(exam.getMaxRooms())); 601 if (exam.getPrintOffset() != null) 602 ex.addAttribute("printOffset", exam.getPrintOffset().toString()); 603 if (!anonymize) 604 ex.addAttribute("enrl", String.valueOf(exam.getStudents().size())); 605 if (!anonymize) 606 for (ExamOwner owner : exam.getOwners()) { 607 Element o = ex.addElement("owner"); 608 o.addAttribute("id", getId(anonymize, "owner", String.valueOf(owner.getId()))); 609 o.addAttribute("name", owner.getName()); 610 } 611 for (ExamPeriodPlacement period : exam.getPeriodPlacements()) { 612 Element pe = ex.addElement("period").addAttribute("id", 613 getId(anonymize, "period", String.valueOf(period.getId()))); 614 int penalty = period.getPenalty() - period.getPeriod().getPenalty(); 615 if (penalty != 0) 616 pe.addAttribute("penalty", String.valueOf(penalty)); 617 } 618 for (ExamRoomPlacement room : exam.getRoomPlacements()) { 619 Element re = ex.addElement("room").addAttribute("id", 620 getId(anonymize, "room", String.valueOf(room.getId()))); 621 if (room.getPenalty() != 0) 622 re.addAttribute("penalty", String.valueOf(room.getPenalty())); 623 if (room.getMaxPenalty() != 100) 624 re.addAttribute("maxPenalty", String.valueOf(room.getMaxPenalty())); 625 } 626 if (exam.hasAveragePeriod()) 627 ex.addAttribute("average", String.valueOf(exam.getAveragePeriod())); 628 ExamPlacement p = exam.getAssignment(); 629 if (p != null && saveSolution) { 630 Element asg = ex.addElement("assignment"); 631 asg.addElement("period").addAttribute("id", 632 getId(anonymize, "period", String.valueOf(p.getPeriod().getId()))); 633 for (ExamRoomPlacement r : p.getRoomPlacements()) { 634 asg.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId()))); 635 } 636 } 637 p = exam.getInitialAssignment(); 638 if (p != null && saveInitial) { 639 Element ini = ex.addElement("initial"); 640 ini.addElement("period").addAttribute("id", 641 getId(anonymize, "period", String.valueOf(p.getPeriod().getId()))); 642 for (ExamRoomPlacement r : p.getRoomPlacements()) { 643 ini.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId()))); 644 } 645 } 646 p = exam.getBestAssignment(); 647 if (p != null && saveBest) { 648 Element ini = ex.addElement("best"); 649 ini.addElement("period").addAttribute("id", 650 getId(anonymize, "period", String.valueOf(p.getPeriod().getId()))); 651 for (ExamRoomPlacement r : p.getRoomPlacements()) { 652 ini.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId()))); 653 } 654 } 655 if (iRoomSharing != null) 656 iRoomSharing.save(exam, ex, anonymize ? IdConvertor.getInstance() : null); 657 } 658 Element students = root.addElement("students"); 659 for (ExamStudent student : getStudents()) { 660 Element s = students.addElement("student"); 661 s.addAttribute("id", getId(anonymize, "student", String.valueOf(student.getId()))); 662 for (Exam ex : student.variables()) { 663 Element x = s.addElement("exam").addAttribute("id", 664 getId(anonymize, "exam", String.valueOf(ex.getId()))); 665 if (!anonymize) 666 for (ExamOwner owner : ex.getOwners(student)) { 667 x.addElement("owner").addAttribute("id", 668 getId(anonymize, "owner", String.valueOf(owner.getId()))); 669 } 670 } 671 for (ExamPeriod period : getPeriods()) { 672 if (!student.isAvailable(period)) 673 s.addElement("period").addAttribute("id", 674 getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("available", 675 "false"); 676 } 677 } 678 Element instructors = root.addElement("instructors"); 679 for (ExamInstructor instructor : getInstructors()) { 680 Element i = instructors.addElement("instructor"); 681 i.addAttribute("id", getId(anonymize, "instructor", String.valueOf(instructor.getId()))); 682 if (!anonymize && instructor.hasName()) 683 i.addAttribute("name", instructor.getName()); 684 for (Exam ex : instructor.variables()) { 685 Element x = i.addElement("exam").addAttribute("id", 686 getId(anonymize, "exam", String.valueOf(ex.getId()))); 687 if (!anonymize) 688 for (ExamOwner owner : ex.getOwners(instructor)) { 689 x.addElement("owner").addAttribute("id", 690 getId(anonymize, "owner", String.valueOf(owner.getId()))); 691 } 692 } 693 for (ExamPeriod period : getPeriods()) { 694 if (!instructor.isAvailable(period)) 695 i.addElement("period").addAttribute("id", 696 getId(anonymize, "period", String.valueOf(period.getId()))).addAttribute("available", 697 "false"); 698 } 699 } 700 Element distConstraints = root.addElement("constraints"); 701 for (ExamDistributionConstraint distConstraint : getDistributionConstraints()) { 702 Element dc = distConstraints.addElement(distConstraint.getTypeString()); 703 dc.addAttribute("id", getId(anonymize, "constraint", String.valueOf(distConstraint.getId()))); 704 if (!distConstraint.isHard()) { 705 dc.addAttribute("hard", "false"); 706 dc.addAttribute("weight", String.valueOf(distConstraint.getWeight())); 707 } 708 for (Exam exam : distConstraint.variables()) { 709 dc.addElement("exam").addAttribute("id", getId(anonymize, "exam", String.valueOf(exam.getId()))); 710 } 711 } 712 if (saveConflictTable) { 713 Element conflicts = root.addElement("conflicts"); 714 for (ExamStudent student : getStudents()) { 715 for (ExamPeriod period : getPeriods()) { 716 int nrExams = student.getExams(period).size(); 717 if (nrExams > 1) { 718 Element dir = conflicts.addElement("direct").addAttribute("student", 719 getId(anonymize, "student", String.valueOf(student.getId()))); 720 for (Exam exam : student.getExams(period)) { 721 dir.addElement("exam").addAttribute("id", 722 getId(anonymize, "exam", String.valueOf(exam.getId()))); 723 } 724 } 725 if (nrExams > 0) { 726 if (period.next() != null && !student.getExams(period.next()).isEmpty() 727 && (!isDayBreakBackToBack() || period.next().getDay() == period.getDay())) { 728 for (Exam ex1 : student.getExams(period)) { 729 for (Exam ex2 : student.getExams(period.next())) { 730 Element btb = conflicts.addElement("back-to-back").addAttribute("student", 731 getId(anonymize, "student", String.valueOf(student.getId()))); 732 btb.addElement("exam").addAttribute("id", 733 getId(anonymize, "exam", String.valueOf(ex1.getId()))); 734 btb.addElement("exam").addAttribute("id", 735 getId(anonymize, "exam", String.valueOf(ex2.getId()))); 736 if (getBackToBackDistance() >= 0) { 737 double dist = (ex1.getAssignment()).getDistanceInMeters(ex2.getAssignment()); 738 if (dist > 0) 739 btb.addAttribute("distance", String.valueOf(dist)); 740 } 741 } 742 } 743 } 744 } 745 if (period.next() == null || period.next().getDay() != period.getDay()) { 746 int nrExamsADay = student.getExamsADay(period.getDay()).size(); 747 if (nrExamsADay > 2) { 748 Element mt2 = conflicts.addElement("more-2-day").addAttribute("student", 749 getId(anonymize, "student", String.valueOf(student.getId()))); 750 for (Exam exam : student.getExamsADay(period.getDay())) { 751 mt2.addElement("exam").addAttribute("id", 752 getId(anonymize, "exam", String.valueOf(exam.getId()))); 753 } 754 } 755 } 756 } 757 } 758 759 } 760 return document; 761 } 762 763 /** 764 * Load model (including its solution) from XML. 765 */ 766 public boolean load(Document document) { 767 return load(document, null); 768 } 769 770 /** 771 * Load model (including its solution) from XML. 772 */ 773 public boolean load(Document document, Callback saveBest) { 774 boolean loadInitial = getProperties().getPropertyBoolean("Xml.LoadInitial", true); 775 boolean loadBest = getProperties().getPropertyBoolean("Xml.LoadBest", true); 776 boolean loadSolution = getProperties().getPropertyBoolean("Xml.LoadSolution", true); 777 boolean loadParams = getProperties().getPropertyBoolean("Xml.LoadParameters", false); 778 Integer softPeriods = getProperties().getPropertyInteger("Exam.SoftPeriods", null); 779 Integer softRooms = getProperties().getPropertyInteger("Exam.SoftRooms", null); 780 Integer softDistributions = getProperties().getPropertyInteger("Exam.SoftDistributions", null); 781 Element root = document.getRootElement(); 782 if (!"examtt".equals(root.getName())) 783 return false; 784 if (root.attribute("campus") != null) 785 getProperties().setProperty("Data.Campus", root.attributeValue("campus")); 786 else if (root.attribute("initiative") != null) 787 getProperties().setProperty("Data.Initiative", root.attributeValue("initiative")); 788 if (root.attribute("term") != null) 789 getProperties().setProperty("Data.Term", root.attributeValue("term")); 790 if (root.attribute("year") != null) 791 getProperties().setProperty("Data.Year", root.attributeValue("year")); 792 if (loadParams && root.element("parameters") != null) { 793 Map<String,String> params = new HashMap<String, String>(); 794 for (Iterator<?> i = root.element("parameters").elementIterator("property"); i.hasNext();) { 795 Element e = (Element) i.next(); 796 params.put(e.attributeValue("name"), e.attributeValue("value")); 797 } 798 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 799 if (criterion instanceof ExamCriterion) 800 ((ExamCriterion)criterion).setXmlParameters(params); 801 } 802 try { 803 setMaxRooms(Integer.valueOf(params.get("maxRooms"))); 804 } catch (NumberFormatException e) {} catch (NullPointerException e) {} 805 } 806 for (Iterator<?> i = root.element("periods").elementIterator("period"); i.hasNext();) { 807 Element e = (Element) i.next(); 808 addPeriod(Long.valueOf(e.attributeValue("id")), e.attributeValue("day"), e.attributeValue("time"), Integer 809 .parseInt(e.attributeValue("length")), Integer.parseInt(e.attributeValue("penalty") == null ? e 810 .attributeValue("weight", "0") : e.attributeValue("penalty"))); 811 } 812 HashMap<Long, ExamRoom> rooms = new HashMap<Long, ExamRoom>(); 813 HashMap<String, ArrayList<ExamRoom>> roomGroups = new HashMap<String, ArrayList<ExamRoom>>(); 814 for (Iterator<?> i = root.element("rooms").elementIterator("room"); i.hasNext();) { 815 Element e = (Element) i.next(); 816 String coords = e.attributeValue("coordinates"); 817 ExamRoom room = new ExamRoom(this, Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), 818 Integer.parseInt(e.attributeValue("size")), Integer.parseInt(e.attributeValue("alt")), 819 (coords == null ? null : Double.valueOf(coords.substring(0, coords.indexOf(',')))), 820 (coords == null ? null : Double.valueOf(coords.substring(coords.indexOf(',') + 1)))); 821 addConstraint(room); 822 getRooms().add(room); 823 rooms.put(new Long(room.getId()), room); 824 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 825 Element pe = (Element) j.next(); 826 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 827 if (period == null) continue; 828 if ("false".equals(pe.attributeValue("available"))) { 829 if (softRooms == null) 830 room.setAvailable(period, false); 831 else 832 room.setPenalty(period, softRooms); 833 } else 834 room.setPenalty(period, Integer.parseInt(pe.attributeValue("penalty"))); 835 } 836 String av = e.attributeValue("available"); 837 if (av != null) { 838 for (int j = 0; j < getPeriods().size(); j++) 839 if ('0' == av.charAt(j)) 840 room.setAvailable(getPeriods().get(j), false); 841 } 842 String g = e.attributeValue("groups"); 843 if (g != null) { 844 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 845 String gr = s.nextToken(); 846 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 847 if (roomsThisGrop == null) { 848 roomsThisGrop = new ArrayList<ExamRoom>(); 849 roomGroups.put(gr, roomsThisGrop); 850 } 851 roomsThisGrop.add(room); 852 } 853 } 854 for (Iterator<?> j = e.elementIterator("travel-time"); j.hasNext();) { 855 Element travelTimeEl = (Element)j.next(); 856 getDistanceMetric().addTravelTime(room.getId(), 857 Long.valueOf(travelTimeEl.attributeValue("id")), 858 Integer.valueOf(travelTimeEl.attributeValue("minutes"))); 859 } 860 } 861 ArrayList<ExamPlacement> assignments = new ArrayList<ExamPlacement>(); 862 HashMap<Long, Exam> exams = new HashMap<Long, Exam>(); 863 HashMap<Long, ExamOwner> courseSections = new HashMap<Long, ExamOwner>(); 864 for (Iterator<?> i = root.element("exams").elementIterator("exam"); i.hasNext();) { 865 Element e = (Element) i.next(); 866 ArrayList<ExamPeriodPlacement> periodPlacements = new ArrayList<ExamPeriodPlacement>(); 867 if (softPeriods != null) { 868 for (ExamPeriod period: getPeriods()) { 869 int penalty = softPeriods; 870 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 871 Element pe = (Element) j.next(); 872 if (period.getId().equals(Long.valueOf(pe.attributeValue("id")))) { 873 penalty = Integer.parseInt(pe.attributeValue("penalty", "0")); 874 break; 875 } 876 } 877 periodPlacements.add(new ExamPeriodPlacement(period, penalty)); 878 } 879 } else { 880 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 881 Element pe = (Element) j.next(); 882 ExamPeriod p = getPeriod(Long.valueOf(pe.attributeValue("id"))); 883 if (p != null) 884 periodPlacements.add(new ExamPeriodPlacement(p, Integer.parseInt(pe.attributeValue("penalty", "0")))); 885 } 886 } 887 ArrayList<ExamRoomPlacement> roomPlacements = new ArrayList<ExamRoomPlacement>(); 888 if (softRooms != null) { 889 for (ExamRoom room: getRooms()) { 890 boolean av = false; 891 for (ExamPeriodPlacement p: periodPlacements) { 892 if (room.isAvailable(p.getPeriod()) && room.getPenalty(p.getPeriod()) != softRooms) { av = true; break; } 893 } 894 if (!av) continue; 895 int penalty = softRooms, maxPenalty = softRooms; 896 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 897 Element re = (Element) j.next(); 898 if (room.getId() == Long.parseLong(re.attributeValue("id"))) { 899 penalty = Integer.parseInt(re.attributeValue("penalty", "0")); 900 maxPenalty = Integer.parseInt(re.attributeValue("maxPenalty", softRooms.toString())); 901 } 902 } 903 roomPlacements.add(new ExamRoomPlacement(room, penalty, maxPenalty)); 904 } 905 } else { 906 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 907 Element re = (Element) j.next(); 908 ExamRoomPlacement room = new ExamRoomPlacement(rooms.get(Long.valueOf(re.attributeValue("id"))), 909 Integer.parseInt(re.attributeValue("penalty", "0")), 910 Integer.parseInt(re.attributeValue("maxPenalty", "100"))); 911 if (room.getRoom().isAvailable()) 912 roomPlacements.add(room); 913 } 914 } 915 String g = e.attributeValue("groups"); 916 if (g != null) { 917 HashMap<ExamRoom, Integer> allRooms = new HashMap<ExamRoom, Integer>(); 918 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 919 String gr = s.nextToken(); 920 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 921 if (roomsThisGrop != null) 922 for (ExamRoom r : roomsThisGrop) 923 allRooms.put(r, 0); 924 } 925 for (Iterator<?> j = e.elementIterator("original-room"); j.hasNext();) { 926 allRooms.put((rooms.get(Long.valueOf(((Element) j.next()).attributeValue("id")))), new Integer(-1)); 927 } 928 for (Map.Entry<ExamRoom, Integer> entry : allRooms.entrySet()) { 929 ExamRoomPlacement room = new ExamRoomPlacement(entry.getKey(), entry.getValue(), 100); 930 roomPlacements.add(room); 931 } 932 if (periodPlacements.isEmpty()) { 933 for (ExamPeriod p : getPeriods()) { 934 periodPlacements.add(new ExamPeriodPlacement(p, 0)); 935 } 936 } 937 } 938 Exam exam = new Exam(Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), Integer.parseInt(e 939 .attributeValue("length")), "true".equals(e.attributeValue("alt")), 940 (e.attribute("maxRooms") == null ? getMaxRooms() : Integer.parseInt(e.attributeValue("maxRooms"))), 941 Integer.parseInt(e.attributeValue("minSize", "0")), periodPlacements, roomPlacements); 942 if (e.attributeValue("size") != null) 943 exam.setSizeOverride(Integer.valueOf(e.attributeValue("size"))); 944 if (e.attributeValue("printOffset") != null) 945 exam.setPrintOffset(Integer.valueOf(e.attributeValue("printOffset"))); 946 exams.put(new Long(exam.getId()), exam); 947 addVariable(exam); 948 if (e.attribute("average") != null) 949 exam.setAveragePeriod(Integer.parseInt(e.attributeValue("average"))); 950 Element asg = e.element("assignment"); 951 if (asg != null && loadSolution) { 952 Element per = asg.element("period"); 953 if (per != null) { 954 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 955 for (Iterator<?> j = asg.elementIterator("room"); j.hasNext();) 956 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 957 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 958 if (pp != null) 959 assignments.add(new ExamPlacement(exam, pp, rp)); 960 } 961 } 962 Element ini = e.element("initial"); 963 if (ini != null && loadInitial) { 964 Element per = ini.element("period"); 965 if (per != null) { 966 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 967 for (Iterator<?> j = ini.elementIterator("room"); j.hasNext();) 968 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 969 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 970 if (pp != null) 971 exam.setInitialAssignment(new ExamPlacement(exam, pp, rp)); 972 } 973 } 974 Element best = e.element("best"); 975 if (best != null && loadBest) { 976 Element per = best.element("period"); 977 if (per != null) { 978 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 979 for (Iterator<?> j = best.elementIterator("room"); j.hasNext();) 980 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 981 ExamPeriodPlacement pp = exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))); 982 if (pp != null) 983 exam.setBestAssignment(new ExamPlacement(exam, pp, rp)); 984 } 985 } 986 for (Iterator<?> j = e.elementIterator("owner"); j.hasNext();) { 987 Element f = (Element) j.next(); 988 ExamOwner owner = new ExamOwner(exam, Long.parseLong(f.attributeValue("id")), f.attributeValue("name")); 989 exam.getOwners().add(owner); 990 courseSections.put(new Long(owner.getId()), owner); 991 } 992 if (iRoomSharing != null) 993 iRoomSharing.load(exam, e); 994 } 995 for (Iterator<?> i = root.element("students").elementIterator("student"); i.hasNext();) { 996 Element e = (Element) i.next(); 997 ExamStudent student = new ExamStudent(this, Long.parseLong(e.attributeValue("id"))); 998 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 999 Element x = (Element) j.next(); 1000 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 1001 student.addVariable(ex); 1002 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 1003 Element f = (Element) k.next(); 1004 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 1005 student.getOwners().add(owner); 1006 owner.getStudents().add(student); 1007 } 1008 } 1009 String available = e.attributeValue("available"); 1010 if (available != null) 1011 for (ExamPeriod period : getPeriods()) { 1012 if (available.charAt(period.getIndex()) == '0') 1013 student.setAvailable(period.getIndex(), false); 1014 } 1015 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 1016 Element pe = (Element) j.next(); 1017 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 1018 if (period == null) continue; 1019 if ("false".equals(pe.attributeValue("available"))) 1020 student.setAvailable(period.getIndex(), false); 1021 } 1022 addConstraint(student); 1023 getStudents().add(student); 1024 } 1025 if (root.element("instructors") != null) 1026 for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) { 1027 Element e = (Element) i.next(); 1028 ExamInstructor instructor = new ExamInstructor(this, Long.parseLong(e.attributeValue("id")), e 1029 .attributeValue("name")); 1030 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1031 Element x = (Element) j.next(); 1032 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 1033 instructor.addVariable(ex); 1034 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 1035 Element f = (Element) k.next(); 1036 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 1037 instructor.getOwners().add(owner); 1038 owner.getIntructors().add(instructor); 1039 } 1040 } 1041 String available = e.attributeValue("available"); 1042 if (available != null) 1043 for (ExamPeriod period : getPeriods()) { 1044 if (available.charAt(period.getIndex()) == '0') 1045 instructor.setAvailable(period.getIndex(), false); 1046 } 1047 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 1048 Element pe = (Element) j.next(); 1049 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 1050 if (period == null) continue; 1051 if ("false".equals(pe.attributeValue("available"))) 1052 instructor.setAvailable(period.getIndex(), false); 1053 } 1054 addConstraint(instructor); 1055 getInstructors().add(instructor); 1056 } 1057 if (root.element("constraints") != null) 1058 for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) { 1059 Element e = (Element) i.next(); 1060 ExamDistributionConstraint dc = new ExamDistributionConstraint(Long.parseLong(e.attributeValue("id")), 1061 e.getName(), 1062 softDistributions != null ? false : "true".equals(e.attributeValue("hard", "true")), 1063 (softDistributions != null && "true".equals(e.attributeValue("hard", "true")) ? softDistributions : Integer.parseInt(e.attributeValue("weight", "0")))); 1064 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1065 dc.addVariable(exams.get(Long.valueOf(((Element) j.next()).attributeValue("id")))); 1066 } 1067 addConstraint(dc); 1068 getDistributionConstraints().add(dc); 1069 } 1070 init(); 1071 if (loadBest && saveBest != null) { 1072 for (Exam exam : variables()) { 1073 ExamPlacement placement = exam.getBestAssignment(); 1074 if (placement == null) 1075 continue; 1076 exam.assign(0, placement); 1077 } 1078 saveBest.execute(); 1079 for (Exam exam : variables()) { 1080 if (exam.getAssignment() != null) 1081 exam.unassign(0); 1082 } 1083 } 1084 for (ExamPlacement placement : assignments) { 1085 Exam exam = placement.variable(); 1086 Set<ExamPlacement> conf = conflictValues(placement); 1087 if (!conf.isEmpty()) { 1088 for (Map.Entry<Constraint<Exam, ExamPlacement>, Set<ExamPlacement>> entry : conflictConstraints( 1089 placement).entrySet()) { 1090 Constraint<Exam, ExamPlacement> constraint = entry.getKey(); 1091 Set<ExamPlacement> values = entry.getValue(); 1092 if (constraint instanceof ExamStudent) { 1093 ((ExamStudent) constraint).setAllowDirectConflicts(true); 1094 exam.setAllowDirectConflicts(true); 1095 for (ExamPlacement p : values) 1096 p.variable().setAllowDirectConflicts(true); 1097 } 1098 } 1099 conf = conflictValues(placement); 1100 } 1101 if (conf.isEmpty()) { 1102 exam.assign(0, placement); 1103 } else { 1104 sLog.error("Unable to assign " + exam.getInitialAssignment().getName() + " to exam " + exam.getName()); 1105 sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(exam.getInitialAssignment()), 2)); 1106 } 1107 } 1108 return true; 1109 } 1110 }