net.sf.cpsolver.exam.model.ExamModel.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.cpsolver.exam.model.ExamModel.java

Source

package net.sf.cpsolver.exam.model;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;

import net.sf.cpsolver.coursett.IdConvertor;
import net.sf.cpsolver.ifs.model.Constraint;
import net.sf.cpsolver.ifs.model.Model;
import net.sf.cpsolver.ifs.util.Callback;
import net.sf.cpsolver.ifs.util.DataProperties;
import net.sf.cpsolver.ifs.util.DistanceMetric;
import net.sf.cpsolver.ifs.util.ToolBox;

import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

/**
 * Examination timetabling model. Exams {@link Exam} are modeled as variables,
 * rooms {@link ExamRoom} and students {@link ExamStudent} as constraints.
 * Assignment of an exam to time (modeled as non-overlapping periods
 * {@link ExamPeriod}) and space (set of rooms) is modeled using values
 * {@link ExamPlacement}. In order to be able to model individual period and
 * room preferences, period and room assignments are wrapped with
 * {@link ExamPeriodPlacement} and {@link ExamRoomPlacement} classes
 * respectively. Moreover, additional distribution constraint
 * {@link ExamDistributionConstraint} can be defined in the model. <br>
 * <br>
 * The objective function consists of the following criteria:
 * <ul>
 * <li>Direct student conflicts (a student is enrolled in two exams that are
 * scheduled at the same period, weighted by Exams.DirectConflictWeight)
 * <li>Back-to-Back student conflicts (a student is enrolled in two exams that
 * are scheduled in consecutive periods, weighted by
 * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false,
 * there is no conflict between the last period and the first period of
 * consecutive days.
 * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student
 * conflict, but the maximum distance between rooms in which both exam take
 * place is greater than Exams.BackToBackDistance, weighted by
 * Exams.DistanceBackToBackConflictWeight).
 * <li>More than two exams a day (a student is enrolled in three exams that are
 * scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight).
 * <li>Period penalty (total of period penalties
 * {@link ExamPlacement#getPeriodPenalty()} of all assigned exams, weighted by
 * Exams.PeriodWeight).
 * <li>Room size penalty (total of room size penalties
 * {@link ExamPlacement#getRoomSizePenalty()} of all assigned exams, weighted by
 * Exams.RoomSizeWeight).
 * <li>Room split penalty (total of room split penalties
 * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned exams, weighted
 * by Exams.RoomSplitWeight).
 * <li>Room penalty (total of room penalties
 * {@link ExamPlacement#getRoomPenalty()} of all assigned exams, weighted by
 * Exams.RoomWeight).
 * <li>Distribution penalty (total of distribution constraint weights
 * {@link ExamDistributionConstraint#getWeight()} of all soft distribution
 * constraints that are not satisfied, i.e.,
 * {@link ExamDistributionConstraint#isSatisfied()} = false; weighted by
 * Exams.DistributionWeight).
 * <li>Direct instructor conflicts (an instructor is enrolled in two exams that
 * are scheduled at the same period, weighted by
 * Exams.InstructorDirectConflictWeight)
 * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two exams
 * that are scheduled in consecutive periods, weighted by
 * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack is
 * false, there is no conflict between the last period and the first period of
 * consecutive days.
 * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back
 * instructor conflict, but the maximum distance between rooms in which both
 * exam take place is greater than Exams.BackToBackDistance, weighted by
 * Exams.InstructorDistanceBackToBackConflictWeight).
 * <li>Room split distance penalty (if an examination is assigned between two or
 * three rooms, distance between these rooms can be minimized using this
 * criterion)
 * <li>Front load penalty (large exams can be penalized if assigned on or after
 * a certain period)
 * </ul>
 * 
 * @version ExamTT 1.2 (Examination Timetabling)<br>
 *          Copyright (C) 2008 - 2010 Tomas Muller<br>
 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
 * <br>
 *          This library is free software; you can redistribute it and/or modify
 *          it under the terms of the GNU Lesser General Public License as
 *          published by the Free Software Foundation; either version 3 of the
 *          License, or (at your option) any later version. <br>
 * <br>
 *          This library is distributed in the hope that it will be useful, but
 *          WITHOUT ANY WARRANTY; without even the implied warranty of
 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *          Lesser General Public License for more details. <br>
 * <br>
 *          You should have received a copy of the GNU Lesser General Public
 *          License along with this library; if not see
 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
 */
public class ExamModel extends Model<Exam, ExamPlacement> {
    private static Logger sLog = Logger.getLogger(ExamModel.class);
    private DataProperties iProperties = null;
    private int iMaxRooms = 4;
    private List<ExamPeriod> iPeriods = new ArrayList<ExamPeriod>();
    private List<ExamRoom> iRooms = new ArrayList<ExamRoom>();
    private List<ExamStudent> iStudents = new ArrayList<ExamStudent>();
    private List<ExamDistributionConstraint> iDistributionConstraints = new ArrayList<ExamDistributionConstraint>();
    private List<ExamInstructor> iInstructors = new ArrayList<ExamInstructor>();

    private boolean iDayBreakBackToBack = false;
    private double iDirectConflictWeight = 1000.0;
    private double iMoreThanTwoADayWeight = 100.0;
    private double iBackToBackConflictWeight = 10.0;
    private double iDistanceBackToBackConflictWeight = 25.0;
    private double iPeriodWeight = 1.0;
    private double iPeriodSizeWeight = 1.0;
    private double iPeriodIndexWeight = 0.0000001;
    private double iExamRotationWeight = 0.001;
    private double iRoomSizeWeight = 0.0001;
    private double iRoomSplitWeight = 10.0;
    private double iRoomWeight = 0.1;
    private double iDistributionWeight = 1.0;
    private double iBackToBackDistance = -1; // 67
    private double iInstructorDirectConflictWeight = 1000.0;
    private double iInstructorMoreThanTwoADayWeight = 100.0;
    private double iInstructorBackToBackConflictWeight = 10.0;
    private double iInstructorDistanceBackToBackConflictWeight = 25.0;
    private boolean iMPP = false;
    private double iPerturbationWeight = 0.01;
    private double iRoomPerturbationWeight = 0.01;
    private double iRoomSplitDistanceWeight = 0.01;
    private int iLargeSize = -1;
    private double iLargePeriod = 0.67;
    private double iLargeWeight = 1.0;

    private int iNrDirectConflicts = 0;
    private int iNrNADirectConflicts = 0;
    private int iNrBackToBackConflicts = 0;
    private int iNrDistanceBackToBackConflicts = 0;
    private int iNrMoreThanTwoADayConflicts = 0;
    private int iRoomSizePenalty = 0;
    private int iRoomSplitPenalty = 0;
    private int iRoomSplits = 0;
    private int iRoomSplitPenalties[] = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    private int iRoomPenalty = 0;
    private int iDistributionPenalty = 0;
    private int iPeriodPenalty = 0;
    private int iPeriodSizePenalty = 0;
    private int iPeriodIndexPenalty = 0;
    private int iExamRotationPenalty = 0;
    private int iPerturbationPenalty = 0;
    private int iRoomPerturbationPenalty = 0;
    private int iNrInstructorDirectConflicts = 0;
    private int iNrNAInstructorDirectConflicts = 0;
    private int iNrInstructorBackToBackConflicts = 0;
    private int iNrInstructorDistanceBackToBackConflicts = 0;
    private int iNrInstructorMoreThanTwoADayConflicts = 0;
    private int iLargePenalty = 0;
    private double iRoomSplitDistancePenalty = 0;
    private int iNrLargeExams;

    private DistanceMetric iDistanceMetric = null;

    /**
     * Constructor
     * 
     * @param properties
     *            problem properties
     */
    public ExamModel(DataProperties properties) {
        iAssignedVariables = null;
        iUnassignedVariables = null;
        iPerturbVariables = null;
        iProperties = properties;
        iMaxRooms = properties.getPropertyInt("Exams.MaxRooms", iMaxRooms);
        iDayBreakBackToBack = properties.getPropertyBoolean("Exams.IsDayBreakBackToBack", iDayBreakBackToBack);
        iDirectConflictWeight = properties.getPropertyDouble("Exams.DirectConflictWeight", iDirectConflictWeight);
        iBackToBackConflictWeight = properties.getPropertyDouble("Exams.BackToBackConflictWeight",
                iBackToBackConflictWeight);
        iDistanceBackToBackConflictWeight = properties.getPropertyDouble("Exams.DistanceBackToBackConflictWeight",
                iDistanceBackToBackConflictWeight);
        iMoreThanTwoADayWeight = properties.getPropertyDouble("Exams.MoreThanTwoADayWeight",
                iMoreThanTwoADayWeight);
        iPeriodWeight = properties.getPropertyDouble("Exams.PeriodWeight", iPeriodWeight);
        iPeriodIndexWeight = properties.getPropertyDouble("Exams.PeriodIndexWeight", iPeriodIndexWeight);
        iPeriodSizeWeight = properties.getPropertyDouble("Exams.PeriodSizeWeight", iPeriodSizeWeight);
        iExamRotationWeight = properties.getPropertyDouble("Exams.RotationWeight", iExamRotationWeight);
        iRoomSizeWeight = properties.getPropertyDouble("Exams.RoomSizeWeight", iRoomSizeWeight);
        iRoomWeight = properties.getPropertyDouble("Exams.RoomWeight", iRoomWeight);
        iRoomSplitWeight = properties.getPropertyDouble("Exams.RoomSplitWeight", iRoomSplitWeight);
        iBackToBackDistance = properties.getPropertyDouble("Exams.BackToBackDistance", iBackToBackDistance);
        iDistributionWeight = properties.getPropertyDouble("Exams.DistributionWeight", iDistributionWeight);
        iInstructorDirectConflictWeight = properties.getPropertyDouble("Exams.InstructorDirectConflictWeight",
                iInstructorDirectConflictWeight);
        iInstructorBackToBackConflictWeight = properties
                .getPropertyDouble("Exams.InstructorBackToBackConflictWeight", iInstructorBackToBackConflictWeight);
        iInstructorDistanceBackToBackConflictWeight = properties.getPropertyDouble(
                "Exams.InstructorDistanceBackToBackConflictWeight", iInstructorDistanceBackToBackConflictWeight);
        iInstructorMoreThanTwoADayWeight = properties.getPropertyDouble("Exams.InstructorMoreThanTwoADayWeight",
                iInstructorMoreThanTwoADayWeight);
        iMPP = properties.getPropertyBoolean("General.MPP", iMPP);
        iPerturbationWeight = properties.getPropertyDouble("Exams.PerturbationWeight", iPerturbationWeight);
        iRoomPerturbationWeight = properties.getPropertyDouble("Exams.RoomPerturbationWeight",
                iRoomPerturbationWeight);
        iRoomSplitDistanceWeight = properties.getPropertyDouble("Exams.RoomSplitDistanceWeight",
                iRoomSplitDistanceWeight);
        iLargeSize = properties.getPropertyInt("Exams.LargeSize", iLargeSize);
        iLargePeriod = properties.getPropertyDouble("Exams.LargePeriod", iLargePeriod);
        iLargeWeight = properties.getPropertyDouble("Exams.LargeWeight", iLargeWeight);
        iDistanceMetric = new DistanceMetric(properties);
    }

    public DistanceMetric getDistanceMetric() {
        return iDistanceMetric;
    }

    /**
     * Initialization of the model
     */
    public void init() {
        iNrLargeExams = 0;
        for (Exam exam : variables()) {
            if (getLargeSize() >= 0 && exam.getSize() >= getLargeSize())
                iNrLargeExams++;
            for (ExamRoomPlacement room : exam.getRoomPlacements()) {
                room.getRoom().addVariable(exam);
            }
        }
        iLimits = null;
        iMaxDistributionPenalty = null;
    }

    /**
     * Default maximum number of rooms (can be set by problem property
     * Exams.MaxRooms, or in the input xml file, property maxRooms)
     */
    public int getMaxRooms() {
        return iMaxRooms;
    }

    /**
     * Default maximum number of rooms (can be set by problem property
     * Exams.MaxRooms, or in the input xml file, property maxRooms)
     */
    public void setMaxRooms(int maxRooms) {
        iMaxRooms = maxRooms;
    }

    /**
     * Add a period
     * 
     * @param id
     *            period unique identifier
     * @param day
     *            day (e.g., 07/12/10)
     * @param time
     *            (e.g., 8:00am-10:00am)
     * @param length
     *            length of period in minutes
     * @param penalty
     *            period penalty
     */
    public ExamPeriod addPeriod(Long id, String day, String time, int length, int penalty) {
        ExamPeriod lastPeriod = (iPeriods.isEmpty() ? null : (ExamPeriod) iPeriods.get(iPeriods.size() - 1));
        ExamPeriod p = new ExamPeriod(id, day, time, length, penalty);
        if (lastPeriod == null)
            p.setIndex(iPeriods.size(), 0, 0);
        else if (lastPeriod.getDayStr().equals(day)) {
            p.setIndex(iPeriods.size(), lastPeriod.getDay(), lastPeriod.getTime() + 1);
        } else
            p.setIndex(iPeriods.size(), lastPeriod.getDay() + 1, 0);
        if (lastPeriod != null) {
            lastPeriod.setNext(p);
            p.setPrev(lastPeriod);
        }
        iPeriods.add(p);
        return p;
    }

