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