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}