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