    /**
     * Number of days
     */
    public int getNrDays() {
        return (iPeriods.get(iPeriods.size() - 1)).getDay() + 1;
    }

    /**
     * Number of periods
     */
    public int getNrPeriods() {
        return iPeriods.size();
    }

    /**
     * List of periods, use
     * {@link ExamModel#addPeriod(Long, String, String, int, int)} to add a
     * period
     * 
     * @return list of {@link ExamPeriod}
     */
    public List<ExamPeriod> getPeriods() {
        return iPeriods;
    }

    /** Period of given unique id */
    public ExamPeriod getPeriod(Long id) {
        for (ExamPeriod period : iPeriods) {
            if (period.getId().equals(id))
                return period;
        }
        return null;
    }

    /**
     * Direct student conflict weight (can be set by problem property
     * Exams.DirectConflictWeight, or in the input xml file, property
     * directConflictWeight)
     */
    public double getDirectConflictWeight() {
        return iDirectConflictWeight;
    }

    /**
     * Direct student conflict weight (can be set by problem property
     * Exams.DirectConflictWeight, or in the input xml file, property
     * directConflictWeight)
     */
    public void setDirectConflictWeight(double directConflictWeight) {
        iDirectConflictWeight = directConflictWeight;
    }

    /**
     * Back-to-back student conflict weight (can be set by problem property
     * Exams.BackToBackConflictWeight, or in the input xml file, property
     * backToBackConflictWeight)
     */
    public double getBackToBackConflictWeight() {
        return iBackToBackConflictWeight;
    }

    /**
     * Back-to-back student conflict weight (can be set by problem property
     * Exams.BackToBackConflictWeight, or in the input xml file, property
     * backToBackConflictWeight)
     */
    public void setBackToBackConflictWeight(double backToBackConflictWeight) {
        iBackToBackConflictWeight = backToBackConflictWeight;
    }

    /**
     * Distance back-to-back student conflict weight (can be set by problem
     * property Exams.DistanceBackToBackConflictWeight, or in the input xml
     * file, property distanceBackToBackConflictWeight)
     */
    public double getDistanceBackToBackConflictWeight() {
        return iDistanceBackToBackConflictWeight;
    }

    /**
     * Distance back-to-back student conflict weight (can be set by problem
     * property Exams.DistanceBackToBackConflictWeight, or in the input xml
     * file, property distanceBackToBackConflictWeight)
     */
    public void setDistanceBackToBackConflictWeight(double distanceBackToBackConflictWeight) {
        iDistanceBackToBackConflictWeight = distanceBackToBackConflictWeight;
    }

    /**
     * More than two exams a day student conflict weight (can be set by problem
     * property Exams.MoreThanTwoADayWeight, or in the input xml file, property
     * moreThanTwoADayWeight)
     */
    public double getMoreThanTwoADayWeight() {
        return iMoreThanTwoADayWeight;
    }

    /**
     * More than two exams a day student conflict weight (can be set by problem
     * property Exams.MoreThanTwoADayWeight, or in the input xml file, property
     * moreThanTwoADayWeight)
     */
    public void setMoreThanTwoADayWeight(double moreThanTwoADayWeight) {
        iMoreThanTwoADayWeight = moreThanTwoADayWeight;
    }

    /**
     * Direct instructor conflict weight (can be set by problem property
     * Exams.InstructorDirectConflictWeight, or in the input xml file, property
     * instructorDirectConflictWeight)
     */
    public double getInstructorDirectConflictWeight() {
        return iInstructorDirectConflictWeight;
    }

    /**
     * Direct instructor conflict weight (can be set by problem property
     * Exams.InstructorDirectConflictWeight, or in the input xml file, property
     * instructorDirectConflictWeight)
     */
    public void setInstructorDirectConflictWeight(double directConflictWeight) {
        iInstructorDirectConflictWeight = directConflictWeight;
    }

    /**
     * Back-to-back instructor conflict weight (can be set by problem property
     * Exams.InstructorBackToBackConflictWeight, or in the input xml file,
     * property instructorBackToBackConflictWeight)
     */
    public double getInstructorBackToBackConflictWeight() {
        return iInstructorBackToBackConflictWeight;
    }

    /**
     * Back-to-back instructor conflict weight (can be set by problem property
     * Exams.InstructorBackToBackConflictWeight, or in the input xml file,
     * property instructorBackToBackConflictWeight)
     */
    public void setInstructorBackToBackConflictWeight(double backToBackConflictWeight) {
        iInstructorBackToBackConflictWeight = backToBackConflictWeight;
    }

    /**
     * Distance back-to-back instructor conflict weight (can be set by problem
     * property Exams.InstructorDistanceBackToBackConflictWeight, or in the
     * input xml file, property instructorDistanceBackToBackConflictWeight)
     */
    public double getInstructorDistanceBackToBackConflictWeight() {
        return iInstructorDistanceBackToBackConflictWeight;
    }

    /**
     * Distance back-to-back instructor conflict weight (can be set by problem
     * property Exams.InstructorDistanceBackToBackConflictWeight, or in the
     * input xml file, property instructorDistanceBackToBackConflictWeight)
     */
    public void setInstructorDistanceBackToBackConflictWeight(double distanceBackToBackConflictWeight) {
        iInstructorDistanceBackToBackConflictWeight = distanceBackToBackConflictWeight;
    }

    /**
     * More than two exams a day instructor conflict weight (can be set by
     * problem property Exams.InstructorMoreThanTwoADayWeight, or in the input
     * xml file, property instructorMoreThanTwoADayWeight)
     */
    public double getInstructorMoreThanTwoADayWeight() {
        return iInstructorMoreThanTwoADayWeight;
    }

    /**
     * More than two exams a day instructor conflict weight (can be set by
     * problem property Exams.InstructorMoreThanTwoADayWeight, or in the input
     * xml file, property instructorMoreThanTwoADayWeight)
     */
    public void setInstructorMoreThanTwoADayWeight(double moreThanTwoADayWeight) {
        iInstructorMoreThanTwoADayWeight = moreThanTwoADayWeight;
    }

    /**
     * True when back-to-back student conflict is to be encountered when a
     * student is enrolled into an exam that is on the last period of one day
     * and another exam that is on the first period of the consecutive day. It
     * can be set by problem property Exams.IsDayBreakBackToBack, or in the
     * input xml file, property isDayBreakBackToBack)
     * 
     */
    public boolean isDayBreakBackToBack() {
        return iDayBreakBackToBack;
    }

    /**
     * True when back-to-back student conflict is to be encountered when a
     * student is enrolled into an exam that is on the last period of one day
     * and another exam that is on the first period of the consecutive day. It
     * can be set by problem property Exams.IsDayBreakBackToBack, or in the
     * input xml file, property isDayBreakBackToBack)
     * 
     */
    public void setDayBreakBackToBack(boolean dayBreakBackToBack) {
        iDayBreakBackToBack = dayBreakBackToBack;
    }

    /**
     * A weight for period penalty (used in
     * {@link ExamPlacement#getPeriodPenalty()}, can be set by problem property
     * Exams.PeriodWeight, or in the input xml file, property periodWeight)
     * 
     */
    public double getPeriodWeight() {
        return iPeriodWeight;
    }

    /**
     * A weight for period penalty (used in
     * {@link ExamPlacement#getPeriodPenalty()}, can be set by problem property
     * Exams.PeriodWeight, or in the input xml file, property periodWeight)
     * 
     */
    public void setPeriodWeight(double periodWeight) {
        iPeriodWeight = periodWeight;
    }

    /**
     * A weight for period penalty (used in
     * {@link ExamPlacement#getPeriodPenalty()} multiplied by examination size
     * {@link Exam#getSize()}, can be set by problem property
     * Exams.PeriodSizeWeight, or in the input xml file, property periodWeight)
     * 
     */
    public double getPeriodSizeWeight() {
        return iPeriodSizeWeight;
    }

    /**
     * A weight for period penalty (used in
     * {@link ExamPlacement#getPeriodPenalty()} multiplied by examination size
     * {@link Exam#getSize()}, can be set by problem property
     * Exams.PeriodSizeWeight, or in the input xml file, property periodWeight)
     * 
     */
    public void setPeriodSizeWeight(double periodSizeWeight) {
        iPeriodSizeWeight = periodSizeWeight;
    }

    /**
     * A weight for period index, can be set by problem property
     * Exams.PeriodIndexWeight, or in the input xml file, property periodWeight)
     * 
     */
    public double getPeriodIndexWeight() {
        return iPeriodIndexWeight;
    }

    /**
     * A weight for period index, can be set by problem property
     * Exams.PeriodIndexWeight, or in the input xml file, property periodWeight)
     * 
     */
    public void setPeriodIndexWeight(double periodIndexWeight) {
        iPeriodIndexWeight = periodIndexWeight;
    }

    /**
     * A weight for exam rotation penalty (used in
     * {@link ExamPlacement#getRotationPenalty()} can be set by problem property
     * Exams.RotationWeight, or in the input xml file, property
     * examRotationWeight)
     * 
     */
    public double getExamRotationWeight() {
        return iExamRotationWeight;
    }

    /**
     * A weight for period penalty (used in
     * {@link ExamPlacement#getRotationPenalty()}, can be set by problem
     * property Exams.RotationWeight, or in the input xml file, property
     * examRotationWeight)
     * 
     */
    public void setExamRotationWeight(double examRotationWeight) {
        iExamRotationWeight = examRotationWeight;
    }

    /**
     * A weight for room size penalty (used in
     * {@link ExamPlacement#getRoomSizePenalty()}, can be set by problem
     * property Exams.RoomSizeWeight, or in the input xml file, property
     * roomSizeWeight)
     * 
     */
    public double getRoomSizeWeight() {
        return iRoomSizeWeight;
    }

    /**
     * A weight for room size penalty (used in
     * {@link ExamPlacement#getRoomSizePenalty()}, can be set by problem
     * property Exams.RoomSizeWeight, or in the input xml file, property
     * roomSizeWeight)
     * 
     */
    public void setRoomSizeWeight(double roomSizeWeight) {
        iRoomSizeWeight = roomSizeWeight;
    }

    /**
     * A weight for room penalty weight (used in
     * {@link ExamPlacement#getRoomPenalty()}, can be set by problem property
     * Exams.RoomPreferenceWeight, or in the input xml file, property
     * roomPreferenceWeight)
     * 
     */
    public double getRoomWeight() {
        return iRoomWeight;
    }

    /**
     * A weight for room penalty weight (used in
     * {@link ExamPlacement#getRoomPenalty()}, can be set by problem property
     * Exams.RoomWeight, or in the input xml file, property roomWeight)
     * 
     */
    public void setRoomWeight(double roomWeight) {
        iRoomWeight = roomWeight;
    }

    /**
     * A weight for room split penalty (used in
     * {@link ExamPlacement#getRoomSplitPenalty()}, can be set by problem
     * property Exams.RoomSplitWeight, or in the input xml file, property
     * roomSplitWeight)
     * 
     */
    public double getRoomSplitWeight() {
        return iRoomSplitWeight;
    }

    /**
     * A weight for room split penalty (used in
     * {@link ExamPlacement#getRoomSplitPenalty()}, can be set by problem
     * property Exams.RoomSplitWeight, or in the input xml file, property
     * roomSplitWeight)
     * 
     */
    public void setRoomSplitWeight(double roomSplitWeight) {
        iRoomSplitWeight = roomSplitWeight;
    }

    /**
     * Back-to-back distance (used in
     * {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, can be set by
     * problem property Exams.BackToBackDistance, or in the input xml file,
     * property backToBackDistance)
     */
    public double getBackToBackDistance() {
        return iBackToBackDistance;
    }

    /**
     * Back-to-back distance (used in
     * {@link ExamPlacement#getNrDistanceBackToBackConflicts()}, can be set by
     * problem property Exams.BackToBackDistance, or in the input xml file,
     * property backToBackDistance)
     */
    public void setBackToBackDistance(double backToBackDistance) {
        iBackToBackDistance = backToBackDistance;
    }

    /**
     * A weight of violated distribution soft constraints (see
     * {@link ExamDistributionConstraint}, can be set by problem property
     * Exams.RoomDistributionWeight, or in the input xml file, property
     * roomDistributionWeight)
     */
    public double getDistributionWeight() {
        return iDistributionWeight;
    }

