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