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