    /**
     * A weight of violated distribution soft constraints (see
     * {@link ExamDistributionConstraint}, can be set by problem property
     * Exams.RoomDistributionWeight, or in the input xml file, property
     * roomDistributionWeight)
     * 
     */
    public void setDistributionWeight(double distributionWeight) {
        iDistributionWeight = distributionWeight;
    }

    /**
     * A weight of perturbations (see
     * {@link ExamPlacement#getPerturbationPenalty()}), i.e., a penalty for an
     * assignment of an exam to a place different from the initial one. Can by
     * set by problem property Exams.PerturbationWeight, or in the input xml
     * file, property perturbationWeight)
     */
    public double getPerturbationWeight() {
        return iPerturbationWeight;
    }

    /**
     * A weight of perturbations (see
     * {@link ExamPlacement#getPerturbationPenalty()}), i.e., a penalty for an
     * assignment of an exam to a place different from the initial one. Can by
     * set by problem property Exams.PerturbationWeight, or in the input xml
     * file, property perturbationWeight)
     */
    public void setPerturbationWeight(double perturbationWeight) {
        iPerturbationWeight = perturbationWeight;
    }

    /**
     * A weight of room perturbations (see
     * {@link ExamPlacement#getRoomPerturbationPenalty()}), i.e., a penalty for
     * an assignment of an exam to a room different from the initial one. Can by
     * set by problem property Exams.RoomPerturbationWeight, or in the input xml
     * file, property perturbationWeight)
     */
    public double getRoomPerturbationWeight() {
        return iRoomPerturbationWeight;
    }

    /**
     * A weight of room perturbations (see
     * {@link ExamPlacement#getRoomPerturbationPenalty()}), i.e., a penalty for
     * an assignment of an exam to a room different from the initial one. Can by
     * set by problem property Exams.RoomPerturbationWeight, or in the input xml
     * file, property perturbationWeight)
     */
    public void setRoomPerturbationWeight(double perturbationWeight) {
        iRoomPerturbationWeight = perturbationWeight;
    }

    /**
     * A weight for distance between two or more rooms into which an exam is
     * split. Can by set by problem property Exams.RoomSplitDistanceWeight, or
     * in the input xml file, property roomSplitDistanceWeight)
     **/
    public double getRoomSplitDistanceWeight() {
        return iRoomSplitDistanceWeight;
    }

    /**
     * A weight for distance between two or more rooms into which an exam is
     * split. Can by set by problem property Exams.RoomSplitDistanceWeight, or
     * in the input xml file, property roomSplitDistanceWeight)
     **/
    public void setRoomSplitDistanceWeight(double roomSplitDistanceWeight) {
        iRoomSplitDistanceWeight = roomSplitDistanceWeight;
    }

    /**
     * An exam is considered large, if its size is greater or equal to this
     * large size. Value -1 means all exams are small. Can by set by problem
     * property Exams.LargeSize, or in the input xml file, property largeSize)
     **/
    public int getLargeSize() {
        return iLargeSize;
    }

    /**
     * An exam is considered large, if its size is greater or equal to this
     * large size. Value -1 means all exams are small. Can by set by problem
     * property Exams.LargeSize, or in the input xml file, property largeSize)
     **/
    public void setLargeSize(int largeSize) {
        iLargeSize = largeSize;
    }

    /**
     * Period index (number of periods multiplied by this number) for front load
     * criteria for large exams Can by set by problem property
     * Exams.LargePeriod, or in the input xml file, property largePeriod)
     **/
    public double getLargePeriod() {
        return iLargePeriod;
    }

    /**
     * Period index (number of periods multiplied by this number) for front load
     * criteria for large exams Can by set by problem property
     * Exams.LargePeriod, or in the input xml file, property largePeriod)
     **/
    public void setLargePeriod(double largePeriod) {
        iLargePeriod = largePeriod;
    }

    /**
     * Weight of front load criteria, i.e., a weight for assigning a large exam
     * after large period Can by set by problem property Exams.LargeWeight, or
     * in the input xml file, property largeWeight)
     **/
    public double getLargeWeight() {
        return iLargeWeight;
    }

    /**
     * Weight of front load criteria, i.e., a weight for assigning a large exam
     * after large period Can by set by problem property Exams.LargeWeight, or
     * in the input xml file, property largeWeight)
     **/
    public void setLargeWeight(double largeWeight) {
        iLargeWeight = largeWeight;
    }

    /**
     * Called before a value is unassigned from its variable, optimization
     * criteria are updated
     */
    @Override
    public void beforeUnassigned(long iteration, ExamPlacement placement) {
        super.beforeUnassigned(iteration, placement);
        Exam exam = placement.variable();
        iNrDirectConflicts -= placement.getNrDirectConflicts();
        iNrNADirectConflicts -= placement.getNrNotAvailableConflicts();
        iNrBackToBackConflicts -= placement.getNrBackToBackConflicts();
        iNrMoreThanTwoADayConflicts -= placement.getNrMoreThanTwoADayConflicts();
        iRoomSizePenalty -= placement.getRoomSizePenalty();
        iNrDistanceBackToBackConflicts -= placement.getNrDistanceBackToBackConflicts();
        iRoomSplitPenalty -= placement.getRoomSplitPenalty();
        iRoomSplitPenalties[placement.getRoomPlacements().size()]--;
        iPeriodPenalty -= placement.getPeriodPenalty();
        iPeriodIndexPenalty -= placement.getPeriod().getIndex();
        iPeriodSizePenalty -= placement.getPeriodPenalty() * (exam.getSize() + 1);
        iExamRotationPenalty -= placement.getRotationPenalty();
        iRoomPenalty -= placement.getRoomPenalty();
        iNrInstructorDirectConflicts -= placement.getNrInstructorDirectConflicts();
        iNrNAInstructorDirectConflicts -= placement.getNrInstructorNotAvailableConflicts();
        iNrInstructorBackToBackConflicts -= placement.getNrInstructorBackToBackConflicts();
        iNrInstructorMoreThanTwoADayConflicts -= placement.getNrInstructorMoreThanTwoADayConflicts();
        iNrInstructorDistanceBackToBackConflicts -= placement.getNrInstructorDistanceBackToBackConflicts();
        iPerturbationPenalty -= placement.getPerturbationPenalty();
        iRoomPerturbationPenalty -= placement.getRoomPerturbationPenalty();
        iRoomSplitDistancePenalty -= placement.getRoomSplitDistancePenalty();
        iLargePenalty -= placement.getLargePenalty();
        if (placement.getRoomPlacements().size() > 1)
            iRoomSplits--;
        for (ExamStudent s : exam.getStudents())
            s.afterUnassigned(iteration, placement);
        for (ExamInstructor i : exam.getInstructors())
            i.afterUnassigned(iteration, placement);
        for (ExamRoomPlacement r : placement.getRoomPlacements())
            r.getRoom().afterUnassigned(iteration, placement);
    }

    /**
     * Called after a value is assigned to its variable, optimization criteria
     * are updated
     */
    @Override
    public void afterAssigned(long iteration, ExamPlacement placement) {
        super.afterAssigned(iteration, placement);
        Exam exam = placement.variable();
        iNrDirectConflicts += placement.getNrDirectConflicts();
        iNrNADirectConflicts += placement.getNrNotAvailableConflicts();
        iNrBackToBackConflicts += placement.getNrBackToBackConflicts();
        iNrMoreThanTwoADayConflicts += placement.getNrMoreThanTwoADayConflicts();
        iRoomSizePenalty += placement.getRoomSizePenalty();
        iNrDistanceBackToBackConflicts += placement.getNrDistanceBackToBackConflicts();
        iRoomSplitPenalty += placement.getRoomSplitPenalty();
        iRoomSplitPenalties[placement.getRoomPlacements().size()]++;
        iPeriodPenalty += placement.getPeriodPenalty();
        iPeriodIndexPenalty += placement.getPeriod().getIndex();
        iPeriodSizePenalty += placement.getPeriodPenalty() * (exam.getSize() + 1);
        iExamRotationPenalty += placement.getRotationPenalty();
        iRoomPenalty += placement.getRoomPenalty();
        iNrInstructorDirectConflicts += placement.getNrInstructorDirectConflicts();
        iNrNAInstructorDirectConflicts += placement.getNrInstructorNotAvailableConflicts();
        iNrInstructorBackToBackConflicts += placement.getNrInstructorBackToBackConflicts();
        iNrInstructorMoreThanTwoADayConflicts += placement.getNrInstructorMoreThanTwoADayConflicts();
        iNrInstructorDistanceBackToBackConflicts += placement.getNrInstructorDistanceBackToBackConflicts();
        iPerturbationPenalty += placement.getPerturbationPenalty();
        iRoomPerturbationPenalty += placement.getRoomPerturbationPenalty();
        iRoomSplitDistancePenalty += placement.getRoomSplitDistancePenalty();
        iLargePenalty += placement.getLargePenalty();
        if (placement.getRoomPlacements().size() > 1)
            iRoomSplits++;
        for (ExamStudent s : exam.getStudents())
            s.afterAssigned(iteration, placement);
        for (ExamInstructor i : exam.getInstructors())
            i.afterAssigned(iteration, placement);
        for (ExamRoomPlacement r : placement.getRoomPlacements())
            r.getRoom().afterAssigned(iteration, placement);
    }

    /**
     * Objective function. The objective function consists of the following
     * criteria:
     * <ul>
     * <li>Direct student conflicts (a student is enrolled in two exams that are
     * scheduled at the same period, weighted by Exams.DirectConflictWeight)
     * <li>Back-to-Back student conflicts (a student is enrolled in two exams
     * that are scheduled in consecutive periods, weighted by
     * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false,
     * there is no conflict between the last period and the first period of
     * consecutive days.
     * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student
     * conflict, but the maximum distance between rooms in which both exam take
     * place is greater than Exams.BackToBackDistance, weighted by
     * Exams.DistanceBackToBackConflictWeight).
     * <li>More than two exams a day (a student is enrolled in three exams that
     * are scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight).
     * <li>Period penalty (total of period penalties
     * {@link ExamPlacement#getPeriodPenalty()} of all assigned exams, weighted
     * by Exams.PeriodWeight).
     * <li>Room size penalty (total of room size penalties
     * {@link ExamPlacement#getRoomSizePenalty()} of all assigned exams,
     * weighted by Exams.RoomSizeWeight).
     * <li>Room split penalty (total of room split penalties
     * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned exams,
     * weighted by Exams.RoomSplitWeight).
     * <li>Room split distance penalty
     * {@link ExamPlacement#getRoomSplitDistancePenalty()}, of all assigned
     * exams, weighted by {@link ExamModel#getRoomSplitDistanceWeight()}
     * <li>Room penalty (total of room penalties
     * {@link ExamPlacement#getRoomPenalty()} of all assigned exams, weighted by
     * Exams.RoomWeight).
     * <li>Distribution penalty (total of room split penalties
     * {@link ExamDistributionConstraint#getWeight()} of all soft violated
     * distribution constraints, weighted by Exams.DistributionWeight).
     * <li>Direct instructor conflicts (an instructor is enrolled in two exams
     * that are scheduled at the same period, weighted by
     * Exams.InstructorDirectConflictWeight)
     * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two
     * exams that are scheduled in consecutive periods, weighted by
     * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack
     * is false, there is no conflict between the last period and the first
     * period of consecutive days.
     * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back
     * instructor conflict, but the maximum distance between rooms in which both
     * exam take place is greater than Exams.BackToBackDistance, weighted by
     * Exams.InstructorDistanceBackToBackConflictWeight).
     * <li>More than two exams a day (an instructor is enrolled in three exams
     * that are scheduled at the same day, weighted by
     * Exams.InstructorMoreThanTwoADayWeight).
     * <li>Perturbation penalty (total of period penalties
     * {@link ExamPlacement#getPerturbationPenalty()} of all assigned exams,
     * weighted by Exams.PerturbationWeight).
     * <li>Front load penalty {@link ExamPlacement#getLargePenalty()} of all
     * assigned exams, weighted by Exam.LargeWeight
     * </ul>
     * 
     * @return weighted sum of objective criteria
     */
    @Override
    public double getTotalValue() {
        return getDirectConflictWeight() * getNrDirectConflicts(false)
                + getMoreThanTwoADayWeight() * getNrMoreThanTwoADayConflicts(false)
                + getBackToBackConflictWeight() * getNrBackToBackConflicts(false)
                + getDistanceBackToBackConflictWeight() * getNrDistanceBackToBackConflicts(false)
                + getPeriodWeight() * getPeriodPenalty(false)
                + getPeriodIndexWeight() * getPeriodIndexPenalty(false)
                + getPeriodSizeWeight() * getPeriodSizePenalty(false)
                + getPeriodIndexWeight() * getPeriodIndexPenalty(false)
                + getRoomSizeWeight() * getRoomSizePenalty(false)
                + getRoomSplitWeight() * getRoomSplitPenalty(false) + getRoomWeight() * getRoomPenalty(false)
                + getDistributionWeight() * getDistributionPenalty(false)
                + getInstructorDirectConflictWeight() * getNrInstructorDirectConflicts(false)
                + getInstructorMoreThanTwoADayWeight() * getNrInstructorMoreThanTwoADayConflicts(false)
                + getInstructorBackToBackConflictWeight() * getNrInstructorBackToBackConflicts(false)
                + getInstructorDistanceBackToBackConflictWeight()
                        * getNrInstructorDistanceBackToBackConflicts(false)
                + getExamRotationWeight() * getExamRotationPenalty(false)
                + getPerturbationWeight() * getPerturbationPenalty(false)
                + getRoomPerturbationWeight() * getRoomPerturbationPenalty(false)
                + getRoomSplitDistanceWeight() * getRoomSplitDistancePenalty(false)
                + getLargeWeight() * getLargePenalty(false);
    }

