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}