    001package net.sf.cpsolver.studentsct.model;
    002
    003import java.text.DecimalFormat;
    004import java.util.HashSet;
    005import java.util.Iterator;
    006import java.util.Set;
    007
    008import net.sf.cpsolver.ifs.model.Value;
    009import net.sf.cpsolver.ifs.util.ToolBox;
    010import net.sf.cpsolver.studentsct.StudentSectioningModel;
    011import net.sf.cpsolver.studentsct.extension.DistanceConflict;
    012import net.sf.cpsolver.studentsct.extension.TimeOverlapsCounter;
    013import net.sf.cpsolver.studentsct.reservation.Reservation;
    014
    015/**
    016 * Representation of an enrollment of a student into a course. A student needs
    017 * to be enrolled in a section of each subpart of a selected configuration. When
    018 * parent-child relation is defined among sections, if a student is enrolled in
    019 * a section that has a parent section defined, he/she has be enrolled in the
    020 * parent section as well. Also, the selected sections cannot overlap in time. <br>
    021 * <br>
    022 *
    023 * @version StudentSct 1.2 (Student Sectioning)<br>
    024 *          Copyright (C) 2007 - 2010 Tomáš Müller<br>
    025 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    026 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    027 * <br>
    028 *          This library is free software; you can redistribute it and/or modify
    029 *          it under the terms of the GNU Lesser General Public License as
    030 *          published by the Free Software Foundation; either version 3 of the
    031 *          License, or (at your option) any later version. <br>
    032 * <br>
    033 *          This library is distributed in the hope that it will be useful, but
    034 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    035 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    036 *          Lesser General Public License for more details. <br>
    037 * <br>
    038 *          You should have received a copy of the GNU Lesser General Public
    039 *          License along with this library; if not see
    040 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    041 */
    042
    043public class Enrollment extends Value<Request, Enrollment> {
    044    private static DecimalFormat sDF = new DecimalFormat("0.000");
    045    private Request iRequest = null;
    046    private Config iConfig = null;
    047    private Course iCourse = null;
    048    private Set<? extends Assignment> iAssignments = null;
    049    private Double iCachedPenalty = null;
    050    private int iPriority = 0;
    051    private Reservation iReservation = null;
    052    private Long iTimeStamp = null;
    053    private String iApproval = null;
    054
    055    /**
    056     * Constructor
    057     *
    058     * @param request
    059     *            course / free time request
    060     * @param priority
    061     *            zero for the course, one for the first alternative, two for the second alternative
    062     * @param course
    063     *            selected course
    064     * @param config
    065     *            selected configuration
    066     * @param assignments
    067     *            valid list of sections
    068     */
    069    public Enrollment(Request request, int priority, Course course, Config config, Set<? extends Assignment> assignments, Reservation reservation) {
    070        super(request);
    071        iRequest = request;
    072        iConfig = config;
    073        iAssignments = assignments;
    074        iPriority = priority;
    075        iCourse = course;
    076        if (iConfig != null && iCourse == null)
    077            for (Course c: ((CourseRequest)iRequest).getCourses()) {
    078                if (c.getOffering().getConfigs().contains(iConfig)) {
    079                    iCourse = c;
    080                    break;
    081                }
    082            }
    083        iReservation = reservation;
    084    }
    085
    086    /**
    087     * Constructor
    088     *
    089     * @param request
    090     *            course / free time request
    091     * @param priority
    092     *            zero for the course, one for the first alternative, two for the second alternative
    093     * @param config
    094     *            selected configuration
    095     * @param assignments
    096     *            valid list of sections
    097     */
    098    public Enrollment(Request request, int priority, Config config, Set<? extends Assignment> assignments) {
    099        this(request, priority, null, config, assignments, null);
    100        if (assignments != null)
    101            guessReservation(true);
    102    }
    103
    104    /**
    105     * Guess the reservation based on the enrollment
    106     */
    107    public void guessReservation(boolean onlyAvailable) {
    108        if (iCourse != null) {
    109            Reservation best = null;
    110            boolean canAssignOverTheLimit = (variable().getModel() == null || ((StudentSectioningModel)variable().getModel()).getReservationCanAssignOverTheLimit());
    111            for (Reservation reservation: ((CourseRequest)iRequest).getReservations(iCourse)) {
    112                if (reservation.isIncluded(this)) {
    113                    if (onlyAvailable && reservation.getReservedAvailableSpace(iRequest) < iRequest.getWeight() &&
    114                       (!reservation.canAssignOverLimit() || !canAssignOverTheLimit))
    115                        continue;
    116                    if (best == null || best.getPriority() > reservation.getPriority()) {
    117                        best = reservation;
    118                    } else if (best.getPriority() == reservation.getPriority() &&
    119                        best.getReservedAvailableSpace(iRequest) < reservation.getReservedAvailableSpace(iRequest)) {
    120                        best = reservation;
    121                    }
    122                }
    123            }
    124            iReservation = best;
    125        }
    126    }
    127
    128    /** Student */
    129    public Student getStudent() {
    130        return iRequest.getStudent();
    131    }
    132
    133    /** Request */
    134    public Request getRequest() {
    135        return iRequest;
    136    }
    137
    138    /** True if the request is course request */
    139    public boolean isCourseRequest() {
    140        return iConfig != null;
    141    }
    142
    143    /** Offering of the course request */
    144    public Offering getOffering() {
    145        return (iConfig == null ? null : iConfig.getOffering());
    146    }
    147
    148    /** Config of the course request */
    149    public Config getConfig() {
    150        return iConfig;
    151    }
    152
    153    /** Course of the course request */
    154    public Course getCourse() {
    155        return iCourse;
    156    }
    157
    158    /** List of assignments (selected sections) */
    159    @SuppressWarnings("unchecked")
    160    public Set<Assignment> getAssignments() {
    161        return (Set<Assignment>) iAssignments;
    162    }
    163
    164    /** List of sections (only for course request) */
    165    @SuppressWarnings("unchecked")
    166    public Set<Section> getSections() {
    167        if (isCourseRequest())
    168            return (Set<Section>) iAssignments;
    169        return new HashSet<Section>();
    170    }
    171
    172    /** True when this enrollment is overlapping with the given enrollment */
    173    public boolean isOverlapping(Enrollment enrl) {
    174        if (enrl == null || isAllowOverlap() || enrl.isAllowOverlap())
    175            return false;
    176        for (Assignment a : getAssignments()) {
    177            if (a.isOverlapping(enrl.getAssignments()))
    178                return true;
    179        }
    180        return false;
    181    }
    182
    183    /** Percent of sections that are wait-listed */
    184    public double percentWaitlisted() {
    185        if (!isCourseRequest())
    186            return 0.0;
    187        CourseRequest courseRequest = (CourseRequest) getRequest();
    188        int nrWaitlisted = 0;
    189        for (Section section : getSections()) {
    190            if (courseRequest.isWaitlisted(section))
    191                nrWaitlisted++;
    192        }
    193        return ((double) nrWaitlisted) / getAssignments().size();
    194    }
    195
    196    /** Percent of sections that are selected */
    197    public double percentSelected() {
    198        if (!isCourseRequest())
    199            return 0.0;
    200        CourseRequest courseRequest = (CourseRequest) getRequest();
    201        int nrSelected = 0;
    202        for (Section section : getSections()) {
    203            if (courseRequest.isSelected(section))
    204                nrSelected++;
    205        }
    206        return ((double) nrSelected) / getAssignments().size();
    207    }
    208
    209    /** Percent of sections that are initial */
    210    public double percentInitial() {
    211        if (!isCourseRequest())
    212            return 0.0;
    213        if (getRequest().getInitialAssignment() == null)
    214            return 0.0;
    215        Enrollment inital = getRequest().getInitialAssignment();
    216        int nrInitial = 0;
    217        for (Section section : getSections()) {
    218            if (inital.getAssignments().contains(section))
    219                nrInitial++;
    220        }
    221        return ((double) nrInitial) / getAssignments().size();
    222    }
    223
    224    /** True if all the sections are wait-listed */
    225    public boolean isWaitlisted() {
    226        if (!isCourseRequest())
    227            return false;
    228        CourseRequest courseRequest = (CourseRequest) getRequest();
    229        for (Iterator<? extends Assignment> i = getAssignments().iterator(); i.hasNext();) {
    230            Section section = (Section) i.next();
    231            if (!courseRequest.isWaitlisted(section))
    232                return false;
    233        }
    234        return true;
    235    }
    236
    237    /** True if all the sections are selected */
    238    public boolean isSelected() {
    239        if (!isCourseRequest())
    240            return false;
    241        CourseRequest courseRequest = (CourseRequest) getRequest();
    242        for (Section section : getSections()) {
    243            if (!courseRequest.isSelected(section))
    244                return false;
    245        }
    246        return true;
    247    }
    248
    249    /**
    250     * Enrollment penalty -- sum of section penalties (see
    251     * {@link Section#getPenalty()})
    252     */
    253    public double getPenalty() {
    254        if (iCachedPenalty == null) {
    255            double penalty = 0.0;
    256            if (isCourseRequest()) {
    257                for (Section section : getSections()) {
    258                    penalty += section.getPenalty();
    259                }
    260            }
    261            iCachedPenalty = new Double(penalty / getAssignments().size());
    262        }
    263        return iCachedPenalty.doubleValue();
    264    }
    265
    266    /** Enrollment value */
    267    @Override
    268    public double toDouble() {
    269        return toDouble(true);
    270    }
    271
    272    /** Enrollment value
    273     * @param precise if false, distance conflicts and time overlaps are ignored (i.e., much faster, but less precise computation)
    274     **/
    275    public double toDouble(boolean precise) {
    276        if (precise)
    277            return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(this, distanceConflicts(), timeOverlappingConflicts());
    278        else {
    279            if (getExtra() != null) return - (Double) getExtra();
    280            return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(this);
    281        }
    282    }
    283
    284    /** Enrollment name */
    285    @Override
    286    public String getName() {
    287        if (getRequest() instanceof CourseRequest) {
    288            Course course = null;
    289            CourseRequest courseRequest = (CourseRequest) getRequest();
    290            for (Course c : courseRequest.getCourses()) {
    291                if (c.getOffering().getConfigs().contains(getConfig())) {
    292                    course = c;
    293                    break;
    294                }
    295            }
    296            String ret = (course == null ? getConfig() == null ? "" : getConfig().getName() : course.getName());
    297            for (Iterator<? extends Assignment> i = getAssignments().iterator(); i.hasNext();) {
    298                Section assignment = (Section) i.next();
    299                ret += "\n  " + assignment.getLongName() + (i.hasNext() ? "," : "");
    300            }
    301            return ret;
    302        } else if (getRequest() instanceof FreeTimeRequest) {
    303            return "Free Time " + ((FreeTimeRequest) getRequest()).getTime().getLongName();
    304        } else {
    305            String ret = "";
    306            for (Iterator<? extends Assignment> i = getAssignments().iterator(); i.hasNext();) {
    307                Assignment assignment = i.next();
    308                ret += assignment.toString() + (i.hasNext() ? "," : "");
    309                if (i.hasNext())
    310                    ret += "\n  ";
    311            }
    312            return ret;
    313        }
    314    }
    315
    316    @Override
    317    public String toString() {
    318        if (getAssignments().isEmpty()) return "not assigned";
    319        Set<DistanceConflict.Conflict> dc = distanceConflicts();
    320        Set<TimeOverlapsCounter.Conflict> toc = timeOverlappingConflicts();
    321        int share = 0;
    322        if (toc != null)
    323            for (TimeOverlapsCounter.Conflict c: toc)
    324                share += c.getShare();
    325        String ret = sDF.format(toDouble()) + "/" + sDF.format(getRequest().getBound())
    326                + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty()))
    327                + (dc == null || dc.isEmpty() ? "" : "/dc:" + dc.size())
    328                + (share <= 0 ? "" : "/toc:" + share);
    329        if (getRequest() instanceof CourseRequest) {
    330            ret += " ";
    331            for (Iterator<? extends Assignment> i = getAssignments().iterator(); i.hasNext();) {
    332                Assignment assignment = i.next();
    333                ret += assignment + (i.hasNext() ? ", " : "");
    334            }
    335        }
    336        if (getReservation() != null) ret = "(r) " + ret;
    337        return ret;
    338    }
    339
    340    @Override
    341    public boolean equals(Object o) {
    342        if (o == null || !(o instanceof Enrollment))
    343            return false;
    344        Enrollment e = (Enrollment) o;
    345        if (!ToolBox.equals(getConfig(), e.getConfig()))
    346            return false;
    347        if (!ToolBox.equals(getRequest(), e.getRequest()))
    348            return false;
    349        if (!ToolBox.equals(getAssignments(), e.getAssignments()))
    350            return false;
    351        return true;
    352    }
    353
    354    /** Distance conflicts, in which this enrollment is involved. */
    355    public Set<DistanceConflict.Conflict> distanceConflicts() {
    356        if (!isCourseRequest())
    357            return null;
    358        if (getRequest().getModel() instanceof StudentSectioningModel) {
    359            DistanceConflict dc = ((StudentSectioningModel) getRequest().getModel()).getDistanceConflict();
    360            if (dc == null) return null;
    361            return dc.allConflicts(this);
    362        } else
    363            return null;
    364    }
    365
    366    /** Time overlapping conflicts, in which this enrollment is involved. */
    367    public Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts() {
    368        if (getRequest().getModel() instanceof StudentSectioningModel) {
    369            TimeOverlapsCounter toc = ((StudentSectioningModel) getRequest().getModel()).getTimeOverlaps();
    370            if (toc == null)
    371                return null;
    372            return toc.allConflicts(this);
    373        } else
    374            return null;
    375    }
    376
    377    /**
    378     * Return enrollment priority
    379     * @return zero for the course, one for the first alternative, two for the second alternative
    380     */
    381    public int getPriority() {
    382        return iPriority;
    383    }
    384
    385    /**
    386     * Return total number of slots of all sections in the enrollment.
    387     */
    388    public int getNrSlots() {
    389        int ret = 0;
    390        for (Assignment a: getAssignments()) {
    391            if (a.getTime() != null) ret += a.getTime().getLength() * a.getTime().getNrMeetings();
    392        }
    393        return ret;
    394    }
    395
    396    /**
    397     * Return reservation used for this enrollment
    398     */
    399    public Reservation getReservation() { return iReservation; }
    400
    401    /**
    402     * Set reservation for this enrollment
    403     */
    404    public void setReservation(Reservation reservation) { iReservation = reservation; }
    405
    406    /**
    407     * Time stamp of the enrollment
    408     */
    409    public Long getTimeStamp() {
    410        return iTimeStamp;
    411    }
    412
    413    /**
    414     * Time stamp of the enrollment
    415     */
    416    public void setTimeStamp(Long timeStamp) {
    417        iTimeStamp = timeStamp;
    418    }
    419
    420    /**
    421     * Approval of the enrollment (only used by the online student sectioning)
    422     */
    423    public String getApproval() {
    424        return iApproval;
    425    }
    426
    427    /**
    428     * Approval of the enrollment (only used by the online student sectioning)
    429     */
    430    public void setApproval(String approval) {
    431        iApproval = approval;
    432    }
    433
    434    /**
    435     * True if this enrollment can overlap with other enrollments of the student.
    436     */
    437    public boolean isAllowOverlap() {
    438        return (getReservation() != null && getReservation().isAllowOverlap());
    439    }
    440
    441    /**
    442     * Enrollment limit, i.e., the number of students that would be able to get into the offering using this enrollment (if all the sections are empty)
    443     */
    444    public int getLimit() {
    445        if (!isCourseRequest()) return -1; // free time requests have no limit
    446        Integer limit = null;
    447        for (Section section: getSections())
    448            if (section.getLimit() >= 0) {
    449                if (limit == null)
    450                    limit = section.getLimit();
    451                else
    452                    limit = Math.min(limit, section.getLimit());
    453            }
    454        return (limit == null ? -1 : limit);
    455    }
    456
    457}