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