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