    001package org.cpsolver.instructor.model;
    002
    003import java.util.ArrayList;
    004import java.util.HashSet;
    005import java.util.List;
    006import java.util.Set;
    007
    008import org.cpsolver.coursett.Constants;
    009import org.cpsolver.coursett.model.TimeLocation;
    010import org.cpsolver.coursett.preference.MinMaxPreferenceCombination;
    011import org.cpsolver.coursett.preference.PreferenceCombination;
    012import org.cpsolver.ifs.assignment.Assignment;
    013import org.cpsolver.ifs.assignment.context.AbstractClassWithContext;
    014import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
    015import org.cpsolver.ifs.assignment.context.CanInheritContext;
    016import org.cpsolver.ifs.criteria.Criterion;
    017import org.cpsolver.instructor.criteria.BackToBack;
    018import org.cpsolver.instructor.criteria.DifferentLecture;
    019import org.cpsolver.instructor.criteria.SameCommon;
    020import org.cpsolver.instructor.criteria.SameCourse;
    021import org.cpsolver.instructor.criteria.SameDays;
    022import org.cpsolver.instructor.criteria.SameRoom;
    023import org.cpsolver.instructor.criteria.TimeOverlaps;
    024import org.cpsolver.instructor.criteria.UnusedInstructorLoad;
    025
    026/**
    027 * Instructor. An instructor has an id, a name, a teaching preference, a maximal teaching load, a back-to-back preference.
    028 * It can also have a set of attributes and course and time preferences. Availability is modeled with prohibited time preferences.
    029 *
    030 * @version IFS 1.3 (Instructor Sectioning)<br>
    031 *          Copyright (C) 2016 Tomáš Müller<br>
    032 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    033 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    034 * <br>
    035 *          This library is free software; you can redistribute it and/or modify
    036 *          it under the terms of the GNU Lesser General Public License as
    037 *          published by the Free Software Foundation; either version 3 of the
    038 *          License, or (at your option) any later version. <br>
    039 * <br>
    040 *          This library is distributed in the hope that it will be useful, but
    041 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    042 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    043 *          Lesser General Public License for more details. <br>
    044 * <br>
    045 *          You should have received a copy of the GNU Lesser General Public
    046 *          License along with this library; if not see
    047 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    048 */
    049public class Instructor extends AbstractClassWithContext<TeachingRequest.Variable, TeachingAssignment, Instructor.Context> implements CanInheritContext<TeachingRequest.Variable, TeachingAssignment, Instructor.Context> {
    050    private List<Attribute> iAttributes = new ArrayList<Attribute>();
    051    private List<Preference<TimeLocation>> iTimePreferences = new ArrayList<Preference<TimeLocation>>();
    052    private List<Preference<Course>> iCoursePreferences = new ArrayList<Preference<Course>>();
    053    private InstructorSchedulingModel iModel;
    054    private long iInstructorId;
    055    private String iExternalId;
    056    private String iName;
    057    private int iPreference;
    058    private float iMaxLoad;
    059    private int iBackToBackPreference, iSameDaysPreference, iSameRoomPreference;
    060
    061    /**
    062     * Constructor
    063     * @param id instructor unique id
    064     * @param externalId instructor external id
    065     * @param name instructor name
    066     * @param preference teaching preference
    067     * @param maxLoad maximal teaching load
    068     */
    069    public Instructor(long id, String externalId, String name, int preference, float maxLoad) {
    070        iInstructorId = id; iExternalId = externalId; iName = name; iPreference = preference; iMaxLoad = maxLoad;
    071    }
    072
    073    @Override
    074    public InstructorSchedulingModel getModel() { return iModel; }
    075
    076    /**
    077     * Set current model
    078     * @param model instructional scheduling model
    079     */
    080    public void setModel(InstructorSchedulingModel model) { iModel = model; }
    081
    082    /**
    083     * Instructor unique id that was provided in the constructor
    084     * @return instructor unique id
    085     */
    086    public long getInstructorId() { return iInstructorId; }
    087
    088    /**
    089     * Has instructor external id?
    090     * @return true, if the instructor has an external id set
    091     */
    092    public boolean hasExternalId() { return iExternalId != null && !iExternalId.isEmpty(); }
    093
    094    /**
    095     * Instructor external Id that was provided in the constructor
    096     * @return external id
    097     */
    098    public String getExternalId() { return iExternalId; }
    099
    100    /**
    101     * Has instructor name?
    102     * @return true, if the instructor name is set
    103     */
    104    public boolean hasName() { return iName != null && !iName.isEmpty(); }
    105
    106    /**
    107     * Instructor name that was provided in the constructor
    108     * @return instructor name
    109     */
    110    public String getName() { return iName != null ? iName : iExternalId != null ? iExternalId : ("I" + iInstructorId); }
    111
    112    /**
    113     * Set back-to-back preference (only soft preference can be set at the moment)
    114     * @param backToBackPreference back-to-back preference (e.g., -1 for preferred, 1 for discouraged)
    115     */
    116    public void setBackToBackPreference(int backToBackPreference) { iBackToBackPreference = backToBackPreference; }
    117
    118    /**
    119     * Return back-to-back preference (only soft preference can be set at the moment)
    120     * @return back-to-back preference (e.g., -1 for preferred, 1 for discouraged)
    121     */
    122    public int getBackToBackPreference() { return iBackToBackPreference; }
    123
    124    /**
    125     * Is back-to-back preferred?
    126     * @return true if the back-to-back preference is negative
    127     */
    128    public boolean isBackToBackPreferred() { return iBackToBackPreference < 0; }
    129
    130    /**
    131     * Is back-to-back discouraged?
    132     * @return true if the back-to-back preference is positive
    133     */
    134    public boolean isBackToBackDiscouraged() { return iBackToBackPreference > 0; }
    135
    136    /**
    137     * Set same-days preference (only soft preference can be set at the moment)
    138     * @param sameDaysPreference same-days preference (e.g., -1 for preferred, 1 for discouraged)
    139     */
    140    public void setSameDaysPreference(int sameDaysPreference) { iSameDaysPreference = sameDaysPreference; }
    141
    142    /**
    143     * Return same-days preference (only soft preference can be set at the moment)
    144     * @return same-days preference (e.g., -1 for preferred, 1 for discouraged)
    145     */
    146    public int getSameDaysPreference() { return iSameDaysPreference; }
    147
    148    /**
    149     * Is same-days preferred?
    150     * @return true if the same-days preference is negative
    151     */
    152    public boolean isSameDaysPreferred() { return iSameDaysPreference < 0; }
    153
    154    /**
    155     * Is same-days discouraged?
    156     * @return true if the same-days preference is positive
    157     */
    158    public boolean isSameDaysDiscouraged() { return iSameDaysPreference > 0; }
    159
    160    /**
    161     * Set same-room preference (only soft preference can be set at the moment)
    162     * @param sameRoomPreference same-room preference (e.g., -1 for preferred, 1 for discouraged)
    163     */
    164    public void setSameRoomPreference(int sameRoomPreference) { iSameRoomPreference = sameRoomPreference; }
    165
    166    /**
    167     * Return same-room preference (only soft preference can be set at the moment)
    168     * @return same-room preference (e.g., -1 for preferred, 1 for discouraged)
    169     */
    170    public int getSameRoomPreference() { return iSameRoomPreference; }
    171
    172    /**
    173     * Is same-room preferred?
    174     * @return true if the same-room preference is negative
    175     */
    176    public boolean isSameRoomPreferred() { return iSameRoomPreference < 0; }
    177
    178    /**
    179     * Is same-room discouraged?
    180     * @return true if the same-room preference is positive
    181     */
    182    public boolean isSameRoomDiscouraged() { return iSameRoomPreference > 0; }
    183
    184    /**
    185     * Instructor unavailability string generated from prohibited time preferences
    186     * @return comma separated list of times during which the instructor is not available
    187     */
    188    public String getAvailable() {
    189        if (iTimePreferences == null) return "";
    190        String ret = "";
    191        for (Preference<TimeLocation> tl: iTimePreferences) {
    192            if (tl.isProhibited()) {
    193                if (!ret.isEmpty()) ret += ", ";
    194                ret += tl.getTarget().getLongName(true).trim();
    195            }
    196        }
    197        return ret.isEmpty() ? "" : ret;
    198    }
    199
    200    /**
    201     * Return instructor attributes
    202     * @return list of instructor attributes
    203     */
    204    public List<Attribute> getAttributes() { return iAttributes; }
    205
    206    /**
    207     * Add instructor attribute
    208     * @param attribute instructor attribute
    209     */
    210    public void addAttribute(Attribute attribute) { iAttributes.add(attribute); }
    211
    212    /**
    213     * Return instructor attributes of given type
    214     * @param type attribute type
    215     * @return attributes of this instructor that are of the given type
    216     */
    217    public Set<Attribute> getAttributes(Attribute.Type type) {
    218        Set<Attribute> attributes = new HashSet<Attribute>();
    219        for (Attribute attribute: iAttributes) {
    220            if (type.equals(attribute.getType())) attributes.add(attribute);
    221            Attribute parent = attribute.getParentAttribute();
    222            while (parent != null) {
    223                if (type.equals(parent.getType())) attributes.add(parent);
    224                parent = parent.getParentAttribute();
    225            }
    226        }
    227        return attributes;
    228    }
    229
    230    /**
    231     * Return instructor preferences
    232     * @return list of instructor time preferences
    233     */
    234    public List<Preference<TimeLocation>> getTimePreferences() { return iTimePreferences; }
    235
    236    /**
    237     * Add instructor time preference
    238     * @param pref instructor time preference
    239     */
    240    public void addTimePreference(Preference<TimeLocation> pref) { iTimePreferences.add(pref); }
    241
    242    /**
    243     * Compute time preference for a given time. This is using the {@link MinMaxPreferenceCombination} for all time preferences that are overlapping with the given time.
    244     * @param time given time
    245     * @return computed preference for the given time
    246     */
    247    public PreferenceCombination getTimePreference(TimeLocation time) {
    248        if (iTimePreferences.isEmpty()) return null;
    249        PreferenceCombination comb = new MinMaxPreferenceCombination();
    250        for (Preference<TimeLocation> pref: iTimePreferences)
    251            if (pref.getTarget().hasIntersection(time))
    252                comb.addPreferenceInt(pref.getPreference());
    253        return comb;
    254    }
    255
    256    /**
    257     * Compute time preference for a given teaching request. This is using the {@link MinMaxPreferenceCombination} for all time preferences that are overlapping with the given teaching request.
    258     * When a section that allows for overlaps (see {@link Section#isAllowOverlap()}) overlap with a prohibited time preference, this is only counted as strongly discouraged.
    259     * @param request teaching request that is being considered
    260     * @return computed time preference
    261     */
    262    public PreferenceCombination getTimePreference(TeachingRequest request) {
    263        PreferenceCombination comb = new MinMaxPreferenceCombination();
    264        for (Preference<TimeLocation> pref: iTimePreferences)
    265            for (Section section: request.getSections())
    266                if (section.hasTime() && section.getTime().hasIntersection(pref.getTarget())) {
    267                    if (section.isAllowOverlap() && pref.isProhibited())
    268                        comb.addPreferenceInt(Constants.sPreferenceLevelStronglyDiscouraged);
    269                    else
    270                        comb.addPreferenceInt(pref.getPreference());
    271                }
    272        return comb;
    273    }
    274
    275    /**
    276     * Return course preferences
    277     * @return list of instructor course preferences
    278     */
    279    public List<Preference<Course>> getCoursePreferences() { return iCoursePreferences; }
    280
    281    /**
    282     * Add course preference
    283     * @param pref instructor course preference
    284     */
    285    public void addCoursePreference(Preference<Course> pref) { iCoursePreferences.add(pref); }
    286
    287    /**
    288     * Return preference for the given course
    289     * @param course course that is being considered
    290     * @return course preference for the given course
    291     */
    292    public Preference<Course> getCoursePreference(Course course) {
    293        boolean hasRequired = false;
    294        for (Preference<Course> pref: iCoursePreferences)
    295            if (pref.isRequired()) { hasRequired = true; break; }
    296        for (Preference<Course> pref: iCoursePreferences)
    297            if (pref.getTarget().equals(course)) {
    298                if (hasRequired && !pref.isRequired()) continue;
    299                return pref;
    300            }
    301        if (hasRequired)
    302            return new Preference<Course>(course, Constants.sPreferenceLevelProhibited);
    303        return new Preference<Course>(course, Constants.sPreferenceLevelNeutral);
    304    }
    305
    306    /**
    307     * Return teaching preference
    308     * @return teaching preference of this instructor
    309     */
    310    public int getPreference() { return iPreference; }
    311
    312    /**
    313     * Set teaching preference
    314     * @param preference teaching preference of this instructor
    315     */
    316    public void setPreference(int preference) { iPreference = preference; }
    317
    318    /**
    319     * Maximal load
    320     * @return maximal load of this instructor
    321     */
    322    public float getMaxLoad() { return iMaxLoad; }
    323
    324    /**
    325     * Check if this instructor can teach the given request. This means that the given request is below the maximal teaching load,
    326     * the instructor is available (time preference is not prohibited), the instructor does not prohibit the course (there is no
    327     * prohibited course preference for the given course), and the request's instructor preference is also not prohibited.
    328     * So, the only thing that is not checked are the attribute preferences.
    329     * @param request teaching request that is being considered
    330     * @return true, if the instructor can be assigned to the given teaching request
    331     */
    332    public boolean canTeach(TeachingRequest request) {
    333        if (request.getLoad() > getMaxLoad())
    334            return false;
    335        if (getTimePreference(request).isProhibited())
    336            return false;
    337        if (getCoursePreference(request.getCourse()).isProhibited())
    338            return false;
    339        if (request.getInstructorPreference(this).isProhibited())
    340            return false;
    341        return true;
    342    }
    343
    344    @Override
    345    public int hashCode() {
    346        return new Long(iInstructorId).hashCode();
    347    }
    348
    349    @Override
    350    public boolean equals(Object o) {
    351        if (o == null || !(o instanceof Instructor)) return false;
    352        Instructor i = (Instructor)o;
    353        return getInstructorId() == i.getInstructorId();
    354    }
    355
    356    /**
    357     * Compute time overlaps with instructor availability
    358     * @param request teaching request that is being considered
    359     * @return number of slots during which the instructor has a prohibited time preferences that are overlapping with a section of the request that is allowing for overlaps
    360     */
    361    public int share(TeachingRequest request) {
    362        int share = 0;
    363        for (Section section: request.getSections()) {
    364            if (!section.hasTime() || !section.isAllowOverlap()) continue;
    365            for (Preference<TimeLocation> pref: iTimePreferences)
    366                if (pref.isProhibited() && section.getTime().shareWeeks(pref.getTarget()))
    367                    share += section.getTime().nrSharedDays(pref.getTarget()) * section.getTime().nrSharedHours(pref.getTarget());
    368        }
    369        return share;
    370    }
    371
    372    /**
    373     * Compute time overlaps with instructor availability and other teaching assignments of the instructor
    374     * @param assignment current assignment
    375     * @param value teaching assignment that is being considered
    376     * @return number of overlapping time slots (of the requested assignment) during which the overlaps are allowed
    377     */
    378    public int share(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value) {
    379        int share = 0;
    380        if (value.getInstructor().equals(this)) {
    381            for (TeachingAssignment other : value.getInstructor().getContext(assignment).getAssignments()) {
    382                if (other.variable().equals(value.variable()))
    383                    continue;
    384                share += value.variable().getRequest().share(other.variable().getRequest());
    385            }
    386            share += share(value.variable().getRequest());
    387        }
    388        return share;
    389    }
    390
    391    /**
    392     * Compute different common sections of the given teaching assignment and the other assignments of the instructor
    393     * @param assignment current assignment
    394     * @param value teaching assignment that is being considered
    395     * @return average {@link TeachingRequest#nrSameLectures(TeachingRequest)} between the given and the other existing assignments of the instructor
    396     */
    397    public double differentLectures(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value) {
    398        double same = 0; int count = 0;
    399        if (value.getInstructor().equals(this)) {
    400            for (TeachingAssignment other : value.getInstructor().getContext(assignment).getAssignments()) {
    401                if (other.variable().equals(value.variable()))
    402                    continue;
    403                same += value.variable().getRequest().nrSameLectures(other.variable().getRequest());
    404                count ++;
    405            }
    406        }
    407        return (count == 0 ? 0.0 : (count - same) / count);
    408    }
    409
    410    /**
    411     * Compute number of back-to-back assignments (weighted by the preference) of the given teaching assignment and the other assignments of the instructor
    412     * @param assignment current assignment
    413     * @param value teaching assignment that is being considered
    414     * @param diffRoomWeight different room penalty
    415     * @param diffTypeWeight different instructional type penalty
    416     * @return weighted back-to-back preference, using {@link TeachingRequest#countBackToBacks(TeachingRequest, double, double)}
    417     */
    418    public double countBackToBacks(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value, double diffRoomWeight, double diffTypeWeight) {
    419        double b2b = 0.0;
    420        if (value.getInstructor().equals(this) && getBackToBackPreference() != 0) {
    421            for (TeachingAssignment other : value.getInstructor().getContext(assignment).getAssignments()) {
    422                if (other.variable().equals(value.variable()))
    423                    continue;
    424                if (getBackToBackPreference() < 0) { // preferred
    425                    b2b += (value.variable().getRequest().countBackToBacks(other.variable().getRequest(), diffRoomWeight, diffTypeWeight) - 1.0) * getBackToBackPreference();
    426                } else {
    427                    b2b += value.variable().getRequest().countBackToBacks(other.variable().getRequest(), diffRoomWeight, diffTypeWeight) * getBackToBackPreference();
    428                }
    429            }
    430        }
    431        return b2b;
    432    }
    433
    434    /**
    435     * Compute number of same days assignments (weighted by the preference) of the given teaching assignment and the other assignments of the instructor
    436     * @param assignment current assignment
    437     * @param value teaching assignment that is being considered
    438     * @param diffRoomWeight different room penalty
    439     * @param diffTypeWeight different instructional type penalty
    440     * @return weighted same days preference, using {@link TeachingRequest#countSameDays(TeachingRequest, double, double)}
    441     */
    442    public double countSameDays(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value, double diffRoomWeight, double diffTypeWeight) {
    443        double sd = 0.0;
    444        if (value.getInstructor().equals(this) && getSameDaysPreference() != 0) {
    445            for (TeachingAssignment other : value.getInstructor().getContext(assignment).getAssignments()) {
    446                if (other.variable().equals(value.variable()))
    447                    continue;
    448                if (getSameDaysPreference() < 0) { // preferred
    449                    sd += (value.variable().getRequest().countSameDays(other.variable().getRequest(), diffRoomWeight, diffTypeWeight) - 1.0) * getSameDaysPreference();
    450                } else {
    451                    sd += value.variable().getRequest().countSameDays(other.variable().getRequest(), diffRoomWeight, diffTypeWeight) * getSameDaysPreference();
    452                }
    453            }
    454        }
    455        return sd;
    456    }
    457
    458    /**
    459     * Compute number of same room assignments (weighted by the preference) of the given teaching assignment and the other assignments of the instructor
    460     * @param assignment current assignment
    461     * @param value teaching assignment that is being considered
    462     * @param diffTypeWeight different instructional type penalty
    463     * @return weighted same room preference, using {@link TeachingRequest#countSameRooms(TeachingRequest, double)}
    464     */
    465    public double countSameRooms(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value, double diffTypeWeight) {
    466        double sd = 0.0;
    467        if (value.getInstructor().equals(this) && getSameRoomPreference() != 0) {
    468            for (TeachingAssignment other : value.getInstructor().getContext(assignment).getAssignments()) {
    469                if (other.variable().equals(value.variable()))
    470                    continue;
    471                if (getSameRoomPreference() < 0) { // preferred
    472                    sd += (value.variable().getRequest().countSameRooms(other.variable().getRequest(), diffTypeWeight) - 1.0) * getSameRoomPreference();
    473                } else {
    474                    sd += value.variable().getRequest().countSameRooms(other.variable().getRequest(), diffTypeWeight) * getSameRoomPreference();
    475                }
    476            }
    477        }
    478        return sd;
    479    }
    480
    481    @Override
    482    public String toString() {
    483        return getName();
    484    }
    485
    486    @Override
    487    public Context createAssignmentContext(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
    488        return new Context(assignment);
    489    }
    490
    491
    492    @Override
    493    public Context inheritAssignmentContext(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, Context parentContext) {
    494        return new Context(assignment, parentContext);
    495    }
    496
    497
    498    /**
    499     * Instructor Constraint Context. It keeps the list of current assignments of an instructor.
    500     */
    501    public class Context implements AssignmentConstraintContext<TeachingRequest.Variable, TeachingAssignment> {
    502        private HashSet<TeachingAssignment> iAssignments = new HashSet<TeachingAssignment>();
    503        private int iTimeOverlaps;
    504        private double iBackToBacks, iSameDays, iSameRooms;
    505        private double iDifferentLectures;
    506        private double iUnusedLoad;
    507        private double iSameCoursePenalty, iSameCommonPenalty;
    508
    509        /**
    510         * Constructor
    511         * @param assignment current assignment
    512         */
    513        public Context(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
    514            for (TeachingRequest.Variable request: getModel().variables()) {
    515                TeachingAssignment value = assignment.getValue(request);
    516                if (value != null && value.getInstructor().equals(getInstructor()))
    517                    iAssignments.add(value);
    518            }
    519            if (!iAssignments.isEmpty())
    520                updateCriteria(assignment);
    521        }
    522
    523        /**
    524         * Constructor
    525         * @param assignment current assignment
    526         * @param parentContext parent context
    527         */
    528        public Context(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, Context parentContext) {
    529            iAssignments = new HashSet<TeachingAssignment>(parentContext.getAssignments());
    530            if (!iAssignments.isEmpty())
    531                updateCriteria(assignment);
    532        }
    533
    534        /**
    535         * Instructor
    536         * @return instructor of this context
    537         */
    538        public Instructor getInstructor() { return Instructor.this; }
    539
    540        @Override
    541        public void assigned(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value) {
    542            if (value.getInstructor().equals(getInstructor())) {
    543                iAssignments.add(value);
    544                updateCriteria(assignment);
    545            }
    546        }
    547
    548        @Override
    549        public void unassigned(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, TeachingAssignment value) {
    550            if (value.getInstructor().equals(getInstructor())) {
    551                iAssignments.remove(value);
    552                updateCriteria(assignment);
    553            }
    554        }
    555
    556        /**
    557         * Update optimization criteria
    558         * @param assignment current assignment
    559         */
    560        private void updateCriteria(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
    561            // update back-to-backs
    562            BackToBack b2b = (BackToBack)getModel().getCriterion(BackToBack.class);
    563            if (b2b != null) {
    564                b2b.inc(assignment, -iBackToBacks);
    565                iBackToBacks = countBackToBackPreference(b2b.getDifferentRoomWeight(), b2b.getDifferentTypeWeight());
    566                b2b.inc(assignment, iBackToBacks);
    567            }
    568
    569            // update same-days
    570            SameDays sd = (SameDays)getModel().getCriterion(SameDays.class);
    571            if (sd != null) {
    572                sd.inc(assignment, -iSameDays);
    573                iSameDays = countSameDaysPreference(sd.getDifferentRoomWeight(), sd.getDifferentTypeWeight());
    574                sd.inc(assignment, iSameDays);
    575            }
    576
    577            // update same-days
    578            SameRoom sr = (SameRoom)getModel().getCriterion(SameRoom.class);
    579            if (sr != null) {
    580                sr.inc(assignment, -iSameRooms);
    581                iSameRooms = countSameRoomPreference(sd.getDifferentTypeWeight());
    582                sr.inc(assignment, iSameRooms);
    583            }
    584
    585            // update time overlaps
    586            Criterion<TeachingRequest.Variable, TeachingAssignment> overlaps = getModel().getCriterion(TimeOverlaps.class);
    587            if (overlaps != null) {
    588                overlaps.inc(assignment, -iTimeOverlaps);
    589                iTimeOverlaps = countTimeOverlaps();
    590                overlaps.inc(assignment, iTimeOverlaps);
    591            }
    592
    593            // update same lectures
    594            Criterion<TeachingRequest.Variable, TeachingAssignment> diff = getModel().getCriterion(DifferentLecture.class);
    595            if (diff != null) {
    596                diff.inc(assignment, -iDifferentLectures);
    597                iDifferentLectures = countDifferentLectures();
    598                diff.inc(assignment, iDifferentLectures);
    599            }
    600
    601            // update unused instructor load
    602            Criterion<TeachingRequest.Variable, TeachingAssignment> unused = getModel().getCriterion(UnusedInstructorLoad.class);
    603            if (unused != null) {
    604                unused.inc(assignment, -iUnusedLoad);
    605                iUnusedLoad = getUnusedLoad();
    606                unused.inc(assignment, iUnusedLoad);
    607            }
    608
    609            // same course penalty
    610            Criterion<TeachingRequest.Variable, TeachingAssignment> sameCourse = getModel().getCriterion(SameCourse.class);
    611            if (sameCourse != null) {
    612                sameCourse.inc(assignment, -iSameCoursePenalty);
    613                iSameCoursePenalty = countSameCoursePenalty();
    614                sameCourse.inc(assignment, iSameCoursePenalty);
    615            }
    616
    617            // same common penalty
    618            Criterion<TeachingRequest.Variable, TeachingAssignment> sameCommon = getModel().getCriterion(SameCommon.class);
    619            if (sameCommon != null) {
    620                sameCommon.inc(assignment, -iSameCommonPenalty);
    621                iSameCommonPenalty = countSameCommonPenalty();
    622                sameCommon.inc(assignment, iSameCommonPenalty);
    623            }
    624        }
    625
    626        /**
    627         * Current assignments of this instructor
    628         * @return current teaching assignments
    629         */
    630        public Set<TeachingAssignment> getAssignments() { return iAssignments; }
    631
    632        /**
    633         * Current load of this instructor
    634         * @return current load
    635         */
    636        public float getLoad() {
    637            float load = 0;
    638            for (TeachingAssignment assignment : iAssignments)
    639                load += assignment.variable().getRequest().getLoad();
    640            return load;
    641        }
    642
    643        /**
    644         * Current unused load of this instructor
    645         * @return zero if the instructor is not being used, difference between {@link Instructor#getMaxLoad()} and {@link Context#getLoad()} otherwise
    646         */
    647        public float getUnusedLoad() {
    648            return (iAssignments.isEmpty() ? 0f : getInstructor().getMaxLoad() - getLoad());
    649        }
    650
    651        /**
    652         * If there are classes that allow for overlap, the number of such overlapping slots of this instructor
    653         * @return current time overlaps (number of overlapping slots)
    654         */
    655        public int countTimeOverlaps() {
    656            int share = 0;
    657            for (TeachingAssignment a1 : iAssignments) {
    658                for (TeachingAssignment a2 : iAssignments) {
    659                    if (a1.getId() < a2.getId())
    660                        share += a1.variable().getRequest().share(a2.variable().getRequest());
    661                }
    662                share += getInstructor().share(a1.variable().getRequest());
    663            }
    664            return share;
    665        }
    666
    667        /**
    668         * Number of teaching assignments that have a time assignment of this instructor
    669         * @return current number of teaching assignments that have a time
    670         */
    671        public int countAssignmentsWithTime() {
    672            int ret = 0;
    673            a1: for (TeachingAssignment a1 : iAssignments) {
    674                for (Section s1: a1.variable().getSections())
    675                    if (s1.hasTime()) {
    676                        ret++; continue a1;
    677                    }
    678            }
    679            return ret;
    680        }
    681
    682        /**
    683         * Percentage of common sections that are not same for the instructor (using {@link TeachingRequest#nrSameLectures(TeachingRequest)})
    684         * @return percentage of pairs of common sections that are not the same
    685         */
    686        public double countDifferentLectures() {
    687            double same = 0;
    688            int pairs = 0;
    689            for (TeachingAssignment a1 : iAssignments) {
    690                for (TeachingAssignment a2 : iAssignments) {
    691                    if (a1.getId() < a2.getId()) {
    692                        same += a1.variable().getRequest().nrSameLectures(a2.variable().getRequest());
    693                        pairs++;
    694                    }
    695                }
    696            }
    697            return (pairs == 0 ? 0.0 : (pairs - same) / pairs);
    698        }
    699
    700        /**
    701         * Current back-to-back preference of the instructor (using {@link TeachingRequest#countBackToBacks(TeachingRequest, double, double)})
    702         * @param diffRoomWeight different room weight
    703         * @param diffTypeWeight different instructional type weight
    704         * @return current back-to-back preference
    705         */
    706        public double countBackToBackPreference(double diffRoomWeight, double diffTypeWeight) {
    707            double b2b = 0;
    708            if (getInstructor().isBackToBackPreferred() || getInstructor().isBackToBackDiscouraged())
    709                for (TeachingAssignment a1 : iAssignments) {
    710                    for (TeachingAssignment a2 : iAssignments) {
    711                        if (a1.getId() >= a2.getId()) continue;
    712                        if (getInstructor().getBackToBackPreference() < 0) { // preferred
    713                            b2b += (a1.variable().getRequest().countBackToBacks(a2.variable().getRequest(), diffRoomWeight, diffTypeWeight) - 1.0) * getInstructor().getBackToBackPreference();
    714                        } else {
    715                            b2b += a1.variable().getRequest().countBackToBacks(a2.variable().getRequest(), diffRoomWeight, diffTypeWeight) * getInstructor().getBackToBackPreference();
    716                        }
    717                    }
    718                }
    719            return b2b;
    720        }
    721
    722        /**
    723         * Current back-to-back percentage for this instructor
    724         * @return percentage of assignments that are back-to-back
    725         */
    726        public double countBackToBackPercentage() {
    727            BackToBack c = (BackToBack)getModel().getCriterion(BackToBack.class);
    728            if (c == null) return 0.0;
    729            double b2b = 0.0;
    730            int pairs = 0;
    731            for (TeachingAssignment a1 : iAssignments) {
    732                for (TeachingAssignment a2 : iAssignments) {
    733                    if (a1.getId() >= a2.getId()) continue;
    734                    b2b += a1.variable().getRequest().countBackToBacks(a2.variable().getRequest(), c.getDifferentRoomWeight(), c.getDifferentTypeWeight());
    735                    pairs ++;
    736                }
    737            }
    738            return (pairs == 0 ? 0.0 : b2b / pairs);
    739        }
    740
    741        /**
    742         * Current same days preference of the instructor (using {@link TeachingRequest#countSameDays(TeachingRequest, double, double)})
    743         * @param diffRoomWeight different room weight
    744         * @param diffTypeWeight different instructional type weight
    745         * @return current same days preference
    746         */
    747        public double countSameDaysPreference(double diffRoomWeight, double diffTypeWeight) {
    748            double sd = 0;
    749            if (getInstructor().isSameDaysPreferred() || getInstructor().isSameDaysDiscouraged())
    750                for (TeachingAssignment a1 : iAssignments) {
    751                    for (TeachingAssignment a2 : iAssignments) {
    752                        if (a1.getId() >= a2.getId()) continue;
    753                        if (getInstructor().getSameDaysPreference() < 0) { // preferred
    754                            sd += (a1.variable().getRequest().countSameDays(a2.variable().getRequest(), diffRoomWeight, diffTypeWeight) - 1.0) * getInstructor().getSameDaysPreference();
    755                        } else {
    756                            sd += a1.variable().getRequest().countSameDays(a2.variable().getRequest(), diffRoomWeight, diffTypeWeight) * getInstructor().getSameDaysPreference();
    757                        }
    758                    }
    759                }
    760            return sd;
    761        }
    762
    763        /**
    764         * Current same days percentage for this instructor
    765         * @return percentage of assignments that are back-to-back
    766         */
    767        public double countSameDaysPercentage() {
    768            SameDays c = (SameDays)getModel().getCriterion(SameDays.class);
    769            if (c == null) return 0.0;
    770            double sd = 0.0;
    771            int pairs = 0;
    772            for (TeachingAssignment a1 : iAssignments) {
    773                for (TeachingAssignment a2 : iAssignments) {
    774                    if (a1.getId() >= a2.getId()) continue;
    775                    sd += a1.variable().getRequest().countSameDays(a2.variable().getRequest(), c.getDifferentRoomWeight(), c.getDifferentTypeWeight());
    776                    pairs ++;
    777                }
    778            }
    779            return (pairs == 0 ? 0.0 : sd / pairs);
    780        }
    781
    782        /**
    783         * Current same room preference of the instructor (using {@link TeachingRequest#countSameRooms(TeachingRequest, double)})
    784         * @param diffTypeWeight different instructional type weight
    785         * @return current same room preference
    786         */
    787        public double countSameRoomPreference(double diffTypeWeight) {
    788            double sd = 0;
    789            if (getInstructor().isSameRoomPreferred() || getInstructor().isSameRoomDiscouraged())
    790                for (TeachingAssignment a1 : iAssignments) {
    791                    for (TeachingAssignment a2 : iAssignments) {
    792                        if (a1.getId() >= a2.getId()) continue;
    793                        if (getInstructor().getSameRoomPreference() < 0) { // preferred
    794                            sd += (a1.variable().getRequest().countSameRooms(a2.variable().getRequest(), diffTypeWeight) - 1.0) * getInstructor().getSameRoomPreference();
    795                        } else {
    796                            sd += a1.variable().getRequest().countSameRooms(a2.variable().getRequest(), diffTypeWeight) * getInstructor().getSameRoomPreference();
    797                        }
    798                    }
    799                }
    800            return sd;
    801        }
    802
    803        /**
    804         * Current same room percentage for this instructor
    805         * @return percentage of assignments that are back-to-back
    806         */
    807        public double countSameRoomPercentage() {
    808            SameRoom c = (SameRoom)getModel().getCriterion(SameDays.class);
    809            if (c == null) return 0.0;
    810            double sr = 0.0;
    811            int pairs = 0;
    812            for (TeachingAssignment a1 : iAssignments) {
    813                for (TeachingAssignment a2 : iAssignments) {
    814                    if (a1.getId() >= a2.getId()) continue;
    815                    sr += a1.variable().getRequest().countSameRooms(a2.variable().getRequest(), c.getDifferentTypeWeight());
    816                    pairs ++;
    817                }
    818            }
    819            return (pairs == 0 ? 0.0 : sr / pairs);
    820        }
    821
    822        /**
    823         * Compute same course penalty between all requests of this instructor
    824         * @return same course penalty
    825         */
    826        public double countSameCoursePenalty() {
    827            if (iAssignments.size() <= 1) return 0.0;
    828            double penalty = 0.0;
    829            for (TeachingAssignment a1 : iAssignments) {
    830                for (TeachingAssignment a2 : iAssignments) {
    831                    if (a1.getId() >= a2.getId()) continue;
    832                    penalty += a1.variable().getRequest().getSameCoursePenalty(a2.variable().getRequest());
    833                }
    834            }
    835            return penalty / (iAssignments.size() - 1);
    836        }
    837
    838        /**
    839         * Compute same common penalty between all requests of this instructor
    840         * @return same common penalty
    841         */
    842        public double countSameCommonPenalty() {
    843            if (iAssignments.size() <= 1) return 0.0;
    844            double penalty = 0.0;
    845            for (TeachingAssignment a1 : iAssignments) {
    846                for (TeachingAssignment a2 : iAssignments) {
    847                    if (a1.getId() >= a2.getId()) continue;
    848                    penalty += a1.variable().getRequest().getSameCommonPenalty(a2.variable().getRequest());
    849                }
    850            }
    851            return penalty / (iAssignments.size() - 1);
    852        }
    853    }
    854}