    001package net.sf.cpsolver.exam.model;
    002
    003import java.text.DecimalFormat;
    004import java.util.HashSet;
    005import java.util.Iterator;
    006import java.util.Set;
    007
    008import net.sf.cpsolver.exam.criteria.ExamCriterion;
    009import net.sf.cpsolver.ifs.criteria.Criterion;
    010import net.sf.cpsolver.ifs.model.Value;
    011
    012/**
    013 * Representation of an exam placement (problem value), i.e., assignment of an
    014 * exam to period and room(s). Each placement has defined a period and a set of
    015 * rooms. The exam as well as the rooms have to be available during the given
    016 * period (see {@link Exam#getPeriodPlacements()} and
    017 * {@link Exam#getRoomPlacements()}). The total size of rooms have to be equal
    018 * or greater than the number of students enrolled in the exam
    019 * {@link Exam#getSize()}, using either {@link ExamRoom#getSize()} or
    020 * {@link ExamRoom#getAltSize()}, depending on {@link Exam#hasAltSeating()}.
    021 * Also, the number of rooms has to be smaller or equal to
    022 * {@link Exam#getMaxRooms()}. If {@link Exam#getMaxRooms()} is zero, the exam
    023 * is only to be assigned to period (the set of rooms is empty). <br>
    024 * <br>
    025 * <br>
    026 *
    027 * @version ExamTT 1.2 (Examination Timetabling)<br>
    028 *          Copyright (C) 2008 - 2010 Tomáš Müller<br>
    029 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    030 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    031 * <br>
    032 *          This library is free software; you can redistribute it and/or modify
    033 *          it under the terms of the GNU Lesser General Public License as
    034 *          published by the Free Software Foundation; either version 3 of the
    035 *          License, or (at your option) any later version. <br>
    036 * <br>
    037 *          This library is distributed in the hope that it will be useful, but
    038 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    039 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    040 *          Lesser General Public License for more details. <br>
    041 * <br>
    042 *          You should have received a copy of the GNU Lesser General Public
    043 *          License along with this library; if not see
    044 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    045 */
    046public class ExamPlacement extends Value<Exam, ExamPlacement> {
    047    private ExamPeriodPlacement iPeriodPlacement;
    048    private Set<ExamRoomPlacement> iRoomPlacements;
    049
    050    private Integer iHashCode = null;
    051
    052    /**
    053     * Constructor
    054     *
    055     * @param exam
    056     *            an exam
    057     * @param periodPlacement
    058     *            period placement
    059     * @param roomPlacements
    060     *            a set of room placements {@link ExamRoomPlacement}
    061     */
    062    public ExamPlacement(Exam exam, ExamPeriodPlacement periodPlacement, Set<ExamRoomPlacement> roomPlacements) {
    063        super(exam);
    064        iPeriodPlacement = periodPlacement;
    065        if (roomPlacements == null)
    066            iRoomPlacements = new HashSet<ExamRoomPlacement>();
    067        else
    068            iRoomPlacements = roomPlacements;
    069    }
    070
    071    /**
    072     * Assigned period
    073     */
    074    public ExamPeriod getPeriod() {
    075        return iPeriodPlacement.getPeriod();
    076    }
    077
    078    /**
    079     * Assigned period placement
    080     */
    081    public ExamPeriodPlacement getPeriodPlacement() {
    082        return iPeriodPlacement;
    083    }
    084
    085    /**
    086     * Assigned rooms (it is empty when {@link Exam#getMaxRooms()} is zero)
    087     *
    088     * @return list of {@link ExamRoomPlacement}
    089     */
    090    public Set<ExamRoomPlacement> getRoomPlacements() {
    091        return iRoomPlacements;
    092    }
    093
    094    /**
    095     * Distance between two placements, i.e., maximal distance between a room of
    096     * this placement and a room of the given placement. Method
    097     * {@link ExamRoom#getDistanceInMeters(ExamRoom)} is used to get a distance between
    098     * two rooms.
    099     */
    100    public double getDistanceInMeters(ExamPlacement other) {
    101        if (getRoomPlacements().isEmpty() || other.getRoomPlacements().isEmpty())
    102            return 0;
    103        double maxDistance = 0;
    104        for (ExamRoomPlacement r1 : getRoomPlacements()) {
    105            for (ExamRoomPlacement r2 : other.getRoomPlacements()) {
    106                maxDistance = Math.max(maxDistance, r1.getDistanceInMeters(r2));
    107            }
    108        }
    109        return maxDistance;
    110    }
    111
    112    /**
    113     * Overall cost of using this placement.
    114     */
    115    @Override
    116    public double toDouble() {
    117        double ret = 0.0;
    118        for (Criterion<Exam, ExamPlacement> criterion: variable().getModel().getCriteria())
    119            ret += criterion.getWeightedValue(this, null);
    120        return ret;
    121    }
    122
    123    /**
    124     * Overall cost of using this period.
    125     */
    126    public double getTimeCost() {
    127        double weight = 0.0;
    128        for (Criterion<Exam, ExamPlacement> criterion: variable().getModel().getCriteria()) {
    129            if (((ExamCriterion)criterion).isPeriodCriterion())
    130                weight += criterion.getWeight() * ((ExamCriterion)criterion).getPeriodValue(this);
    131        }
    132        return weight;
    133    }
    134
    135    /**
    136     * Overall cost of using this set or rooms.
    137     */
    138    public double getRoomCost() {
    139        double weight = 0.0;
    140        for (Criterion<Exam, ExamPlacement> criterion: variable().getModel().getCriteria()) {
    141            if (((ExamCriterion)criterion).isRoomCriterion())
    142                weight += criterion.getWeight() * ((ExamCriterion)criterion).getRoomValue(this);
    143        }
    144        return weight;
    145    }
    146
    147    /**
    148     * Room names separated with the given delimiter
    149     */
    150    public String getRoomName(String delim) {
    151        String roomName = "";
    152        for (Iterator<ExamRoomPlacement> i = getRoomPlacements().iterator(); i.hasNext();) {
    153            ExamRoomPlacement r = i.next();
    154            roomName += r.getRoom().getName();
    155            if (i.hasNext())
    156                roomName += delim;
    157        }
    158        return roomName;
    159    }
    160
    161    /**
    162     * Assignment name (period / room(s))
    163     */
    164    @Override
    165    public String getName() {
    166        return getPeriod() + " " + getRoomName(",");
    167    }
    168
    169    /**
    170     * String representation -- returns a list of assignment costs
    171     */
    172    @Override
    173    public String toString() {
    174        String ret = "";
    175        for (Criterion<Exam, ExamPlacement> criterion: variable().getModel().getCriteria()) {
    176            String val = criterion.toString();
    177            if (!val.isEmpty())
    178                ret += (!ret.isEmpty() && !ret.endsWith(",") ? "," : "") + val;
    179        }
    180        return variable().getName() + " = " + getName() + " (" + new DecimalFormat("0.00").format(toDouble()) + "/" + ret + ")";
    181    }
    182
    183    /**
    184     * Compare two assignments for equality
    185     */
    186    @Override
    187    public boolean equals(Object o) {
    188        if (o == null || !(o instanceof ExamPlacement))
    189            return false;
    190        ExamPlacement p = (ExamPlacement) o;
    191        return p.variable().equals(variable()) && p.getPeriod().equals(getPeriod())
    192                && p.getRoomPlacements().equals(getRoomPlacements());
    193    }
    194
    195    /**
    196     * Hash code
    197     */
    198    @Override
    199    public int hashCode() {
    200        if (iHashCode == null) iHashCode = getName().hashCode();
    201        return iHashCode;
    202    }
    203
    204    /**
    205     * True if given room is between {@link ExamPlacement#getRoomPlacements()}
    206     */
    207    public boolean contains(ExamRoom room) {
    208        return getRoomPlacements().contains(new ExamRoomPlacement(room));
    209    }
    210}