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