    001package org.cpsolver.studentsct.model;
    002
    003import java.util.ArrayList;
    004import java.util.HashSet;
    005import java.util.List;
    006import java.util.Set;
    007
    008import org.cpsolver.ifs.assignment.Assignment;
    009import org.cpsolver.ifs.model.Model;
    010import org.cpsolver.studentsct.reservation.Reservation;
    011import org.cpsolver.studentsct.reservation.Restriction;
    012
    013
    014
    015/**
    016 * Representation of an instructional offering. An offering contains id, name,
    017 * the list of course offerings, and the list of possible configurations. See
    018 * {@link Config} and {@link Course}.
    019 *
    020 * <br>
    021 * <br>
    022 *
    023 * @version StudentSct 1.3 (Student Sectioning)<br>
    024 *          Copyright (C) 2007 - 2014 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 */
    042public class Offering {
    043    private long iId = -1;
    044    private String iName = null;
    045    private Model<Request, Enrollment> iModel = null;
    046    private List<Config> iConfigs = new ArrayList<Config>();
    047    private List<Course> iCourses = new ArrayList<Course>();
    048    private List<Reservation> iReservations = new ArrayList<Reservation>();
    049    private List<Restriction> iRestrictions = new ArrayList<Restriction>();
    050
    051    /**
    052     * Constructor
    053     *
    054     * @param id
    055     *            instructional offering unique id
    056     * @param name
    057     *            instructional offering name (this is usually the name of the
    058     *            controlling course)
    059     */
    060    public Offering(long id, String name) {
    061        iId = id;
    062        iName = name;
    063    }
    064
    065    /** Offering id
    066     * @return instructional offering unique id
    067     **/
    068    public long getId() {
    069        return iId;
    070    }
    071
    072    /** Offering name
    073     * @return instructional offering name
    074     **/
    075    public String getName() {
    076        return iName;
    077    }
    078
    079    /** Possible configurations
    080     * @return instructional offering configurations
    081     **/
    082    public List<Config> getConfigs() {
    083        return iConfigs;
    084    }
    085
    086    /**
    087     * List of courses. One instructional offering can contain multiple courses
    088     * (names under which it is offered)
    089     * @return list of course offerings
    090     */
    091    public List<Course> getCourses() {
    092        return iCourses;
    093    }
    094
    095    /**
    096     * Return section of the given id, if it is part of one of this offering
    097     * configurations.
    098     * @param sectionId class unique id
    099     * @return section of the given id
    100     */
    101    public Section getSection(long sectionId) {
    102        for (Config config : getConfigs()) {
    103            for (Subpart subpart : config.getSubparts()) {
    104                for (Section section : subpart.getSections()) {
    105                    if (section.getId() == sectionId)
    106                        return section;
    107                }
    108            }
    109        }
    110        return null;
    111    }
    112
    113    /** Return course, under which the given student enrolls into this offering.
    114     * @param student given student
    115     * @return course of this offering requested by the student
    116     **/
    117    public Course getCourse(Student student) {
    118        if (getCourses().isEmpty())
    119            return null;
    120        if (getCourses().size() == 1)
    121            return getCourses().get(0);
    122        for (Request request : student.getRequests()) {
    123            if (request instanceof CourseRequest) {
    124                for (Course course : ((CourseRequest) request).getCourses()) {
    125                    if (getCourses().contains(course))
    126                        return course;
    127                }
    128            }
    129        }
    130        return getCourses().get(0);
    131    }
    132
    133    /** Return set of instructional types, union over all configurations.
    134     * @return set of instructional types
    135     **/
    136    public Set<String> getInstructionalTypes() {
    137        Set<String> instructionalTypes = new HashSet<String>();
    138        for (Config config : getConfigs()) {
    139            for (Subpart subpart : config.getSubparts()) {
    140                instructionalTypes.add(subpart.getInstructionalType());
    141            }
    142        }
    143        return instructionalTypes;
    144    }
    145
    146    /**
    147     * Return the list of all possible choices of the given instructional type
    148     * for this offering.
    149     * @param instructionalType instructional type
    150     * @return set of choices of the given instructional type
    151     */
    152    public Set<Choice> getChoices(String instructionalType) {
    153        Set<Choice> choices = new HashSet<Choice>();
    154        for (Config config : getConfigs()) {
    155            for (Subpart subpart : config.getSubparts()) {
    156                if (!instructionalType.equals(subpart.getInstructionalType()))
    157                    continue;
    158                choices.addAll(subpart.getChoices());
    159            }
    160        }
    161        return choices;
    162    }
    163
    164    /**
    165     * Return list of all subparts of the given isntructional type for this
    166     * offering.
    167     * @param instructionalType instructional type
    168     * @return subpart of the given instructional type
    169     */
    170    public Set<Subpart> getSubparts(String instructionalType) {
    171        Set<Subpart> subparts = new HashSet<Subpart>();
    172        for (Config config : getConfigs()) {
    173            for (Subpart subpart : config.getSubparts()) {
    174                if (instructionalType.equals(subpart.getInstructionalType()))
    175                    subparts.add(subpart);
    176            }
    177        }
    178        return subparts;
    179    }
    180
    181    /** Minimal penalty from {@link Config#getMinPenalty()}
    182     * @return minimal penalty
    183     **/
    184    public double getMinPenalty() {
    185        double min = Double.MAX_VALUE;
    186        for (Config config : getConfigs()) {
    187            min = Math.min(min, config.getMinPenalty());
    188        }
    189        return (min == Double.MAX_VALUE ? 0.0 : min);
    190    }
    191
    192    /** Maximal penalty from {@link Config#getMaxPenalty()}
    193     * @return maximal penalty
    194     **/
    195    public double getMaxPenalty() {
    196        double max = Double.MIN_VALUE;
    197        for (Config config : getConfigs()) {
    198            max = Math.max(max, config.getMaxPenalty());
    199        }
    200        return (max == Double.MIN_VALUE ? 0.0 : max);
    201    }
    202
    203    @Override
    204    public String toString() {
    205        return iName;
    206    }
    207
    208    /** Reservations associated with this offering
    209     * @return reservations for this offering
    210     **/
    211    public List<Reservation> getReservations() { return iReservations; }
    212
    213    /** True if there are reservations for this offering
    214     * @return true if there is at least one reservation
    215     **/
    216    public boolean hasReservations() { return !iReservations.isEmpty(); }
    217
    218    /** Restrictions associated with this offering
    219     * @return restrictions for this offering
    220     **/
    221    public List<Restriction> getRestrictions() { return iRestrictions; }
    222
    223    /** True if there are restrictions for this offering
    224     * @return true if there is at least one restriction
    225     **/
    226    public boolean hasRestrictions() { return !iRestrictions.isEmpty(); }
    227
    228    /**
    229     * Total space in the offering that is not reserved by any reservation
    230     * @return total unreserved space in the offering
    231     **/
    232    public synchronized double getTotalUnreservedSpace() {
    233        if (iTotalUnreservedSpace == null)
    234            iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
    235        return iTotalUnreservedSpace;
    236    }
    237    Double iTotalUnreservedSpace = null;
    238    private double getTotalUnreservedSpaceNoCache() {
    239        // compute overall available space
    240        double available = 0.0;
    241        for (Config config: getConfigs()) {
    242            available += config.getLimit();
    243            // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too
    244            // (in which case there is no unreserved space)
    245            if (config.getLimit() < 0) {
    246                for (Reservation r: getReservations()) {
    247                    // ignore expired reservations
    248                    if (r.isExpired()) continue;
    249                    // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
    250                    if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
    251                    // there is an unlimited reservation -> no unreserved space
    252                    if (r.getLimit() < 0) return 0.0;
    253                }
    254                return Double.MAX_VALUE;
    255            }
    256        }
    257
    258        // compute maximal reserved space (out of the available space)
    259        double reserved = 0;
    260        for (Reservation r: getReservations()) {
    261            // ignore expired reservations
    262            if (r.isExpired()) continue;
    263            // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
    264            if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
    265            // unlimited reservation -> no unreserved space
    266            if (r.getLimit() < 0) return 0.0;
    267            reserved += r.getLimit();
    268        }
    269
    270        return Math.max(0.0, available - reserved);
    271    }
    272
    273    /**
    274     * Available space in the offering that is not reserved by any reservation
    275     * @param assignment current request
    276     * @param excludeRequest excluding given request (if not null)
    277     * @return remaining unreserved space in the offering
    278     **/
    279    public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
    280        // compute available space
    281        double available = 0.0;
    282        for (Config config: getConfigs()) {
    283            available += config.getLimit() - config.getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
    284            // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too
    285            // (in which case there is no unreserved space)
    286            if (config.getLimit() < 0) {
    287                for (Reservation r: getReservations()) {
    288                    // ignore expired reservations
    289                    if (r.isExpired()) continue;
    290                    // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
    291                    if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
    292                    // there is an unlimited reservation -> no unreserved space
    293                    if (r.getLimit() < 0) return 0.0;
    294                }
    295                return Double.MAX_VALUE;
    296            }
    297        }
    298
    299        // compute reserved space (out of the available space)
    300        double reserved = 0;
    301        for (Reservation r: getReservations()) {
    302            // ignore expired reservations
    303            if (r.isExpired()) continue;
    304            // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
    305            if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
    306            // unlimited reservation -> no unreserved space
    307            if (r.getLimit() < 0) return 0.0;
    308            reserved += Math.max(0.0, r.getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest));
    309        }
    310
    311        return available - reserved;
    312    }
    313
    314
    315    /**
    316     * Clear reservation information that was cached on this offering or below
    317     */
    318    public synchronized void clearReservationCache() {
    319        for (Config c: getConfigs())
    320            c.clearReservationCache();
    321        for (Course c: getCourses())
    322            for (CourseRequest r: c.getRequests())
    323                r.clearReservationCache();
    324        iTotalUnreservedSpace = null;
    325    }
    326
    327    /**
    328     * Clear restriction information that was cached on this offering or below
    329     */
    330    public synchronized void clearRestrictionCache() {
    331        for (Course c: getCourses())
    332            for (CourseRequest r: c.getRequests())
    333                r.clearRestrictionCache();
    334    }
    335
    336    @Override
    337    public boolean equals(Object o) {
    338        if (o == null || !(o instanceof Offering)) return false;
    339        return getId() == ((Offering)o).getId();
    340    }
    341
    342    @Override
    343    public int hashCode() {
    344        return (int) (iId ^ (iId >>> 32));
    345    }
    346
    347    public Model<Request, Enrollment> getModel() { return iModel; }
    348    public void setModel(Model<Request, Enrollment> model) { iModel = model; }
    349}