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