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}