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