    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}