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 Tomáš Müller<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    private boolean iHard = true;
048
049    /**
050     * Constructor
051     * 
052     * @param model
053     *            examination timetabling model
054     * @param id
055     *            unique id
056     * @param name room name
057     * @param size
058     *            room (normal) seating capacity
059     * @param altSize
060     *            room alternating seating capacity (to be used when
061     *            {@link Exam#hasAltSeating()} is true)
062     * @param coordX
063     *            x coordinate
064     * @param coordY
065     *            y coordinate
066     */
067    public ExamRoom(ExamModel model, long id, String name, int size, int altSize, Double coordX, Double coordY) {
068        super();
069        iId = id;
070        iName = name;
071        iCoordX = coordX;
072        iCoordY = coordY;
073        iSize = size;
074        iAltSize = altSize;
075        iAvailable = new boolean[model.getNrPeriods()];
076        iPenalty = new int[model.getNrPeriods()];
077        for (int i = 0; i < iAvailable.length; i++) {
078            iAvailable[i] = true;
079            iPenalty[i] = 0;
080        }
081    }
082    
083    public void setHard(boolean hard) { iHard = hard; }
084    
085    @Override
086    public boolean isHard() { return iHard; }
087    
088    private Map<Long, Double> iDistanceCache = new HashMap<Long, Double>();
089    /**
090     * Distance between two rooms. See {@link DistanceMetric}
091     * 
092     * @param other
093     *            another room
094     * @return distance between this and the given room
095     */
096    public double getDistanceInMeters(ExamRoom other) {
097        synchronized (iDistanceCache) {
098            Double distance = iDistanceCache.get(other.getId());
099            if (distance == null) {
100                distance = ((ExamModel)getModel()).getDistanceMetric().getDistanceInMeters(getId(), getCoordX(), getCoordY(), other.getId(), other.getCoordX(), other.getCoordY());
101                iDistanceCache.put(other.getId(), distance);
102            }
103            return distance;
104        }
105    }
106
107    /**
108     * Normal seating capacity (to be used when {@link Exam#hasAltSeating()} is
109     * false)
110     * @return room normal seating capacity
111     */
112    public int getSize() {
113        return iSize;
114    }
115
116    /**
117     * Alternating seating capacity (to be used when
118     * {@link Exam#hasAltSeating()} is true)
119     * @return room examination seating capacity
120     */
121    public int getAltSize() {
122        return iAltSize;
123    }
124
125    /**
126     * X coordinate
127     * @return X-coordinate (latitude)
128     */
129    public Double getCoordX() {
130        return iCoordX;
131    }
132
133    /**
134     * Y coordinate
135     * @return Y-coordinate (longitude)
136     */
137    public Double getCoordY() {
138        return iCoordY;
139    }
140
141    /**
142     * Exams placed at the given period
143     * 
144     * @param assignment current assignment
145     * @param period
146     *            a period
147     * @return a placement of an exam in this room at the given period, null if
148     *         unused (multiple placements can be returned if the room is shared between
149     *         two or more exams)
150     */
151    public List<ExamPlacement> getPlacements(Assignment<Exam, ExamPlacement> assignment, ExamPeriod period) {
152        return getContext(assignment).getPlacements(period.getIndex());
153    }
154
155    /**
156     * True if the room is available (for examination timetabling) during the
157     * given period
158     * 
159     * @param period
160     *            a period
161     * @return true if an exam can be scheduled into this room at the given
162     *         period, false if otherwise
163     */
164    public boolean isAvailable(ExamPeriod period) {
165        return iAvailable[period.getIndex()];
166    }
167
168    public boolean isAvailable(int period) {
169        return iAvailable[period];
170    }
171    
172    /**
173     * True if the room is available during at least one period,
174     * @return true if there is an examination period at which the room is available
175     */
176    public boolean isAvailable() {
177        for (boolean a: iAvailable)
178            if (a) return true;
179        return false;
180    }
181
182    /**
183     * Set whether the room is available (for examination timetabling) during
184     * the given period
185     * 
186     * @param period
187     *            a period
188     * @param available
189     *            true if an exam can be scheduled into this room at the given
190     *            period, false if otherwise
191     */
192    public void setAvailable(ExamPeriod period, boolean available) {
193        iAvailable[period.getIndex()] = available;
194    }
195
196    public void setAvailable(int period, boolean available) {
197        iAvailable[period] = available;
198    }
199
200    /** Return room penalty for given period 
201     * @param period given period
202     * @return room penalty for the given period
203     **/
204    public int getPenalty(ExamPeriod period) {
205        return iPenalty[period.getIndex()];
206    }
207
208    public int getPenalty(int period) {
209        return iPenalty[period];
210    }
211
212    /** Set room penalty for given period 
213     * @param period given period
214     * @param penalty penalty for the given period
215     **/
216    public void setPenalty(ExamPeriod period, int penalty) {
217        iPenalty[period.getIndex()] = penalty;
218    }
219
220    public void setPenalty(int period, int penalty) {
221        iPenalty[period] = penalty;
222    }
223    
224    
225    public ExamRoomSharing getRoomSharing() {
226        return ((ExamModel)getModel()).getRoomSharing();
227    }
228
229    /**
230     * Compute conflicts between the given assignment of an exam and all the
231     * current assignments (of this room)
232     */
233    @Override
234    public void computeConflicts(Assignment<Exam, ExamPlacement> assignment, ExamPlacement p, Set<ExamPlacement> conflicts) {
235        if (!isHard()) return;
236        if (!p.contains(this)) return;
237        
238        if (getRoomSharing() == null) {
239            for (ExamPlacement conflict: getContext(assignment).getPlacements(p.getPeriod().getIndex()))
240                if (!conflict.variable().equals(p.variable()))
241                    conflicts.add(conflict);
242        } else {
243            getRoomSharing().computeConflicts(p, getContext(assignment).getPlacements(p.getPeriod().getIndex()), this, conflicts);
244        }
245    }
246
247    /**
248     * Checks whether there is a conflict between the given assignment of an
249     * exam and all the current assignments (of this room)
250     */
251    @Override
252    public boolean inConflict(Assignment<Exam, ExamPlacement> assignment, ExamPlacement p) {
253        if (!isHard()) return false;
254        if (!p.contains(this)) return false;
255        
256        if (getRoomSharing() == null) {
257            for (ExamPlacement conflict: getContext(assignment).getPlacements(p.getPeriod().getIndex()))
258                if (!conflict.variable().equals(p.variable())) return true;
259            return false;
260        } else {
261            return getRoomSharing().inConflict(p, getContext(assignment).getPlacements(p.getPeriod().getIndex()), this);
262        }
263    }
264
265    /**
266     * False if the given two assignments are using this room at the same period
267     */
268    @Override
269    public boolean isConsistent(ExamPlacement p1, ExamPlacement p2) {
270        return !isHard() || (p1.getPeriod() != p2.getPeriod() || !p1.contains(this) || !p2.contains(this));
271    }
272
273    /**
274     * An exam was assigned, update room assignment table
275     */
276    @Override
277    public void assigned(Assignment<Exam, ExamPlacement> assignment, long iteration, ExamPlacement p) {
278        if (p.contains(this)) {
279            if (!getContext(assignment).getPlacements(p.getPeriod().getIndex()).isEmpty()) {
280                HashSet<ExamPlacement> confs = new HashSet<ExamPlacement>();
281                computeConflicts(assignment, p, confs);
282                for (ExamPlacement conf: confs)
283                    assignment.unassign(iteration, conf.variable());
284                if (iConstraintListeners != null) {
285                    for (ConstraintListener<Exam, ExamPlacement> listener : iConstraintListeners)
286                        listener.constraintAfterAssigned(assignment, iteration, this, p, confs);
287                }
288            }
289            getContext(assignment).assigned(assignment, p);
290        }
291    }
292
293    /**
294     * An exam was unassigned, update room assignment table
295     */
296    @Override
297    public void unassigned(Assignment<Exam, ExamPlacement> assignment, long iteration, ExamPlacement p) {
298        if (p.contains(this))
299            getContext(assignment).unassigned(assignment, p);
300    }
301
302    /**
303     * Checks two rooms for equality
304     */
305    @Override
306    public boolean equals(Object o) {
307        if (o == null || !(o instanceof ExamRoom))
308            return false;
309        ExamRoom r = (ExamRoom) o;
310        return getId() == r.getId();
311    }
312
313    /**
314     * Hash code
315     */
316    @Override
317    public int hashCode() {
318        return (int) (getId() ^ (getId() >>> 32));
319    }
320
321    /**
322     * Room name
323     */
324    @Override
325    public String getName() {
326        return (hasName() ? iName : String.valueOf(getId()));
327    }
328
329    /**
330     * Room name
331     * @return true if the room name is set and not empty
332     */
333    public boolean hasName() {
334        return (iName != null && iName.length() > 0);
335    }
336
337    /**
338     * Room unique id
339     */
340    @Override
341    public String toString() {
342        return getName();
343    }
344
345    /**
346     * Compare two rooms (by unique id)
347     */
348    @Override
349    public int compareTo(Constraint<Exam, ExamPlacement> o) {
350        return toString().compareTo(o.toString());
351    }
352    
353    @Override
354    public ExamRoomContext createAssignmentContext(Assignment<Exam, ExamPlacement> assignment) {
355        return new ExamRoomContext(assignment);
356    }
357    
358    public class ExamRoomContext implements AssignmentConstraintContext<Exam, ExamPlacement> {
359        private List<ExamPlacement>[] iTable;
360        
361        @SuppressWarnings("unchecked")
362        public ExamRoomContext(Assignment<Exam, ExamPlacement> assignment) {
363            ExamModel model = (ExamModel)getModel();
364            iTable = new List[model.getNrPeriods()];
365            for (int i = 0; i < iTable.length; i++)
366                iTable[i] = new ArrayList<ExamPlacement>();
367            for (Exam exam: variables()) {
368                ExamPlacement placement = assignment.getValue(exam);
369                if (placement != null && placement.contains(ExamRoom.this))
370                    iTable[placement.getPeriod().getIndex()].add(placement);
371            }
372        }
373
374        @Override
375        public void assigned(Assignment<Exam, ExamPlacement> assignment, ExamPlacement placement) {
376            if (placement.contains(ExamRoom.this))
377                iTable[placement.getPeriod().getIndex()].add(placement);
378        }
379        
380        @Override
381        public void unassigned(Assignment<Exam, ExamPlacement> assignment, ExamPlacement placement) {
382            if (placement.contains(ExamRoom.this))
383                iTable[placement.getPeriod().getIndex()].remove(placement);
384        }
385        
386        public List<ExamPlacement> getPlacements(int period) { return iTable[period]; }
387    }
388}