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