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