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