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