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