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 String ret = ""; 401 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 402 String val = criterion.toString(); 403 if (!val.isEmpty()) 404 ret += (!ret.isEmpty() && !ret.endsWith(",") ? "," : "") + val; 405 } 406 return ret; 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 Element root = document.getRootElement(); 779 if (!"examtt".equals(root.getName())) 780 return false; 781 if (root.attribute("campus") != null) 782 getProperties().setProperty("Data.Initiative", root.attributeValue("campus")); 783 else if (root.attribute("initiative") != null) 784 getProperties().setProperty("Data.Initiative", root.attributeValue("initiative")); 785 if (root.attribute("term") != null) 786 getProperties().setProperty("Data.Term", root.attributeValue("term")); 787 if (root.attribute("year") != null) 788 getProperties().setProperty("Data.Year", root.attributeValue("year")); 789 if (loadParams && root.element("parameters") != null) { 790 Map<String,String> params = new HashMap<String, String>(); 791 for (Iterator<?> i = root.element("parameters").elementIterator("property"); i.hasNext();) { 792 Element e = (Element) i.next(); 793 params.put(e.attributeValue("name"), e.attributeValue("value")); 794 } 795 for (Criterion<Exam, ExamPlacement> criterion: getCriteria()) { 796 if (criterion instanceof ExamCriterion) 797 ((ExamCriterion)criterion).setXmlParameters(params); 798 } 799 try { 800 setMaxRooms(Integer.valueOf(params.get("maxRooms"))); 801 } catch (NumberFormatException e) {} catch (NullPointerException e) {} 802 } 803 for (Iterator<?> i = root.element("periods").elementIterator("period"); i.hasNext();) { 804 Element e = (Element) i.next(); 805 addPeriod(Long.valueOf(e.attributeValue("id")), e.attributeValue("day"), e.attributeValue("time"), Integer 806 .parseInt(e.attributeValue("length")), Integer.parseInt(e.attributeValue("penalty") == null ? e 807 .attributeValue("weight", "0") : e.attributeValue("penalty"))); 808 } 809 HashMap<Long, ExamRoom> rooms = new HashMap<Long, ExamRoom>(); 810 HashMap<String, ArrayList<ExamRoom>> roomGroups = new HashMap<String, ArrayList<ExamRoom>>(); 811 for (Iterator<?> i = root.element("rooms").elementIterator("room"); i.hasNext();) { 812 Element e = (Element) i.next(); 813 String coords = e.attributeValue("coordinates"); 814 ExamRoom room = new ExamRoom(this, Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), 815 Integer.parseInt(e.attributeValue("size")), Integer.parseInt(e.attributeValue("alt")), 816 (coords == null ? null : Double.valueOf(coords.substring(0, coords.indexOf(',')))), 817 (coords == null ? null : Double.valueOf(coords.substring(coords.indexOf(',') + 1)))); 818 addConstraint(room); 819 getRooms().add(room); 820 rooms.put(new Long(room.getId()), room); 821 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 822 Element pe = (Element) j.next(); 823 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 824 if ("false".equals(pe.attributeValue("available"))) 825 room.setAvailable(period, false); 826 else 827 room.setPenalty(period, Integer.parseInt(pe.attributeValue("penalty"))); 828 } 829 String av = e.attributeValue("available"); 830 if (av != null) { 831 for (int j = 0; j < getPeriods().size(); j++) 832 if ('0' == av.charAt(j)) 833 room.setAvailable(getPeriods().get(j), false); 834 } 835 String g = e.attributeValue("groups"); 836 if (g != null) { 837 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 838 String gr = s.nextToken(); 839 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 840 if (roomsThisGrop == null) { 841 roomsThisGrop = new ArrayList<ExamRoom>(); 842 roomGroups.put(gr, roomsThisGrop); 843 } 844 roomsThisGrop.add(room); 845 } 846 } 847 for (Iterator<?> j = e.elementIterator("travel-time"); j.hasNext();) { 848 Element travelTimeEl = (Element)j.next(); 849 getDistanceMetric().addTravelTime(room.getId(), 850 Long.valueOf(travelTimeEl.attributeValue("id")), 851 Integer.valueOf(travelTimeEl.attributeValue("minutes"))); 852 } 853 } 854 ArrayList<ExamPlacement> assignments = new ArrayList<ExamPlacement>(); 855 HashMap<Long, Exam> exams = new HashMap<Long, Exam>(); 856 HashMap<Long, ExamOwner> courseSections = new HashMap<Long, ExamOwner>(); 857 for (Iterator<?> i = root.element("exams").elementIterator("exam"); i.hasNext();) { 858 Element e = (Element) i.next(); 859 ArrayList<ExamPeriodPlacement> periodPlacements = new ArrayList<ExamPeriodPlacement>(); 860 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 861 Element pe = (Element) j.next(); 862 periodPlacements.add(new ExamPeriodPlacement(getPeriod(Long.valueOf(pe.attributeValue("id"))), Integer 863 .parseInt(pe.attributeValue("penalty", "0")))); 864 } 865 ArrayList<ExamRoomPlacement> roomPlacements = new ArrayList<ExamRoomPlacement>(); 866 for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) { 867 Element re = (Element) j.next(); 868 ExamRoomPlacement room = new ExamRoomPlacement(rooms.get(Long.valueOf(re.attributeValue("id"))), 869 Integer.parseInt(re.attributeValue("penalty", "0")), Integer.parseInt(re.attributeValue( 870 "maxPenalty", "100"))); 871 roomPlacements.add(room); 872 } 873 String g = e.attributeValue("groups"); 874 if (g != null) { 875 HashMap<ExamRoom, Integer> allRooms = new HashMap<ExamRoom, Integer>(); 876 for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) { 877 String gr = s.nextToken(); 878 ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr); 879 if (roomsThisGrop != null) 880 for (ExamRoom r : roomsThisGrop) 881 allRooms.put(r, 0); 882 } 883 for (Iterator<?> j = e.elementIterator("original-room"); j.hasNext();) { 884 allRooms.put((rooms.get(Long.valueOf(((Element) j.next()).attributeValue("id")))), new Integer(-1)); 885 } 886 for (Map.Entry<ExamRoom, Integer> entry : allRooms.entrySet()) { 887 ExamRoomPlacement room = new ExamRoomPlacement(entry.getKey(), entry.getValue(), 100); 888 roomPlacements.add(room); 889 } 890 if (periodPlacements.isEmpty()) { 891 for (ExamPeriod p : getPeriods()) { 892 periodPlacements.add(new ExamPeriodPlacement(p, 0)); 893 } 894 } 895 } 896 Exam exam = new Exam(Long.parseLong(e.attributeValue("id")), e.attributeValue("name"), Integer.parseInt(e 897 .attributeValue("length")), "true".equals(e.attributeValue("alt")), 898 (e.attribute("maxRooms") == null ? getMaxRooms() : Integer.parseInt(e.attributeValue("maxRooms"))), 899 Integer.parseInt(e.attributeValue("minSize", "0")), periodPlacements, roomPlacements); 900 if (e.attributeValue("size") != null) 901 exam.setSizeOverride(Integer.valueOf(e.attributeValue("size"))); 902 if (e.attributeValue("printOffset") != null) 903 exam.setPrintOffset(Integer.valueOf(e.attributeValue("printOffset"))); 904 exams.put(new Long(exam.getId()), exam); 905 addVariable(exam); 906 if (e.attribute("average") != null) 907 exam.setAveragePeriod(Integer.parseInt(e.attributeValue("average"))); 908 Element asg = e.element("assignment"); 909 if (asg != null && loadSolution) { 910 Element per = asg.element("period"); 911 if (per != null) { 912 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 913 for (Iterator<?> j = asg.elementIterator("room"); j.hasNext();) 914 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 915 ExamPlacement p = new ExamPlacement(exam, exam.getPeriodPlacement(Long.valueOf(per 916 .attributeValue("id"))), rp); 917 assignments.add(p); 918 } 919 } 920 Element ini = e.element("initial"); 921 if (ini != null && loadInitial) { 922 Element per = ini.element("period"); 923 if (per != null) { 924 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 925 for (Iterator<?> j = ini.elementIterator("room"); j.hasNext();) 926 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 927 ExamPlacement p = new ExamPlacement(exam, exam.getPeriodPlacement(Long.valueOf(per 928 .attributeValue("id"))), rp); 929 exam.setInitialAssignment(p); 930 } 931 } 932 Element best = e.element("best"); 933 if (best != null && loadBest) { 934 Element per = best.element("period"); 935 if (per != null) { 936 HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>(); 937 for (Iterator<?> j = best.elementIterator("room"); j.hasNext();) 938 rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id")))); 939 ExamPlacement p = new ExamPlacement(exam, exam.getPeriodPlacement(Long.valueOf(per 940 .attributeValue("id"))), rp); 941 exam.setBestAssignment(p); 942 } 943 } 944 for (Iterator<?> j = e.elementIterator("owner"); j.hasNext();) { 945 Element f = (Element) j.next(); 946 ExamOwner owner = new ExamOwner(exam, Long.parseLong(f.attributeValue("id")), f.attributeValue("name")); 947 exam.getOwners().add(owner); 948 courseSections.put(new Long(owner.getId()), owner); 949 } 950 if (iRoomSharing != null) 951 iRoomSharing.load(exam, e); 952 } 953 for (Iterator<?> i = root.element("students").elementIterator("student"); i.hasNext();) { 954 Element e = (Element) i.next(); 955 ExamStudent student = new ExamStudent(this, Long.parseLong(e.attributeValue("id"))); 956 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 957 Element x = (Element) j.next(); 958 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 959 student.addVariable(ex); 960 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 961 Element f = (Element) k.next(); 962 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 963 student.getOwners().add(owner); 964 owner.getStudents().add(student); 965 } 966 } 967 String available = e.attributeValue("available"); 968 if (available != null) 969 for (ExamPeriod period : getPeriods()) { 970 if (available.charAt(period.getIndex()) == '0') 971 student.setAvailable(period.getIndex(), false); 972 } 973 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 974 Element pe = (Element) j.next(); 975 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 976 if ("false".equals(pe.attributeValue("available"))) 977 student.setAvailable(period.getIndex(), false); 978 } 979 addConstraint(student); 980 getStudents().add(student); 981 } 982 if (root.element("instructors") != null) 983 for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) { 984 Element e = (Element) i.next(); 985 ExamInstructor instructor = new ExamInstructor(this, Long.parseLong(e.attributeValue("id")), e 986 .attributeValue("name")); 987 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 988 Element x = (Element) j.next(); 989 Exam ex = exams.get(Long.valueOf(x.attributeValue("id"))); 990 instructor.addVariable(ex); 991 for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) { 992 Element f = (Element) k.next(); 993 ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id"))); 994 instructor.getOwners().add(owner); 995 owner.getIntructors().add(instructor); 996 } 997 } 998 String available = e.attributeValue("available"); 999 if (available != null) 1000 for (ExamPeriod period : getPeriods()) { 1001 if (available.charAt(period.getIndex()) == '0') 1002 instructor.setAvailable(period.getIndex(), false); 1003 } 1004 for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) { 1005 Element pe = (Element) j.next(); 1006 ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id"))); 1007 if ("false".equals(pe.attributeValue("available"))) 1008 instructor.setAvailable(period.getIndex(), false); 1009 } 1010 addConstraint(instructor); 1011 getInstructors().add(instructor); 1012 } 1013 if (root.element("constraints") != null) 1014 for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) { 1015 Element e = (Element) i.next(); 1016 ExamDistributionConstraint dc = new ExamDistributionConstraint(Long.parseLong(e.attributeValue("id")), 1017 e.getName(), "true".equals(e.attributeValue("hard", "true")), Integer.parseInt(e 1018 .attributeValue("weight", "0"))); 1019 for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) { 1020 dc.addVariable(exams.get(Long.valueOf(((Element) j.next()).attributeValue("id")))); 1021 } 1022 addConstraint(dc); 1023 getDistributionConstraints().add(dc); 1024 } 1025 init(); 1026 if (loadBest && saveBest != null) { 1027 for (Exam exam : variables()) { 1028 ExamPlacement placement = exam.getBestAssignment(); 1029 if (placement == null) 1030 continue; 1031 exam.assign(0, placement); 1032 } 1033 saveBest.execute(); 1034 for (Exam exam : variables()) { 1035 if (exam.getAssignment() != null) 1036 exam.unassign(0); 1037 } 1038 } 1039 for (ExamPlacement placement : assignments) { 1040 Exam exam = placement.variable(); 1041 Set<ExamPlacement> conf = conflictValues(placement); 1042 if (!conf.isEmpty()) { 1043 for (Map.Entry<Constraint<Exam, ExamPlacement>, Set<ExamPlacement>> entry : conflictConstraints( 1044 placement).entrySet()) { 1045 Constraint<Exam, ExamPlacement> constraint = entry.getKey(); 1046 Set<ExamPlacement> values = entry.getValue(); 1047 if (constraint instanceof ExamStudent) { 1048 ((ExamStudent) constraint).setAllowDirectConflicts(true); 1049 exam.setAllowDirectConflicts(true); 1050 for (ExamPlacement p : values) 1051 p.variable().setAllowDirectConflicts(true); 1052 } 1053 } 1054 conf = conflictValues(placement); 1055 } 1056 if (conf.isEmpty()) { 1057 exam.assign(0, placement); 1058 } else { 1059 sLog.error("Unable to assign " + exam.getInitialAssignment().getName() + " to exam " + exam.getName()); 1060 sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(exam.getInitialAssignment()), 2)); 1061 } 1062 } 1063 return true; 1064 } 1065 }