    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}