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