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