001package net.sf.cpsolver.coursett.constraint;
002
003import java.util.ArrayList;
004import java.util.BitSet;
005import java.util.Enumeration;
006import java.util.HashSet;
007import java.util.List;
008import java.util.Set;
009
010import net.sf.cpsolver.coursett.Constants;
011import net.sf.cpsolver.coursett.criteria.BackToBackInstructorPreferences;
012import net.sf.cpsolver.coursett.model.Lecture;
013import net.sf.cpsolver.coursett.model.Placement;
014import net.sf.cpsolver.coursett.model.TimeLocation;
015import net.sf.cpsolver.coursett.model.TimetableModel;
016import net.sf.cpsolver.ifs.model.Constraint;
017import net.sf.cpsolver.ifs.util.DistanceMetric;
018
019/**
020 * Instructor constraint. <br>
021 * Classes with this instructor can not overlap in time. Also, for back-to-back
022 * classes, there is the following reasoning:
023 * <ul>
024 * <li>if the distance is equal or below
025 * {@link DistanceMetric#getInstructorNoPreferenceLimit()} .. no preference
026 * <li>if the distance is above
027 * {@link DistanceMetric#getInstructorNoPreferenceLimit()} and below
028 * {@link DistanceMetric#getInstructorDiscouragedLimit()} .. constraint is
029 * discouraged (soft, preference = 1)
030 * <li>if the distance is above
031 * {@link DistanceMetric#getInstructorDiscouragedLimit()} and below
032 * {@link DistanceMetric#getInstructorProhibitedLimit()} .. constraint is
033 * strongly discouraged (soft, preference = 2)
034 * <li>if the distance is above
035 * {@link DistanceMetric#getInstructorProhibitedLimit()} .. constraint is
036 * prohibited (hard)
037 * </ul>
038 * <br>
039 * When {@link InstructorConstraint#isIgnoreDistances()} is set to true, the
040 * constraint never prohibits two back-to-back classes (but it still tries to
041 * minimize the above back-to-back preferences).
042 * 
043 * @version CourseTT 1.2 (University Course Timetabling)<br>
044 *          Copyright (C) 2006 - 2010 Tomáš Müller<br>
045 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
046 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
047 * <br>
048 *          This library is free software; you can redistribute it and/or modify
049 *          it under the terms of the GNU Lesser General Public License as
050 *          published by the Free Software Foundation; either version 3 of the
051 *          License, or (at your option) any later version. <br>
052 * <br>
053 *          This library is distributed in the hope that it will be useful, but
054 *          WITHOUT ANY WARRANTY; without even the implied warranty of
055 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
056 *          Lesser General Public License for more details. <br>
057 * <br>
058 *          You should have received a copy of the GNU Lesser General Public
059 *          License along with this library; if not see
060 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
061 */
062
063public class InstructorConstraint extends Constraint<Lecture, Placement> {
064
065    public int iPreference = 0;
066
067    /**
068     * table iResource[slot] = lecture using this resource placed in the given
069     * time slot (null if empty)
070     */
071    protected List<Placement>[] iResource;
072    private Long iResourceId;
073    private String iName;
074    private String iPuid;
075    private List<Placement> iUnavailabilities = null;
076    private boolean iIgnoreDistances = false;
077    private Long iType = null;
078
079    /**
080     * Constructor
081     * 
082     * @param id
083     *            instructor id
084     * @param name
085     *            instructor name
086     */
087    @SuppressWarnings("unchecked")
088    public InstructorConstraint(Long id, String puid, String name, boolean ignDist) {
089        iResourceId = id;
090        iName = name;
091        iPuid = puid;
092        iIgnoreDistances = ignDist;
093        iResource = new List[Constants.SLOTS_PER_DAY * Constants.DAY_CODES.length];
094        for (int i = 0; i < iResource.length; i++)
095            iResource[i] = new ArrayList<Placement>(3);
096    }
097
098    public List<Placement> getPlacements(int slot, Placement placement) {
099        return getPlacements(slot, placement.getTimeLocation().getWeekCode());
100    }
101
102    public List<Placement> getPlacements(int slot, BitSet weekCode) {
103        List<Placement> placements = new ArrayList<Placement>(iResource[slot].size());
104        for (Placement p : iResource[slot]) {
105            if (p.getTimeLocation().shareWeeks(weekCode))
106                placements.add(p);
107        }
108        return placements;
109    }
110
111    public Placement getPlacement(int slot, int day) {
112        for (Placement p : iResource[slot]) {
113            if (p.getTimeLocation().hasDay(day))
114                return p;
115        }
116        return null;
117    }
118    
119    public void setNotAvailable(Placement placement) {
120        if (iUnavailabilities == null)
121            iUnavailabilities = new ArrayList<Placement>();
122        iUnavailabilities.add(placement);
123        for (Lecture lecture: variables())
124            lecture.clearValueCache();
125    }
126
127    public boolean isAvailable(Lecture lecture, TimeLocation time) {
128        if (iUnavailabilities == null) return true;
129        for (Placement c: iUnavailabilities) {
130            if (c.getTimeLocation().hasIntersection(time) && !lecture.canShareRoom(c.variable())) return false;
131        }
132        return true;
133    }
134    
135    private DistanceMetric getDistanceMetric() {
136        return ((TimetableModel)getModel()).getDistanceMetric();
137    }
138
139    public boolean isAvailable(Lecture lecture, Placement placement) {
140        if (iUnavailabilities == null) return true;
141        TimeLocation t1 = placement.getTimeLocation();
142        for (Placement c: iUnavailabilities) {
143            if (c.getTimeLocation().hasIntersection(placement.getTimeLocation()) && (!lecture.canShareRoom(c.variable()) || !placement.sameRooms(c)))
144                return false;
145            if (!iIgnoreDistances) {
146                TimeLocation t2 = c.getTimeLocation();
147                if (t1.shareDays(t2) && t1.shareWeeks(t2)) {
148                    if (t1.getStartSlot() + t1.getLength() == t2.getStartSlot() || t2.getStartSlot() + t2.getLength() == t1.getStartSlot()) {
149                        if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit())
150                            return false;
151                    } else if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) {
152                        if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) {
153                            if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, c) > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength()))
154                                return false;
155                        } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) {
156                            if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, c) > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength()))
157                                return false;
158                        }
159                    }
160                }
161            }
162        }
163        return true;
164    }
165
166    public List<Placement> getUnavailabilities() {
167        return iUnavailabilities;
168    }
169
170    @Deprecated
171    @SuppressWarnings("unchecked")
172    public List<Placement>[] getAvailableArray() {
173        if (iUnavailabilities == null) return null;
174        List<Placement>[] available = new List[Constants.SLOTS_PER_DAY * Constants.DAY_CODES.length];
175        for (int i = 0; i < iResource.length; i++)
176            available[i] = null;
177        for (Placement p: iUnavailabilities) {
178            for (Enumeration<Integer> e = p.getTimeLocation().getSlots(); e.hasMoreElements();) {
179                int slot = e.nextElement();
180                if (available[slot] == null)
181                    available[slot] = new ArrayList<Placement>(1);
182                available[slot].add(p);
183            }
184        }
185        return available;
186    }
187
188    /** Back-to-back preference of two placements (3 means prohibited) */
189    public int getDistancePreference(Placement p1, Placement p2) {
190        TimeLocation t1 = p1.getTimeLocation();
191        TimeLocation t2 = p2.getTimeLocation();
192        if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2))
193            return Constants.sPreferenceLevelNeutral;
194        if (t1.getStartSlot() + t1.getLength() == t2.getStartSlot() || t2.getStartSlot() + t2.getLength() == t1.getStartSlot()) {
195            double distance = Placement.getDistanceInMeters(getDistanceMetric(), p1, p2);
196            if (distance <= getDistanceMetric().getInstructorNoPreferenceLimit())
197                return Constants.sPreferenceLevelNeutral;
198            if (distance <= getDistanceMetric().getInstructorDiscouragedLimit())
199                return Constants.sPreferenceLevelDiscouraged;
200            if (iIgnoreDistances || distance <= getDistanceMetric().getInstructorProhibitedLimit())
201                return Constants.sPreferenceLevelStronglyDiscouraged;
202            return Constants.sPreferenceLevelProhibited;
203        } else if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) {
204            if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) {
205                int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), p1, p2);
206                if (distanceInMinutes > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) // too far apart
207                    return (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited);
208                if (distanceInMinutes >= getDistanceMetric().getInstructorLongTravelInMinutes()) // long travel
209                    return Constants.sPreferenceLevelStronglyDiscouraged;
210                if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) // too far if no break time
211                    return Constants.sPreferenceLevelDiscouraged;
212            } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) {
213                int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), p1, p2);
214                if (distanceInMinutes > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) // too far apart
215                    return (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited);
216                if (distanceInMinutes >= getDistanceMetric().getInstructorLongTravelInMinutes()) // long travel
217                    return Constants.sPreferenceLevelStronglyDiscouraged;
218                if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) // too far if no break time
219                    return Constants.sPreferenceLevelDiscouraged;
220            }
221        } 
222        return Constants.sPreferenceLevelNeutral; 
223    }
224
225    /** Resource id */
226    public Long getResourceId() {
227        return iResourceId;
228    }
229
230    /** Resource name */
231    @Override
232    public String getName() {
233        return iName;
234    }
235
236    @Override
237    public void computeConflicts(Placement placement, Set<Placement> conflicts) {
238        Lecture lecture = placement.variable();
239        BitSet weekCode = placement.getTimeLocation().getWeekCode();
240
241        for (Enumeration<Integer> e = placement.getTimeLocation().getSlots(); e.hasMoreElements();) {
242            int slot = e.nextElement();
243            for (Placement p : iResource[slot]) {
244                if (!p.equals(lecture.getAssignment()) && p.getTimeLocation().shareWeeks(weekCode)) {
245                    if (p.canShareRooms(placement) && p.sameRooms(placement))
246                        continue;
247                    conflicts.add(p);
248                }
249            }
250        }
251        if (!iIgnoreDistances) {
252            for (Enumeration<Integer> e = placement.getTimeLocation().getStartSlots(); e.hasMoreElements();) {
253                int startSlot = e.nextElement();
254
255                int prevSlot = startSlot - 1;
256                if (prevSlot >= 0 && (prevSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) {
257                    for (Placement c : getPlacements(prevSlot, placement)) {
258                        if (lecture.equals(c.variable())) continue;
259                        if (c.canShareRooms(placement) && c.sameRooms(placement)) continue;
260                        if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit())
261                            conflicts.add(c);
262                    }
263                }
264                int nextSlot = startSlot + placement.getTimeLocation().getLength();
265                if ((nextSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) {
266                    for (Placement c : getPlacements(nextSlot, placement)) {
267                        if (lecture.equals(c.variable())) continue;
268                        if (c.canShareRooms(placement) && c.sameRooms(placement)) continue;
269                        if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit())
270                            conflicts.add(c);
271                    }
272                }
273                
274                if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) {
275                    TimeLocation t1 = placement.getTimeLocation();
276                    for (Lecture other: assignedVariables()) {
277                        if (other.getAssignment() == null || other.equals(placement.variable())) continue;
278                        TimeLocation t2 = other.getAssignment().getTimeLocation();
279                        if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue;
280                        if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) {
281                            if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, other.getAssignment()) > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength()))
282                                conflicts.add(other.getAssignment());
283                        } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) {
284                            if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, other.getAssignment()) >  t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength()))
285                                conflicts.add(other.getAssignment());
286                        }
287                    }
288                }
289            }
290        }
291    }
292
293    @Override
294    public boolean inConflict(Placement placement) {
295        Lecture lecture = placement.variable();
296        BitSet weekCode = placement.getTimeLocation().getWeekCode();
297        for (Enumeration<Integer> e = placement.getTimeLocation().getSlots(); e.hasMoreElements();) {
298            int slot = e.nextElement();
299            for (Placement p : iResource[slot]) {
300                if (!p.equals(lecture.getAssignment()) && p.getTimeLocation().shareWeeks(weekCode)) {
301                    if (p.canShareRooms(placement) && p.sameRooms(placement))
302                        continue;
303                    return true;
304                }
305            }
306        }
307        if (!iIgnoreDistances) {
308            for (Enumeration<Integer> e = placement.getTimeLocation().getStartSlots(); e.hasMoreElements();) {
309                int startSlot = e.nextElement();
310                
311                int prevSlot = startSlot - 1;
312                if (prevSlot >= 0 && (prevSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) {
313                    for (Placement c : getPlacements(prevSlot, placement)) {
314                        if (lecture.equals(c.variable())) continue;
315                        if (c.canShareRooms(placement) && c.sameRooms(placement)) continue;
316                        if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit())
317                            return true;
318                    }
319                }
320                int nextSlot = startSlot + placement.getTimeLocation().getLength();
321                if ((nextSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) {
322                    for (Placement c : getPlacements(nextSlot, placement)) {
323                        if (lecture.equals(c.variable())) continue;
324                        if (c.canShareRooms(placement) && c.sameRooms(placement)) continue;
325                        if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit())
326                            return true;
327                    }
328                }
329                
330                if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) {
331                    TimeLocation t1 = placement.getTimeLocation();
332                    for (Lecture other: assignedVariables()) {
333                        if (other.getAssignment() == null || other.equals(placement.variable())) continue;
334                        TimeLocation t2 = other.getAssignment().getTimeLocation();
335                        if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue;
336                        if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) {
337                            if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, other.getAssignment()) > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength()))
338                                return true;
339                        } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) {
340                            if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, other.getAssignment()) >  t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength()))
341                                return true;
342                        }
343                    }
344                }
345            }
346        }
347        return false;
348    }
349
350    @Override
351    public boolean isConsistent(Placement p1, Placement p2) {
352        if (p1.canShareRooms(p2) && p1.sameRooms(p2))
353            return true;
354        if (p1.getTimeLocation().hasIntersection(p2.getTimeLocation()))
355            return false;
356        return getDistancePreference(p1, p2) != Constants.sPreferenceLevelProhibited;
357    }
358
359    @Override
360    public void assigned(long iteration, Placement placement) {
361        super.assigned(iteration, placement);
362        // iPreference += getPreference(placement);
363        for (Enumeration<Integer> e = placement.getTimeLocation().getSlots(); e.hasMoreElements();) {
364            int slot = e.nextElement();
365            iResource[slot].add(placement);
366        }
367        getModel().getCriterion(BackToBackInstructorPreferences.class).inc(-iPreference);
368        iPreference = countPreference();
369        getModel().getCriterion(BackToBackInstructorPreferences.class).inc(iPreference);
370    }
371
372    @Override
373    public void unassigned(long iteration, Placement placement) {
374        super.unassigned(iteration, placement);
375        // iPreference -= getPreference(placement);
376        for (Enumeration<Integer> e = placement.getTimeLocation().getSlots(); e.hasMoreElements();) {
377            int slot = e.nextElement();
378            iResource[slot].remove(placement);
379        }
380        getModel().getCriterion(BackToBackInstructorPreferences.class).inc(-iPreference);
381        iPreference = countPreference();
382        getModel().getCriterion(BackToBackInstructorPreferences.class).inc(iPreference);
383    }
384
385    /**
386     * Lookup table getResource()[slot] -> lecture using this resource placed in
387     * the given time slot (null if empty)
388     */
389    public List<Placement> getResource(int slot) {
390        return iResource[slot];
391    }
392
393    public Placement[] getResourceOfWeek(int startDay) {
394        Placement[] ret = new Placement[iResource.length];
395        for (int i = 0; i < iResource.length; i++) {
396            ret[i] = getPlacement(i, startDay + (i / Constants.SLOTS_PER_DAY));
397        }
398        return ret;
399    }
400
401    /** Number of useless slots for this resource */
402    public int countUselessSlots() {
403        int ret = 0;
404        for (int d = 0; d < Constants.DAY_CODES.length; d++) {
405            for (int s = 1; s < Constants.SLOTS_PER_DAY - 1; s++) {
406                int slot = d * Constants.SLOTS_PER_DAY + s;
407                if (iResource[slot - 1] != null && iResource[slot] == null && iResource[slot + 1] != null)
408                    ret++;
409            }
410        }
411        return ret;
412    }
413
414    /** Resource usage usage */
415    protected void printUsage(StringBuffer sb) {
416        for (int slot = 0; slot < iResource.length; slot++) {
417            for (Placement p : iResource[slot]) {
418                int day = slot / Constants.SLOTS_PER_DAY;
419                int time = slot * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
420                int h = time / 60;
421                int m = time % 60;
422                String d = Constants.DAY_NAMES_SHORT[day];
423                int slots = p.getTimeLocation().getLength();
424                time += (30 * slots);
425                int h2 = time / 60;
426                int m2 = time % 60;
427                sb.append(sb.length() == 0 ? "" : ",\n        ").append(
428                        "[" + d + (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a") + "-"
429                                + (h2 > 12 ? h2 - 12 : h2) + ":" + (m2 < 10 ? "0" : "") + m2 + (h2 >= 12 ? "p" : "a")
430                                + "]=").append(p.variable().getName());
431                slot += slots - 1;
432                // sb.append(sb.length()==0?"":", ").append("s"+(slot+1)+"=").append(((Lecture)getResource()[slot]).getName());
433            }
434        }
435    }
436
437    @Override
438    public String toString() {
439        return "Instructor " + getName();
440    }
441
442    /** Back-to-back preference of the given placement */
443    public int getPreference(Placement value) {
444        Lecture lecture = value.variable();
445        Placement placement = value;
446        int pref = 0;
447        HashSet<Placement> checked = new HashSet<Placement>();
448        
449        for (Enumeration<Integer> e = placement.getTimeLocation().getStartSlots(); e.hasMoreElements();) {
450            int startSlot = e.nextElement();
451            
452            int prevSlot = startSlot - 1;
453            if (prevSlot >= 0 && (prevSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) {
454                for (Placement c : getPlacements(prevSlot, placement)) {
455                    if (lecture.equals(c.variable()) || !checked.add(c)) continue;
456                    double dist = Placement.getDistanceInMeters(getDistanceMetric(), placement, c);
457                    if (dist > getDistanceMetric().getInstructorNoPreferenceLimit() && dist <= getDistanceMetric().getInstructorDiscouragedLimit())
458                        pref += Constants.sPreferenceLevelDiscouraged;
459                    if (dist > getDistanceMetric().getInstructorDiscouragedLimit()
460                            && (dist <= getDistanceMetric().getInstructorProhibitedLimit() || iIgnoreDistances))
461                        pref += Constants.sPreferenceLevelStronglyDiscouraged;
462                    if (!iIgnoreDistances && dist > getDistanceMetric().getInstructorProhibitedLimit())
463                        pref += Constants.sPreferenceLevelProhibited;
464                }
465            }
466            int nextSlot = startSlot + placement.getTimeLocation().getLength();
467            if ((nextSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) {
468                for (Placement c : getPlacements(nextSlot, placement)) {
469                    if (lecture.equals(c.variable()) || !checked.add(c)) continue;
470                    double dist = Placement.getDistanceInMeters(getDistanceMetric(), placement, c);
471                    if (dist > getDistanceMetric().getInstructorNoPreferenceLimit() && dist <= getDistanceMetric().getInstructorDiscouragedLimit())
472                        pref += Constants.sPreferenceLevelDiscouraged;
473                    if (dist > getDistanceMetric().getInstructorDiscouragedLimit()
474                            && (dist <= getDistanceMetric().getInstructorProhibitedLimit() || iIgnoreDistances))
475                        pref += Constants.sPreferenceLevelStronglyDiscouraged;
476                    if (!iIgnoreDistances && dist > getDistanceMetric().getInstructorProhibitedLimit())
477                        pref = Constants.sPreferenceLevelProhibited;
478                }
479            }
480            
481            if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) {
482                TimeLocation t1 = placement.getTimeLocation();
483                Placement before = null, after = null;
484                for (Lecture other: assignedVariables()) {
485                    if (other.getAssignment() == null || other.equals(placement.variable())) continue;
486                    TimeLocation t2 = other.getAssignment().getTimeLocation();
487                    if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue;
488                    if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) {
489                        int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), placement, other.getAssignment());
490                        if (distanceInMinutes > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength()))
491                            pref += (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited);
492                        else if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength()))
493                            pref += Constants.sPreferenceLevelDiscouraged;
494                    } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) {
495                        int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), placement, other.getAssignment());
496                        if (distanceInMinutes >  t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength()))
497                            pref += (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited);
498                        else if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength()))
499                            pref += Constants.sPreferenceLevelDiscouraged;
500                    }
501                    if (t1.getStartSlot() + t1.getLength() <= t2.getStartSlot()) {
502                        if (after == null || t2.getStartSlot() < after.getTimeLocation().getStartSlot())
503                            after = other.getAssignment();
504                    } else if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) {
505                        if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot())
506                            before = other.getAssignment();
507                    }
508                }
509                if (iUnavailabilities != null) {
510                    for (Placement c: iUnavailabilities) {
511                        TimeLocation t2 = c.getTimeLocation();
512                        if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue;
513                        if (t1.getStartSlot() + t1.getLength() <= t2.getStartSlot()) {
514                            if (after == null || t2.getStartSlot() < after.getTimeLocation().getStartSlot())
515                                after = c;
516                        } else if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) {
517                            if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot())
518                                before = c;
519                        }
520                    }
521                }
522                if (before != null && Placement.getDistanceInMinutes(getDistanceMetric(), before, placement) > getDistanceMetric().getInstructorLongTravelInMinutes())
523                    pref += Constants.sPreferenceLevelStronglyDiscouraged;
524                if (after != null && Placement.getDistanceInMinutes(getDistanceMetric(), after, placement) > getDistanceMetric().getInstructorLongTravelInMinutes())
525                    pref += Constants.sPreferenceLevelStronglyDiscouraged;
526                if (before != null && after != null && Placement.getDistanceInMinutes(getDistanceMetric(), before, after) > getDistanceMetric().getInstructorLongTravelInMinutes())
527                    pref -= Constants.sPreferenceLevelStronglyDiscouraged;
528            }
529        }
530        return pref;
531    }
532
533    public int getPreferenceCombination(Placement value) {
534        Lecture lecture = value.variable();
535        Placement placement = value;
536        int pref = 0;
537        HashSet<Placement> checked = new HashSet<Placement>();
538        
539        for (Enumeration<Integer> e = placement.getTimeLocation().getStartSlots(); e.hasMoreElements();) {
540            int startSlot = e.nextElement();
541            
542            int prevSlot = startSlot - 1;
543            if (prevSlot >= 0 && (prevSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) {
544                for (Placement c : getPlacements(prevSlot, placement)) {
545                    if (lecture.equals(c.variable()) || !checked.add(c)) continue;
546                    double dist = Placement.getDistanceInMeters(getDistanceMetric(), placement, c);
547                    if (dist > getDistanceMetric().getInstructorNoPreferenceLimit() && dist <= getDistanceMetric().getInstructorDiscouragedLimit())
548                        pref = Math.max(pref, Constants.sPreferenceLevelDiscouraged);
549                    if (dist > getDistanceMetric().getInstructorDiscouragedLimit()
550                            && (dist <= getDistanceMetric().getInstructorProhibitedLimit() || iIgnoreDistances))
551                        pref = Math.max(pref, Constants.sPreferenceLevelStronglyDiscouraged);
552                    if (!iIgnoreDistances && dist > getDistanceMetric().getInstructorProhibitedLimit())
553                        pref = Math.max(pref, Constants.sPreferenceLevelProhibited);
554                }
555            }
556            int nextSlot = startSlot + placement.getTimeLocation().getLength();
557            if ((nextSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) {
558                for (Placement c : getPlacements(nextSlot, placement)) {
559                    if (lecture.equals(c.variable()) || !checked.add(c)) continue;
560                    double dist = Placement.getDistanceInMeters(getDistanceMetric(), placement, c);
561                    if (dist > getDistanceMetric().getInstructorNoPreferenceLimit() && dist <= getDistanceMetric().getInstructorDiscouragedLimit())
562                        pref = Math.max(pref, Constants.sPreferenceLevelDiscouraged);
563                    if (dist > getDistanceMetric().getInstructorDiscouragedLimit()
564                            && (dist <= getDistanceMetric().getInstructorProhibitedLimit() || iIgnoreDistances))
565                        pref = Math.max(pref, Constants.sPreferenceLevelStronglyDiscouraged);
566                    if (!iIgnoreDistances && dist > getDistanceMetric().getInstructorProhibitedLimit())
567                        pref = Constants.sPreferenceLevelProhibited;
568                }
569            }
570            
571            if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) {
572                TimeLocation t1 = placement.getTimeLocation();
573                Placement before = null, after = null;
574                for (Lecture other: assignedVariables()) {
575                    if (other.getAssignment() == null || other.equals(placement.variable())) continue;
576                    TimeLocation t2 = other.getAssignment().getTimeLocation();
577                    if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue;
578                    if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) {
579                        int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), placement, other.getAssignment());
580                        if (distanceInMinutes > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength()))
581                            pref = Math.max(pref, (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited));
582                        else if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength()))
583                            pref = Math.max(pref, Constants.sPreferenceLevelDiscouraged);
584                    } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) {
585                        int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), placement, other.getAssignment());
586                        if (distanceInMinutes >  t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength()))
587                            pref = Math.max(pref, (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited));
588                        else if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength()))
589                            pref = Math.max(pref, Constants.sPreferenceLevelDiscouraged);
590                    }
591                    if (t1.getStartSlot() + t1.getLength() <= t2.getStartSlot()) {
592                        if (after == null || t2.getStartSlot() < after.getTimeLocation().getStartSlot())
593                            after = other.getAssignment();
594                    } else if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) {
595                        if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot())
596                            before = other.getAssignment();
597                    }
598                }
599                if (iUnavailabilities != null) {
600                    for (Placement c: iUnavailabilities) {
601                        TimeLocation t2 = c.getTimeLocation();
602                        if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue;
603                        if (t1.getStartSlot() + t1.getLength() <= t2.getStartSlot()) {
604                            if (after == null || t2.getStartSlot() < after.getTimeLocation().getStartSlot())
605                                after = c;
606                        } else if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) {
607                            if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot())
608                                before = c;
609                        }
610                    }
611                }
612                int tooLongTravel = 0;
613                if (before != null && Placement.getDistanceInMinutes(getDistanceMetric(), before, placement) > getDistanceMetric().getInstructorLongTravelInMinutes())
614                    tooLongTravel++;
615                if (after != null && Placement.getDistanceInMinutes(getDistanceMetric(), after, placement) > getDistanceMetric().getInstructorLongTravelInMinutes())
616                    tooLongTravel++;
617                if (tooLongTravel > 0)
618                    pref += Math.max(pref, Constants.sPreferenceLevelStronglyDiscouraged);
619            }
620        }
621        return pref;
622    }
623
624    /** Overall back-to-back preference of this instructor */
625    public int getPreference() {
626        /*
627         * if (iPreference!=countPreference()) {System.err.println(
628         * "InstructorConstraint.getPreference() is not working properly"); }
629         */
630        return iPreference;
631    }
632
633    public int countPreference() {
634        int pref = 0;
635        HashSet<Placement> checked = new HashSet<Placement>();
636        
637        for (int slot = 1; slot < iResource.length; slot++) {
638            if ((slot % Constants.SLOTS_PER_DAY) == 0) continue;
639            for (Placement placement : iResource[slot]) {
640                for (Placement c : getPlacements(slot - 1, placement)) {
641                    if (placement.variable().equals(c.variable()) || !checked.add(c)) continue;
642                    double dist = Placement.getDistanceInMeters(getDistanceMetric(), c, placement);
643                    if (dist > getDistanceMetric().getInstructorNoPreferenceLimit() && dist <= getDistanceMetric().getInstructorDiscouragedLimit())
644                        pref += Constants.sPreferenceLevelDiscouraged;
645                    if (dist > getDistanceMetric().getInstructorDiscouragedLimit())
646                        pref += Constants.sPreferenceLevelStronglyDiscouraged;
647                }
648            }
649        }
650        
651        if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) {
652            for (Lecture p1: assignedVariables()) {
653                TimeLocation t1 = (p1.getAssignment() == null ? null : p1.getAssignment().getTimeLocation());
654                if (t1 == null) continue;
655                Placement before = null;
656                for (Lecture p2: assignedVariables()) {
657                    if (p2.getAssignment() == null || p2.equals(p1)) continue;
658                    TimeLocation t2 = p2.getAssignment().getTimeLocation();
659                    if (t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue;
660                    if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) {
661                        int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), p1.getAssignment(), p2.getAssignment());
662                        if (distanceInMinutes >  t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength()))
663                            pref += (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited);
664                        else if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength()))
665                            pref += Constants.sPreferenceLevelDiscouraged;
666                    }
667                    if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) {
668                        if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot())
669                            before = p2.getAssignment();
670                    }
671                }
672                if (iUnavailabilities != null) {
673                    for (Placement c: iUnavailabilities) {
674                        TimeLocation t2 = c.getTimeLocation();
675                        if (t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue;
676                        if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) {
677                            if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot())
678                                before = c;
679                        }
680                    }
681                }
682                if (before != null && Placement.getDistanceInMinutes(getDistanceMetric(), before, p1.getAssignment()) > getDistanceMetric().getInstructorLongTravelInMinutes())
683                    pref += Constants.sPreferenceLevelStronglyDiscouraged;
684            }
685        }
686
687        return pref;
688    }
689
690    /** Worst back-to-back preference of this instructor */
691    public int getWorstPreference() {
692        return Constants.sPreferenceLevelStronglyDiscouraged * (variables().size() - 1);
693    }
694
695    public String getPuid() {
696        return iPuid;
697    }
698
699    public boolean isIgnoreDistances() {
700        return iIgnoreDistances;
701    }
702    
703    public void setIgnoreDistances(boolean ignDist) {
704        iIgnoreDistances = ignDist;
705    }
706
707    public Long getType() {
708        return iType;
709    }
710
711    public void setType(Long type) {
712        iType = type;
713    }
714}