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