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