001package net.sf.cpsolver.exam.model; 002 003import java.util.ArrayList; 004import java.util.HashSet; 005import java.util.List; 006import java.util.Set; 007 008import net.sf.cpsolver.ifs.model.Constraint; 009import net.sf.cpsolver.ifs.model.ConstraintListener; 010import 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 Tomáš Müller<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 */ 035public 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}