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