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}