001package org.cpsolver.studentsct.model; 002 003import java.text.DecimalFormat; 004import java.util.ArrayList; 005import java.util.HashMap; 006import java.util.HashSet; 007import java.util.List; 008import java.util.Map; 009import java.util.Set; 010 011import org.cpsolver.coursett.model.Placement; 012import org.cpsolver.coursett.model.RoomLocation; 013import org.cpsolver.coursett.model.TimeLocation; 014import org.cpsolver.ifs.assignment.Assignment; 015import org.cpsolver.ifs.assignment.AssignmentComparable; 016import org.cpsolver.ifs.assignment.context.AbstractClassWithContext; 017import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext; 018import org.cpsolver.ifs.assignment.context.CanInheritContext; 019import org.cpsolver.ifs.model.Model; 020import org.cpsolver.studentsct.reservation.Reservation; 021 022 023/** 024 * Representation of a class. Each section contains id, name, scheduling 025 * subpart, time/room placement, and a limit. Optionally, parent-child relation 026 * between sections can be defined. <br> 027 * <br> 028 * Each student requesting a course needs to be enrolled in a class of each 029 * subpart of a selected configuration. In the case of parent-child relation 030 * between classes, if a student is enrolled in a section that has a parent 031 * section defined, he/she has to be enrolled in the parent section as well. If 032 * there is a parent-child relation between two sections, the same relation is 033 * defined on their subparts as well, i.e., if section A is a parent section B, 034 * subpart of section A isa parent of subpart of section B. <br> 035 * <br> 036 * 037 * @version StudentSct 1.3 (Student Sectioning)<br> 038 * Copyright (C) 2007 - 2014 Tomas Muller<br> 039 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 040 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 041 * <br> 042 * This library is free software; you can redistribute it and/or modify 043 * it under the terms of the GNU Lesser General Public License as 044 * published by the Free Software Foundation; either version 3 of the 045 * License, or (at your option) any later version. <br> 046 * <br> 047 * This library is distributed in the hope that it will be useful, but 048 * WITHOUT ANY WARRANTY; without even the implied warranty of 049 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 050 * Lesser General Public License for more details. <br> 051 * <br> 052 * You should have received a copy of the GNU Lesser General Public 053 * License along with this library; if not see 054 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 055 */ 056public class Section extends AbstractClassWithContext<Request, Enrollment, Section.SectionContext> implements SctAssignment, AssignmentComparable<Section, Request, Enrollment>, CanInheritContext<Request, Enrollment, Section.SectionContext>{ 057 private static DecimalFormat sDF = new DecimalFormat("0.000"); 058 private long iId = -1; 059 private String iName = null; 060 private Map<Long, String> iNameByCourse = null; 061 private Subpart iSubpart = null; 062 private Section iParent = null; 063 private Placement iPlacement = null; 064 private int iLimit = 0; 065 private Choice iChoice = null; 066 private double iPenalty = 0.0; 067 private double iSpaceExpected = 0.0; 068 private double iSpaceHeld = 0.0; 069 private String iNote = null; 070 private Set<Long> iIgnoreConflictsWith = null; 071 072 /** 073 * Constructor 074 * 075 * @param id 076 * section unique id 077 * @param limit 078 * section limit, i.e., the maximal number of students that can 079 * be enrolled in this section at the same time 080 * @param name 081 * section name 082 * @param subpart 083 * subpart of this section 084 * @param placement 085 * time/room placement 086 * @param instructorIds 087 * instructor(s) id -- needed for {@link Section#getChoice()} 088 * @param instructorNames 089 * instructor(s) name -- needed for {@link Section#getChoice()} 090 * @param parent 091 * parent section -- if there is a parent section defined, a 092 * student that is enrolled in this section has to be enrolled in 093 * the parent section as well. Also, the same relation needs to 094 * be defined between subpart of this section and the subpart of 095 * the parent section 096 */ 097 public Section(long id, int limit, String name, Subpart subpart, Placement placement, String instructorIds, 098 String instructorNames, Section parent) { 099 iId = id; 100 iLimit = limit; 101 iName = name; 102 iSubpart = subpart; 103 iSubpart.getSections().add(this); 104 iPlacement = placement; 105 iParent = parent; 106 iChoice = new Choice(getSubpart().getConfig().getOffering(), getSubpart().getInstructionalType(), getTime(), 107 instructorIds, instructorNames); 108 } 109 110 /** Section id */ 111 @Override 112 public long getId() { 113 return iId; 114 } 115 116 /** 117 * Section limit. This is defines the maximal number of students that can be 118 * enrolled into this section at the same time. It is -1 in the case of an 119 * unlimited section 120 * @return class limit 121 */ 122 public int getLimit() { 123 return iLimit; 124 } 125 126 /** Set section limit 127 * @param limit class limit 128 **/ 129 public void setLimit(int limit) { 130 iLimit = limit; 131 } 132 133 /** Section name 134 * @return class name 135 **/ 136 public String getName() { 137 return iName; 138 } 139 140 /** Set section name 141 * @param name class name 142 **/ 143 public void setName(String name) { 144 iName = name; 145 } 146 147 /** Scheduling subpart to which this section belongs 148 * @return scheduling subpart 149 **/ 150 public Subpart getSubpart() { 151 return iSubpart; 152 } 153 154 /** 155 * Parent section of this section (can be null). If there is a parent 156 * section defined, a student that is enrolled in this section has to be 157 * enrolled in the parent section as well. Also, the same relation needs to 158 * be defined between subpart of this section and the subpart of the parent 159 * section. 160 * @return parent class 161 */ 162 public Section getParent() { 163 return iParent; 164 } 165 166 /** 167 * Time/room placement of the section. This can be null, for arranged 168 * sections. 169 * @return time and room assignment of this class 170 */ 171 public Placement getPlacement() { 172 return iPlacement; 173 } 174 175 /** 176 * Set time/room placement of the section. This can be null, for arranged 177 * sections. 178 * @param placement time and room assignment of this class 179 */ 180 public void setPlacement(Placement placement) { 181 iPlacement = placement; 182 } 183 184 /** Time placement of the section. */ 185 @Override 186 public TimeLocation getTime() { 187 return (iPlacement == null ? null : iPlacement.getTimeLocation()); 188 } 189 190 /** True if the time assignment is the same */ 191 public boolean sameTime(Section section) { 192 return getTime() == null ? section.getTime() == null : getTime().equals(section.getTime()); 193 } 194 195 /** Number of rooms in which the section meet. */ 196 @Override 197 public int getNrRooms() { 198 return (iPlacement == null ? 0 : iPlacement.getNrRooms()); 199 } 200 201 /** 202 * Room placement -- list of 203 * {@link org.cpsolver.coursett.model.RoomLocation} 204 */ 205 @Override 206 public List<RoomLocation> getRooms() { 207 if (iPlacement == null) 208 return null; 209 if (iPlacement.getRoomLocations() == null && iPlacement.getRoomLocation() != null) { 210 List<RoomLocation> ret = new ArrayList<RoomLocation>(1); 211 ret.add(iPlacement.getRoomLocation()); 212 return ret; 213 } 214 return iPlacement.getRoomLocations(); 215 } 216 217 /** 218 * True, if this section overlaps with the given assignment in time and 219 * space 220 */ 221 @Override 222 public boolean isOverlapping(SctAssignment assignment) { 223 if (isAllowOverlap() || assignment.isAllowOverlap()) return false; 224 if (getTime() == null || assignment.getTime() == null) return false; 225 if (assignment instanceof Section && isToIgnoreStudentConflictsWith(assignment.getId())) return false; 226 return getTime().hasIntersection(assignment.getTime()); 227 } 228 229 /** 230 * True, if this section overlaps with one of the given set of assignments 231 * in time and space 232 */ 233 @Override 234 public boolean isOverlapping(Set<? extends SctAssignment> assignments) { 235 if (isAllowOverlap()) return false; 236 if (getTime() == null || assignments == null) 237 return false; 238 for (SctAssignment assignment : assignments) { 239 if (assignment.isAllowOverlap()) 240 continue; 241 if (assignment.getTime() == null) 242 continue; 243 if (assignment instanceof Section && isToIgnoreStudentConflictsWith(assignment.getId())) 244 continue; 245 if (getTime().hasIntersection(assignment.getTime())) 246 return true; 247 } 248 return false; 249 } 250 251 /** Called when an enrollment with this section is assigned to a request */ 252 @Override 253 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 254 getContext(assignment).assigned(assignment, enrollment); 255 } 256 257 /** Called when an enrollment with this section is unassigned from a request */ 258 @Override 259 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 260 getContext(assignment).unassigned(assignment, enrollment); 261 } 262 263 /** Long name: subpart name + time long name + room names + instructor names 264 * @param useAmPm use 12-hour format 265 * @return long name 266 **/ 267 public String getLongName(boolean useAmPm) { 268 return getSubpart().getName() + " " + getName() + " " + (getTime() == null ? "" : " " + getTime().getLongName(useAmPm)) 269 + (getNrRooms() == 0 ? "" : " " + getPlacement().getRoomName(",")) 270 + (getChoice().getInstructorNames() == null ? "" : " " + getChoice().getInstructorNames()); 271 } 272 273 @Deprecated 274 public String getLongName() { 275 return getLongName(true); 276 } 277 278 @Override 279 public String toString() { 280 return getSubpart().getConfig().getOffering().getName() + " " + getSubpart().getName() + " " + getName() 281 + (getTime() == null ? "" : " " + getTime().getLongName(true)) 282 + (getNrRooms() == 0 ? "" : " " + getPlacement().getRoomName(",")) 283 + (getChoice().getInstructorNames() == null ? "" : " " + getChoice().getInstructorNames()) + " (L:" 284 + (getLimit() < 0 ? "unlimited" : "" + getLimit()) 285 + (getPenalty() == 0.0 ? "" : ",P:" + sDF.format(getPenalty())) + ")"; 286 } 287 288 /** A (student) choice representing this section. 289 * @return choice for this class 290 **/ 291 public Choice getChoice() { 292 return iChoice; 293 } 294 295 /** 296 * Return penalty which is added to an enrollment that contains this 297 * section. 298 * @return online penalty 299 */ 300 public double getPenalty() { 301 return iPenalty; 302 } 303 304 /** Set penalty which is added to an enrollment that contains this section. 305 * @param penalty online penalty 306 **/ 307 public void setPenalty(double penalty) { 308 iPenalty = penalty; 309 } 310 311 /** 312 * Compare two sections, prefer sections with lower penalty and more open 313 * space 314 */ 315 @Override 316 public int compareTo(Assignment<Request, Enrollment> assignment, Section s) { 317 int cmp = Double.compare(getPenalty(), s.getPenalty()); 318 if (cmp != 0) 319 return cmp; 320 cmp = Double.compare(getLimit() - getContext(assignment).getEnrollmentWeight(assignment, null), s.getLimit() - s.getContext(assignment).getEnrollmentWeight(assignment, null)); 321 if (cmp != 0) 322 return cmp; 323 return Double.compare(getId(), s.getId()); 324 } 325 326 /** 327 * Compare two sections, prefer sections with lower penalty 328 */ 329 @Override 330 public int compareTo(Section s) { 331 int cmp = Double.compare(getPenalty(), s.getPenalty()); 332 if (cmp != 0) 333 return cmp; 334 return Double.compare(getId(), s.getId()); 335 } 336 337 /** 338 * Return the amount of space of this section that is held for incoming 339 * students. This attribute is computed during the batch sectioning (it is 340 * the overall weight of dummy students enrolled in this section) and it is 341 * being updated with each incomming student during the online sectioning. 342 * @return space held 343 */ 344 public double getSpaceHeld() { 345 return iSpaceHeld; 346 } 347 348 /** 349 * Set the amount of space of this section that is held for incoming 350 * students. See {@link Section#getSpaceHeld()} for more info. 351 * @param spaceHeld space held 352 */ 353 public void setSpaceHeld(double spaceHeld) { 354 iSpaceHeld = spaceHeld; 355 } 356 357 /** 358 * Return the amount of space of this section that is expected to be taken 359 * by incoming students. This attribute is computed during the batch 360 * sectioning (for each dummy student that can attend this section (without 361 * any conflict with other enrollments of that student), 1 / x where x is 362 * the number of such sections of this subpart is added to this value). 363 * Also, this value is being updated with each incoming student during the 364 * online sectioning. 365 * @return space expected 366 */ 367 public double getSpaceExpected() { 368 return iSpaceExpected; 369 } 370 371 /** 372 * Set the amount of space of this section that is expected to be taken by 373 * incoming students. See {@link Section#getSpaceExpected()} for more info. 374 * @param spaceExpected space expected 375 */ 376 public void setSpaceExpected(double spaceExpected) { 377 iSpaceExpected = spaceExpected; 378 } 379 380 /** 381 * Online sectioning penalty. 382 * @param assignment current assignment 383 * @return online sectioning penalty 384 */ 385 public double getOnlineSectioningPenalty(Assignment<Request, Enrollment> assignment) { 386 if (getLimit() <= 0) 387 return 0.0; 388 389 double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, null); 390 391 double penalty = (getSpaceExpected() - available) / getLimit(); 392 393 return Math.max(-1.0, Math.min(1.0, penalty)); 394 } 395 396 /** 397 * Return true if overlaps are allowed, but the number of overlapping slots should be minimized. 398 * This can be changed on the subpart, using {@link Subpart#setAllowOverlap(boolean)}. 399 **/ 400 @Override 401 public boolean isAllowOverlap() { 402 return iSubpart.isAllowOverlap(); 403 } 404 405 /** Sections first, then by {@link FreeTimeRequest#getId()} */ 406 @Override 407 public int compareById(SctAssignment a) { 408 if (a instanceof Section) { 409 return new Long(getId()).compareTo(((Section)a).getId()); 410 } else { 411 return -1; 412 } 413 } 414 415 /** 416 * Available space in the section that is not reserved by any section reservation 417 * @param assignment current assignment 418 * @param excludeRequest excluding given request (if not null) 419 * @return unreserved space in this class 420 **/ 421 public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 422 // section is unlimited -> there is unreserved space unless there is an unlimited reservation too 423 // (in which case there is no unreserved space) 424 if (getLimit() < 0) { 425 // exclude reservations that are not directly set on this section 426 for (Reservation r: getSectionReservations()) { 427 // ignore expired reservations 428 if (r.isExpired()) continue; 429 // there is an unlimited reservation -> no unreserved space 430 if (r.getLimit() < 0) return 0.0; 431 } 432 return Double.MAX_VALUE; 433 } 434 435 double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, excludeRequest); 436 // exclude reservations that are not directly set on this section 437 for (Reservation r: getSectionReservations()) { 438 // ignore expired reservations 439 if (r.isExpired()) continue; 440 // unlimited reservation -> all the space is reserved 441 if (r.getLimit() < 0.0) return 0.0; 442 // compute space that can be potentially taken by this reservation 443 double reserved = r.getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest); 444 // deduct the space from available space 445 available -= Math.max(0.0, reserved); 446 } 447 448 return available; 449 } 450 451 /** 452 * Total space in the section that cannot be used by any section reservation 453 * @return total unreserved space in this class 454 **/ 455 public synchronized double getTotalUnreservedSpace() { 456 if (iTotalUnreservedSpace == null) 457 iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache(); 458 return iTotalUnreservedSpace; 459 } 460 private Double iTotalUnreservedSpace = null; 461 private double getTotalUnreservedSpaceNoCache() { 462 // section is unlimited -> there is unreserved space unless there is an unlimited reservation too 463 // (in which case there is no unreserved space) 464 if (getLimit() < 0) { 465 // exclude reservations that are not directly set on this section 466 for (Reservation r: getSectionReservations()) { 467 // ignore expired reservations 468 if (r.isExpired()) continue; 469 // there is an unlimited reservation -> no unreserved space 470 if (r.getLimit() < 0) return 0.0; 471 } 472 return Double.MAX_VALUE; 473 } 474 475 // we need to check all reservations linked with this section 476 double available = getLimit(), reserved = 0, exclusive = 0; 477 Set<Section> sections = new HashSet<Section>(); 478 reservations: for (Reservation r: getSectionReservations()) { 479 // ignore expired reservations 480 if (r.isExpired()) continue; 481 // unlimited reservation -> no unreserved space 482 if (r.getLimit() < 0) return 0.0; 483 for (Section s: r.getSections(getSubpart())) { 484 if (s.equals(this)) continue; 485 if (s.getLimit() < 0) continue reservations; 486 if (sections.add(s)) 487 available += s.getLimit(); 488 } 489 reserved += r.getLimit(); 490 if (r.getSections(getSubpart()).size() == 1) 491 exclusive += r.getLimit(); 492 } 493 494 return Math.min(available - reserved, getLimit() - exclusive); 495 } 496 497 498 /** 499 * Get reservations for this section 500 * @return reservations that can use this class 501 */ 502 public synchronized List<Reservation> getReservations() { 503 if (iReservations == null) { 504 iReservations = new ArrayList<Reservation>(); 505 for (Reservation r: getSubpart().getConfig().getOffering().getReservations()) { 506 if (r.getSections(getSubpart()) == null || r.getSections(getSubpart()).contains(this)) 507 iReservations.add(r); 508 } 509 } 510 return iReservations; 511 } 512 private List<Reservation> iReservations = null; 513 514 /** 515 * Get reservations that require this section 516 * @return reservations that must use this class 517 */ 518 public synchronized List<Reservation> getSectionReservations() { 519 if (iSectionReservations == null) { 520 iSectionReservations = new ArrayList<Reservation>(); 521 for (Reservation r: getSubpart().getSectionReservations()) { 522 if (r.getSections(getSubpart()).contains(this)) 523 iSectionReservations.add(r); 524 } 525 } 526 return iSectionReservations; 527 } 528 private List<Reservation> iSectionReservations = null; 529 530 /** 531 * Clear reservation information that was cached on this section 532 */ 533 public synchronized void clearReservationCache() { 534 iReservations = null; 535 iSectionReservations = null; 536 iTotalUnreservedSpace = null; 537 } 538 539 /** 540 * Return course-dependent section name 541 * @param courseId course offering unique id 542 * @return course dependent class name 543 */ 544 public String getName(long courseId) { 545 if (iNameByCourse == null) return getName(); 546 String name = iNameByCourse.get(courseId); 547 return (name == null ? getName() : name); 548 } 549 550 /** 551 * Set course-dependent section name 552 * @param courseId course offering unique id 553 * @param name course dependent class name 554 */ 555 public void setName(long courseId, String name) { 556 if (iNameByCourse == null) iNameByCourse = new HashMap<Long, String>(); 557 iNameByCourse.put(courseId, name); 558 } 559 560 /** 561 * Return course-dependent section names 562 * @return map of course-dependent class names 563 */ 564 public Map<Long, String> getNameByCourse() { return iNameByCourse; } 565 566 @Override 567 public boolean equals(Object o) { 568 if (o == null || !(o instanceof Section)) return false; 569 return getId() == ((Section)o).getId(); 570 } 571 572 @Override 573 public int hashCode() { 574 return (int) (iId ^ (iId >>> 32)); 575 } 576 577 /** 578 * Section note 579 * @return scheduling note 580 */ 581 public String getNote() { return iNote; } 582 583 /** 584 * Section note 585 * @param note scheduling note 586 */ 587 public void setNote(String note) { iNote = note; } 588 589 /** 590 * Add section id of a section that student conflicts are to be ignored with 591 * @param sectionId class unique id 592 */ 593 public void addIgnoreConflictWith(long sectionId) { 594 if (iIgnoreConflictsWith == null) iIgnoreConflictsWith = new HashSet<Long>(); 595 iIgnoreConflictsWith.add(sectionId); 596 } 597 598 /** 599 * Returns true if student conflicts between this section and the given one are to be ignored 600 * @param sectionId class unique id 601 * @return true if student conflicts between these two sections are to be ignored 602 */ 603 public boolean isToIgnoreStudentConflictsWith(long sectionId) { 604 return iIgnoreConflictsWith != null && iIgnoreConflictsWith.contains(sectionId); 605 } 606 607 /** 608 * Returns a set of ids of sections that student conflicts are to be ignored with (between this section and the others) 609 * @return set of class unique ids of the sections that student conflicts are to be ignored with 610 */ 611 public Set<Long> getIgnoreConflictWithSectionIds() { 612 return iIgnoreConflictsWith; 613 } 614 615 /** Set of assigned enrollments */ 616 @Override 617 public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) { 618 return getContext(assignment).getEnrollments(); 619 } 620 621 /** 622 * Enrollment weight -- weight of all requests which have an enrollment that 623 * contains this section, excluding the given one. See 624 * {@link Request#getWeight()}. 625 * @param assignment current assignment 626 * @param excludeRequest course request to ignore, if any 627 * @return enrollment weight 628 */ 629 public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 630 return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest); 631 } 632 633 /** 634 * Maximal weight of a single enrollment in the section 635 * @param assignment current assignment 636 * @return maximal enrollment weight 637 */ 638 public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) { 639 return getContext(assignment).getMaxEnrollmentWeight(); 640 } 641 642 /** 643 * Minimal weight of a single enrollment in the section 644 * @param assignment current assignment 645 * @return minimal enrollment weight 646 */ 647 public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) { 648 return getContext(assignment).getMinEnrollmentWeight(); 649 } 650 651 @Override 652 public Model<Request, Enrollment> getModel() { 653 return getSubpart().getConfig().getOffering().getModel(); 654 } 655 656 @Override 657 public SectionContext createAssignmentContext(Assignment<Request, Enrollment> assignment) { 658 return new SectionContext(assignment); 659 } 660 661 @Override 662 public SectionContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, SectionContext parentContext) { 663 return new SectionContext(parentContext); 664 } 665 666 public class SectionContext implements AssignmentConstraintContext<Request, Enrollment> { 667 private Set<Enrollment> iEnrollments = null; 668 private double iEnrollmentWeight = 0.0; 669 private double iMaxEnrollmentWeight = 0.0; 670 private double iMinEnrollmentWeight = 0.0; 671 private boolean iReadOnly = false; 672 673 public SectionContext(Assignment<Request, Enrollment> assignment) { 674 iEnrollments = new HashSet<Enrollment>(); 675 for (Course course: getSubpart().getConfig().getOffering().getCourses()) { 676 for (CourseRequest request: course.getRequests()) { 677 Enrollment enrollment = assignment.getValue(request); 678 if (enrollment != null && enrollment.getSections().contains(Section.this)) 679 assigned(assignment, enrollment); 680 } 681 } 682 } 683 684 public SectionContext(SectionContext parent) { 685 iEnrollmentWeight = parent.iEnrollmentWeight; 686 iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight; 687 iMinEnrollmentWeight = parent.iMinEnrollmentWeight; 688 iEnrollments = parent.iEnrollments; 689 iReadOnly = true; 690 } 691 692 /** Called when an enrollment with this section is assigned to a request */ 693 @Override 694 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 695 if (iReadOnly) { 696 iEnrollments = new HashSet<Enrollment>(iEnrollments); 697 iReadOnly = false; 698 } 699 if (iEnrollments.isEmpty()) { 700 iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight(); 701 } else { 702 iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight()); 703 iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight()); 704 } 705 if (iEnrollments.add(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 706 iEnrollmentWeight += enrollment.getRequest().getWeight(); 707 } 708 709 /** Called when an enrollment with this section is unassigned from a request */ 710 @Override 711 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 712 if (iReadOnly) { 713 iEnrollments = new HashSet<Enrollment>(iEnrollments); 714 iReadOnly = false; 715 } 716 if (iEnrollments.remove(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 717 iEnrollmentWeight -= enrollment.getRequest().getWeight(); 718 if (iEnrollments.isEmpty()) { 719 iMinEnrollmentWeight = iMaxEnrollmentWeight = 0; 720 } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) { 721 if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) { 722 double newMinEnrollmentWeight = Double.MAX_VALUE; 723 for (Enrollment e : iEnrollments) { 724 if (e.getRequest().getWeight() == iMinEnrollmentWeight) { 725 newMinEnrollmentWeight = iMinEnrollmentWeight; 726 break; 727 } else { 728 newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight()); 729 } 730 } 731 iMinEnrollmentWeight = newMinEnrollmentWeight; 732 } 733 if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) { 734 double newMaxEnrollmentWeight = Double.MIN_VALUE; 735 for (Enrollment e : iEnrollments) { 736 if (e.getRequest().getWeight() == iMaxEnrollmentWeight) { 737 newMaxEnrollmentWeight = iMaxEnrollmentWeight; 738 break; 739 } else { 740 newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight()); 741 } 742 } 743 iMaxEnrollmentWeight = newMaxEnrollmentWeight; 744 } 745 } 746 } 747 748 /** Set of assigned enrollments 749 * @return assigned enrollments of this section 750 **/ 751 public Set<Enrollment> getEnrollments() { 752 return iEnrollments; 753 } 754 755 /** 756 * Enrollment weight -- weight of all requests which have an enrollment that 757 * contains this section, excluding the given one. See 758 * {@link Request#getWeight()}. 759 * @param assignment current assignment 760 * @param excludeRequest course request to ignore, if any 761 * @return enrollment weight 762 */ 763 public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 764 double weight = iEnrollmentWeight; 765 if (excludeRequest != null) { 766 Enrollment enrollment = assignment.getValue(excludeRequest); 767 if (enrollment!= null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 768 weight -= excludeRequest.getWeight(); 769 } 770 return weight; 771 } 772 773 /** 774 * Maximal weight of a single enrollment in the section 775 * @return maximal enrollment weight 776 */ 777 public double getMaxEnrollmentWeight() { 778 return iMaxEnrollmentWeight; 779 } 780 781 /** 782 * Minimal weight of a single enrollment in the section 783 * @return minimal enrollment weight 784 */ 785 public double getMinEnrollmentWeight() { 786 return iMinEnrollmentWeight; 787 } 788 } 789}