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