    001package org.cpsolver.studentsct.model;
    002
    003import java.util.Collections;
    004import java.util.HashSet;
    005import java.util.Set;
    006import java.util.regex.Matcher;
    007import java.util.regex.Pattern;
    008
    009import org.cpsolver.ifs.assignment.Assignment;
    010import org.cpsolver.ifs.assignment.context.AbstractClassWithContext;
    011import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
    012import org.cpsolver.ifs.assignment.context.CanInheritContext;
    013import org.cpsolver.ifs.model.Model;
    014
    015
    016/**
    017 * Representation of a course offering. A course offering contains id, subject
    018 * area, course number and an instructional offering. <br>
    019 * <br>
    020 * Each instructional offering (see {@link Offering}) is offered under one or
    021 * more course offerings.
    022 *
    023 * <br>
    024 * <br>
    025 *
    026 * @author  Tomáš Müller
    027 * @version StudentSct 1.3 (Student Sectioning)<br>
    028 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    029 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    030 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    031 * <br>
    032 *          This library is free software; you can redistribute it and/or modify
    033 *          it under the terms of the GNU Lesser General Public License as
    034 *          published by the Free Software Foundation; either version 3 of the
    035 *          License, or (at your option) any later version. <br>
    036 * <br>
    037 *          This library is distributed in the hope that it will be useful, but
    038 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    039 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    040 *          Lesser General Public License for more details. <br>
    041 * <br>
    042 *          You should have received a copy of the GNU Lesser General Public
    043 *          License along with this library; if not see
    044 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    045 */
    046public class Course extends AbstractClassWithContext<Request, Enrollment, Course.CourseContext> implements CanInheritContext<Request, Enrollment, Course.CourseContext> {
    047    private long iId = -1;
    048    private String iSubjectArea = null;
    049    private String iCourseNumber = null;
    050    private Offering iOffering = null;
    051    private int iLimit = 0, iProjected = 0;
    052    private Set<CourseRequest> iRequests = Collections.synchronizedSet(new HashSet<CourseRequest>());
    053    private String iNote = null;
    054    private Set<RequestGroup> iRequestGroups = new HashSet<RequestGroup>();
    055    private String iCredit = null;
    056    private Float iCreditValue = null;
    057    private String iTitle = null;
    058    private String iType = null;
    059
    060    /**
    061     * Constructor
    062     *
    063     * @param id
    064     *            course offering unique id
    065     * @param subjectArea
    066     *            subject area (e.g., MA, CS, ENGL)
    067     * @param courseNumber
    068     *            course number under the given subject area
    069     * @param offering
    070     *            instructional offering which is offered under this course
    071     *            offering
    072     */
    073    public Course(long id, String subjectArea, String courseNumber, Offering offering) {
    074        iId = id;
    075        iSubjectArea = subjectArea;
    076        iCourseNumber = courseNumber;
    077        iOffering = offering;
    078        iOffering.getCourses().add(this);
    079    }
    080
    081    /**
    082     * Constructor
    083     *
    084     * @param id
    085     *            course offering unique id
    086     * @param subjectArea
    087     *            subject area (e.g., MA, CS, ENGL)
    088     * @param courseNumber
    089     *            course number under the given subject area
    090     * @param offering
    091     *            instructional offering which is offered under this course
    092     *            offering
    093     * @param limit
    094     *            course offering limit (-1 for unlimited)
    095     * @param projected
    096     *            projected demand
    097     */
    098    public Course(long id, String subjectArea, String courseNumber, Offering offering, int limit, int projected) {
    099        iId = id;
    100        iSubjectArea = subjectArea;
    101        iCourseNumber = courseNumber;
    102        iOffering = offering;
    103        iOffering.getCourses().add(this);
    104        iLimit = limit;
    105        iProjected = projected;
    106    }
    107
    108    /** Course offering unique id
    109     * @return coure offering unqiue id
    110     **/
    111    public long getId() {
    112        return iId;
    113    }
    114
    115    /** Subject area
    116     * @return subject area abbreviation
    117     **/
    118    public String getSubjectArea() {
    119        return iSubjectArea;
    120    }
    121
    122    /** Course number
    123     * @return course number
    124     **/
    125    public String getCourseNumber() {
    126        return iCourseNumber;
    127    }
    128
    129    /** Course offering name: subject area + course number
    130     * @return course name
    131     **/
    132    public String getName() {
    133        return iSubjectArea + " " + iCourseNumber;
    134    }
    135
    136    @Override
    137    public String toString() {
    138        return getName();
    139    }
    140
    141    /** Instructional offering which is offered under this course offering.
    142     * @return instructional offering
    143     **/
    144    public Offering getOffering() {
    145        return iOffering;
    146    }
    147
    148    /** Course offering limit
    149     * @return course offering limit, -1 if unlimited
    150     **/
    151    public int getLimit() {
    152        return iLimit;
    153    }
    154
    155    /** Set course offering limit
    156     * @param limit course offering limit, -1 if unlimited
    157     **/
    158    public void setLimit(int limit) {
    159        iLimit = limit;
    160    }
    161
    162    /** Course offering projected number of students
    163     * @return course projection
    164     **/
    165    public int getProjected() {
    166        return iProjected;
    167    }
    168
    169    /** Called when an enrollment with this course is assigned to a request
    170     * @param assignment current assignment
    171     * @param enrollment assigned enrollment
    172     **/
    173    public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    174        getContext(assignment).assigned(assignment, enrollment);
    175    }
    176
    177    /** Called when an enrollment with this course is unassigned from a request
    178     * @param assignment current assignment
    179     * @param enrollment unassigned enrollment
    180     */
    181    public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    182        getContext(assignment).unassigned(assignment, enrollment);
    183    }
    184
    185    /** Set of course requests requesting this course
    186     * @return request for this course
    187     **/
    188    public Set<CourseRequest> getRequests() {
    189        return iRequests;
    190    }
    191
    192    /**
    193     * Course note
    194     * @return course note
    195     */
    196    public String getNote() { return iNote; }
    197
    198    /**
    199     * Course note
    200     * @param note course note
    201     */
    202    public void setNote(String note) { iNote = note; }
    203
    204    @Override
    205    public boolean equals(Object o) {
    206        if (o == null || !(o instanceof Course)) return false;
    207        return getId() == ((Course)o).getId();
    208    }
    209
    210    @Override
    211    public int hashCode() {
    212        return (int) (iId ^ (iId >>> 32));
    213    }
    214
    215    @Override
    216    public Model<Request, Enrollment> getModel() {
    217        return getOffering().getModel();
    218    }
    219
    220    /**
    221     * Enrollment weight -- weight of all requests that are enrolled into this course,
    222     * excluding the given one. See
    223     * {@link Request#getWeight()}.
    224     * @param assignment current assignment
    225     * @param excludeRequest request to exclude
    226     * @return enrollment weight
    227     */
    228    public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
    229        return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
    230    }
    231
    232    /** Set of assigned enrollments
    233     * @param assignment current assignment
    234     * @return assigned enrollments for this course offering
    235     **/
    236    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
    237        return getContext(assignment).getEnrollments();
    238    }
    239
    240    /**
    241     * Maximal weight of a single enrollment in the course
    242     * @param assignment current assignment
    243     * @return maximal enrollment weight
    244     */
    245    public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
    246        return getContext(assignment).getMaxEnrollmentWeight();
    247    }
    248
    249    /**
    250     * Minimal weight of a single enrollment in the course
    251     * @param assignment current assignment
    252     * @return minimal enrollment weight
    253     */
    254    public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
    255        return getContext(assignment).getMinEnrollmentWeight();
    256    }
    257
    258    /**
    259     * Add request group of this course. This is automatically called
    260     * by the constructor of the {@link RequestGroup}.
    261     * @param group request group to be added
    262     */
    263    public void addRequestGroup(RequestGroup group) {
    264        iRequestGroups.add(group);
    265    }
    266
    267    /**
    268     * Remove request group from this course.
    269     * @param group request group to be removed
    270     */
    271    public void removeRequestGroup(RequestGroup group) {
    272        iRequestGroups.remove(group);
    273    }
    274
    275    /**
    276     * Lists all the request groups of this course
    277     * @return all request groups of this course
    278     */
    279    public Set<RequestGroup> getRequestGroups() {
    280        return iRequestGroups;
    281    }
    282
    283    /**
    284     * Set credit (Online Student Scheduling only)
    285     * @param credit scheduling course credit
    286     */
    287    public void setCredit(String credit) {
    288        iCredit = credit;
    289        if (iCreditValue == null && credit != null) {
    290            int split = credit.indexOf('|');
    291            String abbv = null;
    292            if (split >= 0) {
    293                abbv = credit.substring(0, split);
    294            } else {
    295                abbv = credit;
    296            }
    297            Matcher m = Pattern.compile("(^| )(\\d+\\.?\\d*)([,-]?(\\d+\\.?\\d*))?($| )").matcher(abbv);
    298            if (m.find())
    299                iCreditValue = Float.parseFloat(m.group(2));
    300        }
    301    }
    302
    303    /**
    304     * Get credit (Online Student Scheduling only)
    305     * @return scheduling course credit
    306     */
    307    public String getCredit() { return iCredit; }
    308
    309    /**
    310     * True if this course has a credit value defined
    311     * @return true if a credit value is set
    312     */
    313    public boolean hasCreditValue() { return iCreditValue != null; }
    314
    315    /**
    316     * Set course credit value (null if not set)
    317     * @param creditValue course credit value
    318     */
    319    public void setCreditValue(Float creditValue) { iCreditValue = creditValue; }
    320
    321    /**
    322     * Get course credit value (null if not set)
    323     * return course credit value
    324     */
    325    public Float getCreditValue() { return iCreditValue; }
    326
    327    @Override
    328    public CourseContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
    329        return new CourseContext(assignment);
    330    }
    331
    332
    333    @Override
    334    public CourseContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, CourseContext parentContext) {
    335        return new CourseContext(parentContext);
    336    }
    337
    338    public class CourseContext implements AssignmentConstraintContext<Request, Enrollment> {
    339        private double iEnrollmentWeight = 0.0;
    340        private Set<Enrollment> iEnrollments = null;
    341        private double iMaxEnrollmentWeight = 0.0;
    342        private double iMinEnrollmentWeight = 0.0;
    343        private boolean iReadOnly = false;
    344
    345        public CourseContext(Assignment<Request, Enrollment> assignment) {
    346            iEnrollments = new HashSet<Enrollment>();
    347            for (CourseRequest request: getRequests()) {
    348                Enrollment enrollment = assignment.getValue(request);
    349                if (enrollment != null && Course.this.equals(enrollment.getCourse()))
    350                    assigned(assignment, enrollment);
    351            }
    352        }
    353
    354        public CourseContext(CourseContext parent) {
    355            iEnrollmentWeight = parent.iEnrollmentWeight;
    356            iMinEnrollmentWeight = parent.iMinEnrollmentWeight;
    357            iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight;
    358            iEnrollments = parent.iEnrollments;
    359            iReadOnly = true;
    360        }
    361
    362        @Override
    363        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    364            if (iReadOnly) {
    365                iEnrollments = new HashSet<Enrollment>(iEnrollments);
    366                iReadOnly = false;
    367            }
    368            if (iEnrollments.isEmpty()) {
    369                iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
    370            } else {
    371                iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
    372                iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
    373            }
    374            if (iEnrollments.add(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
    375                iEnrollmentWeight += enrollment.getRequest().getWeight();
    376        }
    377
    378        @Override
    379        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    380            if (iReadOnly) {
    381                iEnrollments = new HashSet<Enrollment>(iEnrollments);
    382                iReadOnly = false;
    383            }
    384            if (iEnrollments.remove(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
    385                iEnrollmentWeight -= enrollment.getRequest().getWeight();
    386            if (iEnrollments.isEmpty()) {
    387                iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
    388            } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) {
    389                if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) {
    390                    double newMinEnrollmentWeight = Double.MAX_VALUE;
    391                    for (Enrollment e : iEnrollments) {
    392                        if (e.getRequest().getWeight() == iMinEnrollmentWeight) {
    393                            newMinEnrollmentWeight = iMinEnrollmentWeight;
    394                            break;
    395                        } else {
    396                            newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
    397                        }
    398                    }
    399                    iMinEnrollmentWeight = newMinEnrollmentWeight;
    400                }
    401                if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) {
    402                    double newMaxEnrollmentWeight = Double.MIN_VALUE;
    403                    for (Enrollment e : iEnrollments) {
    404                        if (e.getRequest().getWeight() == iMaxEnrollmentWeight) {
    405                            newMaxEnrollmentWeight = iMaxEnrollmentWeight;
    406                            break;
    407                        } else {
    408                            newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
    409                        }
    410                    }
    411                    iMaxEnrollmentWeight = newMaxEnrollmentWeight;
    412                }
    413            }
    414        }
    415
    416        /**
    417         * Enrollment weight -- weight of all requests that are enrolled into this course,
    418         * excluding the given one. See
    419         * {@link Request#getWeight()}.
    420         * @param assignment current assignment
    421         * @param excludeRequest request to exclude
    422         * @return enrollment weight
    423         */
    424        public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
    425            double weight = iEnrollmentWeight;
    426            if (excludeRequest != null) {
    427                Enrollment enrollment = assignment.getValue(excludeRequest);
    428                if (enrollment!= null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
    429                    weight -= excludeRequest.getWeight();
    430            }
    431            return weight;
    432        }
    433
    434        /** Set of assigned enrollments
    435         * @return assigned enrollments for this course offering
    436         **/
    437        public Set<Enrollment> getEnrollments() {
    438            return iEnrollments;
    439        }
    440
    441        /**
    442         * Maximal weight of a single enrollment in the course
    443         * @return maximal enrollment weight
    444         */
    445        public double getMaxEnrollmentWeight() {
    446            return iMaxEnrollmentWeight;
    447        }
    448
    449        /**
    450         * Minimal weight of a single enrollment in the course
    451         * @return minimal enrollment weight
    452         */
    453        public double getMinEnrollmentWeight() {
    454            return iMinEnrollmentWeight;
    455        }
    456    }
    457
    458    public double getOnlineBound() {
    459        double bound = 1.0;
    460        for (Config config: getOffering().getConfigs()) {
    461            int online = config.getNrOnline();
    462            if (online == 0) return 0.0;
    463            double factor = ((double)online) / config.getSubparts().size();
    464            if (factor < bound) bound = factor;
    465        }
    466        return bound;
    467    }
    468
    469    public double getArrHrsBound() {
    470        double bound = 1.0;
    471        for (Config config: getOffering().getConfigs()) {
    472            int arrHrs = config.getNrArrHours();
    473            if (arrHrs == 0) return 0.0;
    474            double factor = ((double)arrHrs) / config.getSubparts().size();
    475            if (factor < bound) bound = factor;
    476        }
    477        return bound;
    478    }
    479
    480    public double getPastBound() {
    481        double bound = 1.0;
    482        for (Config config: getOffering().getConfigs()) {
    483            int past = config.getNrPast();
    484            if (past == 0) return 0.0;
    485            double factor = ((double)past) / config.getSubparts().size();
    486            if (factor < bound) bound = factor;
    487        }
    488        return bound;
    489    }
    490
    491    /**
    492     * Course title
    493     */
    494    public String getTitle() {
    495        return iTitle;
    496    }
    497
    498    /**
    499     * Course title
    500     */
    501    public void setTitle(String title) {
    502        iTitle = title;
    503    }
    504
    505    /**
    506     * Course type
    507     */
    508    public String getType() {
    509        return iType;
    510    }
    511
    512    /**
    513     * Course type
    514     */
    515    public void setType(String type) {
    516        iType = type;
    517    }
    518}