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