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