001 package net.sf.cpsolver.exam.model;
002
003 import java.util.ArrayList;
004 import java.util.HashSet;
005 import java.util.List;
006 import java.util.Set;
007
008 import net.sf.cpsolver.ifs.model.Constraint;
009 import net.sf.cpsolver.ifs.model.ConstraintListener;
010 import net.sf.cpsolver.ifs.util.DistanceMetric;
011
012 /**
013 * A room. Only one exam can use a room at a time (period). <br>
014 * <br>
015 *
016 * @version ExamTT 1.2 (Examination Timetabling)<br>
017 * Copyright (C) 2008 - 2010 Tomas Muller<br>
018 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
019 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
020 * <br>
021 * This library is free software; you can redistribute it and/or modify
022 * it under the terms of the GNU Lesser General Public License as
023 * published by the Free Software Foundation; either version 3 of the
024 * License, or (at your option) any later version. <br>
025 * <br>
026 * This library is distributed in the hope that it will be useful, but
027 * WITHOUT ANY WARRANTY; without even the implied warranty of
028 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
029 * Lesser General Public License for more details. <br>
030 * <br>
031 * You should have received a copy of the GNU Lesser General Public
032 * License along with this library; if not see
033 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
034 */
035 public class ExamRoom extends Constraint<Exam, ExamPlacement> {
036 private List<ExamPlacement>[] iTable;
037 private boolean[] iAvailable;
038 private int[] iPenalty;
039 private String iName;
040 private int iSize, iAltSize;
041 private Double iCoordX, iCoordY;
042
043 /**
044 * Constructor
045 *
046 * @param model
047 * examination timetabling model
048 * @param id
049 * unique id
050 * @param size
051 * room (normal) seating capacity
052 * @param altSize
053 * room alternating seating capacity (to be used when
054 * {@link Exam#hasAltSeating()} is true)
055 * @param coordX
056 * x coordinate
057 * @param coordY
058 * y coordinate
059 */
060 @SuppressWarnings("unchecked")
061 public ExamRoom(ExamModel model, long id, String name, int size, int altSize, Double coordX, Double coordY) {
062 super();
063 iAssignedVariables = null;
064 iId = id;
065 iName = name;
066 iCoordX = coordX;
067 iCoordY = coordY;
068 iSize = size;
069 iAltSize = altSize;
070 iTable = new List[model.getNrPeriods()];
071 iAvailable = new boolean[model.getNrPeriods()];
072 iPenalty = new int[model.getNrPeriods()];
073 for (int i = 0; i < iTable.length; i++) {
074 iTable[i] = new ArrayList<ExamPlacement>();
075 iAvailable[i] = true;
076 iPenalty[i] = 0;
077 }
078 }
079
080 /**
081 * Distance between two rooms. See {@link DistanceMetric}
082 *
083 * @param other
084 * another room
085 * @return distance between this and the given room
086 */
087 public double getDistanceInMeters(ExamRoom other) {
088 return ((ExamModel)getModel()).getDistanceMetric().getDistanceInMeters(getId(), getCoordX(), getCoordY(), other.getId(), other.getCoordX(), other.getCoordY());
089 }
090
091 /**
092 * Normal seating capacity (to be used when {@link Exam#hasAltSeating()} is
093 * false)
094 */
095 public int getSize() {
096 return iSize;
097 }
098
099 /**
100 * Alternating seating capacity (to be used when
101 * {@link Exam#hasAltSeating()} is true)
102 */
103 public int getAltSize() {
104 return iAltSize;
105 }
106
107 /**
108 * X coordinate
109 */
110 public Double getCoordX() {
111 return iCoordX;
112 }
113
114 /**
115 * Y coordinate
116 */
117 public Double getCoordY() {
118 return iCoordY;
119 }
120
121 /**
122 * An exam placed at the given period.
123 *
124 * @param period
125 * a period
126 * @return a placement of an exam in this room at the given period, null if
127 * unused
128 * @deprecated If room sharing is allowed, this method only returns first exam. Use {@link ExamRoom#getPlacements(ExamPeriod)} instead.
129 */
130 @Deprecated
131 public ExamPlacement getPlacement(ExamPeriod period) {
132 return (iTable[period.getIndex()].isEmpty() ? null : iTable[period.getIndex()].iterator().next());
133 }
134
135 /**
136 * Exams placed at the given period
137 *
138 * @param period
139 * a period
140 * @return a placement of an exam in this room at the given period, null if
141 * unused (multiple placements can be returned if the room is shared between
142 * two or more exams)
143 */
144 public List<ExamPlacement> getPlacements(ExamPeriod period) {
145 return iTable[period.getIndex()];
146 }
147
148 /**
149 * True if the room is available (for examination timetabling) during the
150 * given period
151 *
152 * @param period
153 * a period
154 * @return true if an exam can be scheduled into this room at the given
155 * period, false if otherwise
156 */
157 public boolean isAvailable(ExamPeriod period) {
158 return iAvailable[period.getIndex()];
159 }
160
161 public boolean isAvailable(int period) {
162 return iAvailable[period];
163 }
164
165 /**
166 * True if the room is available during at least one period,
167 * @return true if there is an examination period at which the room is available
168 */
169 public boolean isAvailable() {
170 for (boolean a: iAvailable)
171 if (a) return true;
172 return false;
173 }
174
175 /**
176 * Set whether the room is available (for examination timetabling) during
177 * the given period
178 *
179 * @param period
180 * a period
181 * @param available
182 * true if an exam can be scheduled into this room at the given
183 * period, false if otherwise
184 */
185 public void setAvailable(ExamPeriod period, boolean available) {
186 iAvailable[period.getIndex()] = available;
187 }
188
189 public void setAvailable(int period, boolean available) {
190 iAvailable[period] = available;
191 }
192
193 /** Return room penalty for given period */
194 public int getPenalty(ExamPeriod period) {
195 return iPenalty[period.getIndex()];
196 }
197
198 public int getPenalty(int period) {
199 return iPenalty[period];
200 }
201
202 /** Set room penalty for given period */
203 public void setPenalty(ExamPeriod period, int penalty) {
204 iPenalty[period.getIndex()] = penalty;
205 }
206
207 public void setPenalty(int period, int penalty) {
208 iPenalty[period] = penalty;
209 }
210
211
212 public ExamRoomSharing getRoomSharing() {
213 return ((ExamModel)getModel()).getRoomSharing();
214 }
215
216 /**
217 * Compute conflicts between the given assignment of an exam and all the
218 * current assignments (of this room)
219 */
220 @Override
221 public void computeConflicts(ExamPlacement p, Set<ExamPlacement> conflicts) {
222 if (!p.contains(this)) return;
223
224 if (getRoomSharing() == null) {
225 for (ExamPlacement conflict: iTable[p.getPeriod().getIndex()])
226 if (!conflict.variable().equals(p.variable()))
227 conflicts.add(conflict);
228 } else {
229 getRoomSharing().computeConflicts(p, iTable[p.getPeriod().getIndex()], this, conflicts);
230 }
231 }
232
233 /**
234 * Checks whether there is a conflict between the given assignment of an
235 * exam and all the current assignments (of this room)
236 */
237 @Override
238 public boolean inConflict(ExamPlacement p) {
239 if (!p.contains(this)) return false;
240
241 if (getRoomSharing() == null) {
242 for (ExamPlacement conflict: iTable[p.getPeriod().getIndex()])
243 if (!conflict.variable().equals(p.variable())) return true;
244 return false;
245 } else {
246 return getRoomSharing().inConflict(p, iTable[p.getPeriod().getIndex()], this);
247 }
248 }
249
250 /**
251 * False if the given two assignments are using this room at the same period
252 */
253 @Override
254 public boolean isConsistent(ExamPlacement p1, ExamPlacement p2) {
255 return (p1.getPeriod() != p2.getPeriod() || !p1.contains(this) || !p2.contains(this));
256 }
257
258 /**
259 * An exam was assigned, update room assignment table
260 */
261 @Override
262 public void assigned(long iteration, ExamPlacement p) {
263 if (p.contains(this) && !iTable[p.getPeriod().getIndex()].isEmpty()) {
264 HashSet<ExamPlacement> confs = new HashSet<ExamPlacement>();
265 computeConflicts(p, confs);
266 for (ExamPlacement conf: confs)
267 conf.variable().unassign(iteration);
268 if (iConstraintListeners != null) {
269 for (ConstraintListener<ExamPlacement> listener : iConstraintListeners)
270 listener.constraintAfterAssigned(iteration, this, p, confs);
271 }
272 }
273 }
274
275 /**
276 * An exam was assigned, update room assignment table
277 */
278 public void afterAssigned(long iteration, ExamPlacement p) {
279 if (p.contains(this))
280 iTable[p.getPeriod().getIndex()].add(p);
281 }
282
283 /**
284 * An exam was unassigned, update room assignment table
285 */
286 @Override
287 public void unassigned(long iteration, ExamPlacement p) {
288 // super.unassigned(iteration, p);
289 }
290
291 /**
292 * An exam was unassigned, update room assignment table
293 */
294 public void afterUnassigned(long iteration, ExamPlacement p) {
295 if (p.contains(this)) {
296 iTable[p.getPeriod().getIndex()].remove(p);
297 }
298 }
299
300 /**
301 * Checks two rooms for equality
302 */
303 @Override
304 public boolean equals(Object o) {
305 if (o == null || !(o instanceof ExamRoom))
306 return false;
307 ExamRoom r = (ExamRoom) o;
308 return getId() == r.getId();
309 }
310
311 /**
312 * Hash code
313 */
314 @Override
315 public int hashCode() {
316 return (int) (getId() ^ (getId() >>> 32));
317 }
318
319 /**
320 * Room name
321 */
322 @Override
323 public String getName() {
324 return (hasName() ? iName : String.valueOf(getId()));
325 }
326
327 /**
328 * Room name
329 */
330 public boolean hasName() {
331 return (iName != null && iName.length() > 0);
332 }
333
334 /**
335 * Room unique id
336 */
337 @Override
338 public String toString() {
339 return getName();
340 }
341
342 /**
343 * Compare two rooms (by unique id)
344 */
345 @Override
346 public int compareTo(Constraint<Exam, ExamPlacement> o) {
347 return toString().compareTo(o.toString());
348 }
349 }