    /**
     * Return weighted individual objective criteria. The objective function
     * consists of the following criteria:
     * <ul>
     * <li>Direct student conflicts (a student is enrolled in two exams that are
     * scheduled at the same period, weighted by Exams.DirectConflictWeight)
     * <li>Back-to-Back student conflicts (a student is enrolled in two exams
     * that are scheduled in consecutive periods, weighted by
     * Exams.BackToBackConflictWeight). If Exams.IsDayBreakBackToBack is false,
     * there is no conflict between the last period and the first period of
     * consecutive days.
     * <li>Distance Back-to-Back student conflicts (same as Back-to-Back student
     * conflict, but the maximum distance between rooms in which both exam take
     * place is greater than Exams.BackToBackDistance, weighted by
     * Exams.DistanceBackToBackConflictWeight).
     * <li>More than two exams a day (a student is enrolled in three exams that
     * are scheduled at the same day, weighted by Exams.MoreThanTwoADayWeight).
     * <li>Period penalty (total of period penalties
     * {@link ExamPlacement#getPeriodPenalty()} of all assigned exams, weighted
     * by Exams.PeriodWeight).
     * <li>Room size penalty (total of room size penalties
     * {@link ExamPlacement#getRoomSizePenalty()} of all assigned exams,
     * weighted by Exams.RoomSizeWeight).
     * <li>Room split penalty (total of room split penalties
     * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned exams,
     * weighted by Exams.RoomSplitWeight).
     * <li>Room split distance penalty
     * {@link ExamPlacement#getRoomSplitDistancePenalty()}, of all assigned
     * exams, weighted by {@link ExamModel#getRoomSplitDistanceWeight()}
     * <li>Room penalty (total of room penalties
     * {@link ExamPlacement#getRoomPenalty()} of all assigned exams, weighted by
     * Exams.RoomWeight).
     * <li>Distribution penalty (total of room split penalties
     * {@link ExamDistributionConstraint#getWeight()} of all soft violated
     * distribution constraints, weighted by Exams.DistributionWeight).
     * <li>Direct instructor conflicts (an instructor is enrolled in two exams
     * that are scheduled at the same period, weighted by
     * Exams.InstructorDirectConflictWeight)
     * <li>Back-to-Back instructor conflicts (an instructor is enrolled in two
     * exams that are scheduled in consecutive periods, weighted by
     * Exams.InstructorBackToBackConflictWeight). If Exams.IsDayBreakBackToBack
     * is false, there is no conflict between the last period and the first
     * period of consecutive days.
     * <li>Distance Back-to-Back instructor conflicts (same as Back-to-Back
     * instructor conflict, but the maximum distance between rooms in which both
     * exam take place is greater than Exams.BackToBackDistance, weighted by
     * Exams.InstructorDistanceBackToBackConflictWeight).
     * <li>More than two exams a day (an instructor is enrolled in three exams
     * that are scheduled at the same day, weighted by
     * Exams.InstructorMoreThanTwoADayWeight).
     * <li>Perturbation penalty (total of period penalties
     * {@link ExamPlacement#getPerturbationPenalty()} of all assigned exams,
     * weighted by Exams.PerturbationWeight).
     * <li>Front load penalty {@link ExamPlacement#getLargePenalty()} of all
     * assigned exams, weighted by Exam.LargeWeight
     * </ul>
     * 
     * @return an array of weighted objective criteria
     */
    public double[] getTotalMultiValue() {
        return new double[] { getDirectConflictWeight() * getNrDirectConflicts(false),
                getMoreThanTwoADayWeight() * getNrMoreThanTwoADayConflicts(false),
                getBackToBackConflictWeight() * getNrBackToBackConflicts(false),
                getDistanceBackToBackConflictWeight() * getNrDistanceBackToBackConflicts(false),
                getPeriodWeight() * getPeriodPenalty(false), getPeriodSizeWeight() * getPeriodSizePenalty(false),
                getPeriodIndexWeight() * getPeriodIndexPenalty(false),
                getRoomSizeWeight() * getRoomSizePenalty(false), getRoomSplitWeight() * getRoomSplitPenalty(false),
                getRoomSplitDistanceWeight() * getRoomSplitDistancePenalty(false),
                getRoomWeight() * getRoomPenalty(false), getDistributionWeight() * getDistributionPenalty(false),
                getInstructorDirectConflictWeight() * getNrInstructorDirectConflicts(false),
                getInstructorMoreThanTwoADayWeight() * getNrInstructorMoreThanTwoADayConflicts(false),
                getInstructorBackToBackConflictWeight() * getNrInstructorBackToBackConflicts(false),
                getInstructorDistanceBackToBackConflictWeight() * getNrInstructorDistanceBackToBackConflicts(false),
                getExamRotationWeight() * getExamRotationPenalty(false),
                getPerturbationWeight() * getPerturbationPenalty(false),
                getRoomPerturbationWeight() * getRoomPerturbationPenalty(false),
                getLargeWeight() * getLargePenalty(false) };
    }

    /**
     * String representation -- returns a list of values of objective criteria
     */
    @Override
    public String toString() {
        return "DC:" + getNrDirectConflicts(false) + "," + "M2D:" + getNrMoreThanTwoADayConflicts(false) + ","
                + "BTB:" + getNrBackToBackConflicts(false) + ","
                + (getBackToBackDistance() < 0 ? "" : "dBTB:" + getNrDistanceBackToBackConflicts(false) + ",")
                + "PP:" + getPeriodPenalty(false) + "," + "PSP:" + getPeriodSizePenalty(false) + "," + "PX:"
                + getPeriodIndexPenalty(false) + "," + "@P:" + getExamRotationPenalty(false) + "," + "RSz:"
                + getRoomSizePenalty(false) + "," + "RSp:" + getRoomSplitPenalty(false) + "," + "RD:"
                + sDoubleFormat.format(getRoomSplitDistancePenalty(false)) + "," + "RP:" + getRoomPenalty(false)
                + "," + "DP:" + getDistributionPenalty(false)
                + (getLargeSize() >= 0 ? ",LP:" + getLargePenalty(false) : "")
                + (isMPP() ? ",IP:" + getPerturbationPenalty(false) + ",IRP:" + getRoomPerturbationPenalty(false)
                        : "");
    }

    /**
     * Return number of direct student conflicts, i.e., the total number of
     * cases where a student is enrolled into two exams that are scheduled at
     * the same period.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return number of direct student conflicts
     */
    public int getNrDirectConflicts(boolean precise) {
        if (!precise)
            return iNrDirectConflicts;
        int conflicts = 0;
        for (ExamStudent student : getStudents()) {
            for (ExamPeriod period : getPeriods()) {
                int nrExams = student.getExams(period).size();
                if (!student.isAvailable(period))
                    conflicts += nrExams;
                else if (nrExams > 1)
                    conflicts += nrExams - 1;
            }
        }
        return conflicts;
    }

    /**
     * Return number of back-to-back student conflicts, i.e., the total number
     * of cases where a student is enrolled into two exams that are scheduled at
     * consecutive periods. If {@link ExamModel#isDayBreakBackToBack()} is
     * false, the last period of one day and the first period of the following
     * day are not considered as consecutive periods.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return number of back-to-back student conflicts
     */
    public int getNrBackToBackConflicts(boolean precise) {
        if (!precise)
            return iNrBackToBackConflicts;
        int conflicts = 0;
        for (ExamStudent student : getStudents()) {
            for (ExamPeriod period : getPeriods()) {
                int nrExams = student.getExams(period).size();
                if (nrExams == 0)
                    continue;
                if (period.next() != null && !student.getExams(period.next()).isEmpty()
                        && (isDayBreakBackToBack() || period.next().getDay() == period.getDay()))
                    conflicts += nrExams * student.getExams(period.next()).size();
            }
        }
        return conflicts;
    }

    /**
     * Return number of distance back-to-back student conflicts, i.e., the total
     * number of back-to-back student conflicts where the two exam take place in
     * rooms that are too far a part (i.e.,
     * {@link ExamPlacement#getDistanceInMeters(ExamPlacement)} is greater than
     * {@link ExamModel#getBackToBackDistance()}).
     * 
     * @param precise
     *            if false, the cached value is used
     * @return number of distance back-to-back student conflicts
     */
    public int getNrDistanceBackToBackConflicts(boolean precise) {
        if (getBackToBackDistance() < 0)
            return 0;
        if (!precise)
            return iNrDistanceBackToBackConflicts;
        int conflicts = 0;
        for (ExamStudent student : getStudents()) {
            for (ExamPeriod period : getPeriods()) {
                Set<Exam> exams = student.getExams(period);
                if (exams.isEmpty())
                    continue;
                if (period.next() != null && !student.getExams(period.next()).isEmpty()
                        && period.next().getDay() == period.getDay()) {
                    for (Exam x1 : exams) {
                        ExamPlacement p1 = x1.getAssignment();
                        for (Exam x2 : student.getExams(period.next())) {
                            ExamPlacement p2 = x2.getAssignment();
                            if (p1.getDistanceInMeters(p2) > getBackToBackDistance())
                                conflicts++;
                        }
                    }
                }
            }
        }
        return conflicts;
    }

    /**
     * Return number of more than two exams a day student conflicts, i.e., the
     * total number of cases where a student is enrolled into three exams that
     * are scheduled at the same day (i.e., {@link ExamPeriod#getDay()} is the
     * same).
     * 
     * @param precise
     *            if false, the cached value is used
     * @return number of more than two exams a day student conflicts
     */
    public int getNrMoreThanTwoADayConflicts(boolean precise) {
        if (!precise)
            return iNrMoreThanTwoADayConflicts;
        int conflicts = 0;
        for (ExamStudent student : getStudents()) {
            for (int d = 0; d < getNrDays(); d++) {
                int nrExams = student.getExamsADay(d).size();
                if (nrExams > 2)
                    conflicts += nrExams - 2;
            }
        }
        return conflicts;
    }

    /**
     * Return number of direct instructor conflicts, i.e., the total number of
     * cases where an instructor is enrolled into two exams that are scheduled
     * at the same period.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return number of direct instructor conflicts
     */
    public int getNrInstructorDirectConflicts(boolean precise) {
        if (!precise)
            return iNrInstructorDirectConflicts;
        int conflicts = 0;
        for (ExamInstructor instructor : getInstructors()) {
            for (ExamPeriod period : getPeriods()) {
                int nrExams = instructor.getExams(period).size();
                if (!instructor.isAvailable(period))
                    conflicts += nrExams;
                else if (nrExams > 1)
                    conflicts += nrExams - 1;
            }
        }
        return conflicts;
    }

    /**
     * Return number of back-to-back instructor conflicts, i.e., the total
     * number of cases where an instructor is enrolled into two exams that are
     * scheduled at consecutive periods. If
     * {@link ExamModel#isDayBreakBackToBack()} is false, the last period of one
     * day and the first period of the following day are not considered as
     * consecutive periods.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return number of back-to-back instructor conflicts
     */
    public int getNrInstructorBackToBackConflicts(boolean precise) {
        if (!precise)
            return iNrInstructorBackToBackConflicts;
        int conflicts = 0;
        for (ExamInstructor instructor : getInstructors()) {
            for (ExamPeriod period : getPeriods()) {
                int nrExams = instructor.getExams(period).size();
                if (nrExams == 0)
                    continue;
                if (period.next() != null && !instructor.getExams(period.next()).isEmpty()
                        && (isDayBreakBackToBack() || period.next().getDay() == period.getDay()))
                    conflicts += nrExams * instructor.getExams(period.next()).size();
            }
        }
        return conflicts;
    }

