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