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