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}