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