    001package org.cpsolver.instructor.model;
    002
    003import java.util.ArrayList;
    004import java.util.Collection;
    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.PreferenceCombination;
    011import org.cpsolver.coursett.preference.SumPreferenceCombination;
    012import org.cpsolver.ifs.assignment.Assignment;
    013
    014/**
    015 * Teaching request. A set of sections of a course to be assigned to an instructor.
    016 * Each teaching request has a teaching load. The maximal teaching load of an instructor
    017 * cannot be breached.
    018 *
    019 * @version IFS 1.3 (Instructor Sectioning)<br>
    020 *          Copyright (C) 2016 Tomáš Müller<br>
    021 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    022 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    023 * <br>
    024 *          This library is free software; you can redistribute it and/or modify
    025 *          it under the terms of the GNU Lesser General Public License as
    026 *          published by the Free Software Foundation; either version 3 of the
    027 *          License, or (at your option) any later version. <br>
    028 * <br>
    029 *          This library is distributed in the hope that it will be useful, but
    030 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    031 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    032 *          Lesser General Public License for more details. <br>
    033 * <br>
    034 *          You should have received a copy of the GNU Lesser General Public
    035 *          License along with this library; if not see
    036 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    037 */
    038public class TeachingRequest {
    039    private long iRequestId;
    040    private Course iCourse;
    041    private float iLoad;
    042    private List<Section> iSections = new ArrayList<Section>();
    043    private List<Preference<Attribute>> iAttributePreferences = new ArrayList<Preference<Attribute>>();
    044    private List<Preference<Instructor>> iInstructorPreferences = new ArrayList<Preference<Instructor>>();
    045    private Variable[] iVariables;
    046    private int iSameCoursePreference, iSameCommonPreference;
    047
    048    /**
    049     * Constructor
    050     * @param requestId teaching request id
    051     * @param nrVariables number of instructors for this teaching request
    052     * @param course course
    053     * @param load teaching load
    054     * @param sections list of sections
    055     * @param sameCoursePreference same course preference
    056     * @param sameCommonPreference same common preference (two requests of the same course share the common part)
    057     */
    058    public TeachingRequest(long requestId, int nrVariables, Course course, float load, Collection<Section> sections, int sameCoursePreference, int sameCommonPreference) {
    059        super();
    060        iRequestId = requestId;
    061        iCourse = course;
    062        iLoad = load;
    063        iSections.addAll(sections);
    064        iVariables = new Variable[nrVariables];
    065        for (int i = 0; i < nrVariables; i++)
    066            iVariables[i] = new Variable(i);
    067        iSameCoursePreference = sameCoursePreference;
    068        iSameCommonPreference = sameCommonPreference;
    069    }
    070
    071    /**
    072     * Teaching request id that was provided in the constructor
    073     * @return request id
    074     */
    075    public long getRequestId() {
    076        return iRequestId;
    077    }
    078
    079
    080    /**
    081     * Preference of an instructor taking this request together with some other request of the same / different course.
    082     * @return same course preference
    083     */
    084    public int getSameCoursePreference() { return iSameCoursePreference; }
    085
    086    /**
    087     * Is same course required?
    088     * @return same course preference is required
    089     */
    090    public boolean isSameCourseRequired() {
    091        return Constants.sPreferenceRequired.equals(Constants.preferenceLevel2preference(iSameCoursePreference));
    092    }
    093
    094    /**
    095     * Is same course prohibited?
    096     * @return same course preference is prohibited
    097     */
    098    public boolean isSameCourseProhibited() {
    099        return Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(iSameCoursePreference));
    100    }
    101
    102    /**
    103     * Whether to ensure that multiple assignments given to the same instructor share the common part. If required, all assignments of this
    104     * course that are given to the same student must share the sections that are marked as common (see {@link Section#isCommon()}).
    105     * @return same common preference
    106     */
    107    public int getSameCommonPreference() { return iSameCommonPreference; }
    108
    109    /**
    110     * Is same common required?
    111     * @return same common preference is required
    112     */
    113    public boolean isSameCommonRequired() {
    114        return Constants.sPreferenceRequired.equals(Constants.preferenceLevel2preference(iSameCommonPreference));
    115    }
    116
    117    /**
    118     * Is same common prohibited?
    119     * @return same common preference is prohibited
    120     */
    121    public boolean isSameCommonProhibited() {
    122        return Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(iSameCommonPreference));
    123    }
    124
    125    /**
    126     * Get single instructor assignment variables
    127     * @return variables for this request
    128     */
    129    public Variable[] getVariables() {
    130        return iVariables;
    131    }
    132
    133    /**
    134     * Get single instructor assignment variable
    135     * @param index index of the variable
    136     * @return variable for the index-th instructor assignment
    137     */
    138    public Variable getVariable(int index) {
    139        return iVariables[index];
    140    }
    141
    142    /**
    143     * Get number of instructors needed
    144     * @return number of variables for this request
    145     */
    146    public int getNrInstructors() {
    147        return iVariables.length;
    148    }
    149
    150    /**
    151     * Return attribute preferences for this request
    152     * @return attribute preferences
    153     */
    154    public List<Preference<Attribute>> getAttributePreferences() { return iAttributePreferences; }
    155
    156    /**
    157     * Add attribute preference
    158     * @param pref attribute preference
    159     */
    160    public void addAttributePreference(Preference<Attribute> pref) { iAttributePreferences.add(pref); }
    161
    162    /**
    163     * Compute attribute preference for the given instructor and attribute type
    164     * @param instructor an instructor
    165     * @param type an attribute type
    166     * @return combined preference using {@link Attribute.Type#isConjunctive()} and {@link Attribute.Type#isRequired()} properties
    167     */
    168    public int getAttributePreference(Instructor instructor, Attribute.Type type) {
    169        Set<Attribute> attributes = instructor.getAttributes(type);
    170        boolean hasReq = false, hasPref = false, needReq = false, hasType = false;
    171        PreferenceCombination ret = new SumPreferenceCombination();
    172        for (Preference<Attribute> pref: iAttributePreferences) {
    173            if (!type.equals(pref.getTarget().getType())) continue;
    174            hasType = true;
    175            if (pref.isRequired()) needReq = true;
    176            if (attributes.contains(pref.getTarget())) {
    177                if (pref.isProhibited()) return Constants.sPreferenceLevelProhibited;
    178                else if (pref.isRequired()) hasReq = true;
    179                else ret.addPreferenceInt(pref.getPreference());
    180                hasPref = true;
    181            } else {
    182                if (pref.isRequired() && type.isConjunctive()) return Constants.sPreferenceLevelProhibited;
    183            }
    184        }
    185        if (needReq && !hasReq) return Constants.sPreferenceLevelProhibited;
    186        if (type.isRequired() && hasType && !hasPref) return Constants.sPreferenceLevelProhibited;
    187        if (!type.isRequired() && hasType && !hasPref) return 16;
    188        return ret.getPreferenceInt();
    189    }
    190
    191    /**
    192     * Compute attribute preference for the given instructor
    193     * @param instructor an instructor
    194     * @return using {@link SumPreferenceCombination} for the preferences of each attribute type (using {@link TeachingRequest#getAttributePreference(Instructor, org.cpsolver.instructor.model.Attribute.Type)})
    195     */
    196    public PreferenceCombination getAttributePreference(Instructor instructor) {
    197        PreferenceCombination preference = new SumPreferenceCombination();
    198        for (Attribute.Type type: ((InstructorSchedulingModel)getVariables()[0].getModel()).getAttributeTypes())
    199            preference.addPreferenceInt(getAttributePreference(instructor, type));
    200        return preference;
    201    }
    202
    203    /**
    204     * Return instructor preferences for this request
    205     * @return instructor preferences
    206     */
    207    public List<Preference<Instructor>> getInstructorPreferences() { return iInstructorPreferences; }
    208
    209    /**
    210     * Add instructor preference
    211     * @param pref instructor preference
    212     */
    213    public void addInstructorPreference(Preference<Instructor> pref) { iInstructorPreferences.add(pref); }
    214
    215    /**
    216     * Return instructor preference for the given instructor
    217     * @param instructor an instructor
    218     * @return instructor preference for the given instructor
    219     */
    220    public Preference<Instructor> getInstructorPreference(Instructor instructor) {
    221        boolean hasRequired = false;
    222        for (Preference<Instructor> pref: iInstructorPreferences)
    223            if (pref.isRequired()) { hasRequired = true; break; }
    224        for (Preference<Instructor> pref: iInstructorPreferences)
    225            if (pref.getTarget().equals(instructor)) {
    226                if (hasRequired && !pref.isRequired()) continue;
    227                return pref;
    228            }
    229        if (hasRequired)
    230            return new Preference<Instructor>(instructor, Constants.sPreferenceLevelProhibited);
    231        return new Preference<Instructor>(instructor, Constants.sPreferenceLevelNeutral);
    232    }
    233
    234    /**
    235     * Course of the request that was provided in the constructor
    236     * @return course of the request
    237     */
    238    public Course getCourse() {
    239        return iCourse;
    240    }
    241
    242    /**
    243     * Sections of the request that was provided in the constructor
    244     * @return sections of the request
    245     */
    246    public List<Section> getSections() { return iSections; }
    247
    248    /**
    249     * Return teaching load of the request
    250     * @return teaching load
    251     */
    252    public float getLoad() { return iLoad; }
    253
    254    /**
    255     * Set teaching load of the request
    256     * @param load teaching load
    257     */
    258    public void setLoad(float load) { iLoad = load; }
    259
    260    @Override
    261    public String toString() {
    262        return iCourse.getCourseName() + " " + getSections();
    263    }
    264
    265    /**
    266     * Check if the given request fully share the common sections with this request
    267     * @param request the other teaching request
    268     * @return true, if all common sections of this request are also present in the other request
    269     */
    270    public boolean sameCommon(TeachingRequest request) {
    271        for (Section section: getSections())
    272            if (section.isCommon() && !request.getSections().contains(section))
    273                return false;
    274        for (Section section: request.getSections())
    275            if (section.isCommon() && !getSections().contains(section))
    276                return false;
    277        return true;
    278    }
    279
    280    /**
    281     * Check if the given request (partially) share the common sections with this request
    282     * @param request the other teaching request
    283     * @return true, if there is at least one common section of this request that is also present in the other request
    284     */
    285    public boolean shareCommon(TeachingRequest request) {
    286        for (Section section: getSections())
    287            if (section.isCommon() && request.getSections().contains(section))
    288                return true;
    289        for (Section section: request.getSections())
    290            if (section.isCommon() && getSections().contains(section))
    291                return true;
    292        return false;
    293    }
    294
    295    /**
    296     * Check if this request and the given one can be assigned to the same instructor without violating the same common constraint
    297     * @param request the other teaching request
    298     * @return same common constraint is violated
    299     */
    300    public boolean isSameCommonViolated(TeachingRequest request) {
    301        if (!sameCourse(request)) return false;
    302        if ((isSameCommonRequired() || request.isSameCommonRequired()) && !sameCommon(request))
    303            return true;
    304        if ((isSameCommonProhibited() || request.isSameCommonProhibited()) && shareCommon(request))
    305            return true;
    306        return false;
    307    }
    308
    309    /**
    310     * Return same common penalty of this request and the given request being assigned to the same instructor
    311     * @param request the other teaching request
    312     * @return same common penalty between the two teaching requests
    313     */
    314    public double getSameCommonPenalty(TeachingRequest request) {
    315        if (!sameCourse(request)) return 0; // not applicable
    316        int penalty = 0;
    317        // preferred and same
    318        if ((getSameCommonPreference() < 0 || request.getSameCommonPreference() < 0) && sameCommon(request)) {
    319            penalty += (!isSameCommonRequired() && getSameCommonPreference() < 0 ? getSameCommonPreference() : 0)
    320                    + (!request.isSameCommonRequired() && request.getSameCommonPreference() < 0 ? request.getSameCommonPreference() : 0);
    321        }
    322        // discouraged and sharing common
    323        if ((getSameCommonPreference() > 0 || request.getSameCommonPreference() > 0) && shareCommon(request)) {
    324            penalty += (getSameCommonPreference() > 0 ? getSameCommonPreference() : 0) + (request.getSameCommonPreference() > 0 ? request.getSameCommonPreference() : 0);
    325        }
    326        return penalty;
    327    }
    328
    329    /**
    330     * Count the number of common sections that the given request share with this request
    331     * @param request the other teaching request
    332     * @return the number of shared common sections
    333     */
    334    public double nrSameLectures(TeachingRequest request) {
    335        if (!sameCourse(request)) return 0.0;
    336        double same = 0; int common = 0;
    337        for (Section section: getSections())
    338            if (section.isCommon()) {
    339                common ++;
    340                if (request.getSections().contains(section)) same++;
    341            }
    342        return (common == 0 ? 1.0 : same / common);
    343    }
    344
    345    /**
    346     * Check if this request and the given request are of the same course
    347     * @param request the other teaching request
    348     * @return true, if the course of the given request is the same as the course of this request
    349     */
    350    public boolean sameCourse(TeachingRequest request) {
    351        return getCourse().equals(request.getCourse());
    352    }
    353
    354    /**
    355     * Check if this request and the given one can be assigned to the same instructor without violating the same course constraint
    356     * @param request the other teaching request
    357     * @return same course constraint is violated
    358     */
    359    public boolean isSameCourseViolated(TeachingRequest request) {
    360        if (sameCourse(request)) { // same course
    361            return isSameCourseProhibited() || request.isSameCourseProhibited();
    362        } else { // not same course
    363            return isSameCourseRequired() || request.isSameCourseRequired();
    364        }
    365    }
    366
    367    /**
    368     * Return same course penalty of this request and the given request being assigned to the same instructor
    369     * @param request the other teaching request
    370     * @return same course penalty between the two teaching requests
    371     */
    372    public double getSameCoursePenalty(TeachingRequest request) {
    373        if (!sameCourse(request)) return 0;
    374        return (isSameCourseRequired() ? 0 : getSameCoursePreference()) + (request.isSameCourseRequired() ? 0 : request.getSameCoursePreference());
    375    }
    376
    377    /**
    378     * Check if this request overlaps with the given one
    379     * @param request the other teaching request
    380     * @return true, if there are two sections that are overlapping in time (that are not allowed to overlap)
    381     */
    382    public boolean overlaps(TeachingRequest request) {
    383        for (Section section: getSections()) {
    384            if (section.isAllowOverlap() || section.getTime() == null || request.getSections().contains(section)) continue;
    385            for (Section other: request.getSections()) {
    386                if (other.isAllowOverlap() || other.getTime() == null || getSections().contains(other)) continue;
    387                if (section.getTime().hasIntersection(other.getTime())) return true;
    388            }
    389        }
    390        return false;
    391    }
    392
    393    /**
    394     * Count the number of (allowed) overlapping time slots between this request and the given one
    395     * @param request the other teaching request
    396     * @return the number of overlapping time slots
    397     */
    398    public int share(TeachingRequest request) {
    399        int ret = 0;
    400        for (Section section: getSections())
    401            ret += section.share(request.getSections());
    402        return ret;
    403    }
    404
    405    /**
    406     * Count the number of overlapping time slots between this request and the given time
    407     * @param time a time
    408     * @return the number of overlapping time slots
    409     */
    410    public int share(TimeLocation time) {
    411        int ret = 0;
    412        for (Section section: getSections())
    413            ret += section.share(time);
    414        return ret;
    415    }
    416
    417    /**
    418     * Average value of the back-to-backs between this request and the given one
    419     * @param request the other teaching request
    420     * @param diffRoomWeight different room penalty
    421     * @param diffTypeWeight different instructional type penalty
    422     * @return average value of {@link Section#countBackToBacks(Collection, double, double)} between the two, common sections are ignored
    423     */
    424    public double countBackToBacks(TeachingRequest request, double diffRoomWeight, double diffTypeWeight) {
    425        double b2b = 0.0;
    426        int count = 0;
    427        for (Section section: getSections()) {
    428            if (!section.isCommon() || !sameCourse(request) || !request.getSections().contains(section)) {
    429                b2b += section.countBackToBacks(request.getSections(), diffRoomWeight, diffTypeWeight);
    430                count ++;
    431            }
    432        }
    433        return (count == 0 ? 0.0 : b2b / count);
    434    }
    435
    436    /**
    437     * Average value of the same days between this request and the given one
    438     * @param request the other teaching request
    439     * @param diffRoomWeight different room penalty
    440     * @param diffTypeWeight different instructional type penalty
    441     * @return average value of {@link Section#countSameDays(Collection, double, double)} between the two, common sections are ignored
    442     */
    443    public double countSameDays(TeachingRequest request, double diffRoomWeight, double diffTypeWeight) {
    444        double sd = 0.0;
    445        int count = 0;
    446        for (Section section: getSections()) {
    447            if (!section.isCommon() || !sameCourse(request) || !request.getSections().contains(section)) {
    448                sd += section.countSameDays(request.getSections(), diffRoomWeight, diffTypeWeight);
    449                count ++;
    450            }
    451        }
    452        return (count == 0 ? 0.0 : sd / count);
    453    }
    454
    455    /**
    456     * Average value of the same rooms between this request and the given one
    457     * @param request the other teaching request
    458     * @param diffTypeWeight different instructional type penalty
    459     * @return average value of {@link Section#countSameRooms(Collection, double)} between the two, common sections are ignored
    460     */
    461    public double countSameRooms(TeachingRequest request, double diffTypeWeight) {
    462        double sr = 0.0;
    463        int count = 0;
    464        for (Section section: getSections()) {
    465            if (!section.isCommon() || !sameCourse(request) || !request.getSections().contains(section)) {
    466                sr += section.countSameRooms(request.getSections(), diffTypeWeight);
    467                count ++;
    468            }
    469        }
    470        return (count == 0 ? 0.0 : sr / count);
    471    }
    472
    473    @Override
    474    public int hashCode() {
    475        return (int)(iRequestId ^ (iRequestId >>> 32));
    476    }
    477
    478    @Override
    479    public boolean equals(Object o) {
    480        if (o == null || !(o instanceof TeachingRequest)) return false;
    481        TeachingRequest tr = (TeachingRequest)o;
    482        return getRequestId() == tr.getRequestId();
    483    }
    484
    485    /**
    486     * Single instructor assignment to this teaching request
    487     */
    488    public class Variable extends org.cpsolver.ifs.model.Variable<TeachingRequest.Variable, TeachingAssignment> {
    489        private int iIndex;
    490
    491        /**
    492         * Constructor
    493         * @param index instructor index (if a class can be taught by multiple instructors, the index identifies the particular request)
    494         */
    495        public Variable(int index) {
    496            iId = (iRequestId << 8) + index;
    497            iIndex = index;
    498        }
    499
    500        /**
    501         * Instructor index that was provided in the constructor
    502         * @return instructor index
    503         */
    504        public int getInstructorIndex() {
    505            return iIndex;
    506        }
    507
    508        /**
    509         * Teaching request for this variable
    510         * @return teaching request
    511         */
    512        public TeachingRequest getRequest() {
    513            return TeachingRequest.this;
    514        }
    515
    516        /**
    517         * Course of the request that was provided in the constructor
    518         * @return course of the request
    519         */
    520        public Course getCourse() {
    521            return iCourse;
    522        }
    523
    524        /**
    525         * Sections of the request that was provided in the constructor
    526         * @return sections of the request
    527         */
    528        public List<Section> getSections() { return iSections; }
    529
    530        @Override
    531        public List<TeachingAssignment> values(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
    532            List<TeachingAssignment> values = super.values(assignment);
    533            if (values == null) {
    534                values = new ArrayList<TeachingAssignment>();
    535                for (Instructor instructor: ((InstructorSchedulingModel)getModel()).getInstructors()) {
    536                    if (instructor.canTeach(getRequest())) {
    537                        PreferenceCombination attributePref = getAttributePreference(instructor);
    538                        if (attributePref.isProhibited()) continue;
    539                        values.add(new TeachingAssignment(this, instructor, attributePref.getPreferenceInt()));
    540                    }
    541                }
    542                setValues(values);
    543            }
    544            return values;
    545        }
    546
    547        @Override
    548        public void variableAssigned(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, long iteration, TeachingAssignment ta) {
    549            super.variableAssigned(assignment, iteration, ta);
    550            ta.getInstructor().getContext(assignment).assigned(assignment, ta);
    551        }
    552
    553        @Override
    554        public void variableUnassigned(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, long iteration, TeachingAssignment ta) {
    555            super.variableUnassigned(assignment, iteration, ta);
    556            ta.getInstructor().getContext(assignment).unassigned(assignment, ta);
    557        }
    558
    559        @Override
    560        public int hashCode() {
    561            return new Long(iRequestId << 8 + iIndex).hashCode();
    562        }
    563
    564        @Override
    565        public boolean equals(Object o) {
    566            if (o == null || !(o instanceof Variable)) return false;
    567            Variable tr = (Variable)o;
    568            return getRequest().getRequestId() == tr.getRequest().getRequestId() && getInstructorIndex() == tr.getInstructorIndex();
    569        }
    570
    571        @Override
    572        public String getName() {
    573            return iCourse.getCourseName() + (getNrInstructors() > 1 ? "[" + getInstructorIndex() + "]" : "") + " " + getSections();
    574        }
    575    }
    576}