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