    /**
     * Return number of distance back-to-back instructor conflicts, i.e., the
     * total number of back-to-back instructor conflicts where the two exam take
     * place in rooms that are too far a part (i.e.,
     * {@link ExamPlacement#getDistanceInMeters(ExamPlacement)} is greater than
     * {@link ExamModel#getBackToBackDistance()}).
     * 
     * @param precise
     *            if false, the cached value is used
     * @return number of distance back-to-back student conflicts
     */
    public int getNrInstructorDistanceBackToBackConflicts(boolean precise) {
        if (getBackToBackDistance() < 0)
            return 0;
        if (!precise)
            return iNrInstructorDistanceBackToBackConflicts;
        int conflicts = 0;
        for (ExamInstructor instructor : getInstructors()) {
            for (ExamPeriod period : getPeriods()) {
                Set<Exam> exams = instructor.getExams(period);
                if (exams.isEmpty())
                    continue;
                if (period.next() != null && !instructor.getExams(period.next()).isEmpty()
                        && period.next().getDay() == period.getDay()) {
                    for (Exam x1 : exams) {
                        ExamPlacement p1 = x1.getAssignment();
                        for (Exam x2 : instructor.getExams(period.next())) {
                            ExamPlacement p2 = x2.getAssignment();
                            if (p1.getDistanceInMeters(p2) > getBackToBackDistance())
                                conflicts++;
                        }
                    }
                }
            }
        }
        return conflicts;
    }

    /**
     * Return number of more than two exams a day instructor conflicts, i.e.,
     * the total number of cases where an instructor is enrolled into three
     * exams that are scheduled at the same day (i.e.,
     * {@link ExamPeriod#getDay()} is the same).
     * 
     * @param precise
     *            if false, the cached value is used
     * @return number of more than two exams a day student conflicts
     */
    public int getNrInstructorMoreThanTwoADayConflicts(boolean precise) {
        if (!precise)
            return iNrInstructorMoreThanTwoADayConflicts;
        int conflicts = 0;
        for (ExamInstructor instructor : getInstructors()) {
            for (int d = 0; d < getNrDays(); d++) {
                int nrExams = instructor.getExamsADay(d).size();
                if (nrExams > 2)
                    conflicts += nrExams - 2;
            }
        }
        return conflicts;
    }

