    001package net.sf.cpsolver.studentsct.model;
    002
    003import java.util.ArrayList;
    004import java.util.HashSet;
    005import java.util.List;
    006import java.util.Set;
    007
    008import net.sf.cpsolver.studentsct.reservation.Reservation;
    009
    010
    011
    012/**
    013 * Representation of a configuration of an offering. A configuration contains
    014 * id, name, an offering and a list of subparts. <br>
    015 * <br>
    016 * Each instructional offering (see {@link Offering}) contains one or more
    017 * configurations. Each configuration contain one or more subparts. Each student
    018 * has to take a class of each subpart of one of the possible configurations.
    019 *
    020 * <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 */
    042public class Config {
    043    private long iId = -1;
    044    private String iName = null;
    045    private Offering iOffering = null;
    046    private int iLimit = -1;
    047    private List<Subpart> iSubparts = new ArrayList<Subpart>();
    048    private double iEnrollmentWeight = 0.0;
    049    private double iMaxEnrollmentWeight = 0.0;
    050    private double iMinEnrollmentWeight = 0.0;
    051    private Set<Enrollment> iEnrollments = new HashSet<Enrollment>();
    052
    053    /**
    054     * Constructor
    055     *
    056     * @param id
    057     *            instructional offering configuration unique id
    058     * @param limit
    059     *            configuration limit (-1 for unlimited)
    060     * @param name
    061     *            configuration name
    062     * @param offering
    063     *            instructional offering to which this configuration belongs
    064     */
    065    public Config(long id, int limit, String name, Offering offering) {
    066        iId = id;
    067        iLimit = limit;
    068        iName = name;
    069        iOffering = offering;
    070        iOffering.getConfigs().add(this);
    071    }
    072
    073    /** Configuration id */
    074    public long getId() {
    075        return iId;
    076    }
    077
    078    /**
    079     * Configuration limit. This is defines the maximal number of students that can be
    080     * enrolled into this configuration at the same time. It is -1 in the case of an
    081     * unlimited configuration
    082     */
    083    public int getLimit() {
    084        return iLimit;
    085    }
    086
    087    /** Set configuration limit */
    088    public void setLimit(int limit) {
    089        iLimit = limit;
    090    }
    091
    092
    093
    094    /** Configuration name */
    095    public String getName() {
    096        return iName;
    097    }
    098
    099    /** Instructional offering to which this configuration belongs. */
    100    public Offering getOffering() {
    101        return iOffering;
    102    }
    103
    104    /** List of subparts */
    105    public List<Subpart> getSubparts() {
    106        return iSubparts;
    107    }
    108
    109    @Override
    110    public String toString() {
    111        return getName();
    112    }
    113
    114    /** Average minimal penalty from {@link Subpart#getMinPenalty()} */
    115    public double getMinPenalty() {
    116        double min = 0.0;
    117        for (Subpart subpart : getSubparts()) {
    118            min += subpart.getMinPenalty();
    119        }
    120        return min / getSubparts().size();
    121    }
    122
    123    /** Average maximal penalty from {@link Subpart#getMaxPenalty()} */
    124    public double getMaxPenalty() {
    125        double max = 0.0;
    126        for (Subpart subpart : getSubparts()) {
    127            max += subpart.getMinPenalty();
    128        }
    129        return max / getSubparts().size();
    130    }
    131
    132    /** Called when an enrollment with this config is assigned to a request */
    133    public void assigned(Enrollment enrollment) {
    134        if (iEnrollments.isEmpty()) {
    135            iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
    136        } else {
    137            iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
    138            iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
    139        }
    140        iEnrollments.add(enrollment);
    141        iEnrollmentWeight += enrollment.getRequest().getWeight();
    142    }
    143
    144    /** Called when an enrollment with this config is unassigned from a request */
    145    public void unassigned(Enrollment enrollment) {
    146        iEnrollments.remove(enrollment);
    147        iEnrollmentWeight -= enrollment.getRequest().getWeight();
    148        if (iEnrollments.isEmpty()) {
    149            iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
    150        } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) {
    151            if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) {
    152                double newMinEnrollmentWeight = Double.MAX_VALUE;
    153                for (Enrollment e : iEnrollments) {
    154                    if (e.getRequest().getWeight() == iMinEnrollmentWeight) {
    155                        newMinEnrollmentWeight = iMinEnrollmentWeight;
    156                        break;
    157                    } else {
    158                        newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
    159                    }
    160                }
    161                iMinEnrollmentWeight = newMinEnrollmentWeight;
    162            }
    163            if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) {
    164                double newMaxEnrollmentWeight = Double.MIN_VALUE;
    165                for (Enrollment e : iEnrollments) {
    166                    if (e.getRequest().getWeight() == iMaxEnrollmentWeight) {
    167                        newMaxEnrollmentWeight = iMaxEnrollmentWeight;
    168                        break;
    169                    } else {
    170                        newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
    171                    }
    172                }
    173                iMaxEnrollmentWeight = newMaxEnrollmentWeight;
    174            }
    175        }
    176    }
    177    /**
    178     * Enrollment weight -- weight of all requests which have an enrollment that
    179     * contains this config, excluding the given one. See
    180     * {@link Request#getWeight()}.
    181     */
    182    public double getEnrollmentWeight(Request excludeRequest) {
    183        double weight = iEnrollmentWeight;
    184        if (excludeRequest != null && excludeRequest.getAssignment() != null
    185                && iEnrollments.contains(excludeRequest.getAssignment()))
    186            weight -= excludeRequest.getWeight();
    187        return weight;
    188    }
    189
    190    /** Set of assigned enrollments */
    191    public Set<Enrollment> getEnrollments() {
    192        return iEnrollments;
    193    }
    194
    195    /**
    196     * Maximal weight of a single enrollment in the config
    197     */
    198    public double getMaxEnrollmentWeight() {
    199        return iMaxEnrollmentWeight;
    200    }
    201
    202    /**
    203     * Minimal weight of a single enrollment in the config
    204     */
    205    public double getMinEnrollmentWeight() {
    206        return iMinEnrollmentWeight;
    207    }
    208
    209    /**
    210     * Available space in the configuration that is not reserved by any config reservation
    211     * @param excludeRequest excluding given request (if not null)
    212     **/
    213    public double getUnreservedSpace(Request excludeRequest) {
    214        // configuration is unlimited -> there is unreserved space unless there is an unlimited reservation too
    215        // (in which case there is no unreserved space)
    216        if (getLimit() < 0) {
    217            // exclude reservations that are not directly set on this section
    218            for (Reservation r: getConfigReservations()) {
    219                // ignore expired reservations
    220                if (r.isExpired()) continue;
    221                // there is an unlimited reservation -> no unreserved space
    222                if (r.getLimit() < 0) return 0.0;
    223            }
    224            return Double.MAX_VALUE;
    225        }
    226
    227        double available = getLimit() - getEnrollmentWeight(excludeRequest);
    228        // exclude reservations that are not directly set on this section
    229        for (Reservation r: getConfigReservations()) {
    230            // ignore expired reservations
    231            if (r.isExpired()) continue;
    232            // unlimited reservation -> all the space is reserved
    233            if (r.getLimit() < 0.0) return 0.0;
    234            // compute space that can be potentially taken by this reservation
    235            double reserved = r.getReservedAvailableSpace(excludeRequest);
    236            // deduct the space from available space
    237            available -= Math.max(0.0, reserved);
    238        }
    239
    240        return available;
    241    }
    242
    243    /**
    244     * Total space in the configuration that cannot be reserved by any config reservation
    245     **/
    246    public double getTotalUnreservedSpace() {
    247        if (iTotalUnreservedSpace == null)
    248            iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
    249        return iTotalUnreservedSpace;
    250    }
    251    private Double iTotalUnreservedSpace = null;
    252    private double getTotalUnreservedSpaceNoCache() {
    253        // configuration is unlimited -> there is unreserved space unless there is an unlimited reservation too
    254        // (in which case there is no unreserved space)
    255        if (getLimit() < 0) {
    256            // exclude reservations that are not directly set on this section
    257            for (Reservation r: getConfigReservations()) {
    258                // ignore expired reservations
    259                if (r.isExpired()) continue;
    260                // there is an unlimited reservation -> no unreserved space
    261                if (r.getLimit() < 0) return 0.0;
    262            }
    263            return Double.MAX_VALUE;
    264        }
    265
    266        // we need to check all reservations linked with this section
    267        double available = getLimit(), reserved = 0, exclusive = 0;
    268        Set<Config> configs = new HashSet<Config>();
    269        reservations: for (Reservation r: getConfigReservations()) {
    270            // ignore expired reservations
    271            if (r.isExpired()) continue;
    272            // unlimited reservation -> no unreserved space
    273            if (r.getLimit() < 0) return 0.0;
    274            for (Config s: r.getConfigs()) {
    275                if (s.equals(this)) continue;
    276                if (s.getLimit() < 0) continue reservations;
    277                if (configs.add(s))
    278                    available += s.getLimit();
    279            }
    280            reserved += r.getLimit();
    281            if (r.getConfigs().size() == 1)
    282                exclusive += r.getLimit();
    283        }
    284
    285        return Math.min(available - reserved, getLimit() - exclusive);
    286    }
    287
    288    /**
    289     * Get reservations for this configuration
    290     */
    291    public List<Reservation> getReservations() {
    292        if (iReservations == null) {
    293            iReservations = new ArrayList<Reservation>();
    294            for (Reservation r: getOffering().getReservations()) {
    295                if (r.getConfigs().isEmpty() || r.getConfigs().contains(this))
    296                    iReservations.add(r);
    297            }
    298        }
    299        return iReservations;
    300    }
    301    List<Reservation> iReservations = null;
    302
    303    /**
    304     * Get reservations that require this configuration
    305     */
    306    public List<Reservation> getConfigReservations() {
    307        if (iConfigReservations == null) {
    308            iConfigReservations = new ArrayList<Reservation>();
    309            for (Reservation r: getOffering().getReservations()) {
    310                if (!r.getConfigs().isEmpty() && r.getConfigs().contains(this))
    311                    iConfigReservations.add(r);
    312            }
    313        }
    314        return iConfigReservations;
    315    }
    316    List<Reservation> iConfigReservations = null;
    317
    318    /**
    319     * Clear reservation information that was cached on this configuration or below
    320     */
    321    public void clearReservationCache() {
    322        for (Subpart s: getSubparts())
    323            s.clearReservationCache();
    324        iReservations = null;
    325        iConfigReservations = null;
    326        iTotalUnreservedSpace = null;
    327    }
    328
    329    @Override
    330    public boolean equals(Object o) {
    331        if (o == null || !(o instanceof Config)) return false;
    332        return getId() == ((Config)o).getId();
    333    }
    334
    335    @Override
    336    public int hashCode() {
    337        return new Long(getId()).hashCode();
    338    }
    339}