    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 * @author  Tomáš Müller
    024 * @version StudentSct 1.3 (Student Sectioning)<br>
    025 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    026 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    027 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    028 * <br>
    029 *          This library is free software; you can redistribute it and/or modify
    030 *          it under the terms of the GNU Lesser General Public License as
    031 *          published by the Free Software Foundation; either version 3 of the
    032 *          License, or (at your option) any later version. <br>
    033 * <br>
    034 *          This library is distributed in the hope that it will be useful, but
    035 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    036 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    037 *          Lesser General Public License for more details. <br>
    038 * <br>
    039 *          You should have received a copy of the GNU Lesser General Public
    040 *          License along with this library; if not see
    041 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    042 */
    043public class Offering {
    044    private long iId = -1;
    045    private String iName = null;
    046    private Model<Request, Enrollment> iModel = null;
    047    private List<Config> iConfigs = new ArrayList<Config>();
    048    private List<Course> iCourses = new ArrayList<Course>();
    049    private List<Reservation> iReservations = new ArrayList<Reservation>();
    050    private List<Restriction> iRestrictions = new ArrayList<Restriction>();
    051    private boolean iDummy = false;
    052
    053    /**
    054     * Constructor
    055     *
    056     * @param id
    057     *            instructional offering unique id
    058     * @param name
    059     *            instructional offering name (this is usually the name of the
    060     *            controlling course)
    061     */
    062    public Offering(long id, String name) {
    063        iId = id;
    064        iName = name;
    065    }
    066
    067    /** Offering id
    068     * @return instructional offering unique id
    069     **/
    070    public long getId() {
    071        return iId;
    072    }
    073
    074    /** Offering name
    075     * @return instructional offering name
    076     **/
    077    public String getName() {
    078        return iName;
    079    }
    080
    081    /** Possible configurations
    082     * @return instructional offering configurations
    083     **/
    084    public List<Config> getConfigs() {
    085        return iConfigs;
    086    }
    087
    088    /**
    089     * List of courses. One instructional offering can contain multiple courses
    090     * (names under which it is offered)
    091     * @return list of course offerings
    092     */
    093    public List<Course> getCourses() {
    094        return iCourses;
    095    }
    096
    097    /**
    098     * Return section of the given id, if it is part of one of this offering
    099     * configurations.
    100     * @param sectionId class unique id
    101     * @return section of the given id
    102     */
    103    public Section getSection(long sectionId) {
    104        for (Config config : getConfigs()) {
    105            for (Subpart subpart : config.getSubparts()) {
    106                for (Section section : subpart.getSections()) {
    107                    if (section.getId() == sectionId)
    108                        return section;
    109                }
    110            }
    111        }
    112        return null;
    113    }
    114
    115    /** Return course, under which the given student enrolls into this offering.
    116     * @param student given student
    117     * @return course of this offering requested by the student
    118     **/
    119    public Course getCourse(Student student) {
    120        if (getCourses().isEmpty())
    121            return null;
    122        if (getCourses().size() == 1)
    123            return getCourses().get(0);
    124        for (Request request : student.getRequests()) {
    125            if (request instanceof CourseRequest) {
    126                for (Course course : ((CourseRequest) request).getCourses()) {
    127                    if (getCourses().contains(course))
    128                        return course;
    129                }
    130            }
    131        }
    132        return getCourses().get(0);
    133    }
    134
    135    /** Return set of instructional types, union over all configurations.
    136     * @return set of instructional types
    137     **/
    138    public Set<String> getInstructionalTypes() {
    139        Set<String> instructionalTypes = new HashSet<String>();
    140        for (Config config : getConfigs()) {
    141            for (Subpart subpart : config.getSubparts()) {
    142                instructionalTypes.add(subpart.getInstructionalType());
    143            }
    144        }
    145        return instructionalTypes;
    146    }
    147
    148    /**
    149     * Return the list of all possible choices of the given instructional type
    150     * for this offering.
    151     * @param instructionalType instructional type
    152     * @return set of choices of the given instructional type
    153     */
    154    public Set<Choice> getChoices(String instructionalType) {
    155        Set<Choice> choices = new HashSet<Choice>();
    156        for (Config config : getConfigs()) {
    157            for (Subpart subpart : config.getSubparts()) {
    158                if (!instructionalType.equals(subpart.getInstructionalType()))
    159                    continue;
    160                choices.addAll(subpart.getChoices());
    161            }
    162        }
    163        return choices;
    164    }
    165
    166    /**
    167     * Return list of all subparts of the given isntructional type for this
    168     * offering.
    169     * @param instructionalType instructional type
    170     * @return subpart of the given instructional type
    171     */
    172    public Set<Subpart> getSubparts(String instructionalType) {
    173        Set<Subpart> subparts = new HashSet<Subpart>();
    174        for (Config config : getConfigs()) {
    175            for (Subpart subpart : config.getSubparts()) {
    176                if (instructionalType.equals(subpart.getInstructionalType()))
    177                    subparts.add(subpart);
    178            }
    179        }
    180        return subparts;
    181    }
    182
    183    /** Minimal penalty from {@link Config#getMinPenalty()}
    184     * @return minimal penalty
    185     **/
    186    public double getMinPenalty() {
    187        double min = Double.MAX_VALUE;
    188        for (Config config : getConfigs()) {
    189            min = Math.min(min, config.getMinPenalty());
    190        }
    191        return (min == Double.MAX_VALUE ? 0.0 : min);
    192    }
    193
    194    /** Maximal penalty from {@link Config#getMaxPenalty()}
    195     * @return maximal penalty
    196     **/
    197    public double getMaxPenalty() {
    198        double max = Double.MIN_VALUE;
    199        for (Config config : getConfigs()) {
    200            max = Math.max(max, config.getMaxPenalty());
    201        }
    202        return (max == Double.MIN_VALUE ? 0.0 : max);
    203    }
    204
    205    @Override
    206    public String toString() {
    207        return iName;
    208    }
    209
    210    /** Reservations associated with this offering
    211     * @return reservations for this offering
    212     **/
    213    public List<Reservation> getReservations() { return iReservations; }
    214
    215    /** True if there are reservations for this offering
    216     * @return true if there is at least one reservation
    217     **/
    218    public boolean hasReservations() { return !iReservations.isEmpty(); }
    219
    220    /** Restrictions associated with this offering
    221     * @return restrictions for this offering
    222     **/
    223    public List<Restriction> getRestrictions() { return iRestrictions; }
    224
    225    /** True if there are restrictions for this offering
    226     * @return true if there is at least one restriction
    227     **/
    228    public boolean hasRestrictions() { return !iRestrictions.isEmpty(); }
    229
    230    /**
    231     * Total space in the offering that is not reserved by any reservation
    232     * @return total unreserved space in the offering
    233     **/
    234    public synchronized double getTotalUnreservedSpace() {
    235        if (iTotalUnreservedSpace == null)
    236            iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
    237        return iTotalUnreservedSpace;
    238    }
    239    Double iTotalUnreservedSpace = null;
    240    private double getTotalUnreservedSpaceNoCache() {
    241        // compute overall available space
    242        double available = 0.0;
    243        for (Config config: getConfigs()) {
    244            available += config.getLimit();
    245            // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too
    246            // (in which case there is no unreserved space)
    247            if (config.getLimit() < 0) {
    248                for (Reservation r: getReservations()) {
    249                    // ignore expired reservations
    250                    if (r.isExpired()) continue;
    251                    // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
    252                    if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
    253                    // there is an unlimited reservation -> no unreserved space
    254                    if (r.getLimit(config) < 0) return 0.0;
    255                }
    256                return Double.MAX_VALUE;
    257            }
    258        }
    259
    260        // compute maximal reserved space (out of the available space)
    261        double reserved = 0;
    262        for (Reservation r: getReservations()) {
    263            // ignore expired reservations
    264            if (r.isExpired()) continue;
    265            // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
    266            if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
    267            // unlimited reservation -> no unreserved space
    268            if (r.getLimit() < 0) return 0.0;
    269            reserved += r.getLimit();
    270        }
    271
    272        return Math.max(0.0, available - reserved);
    273    }
    274
    275    /**
    276     * Available space in the offering that is not reserved by any reservation
    277     * @param assignment current request
    278     * @param excludeRequest excluding given request (if not null)
    279     * @return remaining unreserved space in the offering
    280     **/
    281    public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
    282        // compute available space
    283        double available = 0.0;
    284        for (Config config: getConfigs()) {
    285            available += config.getLimit() - config.getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
    286            // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too
    287            // (in which case there is no unreserved space)
    288            if (config.getLimit() < 0) {
    289                for (Reservation r: getReservations()) {
    290                    // ignore expired reservations
    291                    if (r.isExpired()) continue;
    292                    // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
    293                    if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
    294                    // there is an unlimited reservation -> no unreserved space
    295                    if (r.getLimit(config) < 0) return 0.0;
    296                }
    297                return Double.MAX_VALUE;
    298            }
    299        }
    300
    301        // compute reserved space (out of the available space)
    302        double reserved = 0;
    303        for (Reservation r: getReservations()) {
    304            // ignore expired reservations
    305            if (r.isExpired()) continue;
    306            // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
    307            if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
    308            // unlimited reservation -> no unreserved space
    309            if (r.getLimit() < 0) return 0.0;
    310            reserved += Math.max(0.0, r.getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest));
    311        }
    312
    313        return available - reserved;
    314    }
    315
    316
    317    /**
    318     * Clear reservation information that was cached on this offering or below
    319     */
    320    public synchronized void clearReservationCache() {
    321        for (Config c: getConfigs())
    322            c.clearReservationCache();
    323        for (Course c: getCourses())
    324            for (CourseRequest r: c.getRequests())
    325                r.clearReservationCache();
    326        iTotalUnreservedSpace = null;
    327    }
    328
    329    /**
    330     * Clear restriction information that was cached on this offering or below
    331     */
    332    public synchronized void clearRestrictionCache() {
    333        for (Course c: getCourses())
    334            for (CourseRequest r: c.getRequests())
    335                r.clearRestrictionCache();
    336    }
    337
    338    @Override
    339    public boolean equals(Object o) {
    340        if (o == null || !(o instanceof Offering)) return false;
    341        return getId() == ((Offering)o).getId();
    342    }
    343
    344    @Override
    345    public int hashCode() {
    346        return (int) (iId ^ (iId >>> 32));
    347    }
    348
    349    public Model<Request, Enrollment> getModel() { return iModel; }
    350    public void setModel(Model<Request, Enrollment> model) { iModel = model; }
    351
    352    /**
    353     * Dummy courses that should show on the solver dashboard
    354     * (e.g., because they are loaded due to an other session unavailability)
    355     * @return true if the course is "dummy"
    356     */
    357    public boolean isDummy() { return iDummy; }
    358
    359    /**
    360     * Dummy courses that should show on the solver dashboard
    361     * (e.g., because they are loaded due to an other session unavailability)
    362    */
    363    public void setDummy(boolean dummy) { iDummy = dummy; }
    364}