    001package org.cpsolver.studentsct.reservation;
    002
    003import java.util.HashMap;
    004import java.util.HashSet;
    005import java.util.Map;
    006import java.util.Set;
    007
    008import org.cpsolver.ifs.assignment.Assignment;
    009import org.cpsolver.ifs.assignment.AssignmentComparable;
    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;
    014import org.cpsolver.studentsct.StudentSectioningModel;
    015import org.cpsolver.studentsct.model.Config;
    016import org.cpsolver.studentsct.model.Course;
    017import org.cpsolver.studentsct.model.CourseRequest;
    018import org.cpsolver.studentsct.model.Enrollment;
    019import org.cpsolver.studentsct.model.Offering;
    020import org.cpsolver.studentsct.model.Request;
    021import org.cpsolver.studentsct.model.Section;
    022import org.cpsolver.studentsct.model.Student;
    023import org.cpsolver.studentsct.model.Subpart;
    024
    025
    026
    027/**
    028 * Abstract reservation. This abstract class allow some section, courses,
    029 * and other parts to be reserved to particular group of students. A reservation
    030 * can be unlimited (any number of students of that particular group can attend
    031 * a course, section, etc.) or with a limit (only given number of seats is
    032 * reserved to the students of the particular group).
    033 *
    034 * <br>
    035 * <br>
    036 *
    037 * @version StudentSct 1.3 (Student Sectioning)<br>
    038 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    039 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    040 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    041 * <br>
    042 *          This library is free software; you can redistribute it and/or modify
    043 *          it under the terms of the GNU Lesser General Public License as
    044 *          published by the Free Software Foundation; either version 3 of the
    045 *          License, or (at your option) any later version. <br>
    046 * <br>
    047 *          This library is distributed in the hope that it will be useful, but
    048 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    049 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    050 *          Lesser General Public License for more details. <br>
    051 * <br>
    052 *          You should have received a copy of the GNU Lesser General Public
    053 *          License along with this library; if not see
    054 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    055 */
    056public abstract class Reservation extends AbstractClassWithContext<Request, Enrollment, Reservation.ReservationContext>
    057    implements AssignmentComparable<Reservation, Request, Enrollment>, CanInheritContext<Request, Enrollment, Reservation.ReservationContext> {
    058    /** Reservation unique id */
    059    private long iId = 0;
    060
    061    /** Is reservation expired? */
    062    private boolean iExpired;
    063
    064    /** Instructional offering on which the reservation is set, required */
    065    private Offering iOffering;
    066
    067    /** One or more configurations, if applicable */
    068    private Set<Config> iConfigs = new HashSet<Config>();
    069
    070    /** One or more sections, if applicable */
    071    private Map<Subpart, Set<Section>> iSections = new HashMap<Subpart, Set<Section>>();
    072
    073    /** Reservation priority */
    074    private int iPriority = 100;
    075
    076    /** Must this reservation be used */
    077    private boolean iMustBeUsed = false;
    078
    079    /** Can assign over class / configuration / course limit */
    080    private boolean iCanAssignOverLimit = false;
    081
    082    /** Does this reservation allow for overlaps */
    083    private boolean iAllowOverlap = false;
    084
    085    /** Does this reservation allow for disabled sections */
    086    private boolean iAllowDisabled = false;
    087
    088    /** No enrollment is matching this reservation when set to true */
    089    private boolean iNeverIncluded = false;
    090
    091    /** Can break linked-sections constraint */
    092    private boolean iBreakLinkedSections = false;
    093
    094
    095    /**
    096     * Constructor
    097     * @param id reservation unique id
    098     * @param offering instructional offering on which the reservation is set
    099     * @param priority reservation priority
    100     * @param mustBeUsed must this reservation be used
    101     * @param canAssignOverLimit can assign over class / configuration / course limit
    102     * @param allowOverlap does this reservation allow for overlaps
    103     */
    104    public Reservation(long id, Offering offering, int priority, boolean mustBeUsed, boolean canAssignOverLimit, boolean allowOverlap) {
    105        iId = id;
    106        iOffering = offering;
    107        iOffering.getReservations().add(this);
    108        iOffering.clearReservationCache();
    109        iPriority = priority;
    110        iMustBeUsed = mustBeUsed;
    111        iCanAssignOverLimit = canAssignOverLimit;
    112        iAllowOverlap = allowOverlap;
    113    }
    114
    115    /**
    116     * Reservation  id
    117     * @return reservation unique id
    118     */
    119    public long getId() { return iId; }
    120
    121    /**
    122     * Reservation limit
    123     * @return reservation limit, -1 for unlimited
    124     */
    125    public abstract double getReservationLimit();
    126
    127
    128    /** Reservation priority (e.g., individual reservations first)
    129     * @return reservation priority
    130     **/
    131    public int getPriority() {
    132        return iPriority;
    133    }
    134
    135    /**
    136     * Set reservation priority (e.g., individual reservations first)
    137     * @param priority reservation priority
    138     */
    139    public void setPriority(int priority) {
    140        iPriority = priority;
    141    }
    142
    143    /**
    144     * Returns true if the student is applicable for the reservation
    145     * @param student a student
    146     * @return true if student can use the reservation to get into the course / configuration / section
    147     */
    148    public abstract boolean isApplicable(Student student);
    149
    150    /**
    151     * Instructional offering on which the reservation is set.
    152     * @return instructional offering
    153     */
    154    public Offering getOffering() { return iOffering; }
    155
    156    /**
    157     * One or more configurations on which the reservation is set (optional).
    158     * @return instructional offering configurations
    159     */
    160    public Set<Config> getConfigs() { return iConfigs; }
    161
    162    /**
    163     * Add a configuration (of the offering {@link Reservation#getOffering()}) to this reservation
    164     * @param config instructional offering configuration
    165     */
    166    public void addConfig(Config config) {
    167        iConfigs.add(config);
    168        clearLimitCapCache();
    169    }
    170
    171    /**
    172     * One or more sections on which the reservation is set (optional).
    173     * @return class restrictions
    174     */
    175    public Map<Subpart, Set<Section>> getSections() { return iSections; }
    176
    177    /**
    178     * One or more sections on which the reservation is set (optional).
    179     * @param subpart scheduling subpart
    180     * @return class restrictions for the given scheduling subpart
    181     */
    182    public Set<Section> getSections(Subpart subpart) {
    183        return iSections.get(subpart);
    184    }
    185
    186    /**
    187     * Add a section (of the offering {@link Reservation#getOffering()}) to this reservation.
    188     * This will also add all parent sections and the appropriate configuration to the offering.
    189     * @param section a class restriction
    190     */
    191    public void addSection(Section section, boolean inclusive) {
    192        if (inclusive) {
    193            addConfig(section.getSubpart().getConfig());
    194            while (section != null) {
    195                Set<Section> sections = iSections.get(section.getSubpart());
    196                if (sections == null) {
    197                    sections = new HashSet<Section>();
    198                    iSections.put(section.getSubpart(), sections);
    199                }
    200                sections.add(section);
    201                section = section.getParent();
    202            }
    203        } else {
    204            Set<Section> sections = iSections.get(section.getSubpart());
    205            if (sections == null) {
    206                sections = new HashSet<Section>();
    207                iSections.put(section.getSubpart(), sections);
    208            }
    209            sections.add(section);
    210        }
    211        clearLimitCapCache();
    212    }
    213
    214    public void addSection(Section section) {
    215        addSection(section, true);
    216    }
    217
    218    /**
    219     * Return true if the given enrollment meets the reservation.
    220     * @param enrollment given enrollment
    221     * @return true if the given enrollment meets the reservation
    222     */
    223    public boolean isIncluded(Enrollment enrollment) {
    224        // Never included flag is set -- return false
    225        if (neverIncluded()) return false;
    226
    227        // Free time request are never included
    228        if (enrollment.getConfig() == null) return false;
    229
    230        // Check the offering
    231        if (!iOffering.equals(enrollment.getConfig().getOffering())) return false;
    232
    233        if (areRestrictionsInclusive()) {
    234            // If there are configurations, check the configuration
    235            if (!iConfigs.isEmpty() && !iConfigs.contains(enrollment.getConfig())) return false;
    236
    237            // Check all the sections of the enrollment
    238            for (Section section: enrollment.getSections()) {
    239                Set<Section> sections = iSections.get(section.getSubpart());
    240                if (sections != null && !sections.contains(section))
    241                    return false;
    242            }
    243            return true;
    244        } else {
    245            // no restrictions -> true
    246            if (iConfigs.isEmpty() && iSections.isEmpty()) return true;
    247
    248            // configuration match -> true
    249            if (iConfigs.contains(enrollment.getConfig())) return true;
    250
    251            // section match -> true
    252            for (Section section: enrollment.getSections()) {
    253                Set<Section> sections = iSections.get(section.getSubpart());
    254                if (sections != null && sections.contains(section))
    255                    return true;
    256            }
    257
    258            // no match -> false
    259            return false;
    260        }
    261    }
    262
    263    /**
    264     * True if the enrollment can be done using this reservation
    265     * @param assignment current assignment
    266     * @param enrollment given enrollment
    267     * @return true if the given enrollment can be assigned
    268     */
    269    public boolean canEnroll(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    270        // Check if student can use this reservation
    271        if (!isApplicable(enrollment.getStudent())) return false;
    272
    273        // Check if the enrollment meets the reservation
    274        if (!isIncluded(enrollment)) return false;
    275
    276        // Check the limit
    277        return getLimit() < 0 || getContext(assignment).getUsedSpace() + enrollment.getRequest().getWeight() <= getLimit();
    278    }
    279
    280    /**
    281     * True if can go over the course / config / section limit. Only to be used in the online sectioning.
    282     * @return can assign over class / configuration / course limit
    283      */
    284    public boolean canAssignOverLimit() {
    285        return iCanAssignOverLimit;
    286    }
    287
    288    /**
    289     * True if the batch solver can assign the reservation over the course / config / section limit.
    290     * @return {@link Reservation#canAssignOverLimit()} and {@link StudentSectioningModel#getReservationCanAssignOverTheLimit()}
    291     */
    292    public boolean canBatchAssignOverLimit() {
    293        return canAssignOverLimit() && (iOffering.getModel() == null || ((StudentSectioningModel)iOffering.getModel()).getReservationCanAssignOverTheLimit());
    294    }
    295
    296    /**
    297     * Set to true if a student meeting this reservation can go over the course / config / section limit.
    298     * @param canAssignOverLimit can assign over class / configuration / course limit
    299     */
    300    public void setCanAssignOverLimit(boolean canAssignOverLimit) {
    301        iCanAssignOverLimit = canAssignOverLimit;
    302    }
    303
    304    /**
    305     * If true, student must use the reservation (if applicable). Expired reservations do not need to be used.
    306     * @return must this reservation be used
    307     */
    308    public boolean mustBeUsed() {
    309        return iMustBeUsed && !isExpired();
    310    }
    311
    312    /**
    313     * If true, student must use the reservation (if applicable). Expiration date is ignored.
    314     * @return must this reservation be used
    315     */
    316    public boolean mustBeUsedIgnoreExpiration() {
    317        return iMustBeUsed;
    318    }
    319
    320    /**
    321     * Set to true if the student must use the reservation (if applicable)
    322     * @param mustBeUsed must this reservation be used
    323     */
    324    public void setMustBeUsed(boolean mustBeUsed) {
    325        iMustBeUsed = mustBeUsed;
    326    }
    327
    328    /**
    329     * Reservation restrictivity (estimated percentage of enrollments that include this reservation, 1.0 reservation on the whole offering)
    330     * @return computed restrictivity
    331     */
    332    public double getRestrictivity() {
    333        if (iCachedRestrictivity == null) {
    334            boolean inclusive = areRestrictionsInclusive();
    335            if (getConfigs().isEmpty()) return 1.0;
    336            int nrChoices = 0, nrMatchingChoices = 0;
    337            for (Config config: getOffering().getConfigs()) {
    338                int x[] = nrChoices(config, 0, new HashSet<Section>(), getConfigs().contains(config), inclusive);
    339                nrChoices += x[0];
    340                nrMatchingChoices += x[1];
    341            }
    342            iCachedRestrictivity = ((double)nrMatchingChoices) / nrChoices;
    343        }
    344        return iCachedRestrictivity;
    345    }
    346    private Double iCachedRestrictivity = null;
    347
    348
    349    /** Number of choices and number of chaing choices in the given sub enrollment */
    350    private int[] nrChoices(Config config, int idx, HashSet<Section> sections, boolean matching, boolean inclusive) {
    351        if (config.getSubparts().size() == idx) {
    352            return new int[]{1, matching ? 1 : 0};
    353        } else {
    354            Subpart subpart = config.getSubparts().get(idx);
    355            Set<Section> matchingSections = getSections(subpart);
    356            int choicesThisSubpart = 0;
    357            int matchingChoicesThisSubpart = 0;
    358            for (Section section : subpart.getSections()) {
    359                if (section.getParent() != null && !sections.contains(section.getParent()))
    360                    continue;
    361                if (section.isOverlapping(sections))
    362                    continue;
    363                sections.add(section);
    364                boolean m = (inclusive
    365                        ? matching && (matchingSections == null || matchingSections.contains(section))
    366                        : matching || (matchingSections != null && matchingSections.contains(section))
    367                       );
    368                int[] x = nrChoices(config, 1 + idx, sections, m, inclusive);
    369                choicesThisSubpart += x[0];
    370                matchingChoicesThisSubpart += x[1];
    371                sections.remove(section);
    372            }
    373            return new int[] {choicesThisSubpart, matchingChoicesThisSubpart};
    374        }
    375    }
    376
    377    /**
    378     * Priority first, than restrictivity (more restrictive first), than availability (more available first), than id
    379     */
    380    @Override
    381    public int compareTo(Assignment<Request, Enrollment> assignment, Reservation r) {
    382        if (mustBeUsed() != r.mustBeUsed()) {
    383            return (mustBeUsed() ? -1 : 1);
    384        }
    385        if (getPriority() != r.getPriority()) {
    386            return (getPriority() < r.getPriority() ? -1 : 1);
    387        }
    388        int cmp = Double.compare(getRestrictivity(), r.getRestrictivity());
    389        if (cmp != 0) return cmp;
    390        cmp = - Double.compare(getContext(assignment).getReservedAvailableSpace(assignment, null), r.getContext(assignment).getReservedAvailableSpace(assignment, null));
    391        if (cmp != 0) return cmp;
    392        return new Long(getId()).compareTo(r.getId());
    393    }
    394
    395    /**
    396     * Priority first, than restrictivity (more restrictive first), than id
    397     */
    398    @Override
    399    public int compareTo(Reservation r) {
    400        if (mustBeUsed() != r.mustBeUsed()) {
    401            return (mustBeUsed() ? -1 : 1);
    402        }
    403        if (getPriority() != r.getPriority()) {
    404            return (getPriority() < r.getPriority() ? -1 : 1);
    405        }
    406        int cmp = Double.compare(getRestrictivity(), r.getRestrictivity());
    407        if (cmp != 0) return cmp;
    408        return new Long(getId()).compareTo(r.getId());
    409    }
    410
    411    /**
    412     * Return minimum of two limits where -1 counts as unlimited (any limit is smaller)
    413     */
    414    private static double min(double l1, double l2) {
    415        return (l1 < 0 ? l2 : l2 < 0 ? l1 : Math.min(l1, l2));
    416    }
    417
    418    /**
    419     * Add two limits where -1 counts as unlimited (unlimited plus anything is unlimited)
    420     */
    421    private static double add(double l1, double l2) {
    422        return (l1 < 0 ? -1 : l2 < 0 ? -1 : l1 + l2);
    423    }
    424
    425
    426    /** Limit cap cache */
    427    private Double iLimitCap = null;
    428
    429    /**
    430     * Compute limit cap (maximum number of students that can get into the offering using this reservation)
    431     * @return reservation limit cap
    432     */
    433    public double getLimitCap() {
    434        if (iLimitCap == null) iLimitCap = getLimitCapNoCache();
    435        return iLimitCap;
    436    }
    437
    438    /**
    439     * Check if restrictions are inclusive (that is for each section, the reservation also contains all its parents and the configuration)
    440     */
    441    public boolean areRestrictionsInclusive() {
    442        for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
    443            if (getConfigs().contains(entry.getKey().getConfig())) return true;
    444        }
    445        return false;
    446    }
    447
    448    /**
    449     * Compute limit cap (maximum number of students that can get into the offering using this reservation)
    450     */
    451    private double getLimitCapNoCache() {
    452        if (getConfigs().isEmpty()) return -1; // no config -> can be unlimited
    453
    454        if (canAssignOverLimit()) return -1; // can assign over limit -> no cap
    455
    456        double cap = 0;
    457        if (areRestrictionsInclusive()) {
    458            // for each config
    459            for (Config config: getConfigs()) {
    460                // config cap
    461                double configCap = config.getLimit();
    462
    463                for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
    464                    if (!config.equals(entry.getKey().getConfig())) continue;
    465                    Set<Section> sections = entry.getValue();
    466
    467                    // subpart cap
    468                    double subpartCap = 0;
    469                    for (Section section: sections)
    470                        subpartCap = add(subpartCap, section.getLimit());
    471
    472                    // minimize
    473                    configCap = min(configCap, subpartCap);
    474                }
    475
    476                // add config cap
    477                cap = add(cap, configCap);
    478            }
    479        } else {
    480            // for each config
    481            for (Config config: getConfigs())
    482               cap = add(cap, config.getLimit());
    483            // for each subpart
    484            for (Map.Entry<Subpart, Set<Section>> entry: getSections().entrySet()) {
    485                Set<Section> sections = entry.getValue();
    486                // subpart cap
    487                double subpartCap = 0;
    488                for (Section section: sections)
    489                    subpartCap = add(subpartCap, section.getLimit());
    490                cap = add(cap, subpartCap);
    491            }
    492        }
    493
    494        return cap;
    495    }
    496
    497    /**
    498     * Clear limit cap cache
    499     */
    500    private void clearLimitCapCache() {
    501        iLimitCap = null;
    502    }
    503
    504    /**
    505     * Reservation limit capped the limit cap (see {@link Reservation#getLimitCap()})
    506     * @return reservation limit, -1 if unlimited
    507     */
    508    public double getLimit() {
    509        return min(getLimitCap(), getReservationLimit());
    510    }
    511
    512    /**
    513     * True if holding this reservation allows a student to have attend overlapping class.
    514     * @return does this reservation allow for overlaps
    515     */
    516    public boolean isAllowOverlap() {
    517        return iAllowOverlap;
    518    }
    519
    520    /**
    521     * Set to true if holding this reservation allows a student to have attend overlapping class.
    522     * @param allowOverlap does this reservation allow for overlaps
    523     */
    524    public void setAllowOverlap(boolean allowOverlap) {
    525        iAllowOverlap = allowOverlap;
    526    }
    527
    528    /**
    529     * True if holding this reservation allows a student to attend a disabled class.
    530     * @return does this reservation allow for disabled sections
    531     */
    532    public boolean isAllowDisabled() {
    533        return iAllowDisabled;
    534    }
    535
    536    /**
    537     * Set to true if holding this reservation allows a student to attend a disabled class
    538     * @param allowDisabled does this reservation allow for disabled sections
    539     */
    540    public void setAllowDisabled(boolean allowDisabled) {
    541        iAllowDisabled = allowDisabled;
    542    }
    543
    544    /**
    545     * Set reservation expiration. If a reservation is expired, it works as ordinary reservation
    546     * (especially the flags mutBeUsed and isAllowOverlap), except it does not block other students
    547     * of getting into the offering / config / section.
    548     * @param expired is this reservation expired
    549     */
    550    public void setExpired(boolean expired) {
    551        iExpired = expired;
    552    }
    553
    554    /**
    555     * True if the reservation is expired. If a reservation is expired, it works as ordinary reservation
    556     * (especially the flags mutBeUsed and isAllowOverlap), except it does not block other students
    557     * of getting into the offering / config / section.
    558     * @return is this reservation expired
    559     */
    560    public boolean isExpired() {
    561        return iExpired;
    562    }
    563
    564    /**
    565     * No enrollment is matching this reservation when set to true
    566     */
    567    public boolean neverIncluded() { return iNeverIncluded; }
    568
    569    /**
    570     * No enrollment is matching this reservation when set to true
    571     */
    572    public void setNeverIncluded(boolean neverIncluded) { iNeverIncluded = neverIncluded; }
    573
    574    /**
    575     * Can break linked-section constraints when set to true
    576     */
    577    public boolean canBreakLinkedSections() { return iBreakLinkedSections; }
    578
    579    /**
    580     * Can break linked-section constraints when set to true
    581     */
    582    public void setBreakLinkedSections(boolean breakLinkedSections) { iBreakLinkedSections = breakLinkedSections; }
    583
    584
    585    @Override
    586    public Model<Request, Enrollment> getModel() {
    587        return getOffering().getModel();
    588    }
    589
    590    /**
    591     * Available reserved space
    592     * @param assignment current assignment
    593     * @param excludeRequest excluding given request (if not null)
    594     * @return available reserved space
    595     **/
    596    public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
    597        return getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest);
    598    }
    599
    600    /** Enrollments assigned using this reservation
    601     * @param assignment current assignment
    602     * @return assigned enrollments of this reservation
    603     **/
    604    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
    605        return getContext(assignment).getEnrollments();
    606    }
    607
    608    @Override
    609    public ReservationContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
    610        return new ReservationContext(assignment);
    611    }
    612
    613
    614    @Override
    615    public ReservationContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, ReservationContext parentContext) {
    616        return new ReservationContext(parentContext);
    617    }
    618
    619
    620    public class ReservationContext implements AssignmentConstraintContext<Request, Enrollment> {
    621        /** Enrollments included in this reservation */
    622        private Set<Enrollment> iEnrollments = new HashSet<Enrollment>();
    623
    624        /** Used part of the limit */
    625        private double iUsed = 0;
    626        private boolean iReadOnly = false;
    627
    628        public ReservationContext(Assignment<Request, Enrollment> assignment) {
    629            for (Course course: getOffering().getCourses())
    630                for (CourseRequest request: course.getRequests()) {
    631                    Enrollment enrollment = assignment.getValue(request);
    632                    if (enrollment != null && Reservation.this.equals(enrollment.getReservation()))
    633                        assigned(assignment, enrollment);
    634                }
    635        }
    636
    637        public ReservationContext(ReservationContext parent) {
    638            iUsed = parent.iUsed;
    639            iEnrollments = parent.iEnrollments;
    640            iReadOnly = true;
    641        }
    642
    643        /** Notify reservation about an unassignment */
    644        @Override
    645        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    646            if (iReadOnly) {
    647                iEnrollments = new HashSet<Enrollment>(iEnrollments);
    648                iReadOnly = false;
    649            }
    650            if (iEnrollments.add(enrollment))
    651                iUsed += enrollment.getRequest().getWeight();
    652        }
    653
    654        /** Notify reservation about an assignment */
    655        @Override
    656        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    657            if (iReadOnly) {
    658                iEnrollments = new HashSet<Enrollment>(iEnrollments);
    659                iReadOnly = false;
    660            }
    661            if (iEnrollments.remove(enrollment))
    662                iUsed -= enrollment.getRequest().getWeight();
    663        }
    664
    665        /** Enrollments assigned using this reservation
    666         * @return assigned enrollments of this reservation
    667         **/
    668        public Set<Enrollment> getEnrollments() {
    669            return iEnrollments;
    670        }
    671
    672        /** Used space
    673         * @return spaced used of this reservation
    674         **/
    675        public double getUsedSpace() {
    676            return iUsed;
    677        }
    678
    679        /**
    680         * Available reserved space
    681         * @param assignment current assignment
    682         * @param excludeRequest excluding given request (if not null)
    683         * @return available reserved space
    684         **/
    685        public double getReservedAvailableSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
    686            // Unlimited
    687            if (getLimit() < 0) return Double.MAX_VALUE;
    688
    689            double reserved = getLimit() - getContext(assignment).getUsedSpace();
    690            if (excludeRequest != null && assignment.getValue(excludeRequest) != null && iEnrollments.contains(assignment.getValue(excludeRequest)))
    691                reserved += excludeRequest.getWeight();
    692
    693            return reserved;
    694        }
    695    }
    696}