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