001    package net.sf.cpsolver.coursett.constraint;
002    
003    import java.util.ArrayList;
004    import java.util.BitSet;
005    import java.util.Enumeration;
006    import java.util.HashSet;
007    import java.util.List;
008    import java.util.Set;
009    
010    import net.sf.cpsolver.coursett.Constants;
011    import net.sf.cpsolver.coursett.criteria.BackToBackInstructorPreferences;
012    import net.sf.cpsolver.coursett.model.Lecture;
013    import net.sf.cpsolver.coursett.model.Placement;
014    import net.sf.cpsolver.coursett.model.TimeLocation;
015    import net.sf.cpsolver.coursett.model.TimetableModel;
016    import net.sf.cpsolver.ifs.model.Constraint;
017    import 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 Tomas Muller<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    
063    public 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    }