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