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}