    /**
     * Return total room size penalty, i.e., the sum of
     * {@link ExamPlacement#getRoomSizePenalty()} of all assigned placements.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total room size penalty
     */
    public int getRoomSizePenalty(boolean precise) {
        if (!precise)
            return iRoomSizePenalty;
        int penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += exam.getAssignment().getRoomSizePenalty();
        }
        return penalty;
    }

    /**
     * Return total room split penalty, i.e., the sum of
     * {@link ExamPlacement#getRoomSplitPenalty()} of all assigned placements.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total room split penalty
     */
    public int getRoomSplitPenalty(boolean precise) {
        if (!precise)
            return iRoomSplitPenalty;
        int penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += exam.getAssignment().getRoomSplitPenalty();
        }
        return penalty;
    }

    /**
     * Return total period penalty, i.e., the sum of
     * {@link ExamPlacement#getPeriodPenalty()} of all assigned placements.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total period penalty
     */
    public int getPeriodPenalty(boolean precise) {
        if (!precise)
            return iPeriodPenalty;
        int penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += exam.getAssignment().getPeriodPenalty();
        }
        return penalty;
    }

    /**
     * Return total period index of all assigned placements.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total period penalty
     */
    public int getPeriodIndexPenalty(boolean precise) {
        if (!precise)
            return iPeriodIndexPenalty;
        int penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += (exam.getAssignment()).getPeriod().getIndex();
        }
        return penalty;
    }

    /**
     * Return total period size penalty, i.e., the sum of
     * {@link ExamPlacement#getPeriodPenalty()} multiplied by
     * {@link Exam#getSize()} of all assigned placements.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total period penalty
     */
    public int getPeriodSizePenalty(boolean precise) {
        if (!precise)
            return iPeriodSizePenalty;
        int penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += exam.getAssignment().getPeriodPenalty() * (exam.getSize() + 1);
        }
        return penalty;
    }

    /**
     * Return total exam rotation penalty, i.e., the sum of
     * {@link ExamPlacement#getRotationPenalty()} of all assigned placements.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total period penalty
     */
    public int getExamRotationPenalty(boolean precise) {
        if (!precise)
            return iExamRotationPenalty;
        int penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += exam.getAssignment().getRotationPenalty();
        }
        return penalty;
    }

    /**
     * Return total room (weight) penalty, i.e., the sum of
     * {@link ExamPlacement#getRoomPenalty()} of all assigned placements.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total room penalty
     */
    public int getRoomPenalty(boolean precise) {
        if (!precise)
            return iRoomPenalty;
        int penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += exam.getAssignment().getRoomPenalty();
        }
        return penalty;
    }

    /**
     * Return total distribution penalty, i.e., the sum of
     * {@link ExamDistributionConstraint#getWeight()} of all violated soft
     * distribution constraints.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total distribution penalty
     */
    public int getDistributionPenalty(boolean precise) {
        if (!precise)
            return iDistributionPenalty;
        int penalty = 0;
        for (ExamDistributionConstraint dc : getDistributionConstraints()) {
            if (!dc.isSatisfied())
                penalty += dc.getWeight();
        }
        return penalty;
    }

    /**
     * Return total room split distance penalty, i.e., the sum of
     * {@link ExamPlacement#getRoomSplitDistancePenalty()} of all assigned
     * placements.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total room split distance penalty
     */
    public double getRoomSplitDistancePenalty(boolean precise) {
        if (!precise)
            return iRoomSplitDistancePenalty;
        double penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += exam.getAssignment().getRoomSplitDistancePenalty();
        }
        return penalty;
    }

    /**
     * Count exam placements with a room split.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total number of exams that are assigned into two or more rooms
     */
    public double getNrRoomSplits(boolean precise) {
        if (!precise)
            return iRoomSplits;
        int penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += (exam.getAssignment().getRoomPlacements().size() > 1 ? 1 : 0);
        }
        return penalty;
    }

    /**
     * To be called by soft {@link ExamDistributionConstraint} when satisfaction
     * changes.
     */
    protected void addDistributionPenalty(int inc) {
        iDistributionPenalty += inc;
    }

    private Integer iMaxDistributionPenalty = null;

    private int getMaxDistributionPenalty() {
        if (iMaxDistributionPenalty == null) {
            int maxDistributionPenalty = 0;
            for (ExamDistributionConstraint dc : getDistributionConstraints()) {
                if (dc.isHard())
                    continue;
                maxDistributionPenalty += dc.getWeight();
            }
            iMaxDistributionPenalty = new Integer(maxDistributionPenalty);
        }
        return iMaxDistributionPenalty.intValue();
    }

    private int[] iLimits = null;

    private int getMinPenalty(ExamRoom r) {
        int min = Integer.MAX_VALUE;
        for (ExamPeriod p : getPeriods()) {
            if (r.isAvailable(p)) {
                min = Math.min(min, r.getPenalty(p));
            }
        }
        return min;
    }

    private int getMaxPenalty(ExamRoom r) {
        int max = Integer.MIN_VALUE;
        for (ExamPeriod p : getPeriods()) {
            if (r.isAvailable(p)) {
                max = Math.max(max, r.getPenalty(p));
            }
        }
        return max;
    }

    private int[] getLimits() {
        if (iLimits == null) {
            int minPeriodPenalty = 0, maxPeriodPenalty = 0;
            int minPeriodSizePenalty = 0, maxPeriodSizePenalty = 0;
            int minRoomPenalty = 0, maxRoomPenalty = 0;
            for (Exam exam : variables()) {
                if (!exam.getPeriodPlacements().isEmpty()) {
                    int minPenalty = Integer.MAX_VALUE, maxPenalty = Integer.MIN_VALUE;
                    int minSizePenalty = Integer.MAX_VALUE, maxSizePenalty = Integer.MIN_VALUE;
                    for (ExamPeriodPlacement periodPlacement : exam.getPeriodPlacements()) {
                        minPenalty = Math.min(minPenalty, periodPlacement.getPenalty());
                        maxPenalty = Math.max(maxPenalty, periodPlacement.getPenalty());
                        minSizePenalty = Math.min(minSizePenalty,
                                periodPlacement.getPenalty() * (exam.getSize() + 1));
                        maxSizePenalty = Math.max(maxSizePenalty,
                                periodPlacement.getPenalty() * (exam.getSize() + 1));
                    }
                    minPeriodPenalty += minPenalty;
                    maxPeriodPenalty += maxPenalty;
                    minPeriodSizePenalty += minSizePenalty;
                    maxPeriodSizePenalty += maxSizePenalty;
                }
                if (!exam.getRoomPlacements().isEmpty()) {
                    int minPenalty = Integer.MAX_VALUE, maxPenalty = Integer.MIN_VALUE;
                    for (ExamRoomPlacement roomPlacement : exam.getRoomPlacements()) {
                        minPenalty = Math.min(minPenalty,
                                (roomPlacement.getPenalty() != 0 ? roomPlacement.getPenalty()
                                        : getMinPenalty(roomPlacement.getRoom())));
                        maxPenalty = Math.max(maxPenalty,
                                (roomPlacement.getPenalty() != 0 ? roomPlacement.getPenalty()
                                        : getMaxPenalty(roomPlacement.getRoom())));
                    }
                    minRoomPenalty += minPenalty;
                    maxRoomPenalty += maxPenalty;
                }
            }
            iLimits = new int[] { minPeriodPenalty, maxPeriodPenalty, minRoomPenalty, maxRoomPenalty,
                    minPeriodSizePenalty, maxPeriodSizePenalty };
        }
        return iLimits;
    }

    /**
     * Return total perturbation penalty, i.e., the sum of
     * {@link ExamPlacement#getPerturbationPenalty()} of all assigned
     * placements.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total period penalty
     */
    public int getPerturbationPenalty(boolean precise) {
        if (!precise)
            return iPerturbationPenalty;
        int penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += exam.getAssignment().getPerturbationPenalty();
        }
        return penalty;
    }

    /**
     * Return total room perturbation penalty, i.e., the sum of
     * {@link ExamPlacement#getRoomPerturbationPenalty()} of all assigned
     * placements.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total room period penalty
     */
    public int getRoomPerturbationPenalty(boolean precise) {
        if (!precise)
            return iRoomPerturbationPenalty;
        int penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += exam.getAssignment().getRoomPerturbationPenalty();
        }
        return penalty;
    }

    /**
     * Return total front load penalty, i.e., the sum of
     * {@link ExamPlacement#getLargePenalty()} of all assigned placements.
     * 
     * @param precise
     *            if false, the cached value is used
     * @return total period penalty
     */
    public int getLargePenalty(boolean precise) {
        if (!precise)
            return iLargePenalty;
        int penalty = 0;
        for (Exam exam : assignedVariables()) {
            penalty += exam.getAssignment().getLargePenalty();
        }
        return penalty;
    }

    /**
     * Info table
     */
    @Override
    public Map<String, String> getInfo() {
        Map<String, String> info = super.getInfo();
        info.put("Direct Conflicts", getNrDirectConflicts(false)
                + (iNrNADirectConflicts > 0 ? " (" + iNrNADirectConflicts + " N/A)" : ""));
        info.put("More Than 2 A Day Conflicts", String.valueOf(getNrMoreThanTwoADayConflicts(false)));
        info.put("Back-To-Back Conflicts", String.valueOf(getNrBackToBackConflicts(false)));
        if (getBackToBackDistance() >= 0 && getNrDistanceBackToBackConflicts(false) > 0)
            info.put("Distance Back-To-Back Conflicts", String.valueOf(getNrDistanceBackToBackConflicts(false)));
        if (getNrInstructorDirectConflicts(false) > 0)
            info.put("Instructor Direct Conflicts", getNrInstructorDirectConflicts(false)
                    + (iNrNAInstructorDirectConflicts > 0 ? " (" + iNrNAInstructorDirectConflicts + " N/A)" : ""));
        if (getNrInstructorMoreThanTwoADayConflicts(false) > 0)
            info.put("Instructor More Than 2 A Day Conflicts",
                    String.valueOf(getNrInstructorMoreThanTwoADayConflicts(false)));
        if (getNrInstructorBackToBackConflicts(false) > 0)
            info.put("Instructor Back-To-Back Conflicts",
                    String.valueOf(getNrInstructorBackToBackConflicts(false)));
        if (getBackToBackDistance() >= 0 && getNrInstructorDistanceBackToBackConflicts(false) > 0)
            info.put("Instructor Distance Back-To-Back Conflicts",
                    String.valueOf(getNrInstructorDistanceBackToBackConflicts(false)));
        if (nrAssignedVariables() > 0 && getRoomSizePenalty(false) > 0)
            info.put("Room Size Penalty",
                    sDoubleFormat.format(((double) getRoomSizePenalty(false)) / nrAssignedVariables()));
        if (getRoomSplitPenalty(false) > 0) {
            String split = "";
            for (int i = 2; i < getMaxRooms(); i++)
                if (iRoomSplitPenalties[i] > 0) {
                    if (split.length() > 0)
                        split += ", ";
                    split += iRoomSplitPenalties[i] + "&times;" + i;
                }
            info.put("Room Split Penalty", getRoomSplitPenalty(false) + " (" + split + ")");
        }
        info.put("Period Penalty", getPerc(getPeriodPenalty(false), getLimits()[0], getLimits()[1]) + "% ("
                + getPeriodPenalty(false) + ")");
        info.put("Period&times;Size Penalty", getPerc(getPeriodSizePenalty(false), getLimits()[4], getLimits()[5])
                + "% (" + getPeriodSizePenalty(false) + ")");
        info.put("Average Period",
                sDoubleFormat.format(((double) getPeriodIndexPenalty(false)) / nrAssignedVariables()));
        info.put("Room Penalty", getPerc(getRoomPenalty(false), getLimits()[2], getLimits()[3]) + "% ("
                + getRoomPenalty(false) + ")");
        info.put("Distribution Penalty", getPerc(getDistributionPenalty(false), 0, getMaxDistributionPenalty())
                + "% (" + getDistributionPenalty(false) + ")");
        info.put("Room Split Distance Penalty",
                sDoubleFormat.format(getRoomSplitDistancePenalty(false) / getNrRoomSplits(false)));
        if (getExamRotationPenalty(false) > 0)
            info.put("Exam Rotation Penalty", String.valueOf(getExamRotationPenalty(false)));
        if (isMPP()) {
            info.put("Perturbation Penalty",
                    sDoubleFormat.format(((double) getPerturbationPenalty(false)) / nrAssignedVariables()));
            info.put("Room Perturbation Penalty",
                    sDoubleFormat.format(((double) getRoomPerturbationPenalty(false)) / nrAssignedVariables()));
        }
        if (getLargeSize() >= 0)
            info.put("Large Exams Penalty",
                    getPerc(getLargePenalty(false), 0, iNrLargeExams) + "% (" + getLargePenalty(false) + ")");
        return info;
    }

    /**
     * Extended info table
     */
    @Override
    public Map<String, String> getExtendedInfo() {
        Map<String, String> info = super.getExtendedInfo();
        info.put("Direct Conflicts [p]", String.valueOf(getNrDirectConflicts(true)));
        info.put("More Than 2 A Day Conflicts [p]", String.valueOf(getNrMoreThanTwoADayConflicts(true)));
        info.put("Back-To-Back Conflicts [p]", String.valueOf(getNrBackToBackConflicts(true)));
        info.put("Distance Back-To-Back Conflicts [p]", String.valueOf(getNrDistanceBackToBackConflicts(true)));
        info.put("Instructor Direct Conflicts [p]", String.valueOf(getNrInstructorDirectConflicts(true)));
        info.put("Instructor More Than 2 A Day Conflicts [p]",
                String.valueOf(getNrInstructorMoreThanTwoADayConflicts(true)));
        info.put("Instructor Back-To-Back Conflicts [p]", String.valueOf(getNrInstructorBackToBackConflicts(true)));
        info.put("Instructor Distance Back-To-Back Conflicts [p]",
                String.valueOf(getNrInstructorDistanceBackToBackConflicts(true)));
        info.put("Room Size Penalty [p]", String.valueOf(getRoomSizePenalty(true)));
        info.put("Room Split Penalty [p]", String.valueOf(getRoomSplitPenalty(true)));
        info.put("Period Penalty [p]", String.valueOf(getPeriodPenalty(true)));
        info.put("Period Size Penalty [p]", String.valueOf(getPeriodSizePenalty(true)));
        info.put("Period Index Penalty [p]", String.valueOf(getPeriodIndexPenalty(true)));
        info.put("Room Penalty [p]", String.valueOf(getRoomPenalty(true)));
        info.put("Distribution Penalty [p]", String.valueOf(getDistributionPenalty(true)));
        info.put("Perturbation Penalty [p]", String.valueOf(getPerturbationPenalty(true)));
        info.put("Room Perturbation Penalty [p]", String.valueOf(getRoomPerturbationPenalty(true)));
        info.put("Room Split Distance Penalty [p]",
                sDoubleFormat.format(getRoomSplitDistancePenalty(true)) + " / " + getNrRoomSplits(true));
        info.put("Number of Periods", String.valueOf(getPeriods().size()));
        info.put("Number of Exams", String.valueOf(variables().size()));
        info.put("Number of Rooms", String.valueOf(getRooms().size()));
        int avail = 0, availAlt = 0;
        for (ExamRoom room : getRooms()) {
            for (ExamPeriod period : getPeriods()) {
                if (room.isAvailable(period)) {
                    avail += room.getSize();
                    availAlt += room.getAltSize();
                }
            }
        }
        info.put("Number of Students", String.valueOf(getStudents().size()));
        int nrStudentExams = 0;
        for (ExamStudent student : getStudents()) {
            nrStudentExams += student.getOwners().size();
        }
        info.put("Number of Student Exams", String.valueOf(nrStudentExams));
        int nrAltExams = 0, nrSmallExams = 0;
        for (Exam exam : variables()) {
            if (exam.hasAltSeating())
                nrAltExams++;
            if (exam.getMaxRooms() == 0)
                nrSmallExams++;
        }
        info.put("Number of Exams Requiring Alt Seating", String.valueOf(nrAltExams));
        info.put("Number of Small Exams (Exams W/O Room)", String.valueOf(nrSmallExams));
        int[] nbrMtgs = new int[11];
        for (int i = 0; i <= 10; i++)
            nbrMtgs[i] = 0;
        for (ExamStudent student : getStudents()) {
            nbrMtgs[Math.min(10, student.variables().size())]++;
        }
        for (int i = 0; i <= 10; i++) {
            if (nbrMtgs[i] == 0)
                continue;
            info.put("Number of Students with " + (i == 0 ? "no" : String.valueOf(i)) + (i == 10 ? " or more" : "")
                    + " meeting" + (i != 1 ? "s" : ""), String.valueOf(nbrMtgs[i]));
        }
        return info;
    }

    /**
     * Problem properties
     */
    public DataProperties getProperties() {
        return iProperties;
    }

    /**
     * Problem rooms
     * 
     * @return list of {@link ExamRoom}
     */
    public List<ExamRoom> getRooms() {
        return iRooms;
    }

    /**
     * Problem students
     * 
     * @return list of {@link ExamStudent}
     */
    public List<ExamStudent> getStudents() {
        return iStudents;
    }

    /**
     * Problem instructors
     * 
     * @return list of {@link ExamInstructor}
     */
    public List<ExamInstructor> getInstructors() {
        return iInstructors;
    }

    /**
     * Distribution constraints
     * 
     * @return list of {@link ExamDistributionConstraint}
     */
    public List<ExamDistributionConstraint> getDistributionConstraints() {
        return iDistributionConstraints;
    }

    private String getId(boolean anonymize, String type, String id) {
        return (anonymize ? IdConvertor.getInstance().convert(type, id) : id);
    }

    /**
     * Save model (including its solution) into XML.
     */
    public Document save() {
        boolean saveInitial = getProperties().getPropertyBoolean("Xml.SaveInitial", true);
        boolean saveBest = getProperties().getPropertyBoolean("Xml.SaveBest", true);
        boolean saveSolution = getProperties().getPropertyBoolean("Xml.SaveSolution", true);
        boolean saveConflictTable = getProperties().getPropertyBoolean("Xml.SaveConflictTable", false);
        boolean saveParams = getProperties().getPropertyBoolean("Xml.SaveParameters", true);
        boolean anonymize = getProperties().getPropertyBoolean("Xml.Anonymize", false);
        Document document = DocumentHelper.createDocument();
        document.addComment("Examination Timetable");
        if (nrAssignedVariables() > 0) {
            StringBuffer comments = new StringBuffer("Solution Info:\n");
            Map<String, String> solutionInfo = (getProperties().getPropertyBoolean("Xml.ExtendedInfo", false)
                    ? getExtendedInfo()
                    : getInfo());
            for (String key : new TreeSet<String>(solutionInfo.keySet())) {
                String value = solutionInfo.get(key);
                comments.append("    " + key + ": " + value + "\n");
            }
            document.addComment(comments.toString());
        }
        Element root = document.addElement("examtt");
        root.addAttribute("version", "1.0");
        root.addAttribute("campus", getProperties().getProperty("Data.Initiative"));
        root.addAttribute("term", getProperties().getProperty("Data.Term"));
        root.addAttribute("year", getProperties().getProperty("Data.Year"));
        root.addAttribute("created", String.valueOf(new Date()));
        if (saveParams) {
            Element params = root.addElement("parameters");
            params.addElement("property").addAttribute("name", "isDayBreakBackToBack").addAttribute("value",
                    (isDayBreakBackToBack() ? "true" : "false"));
            params.addElement("property").addAttribute("name", "directConflictWeight").addAttribute("value",
                    String.valueOf(getDirectConflictWeight()));
            params.addElement("property").addAttribute("name", "moreThanTwoADayWeight").addAttribute("value",
                    String.valueOf(getMoreThanTwoADayWeight()));
            params.addElement("property").addAttribute("name", "backToBackConflictWeight").addAttribute("value",
                    String.valueOf(getBackToBackConflictWeight()));
            params.addElement("property").addAttribute("name", "distanceBackToBackConflictWeight")
                    .addAttribute("value", String.valueOf(getDistanceBackToBackConflictWeight()));
            params.addElement("property").addAttribute("name", "backToBackDistance").addAttribute("value",
                    String.valueOf(getBackToBackDistance()));
            params.addElement("property").addAttribute("name", "maxRooms").addAttribute("value",
                    String.valueOf(getMaxRooms()));
            params.addElement("property").addAttribute("name", "periodWeight").addAttribute("value",
                    String.valueOf(getPeriodWeight()));
            params.addElement("property").addAttribute("name", "periodSizeWeight").addAttribute("value",
                    String.valueOf(getPeriodSizeWeight()));
            params.addElement("property").addAttribute("name", "periodIndexWeight").addAttribute("value",
                    String.valueOf(getPeriodIndexWeight()));
            params.addElement("property").addAttribute("name", "examRotationWeight").addAttribute("value",
                    String.valueOf(getExamRotationWeight()));
            params.addElement("property").addAttribute("name", "roomSizeWeight").addAttribute("value",
                    String.valueOf(getRoomSizeWeight()));
            params.addElement("property").addAttribute("name", "roomSplitWeight").addAttribute("value",
                    String.valueOf(getRoomSplitWeight()));
            params.addElement("property").addAttribute("name", "roomWeight").addAttribute("value",
                    String.valueOf(getRoomWeight()));
            params.addElement("property").addAttribute("name", "distributionWeight").addAttribute("value",
                    String.valueOf(getDistributionWeight()));
            params.addElement("property").addAttribute("name", "instructorDirectConflictWeight")
                    .addAttribute("value", String.valueOf(getInstructorDirectConflictWeight()));
            params.addElement("property").addAttribute("name", "instructorMoreThanTwoADayWeight")
                    .addAttribute("value", String.valueOf(getInstructorMoreThanTwoADayWeight()));
            params.addElement("property").addAttribute("name", "instructorBackToBackConflictWeight")
                    .addAttribute("value", String.valueOf(getInstructorBackToBackConflictWeight()));
            params.addElement("property").addAttribute("name", "instructorDistanceBackToBackConflictWeight")
                    .addAttribute("value", String.valueOf(getInstructorDistanceBackToBackConflictWeight()));
            params.addElement("property").addAttribute("name", "perturbationWeight").addAttribute("value",
                    String.valueOf(getPerturbationWeight()));
            params.addElement("property").addAttribute("name", "roomPerturbationWeight").addAttribute("value",
                    String.valueOf(getRoomPerturbationWeight()));
            params.addElement("property").addAttribute("name", "roomSplitDistanceWeight").addAttribute("value",
                    String.valueOf(getRoomSplitDistanceWeight()));
            params.addElement("property").addAttribute("name", "largeSize").addAttribute("value",
                    String.valueOf(getLargeSize()));
            params.addElement("property").addAttribute("name", "largePeriod").addAttribute("value",
                    String.valueOf(getLargePeriod()));
            params.addElement("property").addAttribute("name", "largeWeight").addAttribute("value",
                    String.valueOf(getLargeWeight()));
        }
        Element periods = root.addElement("periods");
        for (ExamPeriod period : getPeriods()) {
            periods.addElement("period")
                    .addAttribute("id", getId(anonymize, "period", String.valueOf(period.getId())))
                    .addAttribute("length", String.valueOf(period.getLength()))
                    .addAttribute("day", period.getDayStr()).addAttribute("time", period.getTimeStr())
                    .addAttribute("penalty", String.valueOf(period.getPenalty()));
        }
        Element rooms = root.addElement("rooms");
        for (ExamRoom room : getRooms()) {
            Element r = rooms.addElement("room");
            r.addAttribute("id", getId(anonymize, "room", String.valueOf(room.getId())));
            if (!anonymize && room.hasName())
                r.addAttribute("name", room.getName());
            r.addAttribute("size", String.valueOf(room.getSize()));
            r.addAttribute("alt", String.valueOf(room.getAltSize()));
            if (room.getCoordX() != null && room.getCoordY() != null)
                r.addAttribute("coordinates", room.getCoordX() + "," + room.getCoordY());
            for (ExamPeriod period : getPeriods()) {
                if (!room.isAvailable(period))
                    r.addElement("period")
                            .addAttribute("id", getId(anonymize, "period", String.valueOf(period.getId())))
                            .addAttribute("available", "false");
                else if (room.getPenalty(period) != 0)
                    r.addElement("period")
                            .addAttribute("id", getId(anonymize, "period", String.valueOf(period.getId())))
                            .addAttribute("penalty", String.valueOf(room.getPenalty(period)));
            }
        }
        Element exams = root.addElement("exams");
        for (Exam exam : variables()) {
            Element ex = exams.addElement("exam");
            ex.addAttribute("id", getId(anonymize, "exam", String.valueOf(exam.getId())));
            if (!anonymize && exam.hasName())
                ex.addAttribute("name", exam.getName());
            ex.addAttribute("length", String.valueOf(exam.getLength()));
            if (exam.getSizeOverride() != null)
                ex.addAttribute("size", exam.getSizeOverride().toString());
            if (exam.getMinSize() != 0)
                ex.addAttribute("minSize", String.valueOf(exam.getMinSize()));
            ex.addAttribute("alt", (exam.hasAltSeating() ? "true" : "false"));
            if (exam.getMaxRooms() != getMaxRooms())
                ex.addAttribute("maxRooms", String.valueOf(exam.getMaxRooms()));
            if (exam.getPrintOffset() != null)
                ex.addAttribute("printOffset", exam.getPrintOffset().toString());
            if (!anonymize)
                ex.addAttribute("enrl", String.valueOf(exam.getStudents().size()));
            if (!anonymize)
                for (ExamOwner owner : exam.getOwners()) {
                    Element o = ex.addElement("owner");
                    o.addAttribute("id", getId(anonymize, "owner", String.valueOf(owner.getId())));
                    o.addAttribute("name", owner.getName());
                }
            for (ExamPeriodPlacement period : exam.getPeriodPlacements()) {
                Element pe = ex.addElement("period").addAttribute("id",
                        getId(anonymize, "period", String.valueOf(period.getId())));
                int penalty = period.getPenalty() - period.getPeriod().getPenalty();
                if (penalty != 0)
                    pe.addAttribute("penalty", String.valueOf(penalty));
            }
            for (ExamRoomPlacement room : exam.getRoomPlacements()) {
                Element re = ex.addElement("room").addAttribute("id",
                        getId(anonymize, "room", String.valueOf(room.getId())));
                if (room.getPenalty() != 0)
                    re.addAttribute("penalty", String.valueOf(room.getPenalty()));
                if (room.getMaxPenalty() != 100)
                    re.addAttribute("maxPenalty", String.valueOf(room.getMaxPenalty()));
            }
            if (exam.hasAveragePeriod())
                ex.addAttribute("average", String.valueOf(exam.getAveragePeriod()));
            ExamPlacement p = exam.getAssignment();
            if (p != null && saveSolution) {
                Element asg = ex.addElement("assignment");
                asg.addElement("period").addAttribute("id",
                        getId(anonymize, "period", String.valueOf(p.getPeriod().getId())));
                for (ExamRoomPlacement r : p.getRoomPlacements()) {
                    asg.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId())));
                }
            }
            p = exam.getInitialAssignment();
            if (p != null && saveInitial) {
                Element ini = ex.addElement("initial");
                ini.addElement("period").addAttribute("id",
                        getId(anonymize, "period", String.valueOf(p.getPeriod().getId())));
                for (ExamRoomPlacement r : p.getRoomPlacements()) {
                    ini.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId())));
                }
            }
            p = exam.getBestAssignment();
            if (p != null && saveBest) {
                Element ini = ex.addElement("best");
                ini.addElement("period").addAttribute("id",
                        getId(anonymize, "period", String.valueOf(p.getPeriod().getId())));
                for (ExamRoomPlacement r : p.getRoomPlacements()) {
                    ini.addElement("room").addAttribute("id", getId(anonymize, "room", String.valueOf(r.getId())));
                }
            }
        }
        Element students = root.addElement("students");
        for (ExamStudent student : getStudents()) {
            Element s = students.addElement("student");
            s.addAttribute("id", getId(anonymize, "student", String.valueOf(student.getId())));
            for (Exam ex : student.variables()) {
                Element x = s.addElement("exam").addAttribute("id",
                        getId(anonymize, "exam", String.valueOf(ex.getId())));
                if (!anonymize)
                    for (ExamOwner owner : ex.getOwners(student)) {
                        x.addElement("owner").addAttribute("id",
                                getId(anonymize, "owner", String.valueOf(owner.getId())));
                    }
            }
            for (ExamPeriod period : getPeriods()) {
                if (!student.isAvailable(period))
                    s.addElement("period")
                            .addAttribute("id", getId(anonymize, "period", String.valueOf(period.getId())))
                            .addAttribute("available", "false");
            }
        }
        Element instructors = root.addElement("instructors");
        for (ExamInstructor instructor : getInstructors()) {
            Element i = instructors.addElement("instructor");
            i.addAttribute("id", getId(anonymize, "instructor", String.valueOf(instructor.getId())));
            if (!anonymize && instructor.hasName())
                i.addAttribute("name", instructor.getName());
            for (Exam ex : instructor.variables()) {
                Element x = i.addElement("exam").addAttribute("id",
                        getId(anonymize, "exam", String.valueOf(ex.getId())));
                if (!anonymize)
                    for (ExamOwner owner : ex.getOwners(instructor)) {
                        x.addElement("owner").addAttribute("id",
                                getId(anonymize, "owner", String.valueOf(owner.getId())));
                    }
            }
            for (ExamPeriod period : getPeriods()) {
                if (!instructor.isAvailable(period))
                    i.addElement("period")
                            .addAttribute("id", getId(anonymize, "period", String.valueOf(period.getId())))
                            .addAttribute("available", "false");
            }
        }
        Element distConstraints = root.addElement("constraints");
        for (ExamDistributionConstraint distConstraint : getDistributionConstraints()) {
            Element dc = distConstraints.addElement(distConstraint.getTypeString());
            dc.addAttribute("id", getId(anonymize, "constraint", String.valueOf(distConstraint.getId())));
            if (!distConstraint.isHard()) {
                dc.addAttribute("hard", "false");
                dc.addAttribute("weight", String.valueOf(distConstraint.getWeight()));
            }
            for (Exam exam : distConstraint.variables()) {
                dc.addElement("exam").addAttribute("id", getId(anonymize, "exam", String.valueOf(exam.getId())));
            }
        }
        if (saveConflictTable) {
            Element conflicts = root.addElement("conflicts");
            for (ExamStudent student : getStudents()) {
                for (ExamPeriod period : getPeriods()) {
                    int nrExams = student.getExams(period).size();
                    if (nrExams > 1) {
                        Element dir = conflicts.addElement("direct").addAttribute("student",
                                getId(anonymize, "student", String.valueOf(student.getId())));
                        for (Exam exam : student.getExams(period)) {
                            dir.addElement("exam").addAttribute("id",
                                    getId(anonymize, "exam", String.valueOf(exam.getId())));
                        }
                    }
                    if (nrExams > 0) {
                        if (period.next() != null && !student.getExams(period.next()).isEmpty()
                                && (!isDayBreakBackToBack() || period.next().getDay() == period.getDay())) {
                            for (Exam ex1 : student.getExams(period)) {
                                for (Exam ex2 : student.getExams(period.next())) {
                                    Element btb = conflicts.addElement("back-to-back").addAttribute("student",
                                            getId(anonymize, "student", String.valueOf(student.getId())));
                                    btb.addElement("exam").addAttribute("id",
                                            getId(anonymize, "exam", String.valueOf(ex1.getId())));
                                    btb.addElement("exam").addAttribute("id",
                                            getId(anonymize, "exam", String.valueOf(ex2.getId())));
                                    if (getBackToBackDistance() >= 0) {
                                        double dist = (ex1.getAssignment())
                                                .getDistanceInMeters(ex2.getAssignment());
                                        if (dist > 0)
                                            btb.addAttribute("distance", String.valueOf(dist));
                                    }
                                }
                            }
                        }
                    }
                    if (period.next() == null || period.next().getDay() != period.getDay()) {
                        int nrExamsADay = student.getExamsADay(period.getDay()).size();
                        if (nrExamsADay > 2) {
                            Element mt2 = conflicts.addElement("more-2-day").addAttribute("student",
                                    getId(anonymize, "student", String.valueOf(student.getId())));
                            for (Exam exam : student.getExamsADay(period.getDay())) {
                                mt2.addElement("exam").addAttribute("id",
                                        getId(anonymize, "exam", String.valueOf(exam.getId())));
                            }
                        }
                    }
                }
            }

        }
        return document;
    }

    /**
     * Load model (including its solution) from XML.
     */
    public boolean load(Document document) {
        return load(document, null);
    }

    /**
     * Load model (including its solution) from XML.
     */
    public boolean load(Document document, Callback saveBest) {
        boolean loadInitial = getProperties().getPropertyBoolean("Xml.LoadInitial", true);
        boolean loadBest = getProperties().getPropertyBoolean("Xml.LoadBest", true);
        boolean loadSolution = getProperties().getPropertyBoolean("Xml.LoadSolution", true);
        boolean loadParams = getProperties().getPropertyBoolean("Xml.LoadParameters", false);
        Element root = document.getRootElement();
        if (!"examtt".equals(root.getName()))
            return false;
        if (root.attribute("campus") != null)
            getProperties().setProperty("Data.Initiative", root.attributeValue("campus"));
        else if (root.attribute("initiative") != null)
            getProperties().setProperty("Data.Initiative", root.attributeValue("initiative"));
        if (root.attribute("term") != null)
            getProperties().setProperty("Data.Term", root.attributeValue("term"));
        if (root.attribute("year") != null)
            getProperties().setProperty("Data.Year", root.attributeValue("year"));
        if (loadParams && root.element("parameters") != null)
            for (Iterator<?> i = root.element("parameters").elementIterator("property"); i.hasNext();) {
                Element e = (Element) i.next();
                String name = e.attributeValue("name");
                String value = e.attributeValue("value");
                if ("isDayBreakBackToBack".equals(name))
                    setDayBreakBackToBack("true".equals(value));
                else if ("directConflictWeight".equals(name))
                    setDirectConflictWeight(Double.parseDouble(value));
                else if ("moreThanTwoADayWeight".equals(name))
                    setMoreThanTwoADayWeight(Double.parseDouble(value));
                else if ("backToBackConflictWeight".equals(name))
                    setBackToBackConflictWeight(Double.parseDouble(value));
                else if ("distanceBackToBackConflictWeight".equals(name))
                    setDistanceBackToBackConflictWeight(Double.parseDouble(value));
                else if ("backToBackDistance".equals(name))
                    setBackToBackDistance(Double.parseDouble(value));
                else if ("maxRooms".equals(name))
                    setMaxRooms(Integer.parseInt(value));
                else if ("periodWeight".equals(name))
                    setPeriodWeight(Double.parseDouble(value));
                else if ("periodSizeWeight".equals(name))
                    setPeriodSizeWeight(Double.parseDouble(value));
                else if ("periodIndexWeight".equals(name))
                    setPeriodIndexWeight(Double.parseDouble(value));
                else if ("examRotationWeight".equals(name))
                    setExamRotationWeight(Double.parseDouble(value));
                else if ("roomSizeWeight".equals(name))
                    setRoomSizeWeight(Double.parseDouble(value));
                else if ("roomSplitWeight".equals(name))
                    setRoomSplitWeight(Double.parseDouble(value));
                else if ("roomWeight".equals(name))
                    setRoomWeight(Double.parseDouble(value));
                else if ("distributionWeight".equals(name))
                    setDistributionWeight(Double.parseDouble(value));
                else if ("instructorDirectConflictWeight".equals(name))
                    setInstructorDirectConflictWeight(Double.parseDouble(value));
                else if ("instructorMoreThanTwoADayWeight".equals(name))
                    setInstructorMoreThanTwoADayWeight(Double.parseDouble(value));
                else if ("instructorBackToBackConflictWeight".equals(name))
                    setInstructorBackToBackConflictWeight(Double.parseDouble(value));
                else if ("instructorDistanceBackToBackConflictWeight".equals(name))
                    setInstructorDistanceBackToBackConflictWeight(Double.parseDouble(value));
                else if ("perturbationWeight".equals(name))
                    setPerturbationWeight(Double.parseDouble(value));
                else if ("roomPerturbationWeight".equals(name))
                    setRoomPerturbationWeight(Double.parseDouble(value));
                else if ("roomSplitDistanceWeight".equals(name))
                    setRoomSplitDistanceWeight(Double.parseDouble(value));
                else if ("largeSize".equals(name))
                    setLargeSize(Integer.parseInt(value));
                else if ("largePeriod".equals(name))
                    setLargePeriod(Double.parseDouble(value));
                else if ("largeWeight".equals(name))
                    setLargeWeight(Double.parseDouble(value));
                else
                    getProperties().setProperty(name, value);
            }
        for (Iterator<?> i = root.element("periods").elementIterator("period"); i.hasNext();) {
            Element e = (Element) i.next();
            addPeriod(Long.valueOf(e.attributeValue("id")), e.attributeValue("day"), e.attributeValue("time"),
                    Integer.parseInt(e.attributeValue("length")),
                    Integer.parseInt(e.attributeValue("penalty") == null ? e.attributeValue("weight", "0")
                            : e.attributeValue("penalty")));
        }
        HashMap<Long, ExamRoom> rooms = new HashMap<Long, ExamRoom>();
        HashMap<String, ArrayList<ExamRoom>> roomGroups = new HashMap<String, ArrayList<ExamRoom>>();
        for (Iterator<?> i = root.element("rooms").elementIterator("room"); i.hasNext();) {
            Element e = (Element) i.next();
            String coords = e.attributeValue("coordinates");
            ExamRoom room = new ExamRoom(this, Long.parseLong(e.attributeValue("id")), e.attributeValue("name"),
                    Integer.parseInt(e.attributeValue("size")), Integer.parseInt(e.attributeValue("alt")),
                    (coords == null ? null : Double.valueOf(coords.substring(0, coords.indexOf(',')))),
                    (coords == null ? null : Double.valueOf(coords.substring(coords.indexOf(',') + 1))));
            addConstraint(room);
            getRooms().add(room);
            rooms.put(new Long(room.getId()), room);
            for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
                Element pe = (Element) j.next();
                ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id")));
                if ("false".equals(pe.attributeValue("available")))
                    room.setAvailable(period, false);
                else
                    room.setPenalty(period, Integer.parseInt(pe.attributeValue("penalty")));
            }
            String av = e.attributeValue("available");
            if (av != null) {
                for (int j = 0; j < getPeriods().size(); j++)
                    if ('0' == av.charAt(j))
                        room.setAvailable(getPeriods().get(j), false);
            }
            String g = e.attributeValue("groups");
            if (g != null) {
                for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) {
                    String gr = s.nextToken();
                    ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr);
                    if (roomsThisGrop == null) {
                        roomsThisGrop = new ArrayList<ExamRoom>();
                        roomGroups.put(gr, roomsThisGrop);
                    }
                    roomsThisGrop.add(room);
                }
            }
        }
        ArrayList<ExamPlacement> assignments = new ArrayList<ExamPlacement>();
        HashMap<Long, Exam> exams = new HashMap<Long, Exam>();
        HashMap<Long, ExamOwner> courseSections = new HashMap<Long, ExamOwner>();
        for (Iterator<?> i = root.element("exams").elementIterator("exam"); i.hasNext();) {
            Element e = (Element) i.next();
            ArrayList<ExamPeriodPlacement> periodPlacements = new ArrayList<ExamPeriodPlacement>();
            for (ExamPeriod p : getPeriods()) {
                periodPlacements.add(new ExamPeriodPlacement(p, 0));
            }
            /*
            for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
            Element pe = (Element) j.next();
            periodPlacements.add(new ExamPeriodPlacement(getPeriod(Long.valueOf(pe.attributeValue("id"))), Integer
                    .parseInt(pe.attributeValue("penalty", "0"))));
            }*/
            ArrayList<ExamRoomPlacement> roomPlacements = new ArrayList<ExamRoomPlacement>();
            for (Iterator<?> j = e.elementIterator("room"); j.hasNext();) {
                Element re = (Element) j.next();
                ExamRoomPlacement room = new ExamRoomPlacement(rooms.get(Long.valueOf(re.attributeValue("id"))),
                        Integer.parseInt(re.attributeValue("penalty", "0")),
                        Integer.parseInt(re.attributeValue("maxPenalty", "100")));
                roomPlacements.add(room);
            }
            String g = e.attributeValue("groups");
            if (g != null) {
                HashMap<ExamRoom, Integer> allRooms = new HashMap<ExamRoom, Integer>();
                for (StringTokenizer s = new StringTokenizer(g, ","); s.hasMoreTokens();) {
                    String gr = s.nextToken();
                    ArrayList<ExamRoom> roomsThisGrop = roomGroups.get(gr);
                    if (roomsThisGrop != null)
                        for (ExamRoom r : roomsThisGrop)
                            allRooms.put(r, 0);
                }
                for (Iterator<?> j = e.elementIterator("original-room"); j.hasNext();) {
                    allRooms.put((rooms.get(Long.valueOf(((Element) j.next()).attributeValue("id")))),
                            new Integer(-1));
                }
                for (Map.Entry<ExamRoom, Integer> entry : allRooms.entrySet()) {
                    ExamRoomPlacement room = new ExamRoomPlacement(entry.getKey(), entry.getValue(), 100);
                    roomPlacements.add(room);
                }
                if (periodPlacements.isEmpty()) {
                    for (ExamPeriod p : getPeriods()) {
                        periodPlacements.add(new ExamPeriodPlacement(p, 0));
                    }
                }
            }
            Exam exam = new Exam(Long.parseLong(e.attributeValue("id")), e.attributeValue("name"),
                    Integer.parseInt(e.attributeValue("length")), "true".equals(e.attributeValue("alt")),
                    (e.attribute("maxRooms") == null ? getMaxRooms()
                            : Integer.parseInt(e.attributeValue("maxRooms"))),
                    Integer.parseInt(e.attributeValue("minSize", "0")), periodPlacements, roomPlacements);
            if (e.attributeValue("size") != null)
                exam.setSizeOverride(Integer.valueOf(e.attributeValue("size")));
            if (e.attributeValue("printOffset") != null)
                exam.setPrintOffset(Integer.valueOf(e.attributeValue("printOffset")));
            exams.put(new Long(exam.getId()), exam);
            addVariable(exam);
            if (e.attribute("average") != null)
                exam.setAveragePeriod(Integer.parseInt(e.attributeValue("average")));
            Element asg = e.element("assignment");
            if (asg != null && loadSolution) {
                Element per = asg.element("period");
                if (per != null) {
                    HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>();
                    for (Iterator<?> j = asg.elementIterator("room"); j.hasNext();)
                        rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id"))));
                    ExamPlacement p = new ExamPlacement(exam,
                            exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))), rp);
                    assignments.add(p);
                }
            }
            Element ini = e.element("initial");
            if (ini != null && loadInitial) {
                Element per = ini.element("period");
                if (per != null) {
                    HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>();
                    for (Iterator<?> j = ini.elementIterator("room"); j.hasNext();)
                        rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id"))));
                    ExamPlacement p = new ExamPlacement(exam,
                            exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))), rp);
                    exam.setInitialAssignment(p);
                }
            }
            Element best = e.element("best");
            if (best != null && loadBest) {
                Element per = best.element("period");
                if (per != null) {
                    HashSet<ExamRoomPlacement> rp = new HashSet<ExamRoomPlacement>();
                    for (Iterator<?> j = best.elementIterator("room"); j.hasNext();)
                        rp.add(exam.getRoomPlacement(Long.parseLong(((Element) j.next()).attributeValue("id"))));
                    ExamPlacement p = new ExamPlacement(exam,
                            exam.getPeriodPlacement(Long.valueOf(per.attributeValue("id"))), rp);
                    exam.setBestAssignment(p);
                }
            }
            for (Iterator<?> j = e.elementIterator("owner"); j.hasNext();) {
                Element f = (Element) j.next();
                ExamOwner owner = new ExamOwner(exam, Long.parseLong(f.attributeValue("id")),
                        f.attributeValue("name"));
                exam.getOwners().add(owner);
                courseSections.put(new Long(owner.getId()), owner);
            }
        }
        for (Iterator<?> i = root.element("students").elementIterator("student"); i.hasNext();) {
            Element e = (Element) i.next();
            ExamStudent student = new ExamStudent(this, Long.parseLong(e.attributeValue("id")));
            for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) {
                Element x = (Element) j.next();
                Exam ex = exams.get(Long.valueOf(x.attributeValue("id")));
                student.addVariable(ex);
                for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) {
                    Element f = (Element) k.next();
                    ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id")));
                    student.getOwners().add(owner);
                    owner.getStudents().add(student);
                }
            }
            String available = e.attributeValue("available");
            if (available != null)
                for (ExamPeriod period : getPeriods()) {
                    if (available.charAt(period.getIndex()) == '0')
                        student.setAvailable(period.getIndex(), false);
                }
            for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
                Element pe = (Element) j.next();
                ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id")));
                if ("false".equals(pe.attributeValue("available")))
                    student.setAvailable(period.getIndex(), false);
            }
            addConstraint(student);
            getStudents().add(student);
        }
        if (root.element("instructors") != null)
            for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) {
                Element e = (Element) i.next();
                ExamInstructor instructor = new ExamInstructor(this, Long.parseLong(e.attributeValue("id")),
                        e.attributeValue("name"));
                for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) {
                    Element x = (Element) j.next();
                    Exam ex = exams.get(Long.valueOf(x.attributeValue("id")));
                    instructor.addVariable(ex);
                    for (Iterator<?> k = x.elementIterator("owner"); k.hasNext();) {
                        Element f = (Element) k.next();
                        ExamOwner owner = courseSections.get(Long.valueOf(f.attributeValue("id")));
                        instructor.getOwners().add(owner);
                        owner.getIntructors().add(instructor);
                    }
                }
                String available = e.attributeValue("available");
                if (available != null)
                    for (ExamPeriod period : getPeriods()) {
                        if (available.charAt(period.getIndex()) == '0')
                            instructor.setAvailable(period.getIndex(), false);
                    }
                for (Iterator<?> j = e.elementIterator("period"); j.hasNext();) {
                    Element pe = (Element) j.next();
                    ExamPeriod period = getPeriod(Long.valueOf(pe.attributeValue("id")));
                    if ("false".equals(pe.attributeValue("available")))
                        instructor.setAvailable(period.getIndex(), false);
                }
                addConstraint(instructor);
                getInstructors().add(instructor);
            }
        if (root.element("constraints") != null)
            for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) {
                Element e = (Element) i.next();
                ExamDistributionConstraint dc = new ExamDistributionConstraint(
                        Long.parseLong(e.attributeValue("id")), e.getName(),
                        "true".equals(e.attributeValue("hard", "true")),
                        Integer.parseInt(e.attributeValue("weight", "0")));
                for (Iterator<?> j = e.elementIterator("exam"); j.hasNext();) {
                    dc.addVariable(exams.get(Long.valueOf(((Element) j.next()).attributeValue("id"))));
                }
                addConstraint(dc);
                getDistributionConstraints().add(dc);
            }
        init();
        if (loadBest && saveBest != null) {
            for (Exam exam : variables()) {
                ExamPlacement placement = exam.getBestAssignment();
                if (placement == null)
                    continue;
                exam.assign(0, placement);
            }
            saveBest.execute();
            for (Exam exam : variables()) {
                if (exam.getAssignment() != null)
                    exam.unassign(0);
            }
        }
        for (ExamPlacement placement : assignments) {
            Exam exam = placement.variable();
            Set<ExamPlacement> conf = conflictValues(placement);
            if (!conf.isEmpty()) {
                for (Map.Entry<Constraint<Exam, ExamPlacement>, Set<ExamPlacement>> entry : conflictConstraints(
                        placement).entrySet()) {
                    Constraint<Exam, ExamPlacement> constraint = entry.getKey();
                    Set<ExamPlacement> values = entry.getValue();
                    if (constraint instanceof ExamStudent) {
                        ((ExamStudent) constraint).setAllowDirectConflicts(true);
                        exam.setAllowDirectConflicts(true);
                        for (ExamPlacement p : values)
                            p.variable().setAllowDirectConflicts(true);
                    }
                }
                conf = conflictValues(placement);
            }
            if (conf.isEmpty()) {
                exam.assign(0, placement);
            } else {
                sLog.error(
                        "Unable to assign " + exam.getInitialAssignment().getName() + " to exam " + exam.getName());
                sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(exam.getInitialAssignment()), 2));
            }
        }
        return true;
    }

    public boolean isMPP() {
        return iMPP;
    }
}