001    package net.sf.cpsolver.exam.model;
002    
003    import java.text.DecimalFormat;
004    import java.util.HashSet;
005    import java.util.Iterator;
006    import java.util.Set;
007    
008    import net.sf.cpsolver.exam.criteria.ExamCriterion;
009    import net.sf.cpsolver.ifs.criteria.Criterion;
010    import 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 Tomas Muller<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     */
046    public 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    }