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