    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}