    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.assignment.context.AbstractClassWithContext;
    010import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
    011import org.cpsolver.ifs.assignment.context.CanInheritContext;
    012import org.cpsolver.ifs.model.Model;
    013import org.cpsolver.studentsct.reservation.Reservation;
    014
    015
    016
    017
    018/**
    019 * Representation of a configuration of an offering. A configuration contains
    020 * id, name, an offering and a list of subparts. <br>
    021 * <br>
    022 * Each instructional offering (see {@link Offering}) contains one or more
    023 * configurations. Each configuration contain one or more subparts. Each student
    024 * has to take a class of each subpart of one of the possible configurations.
    025 *
    026 * <br>
    027 * <br>
    028 *
    029 * @version StudentSct 1.3 (Student Sectioning)<br>
    030 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    031 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    032 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    033 * <br>
    034 *          This library is free software; you can redistribute it and/or modify
    035 *          it under the terms of the GNU Lesser General Public License as
    036 *          published by the Free Software Foundation; either version 3 of the
    037 *          License, or (at your option) any later version. <br>
    038 * <br>
    039 *          This library is distributed in the hope that it will be useful, but
    040 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    041 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    042 *          Lesser General Public License for more details. <br>
    043 * <br>
    044 *          You should have received a copy of the GNU Lesser General Public
    045 *          License along with this library; if not see
    046 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    047 */
    048public class Config extends AbstractClassWithContext<Request, Enrollment, Config.ConfigContext> implements CanInheritContext<Request, Enrollment, Config.ConfigContext> {
    049    private long iId = -1;
    050    private String iName = null;
    051    private Offering iOffering = null;
    052    private int iLimit = -1;
    053    private List<Subpart> iSubparts = new ArrayList<Subpart>();
    054    private Long iInstrMethodId;
    055    private String iInstrMethodName;
    056    private String iInstrMethodReference;
    057
    058    /**
    059     * Constructor
    060     *
    061     * @param id
    062     *            instructional offering configuration unique id
    063     * @param limit
    064     *            configuration limit (-1 for unlimited)
    065     * @param name
    066     *            configuration name
    067     * @param offering
    068     *            instructional offering to which this configuration belongs
    069     */
    070    public Config(long id, int limit, String name, Offering offering) {
    071        iId = id;
    072        iLimit = limit;
    073        iName = name;
    074        iOffering = offering;
    075        iOffering.getConfigs().add(this);
    076    }
    077
    078    /** Configuration id
    079     * @return instructional offering configuration unique id
    080     **/
    081    public long getId() {
    082        return iId;
    083    }
    084
    085    /**
    086     * Configuration limit. This is defines the maximal number of students that can be
    087     * enrolled into this configuration at the same time. It is -1 in the case of an
    088     * unlimited configuration
    089     * @return configuration limit
    090     */
    091    public int getLimit() {
    092        return iLimit;
    093    }
    094
    095    /** Set configuration limit
    096     * @param limit configuration limit, -1 if unlimited
    097     **/
    098    public void setLimit(int limit) {
    099        iLimit = limit;
    100    }
    101
    102
    103
    104    /** Configuration name
    105     * @return configuration name
    106     **/
    107    public String getName() {
    108        return iName;
    109    }
    110
    111    /** Instructional offering to which this configuration belongs.
    112     * @return instructional offering
    113     **/
    114    public Offering getOffering() {
    115        return iOffering;
    116    }
    117
    118    /** List of subparts
    119     * @return scheduling subparts
    120     **/
    121    public List<Subpart> getSubparts() {
    122        return iSubparts;
    123    }
    124
    125    /**
    126     * Return instructional method id
    127     * @return instructional method id
    128     */
    129    public Long getInstructionalMethodId() { return iInstrMethodId; }
    130
    131    /**
    132     * Set instructional method id
    133     * @param instrMethodId instructional method id
    134     */
    135    public void setInstructionalMethodId(Long instrMethodId) { iInstrMethodId = instrMethodId; }
    136
    137    /**
    138     * Return instructional method name
    139     * @return instructional method name
    140     */
    141    public String getInstructionalMethodName() { return iInstrMethodName; }
    142
    143    /**
    144     * Set instructional method name
    145     * @param instrMethodName instructional method name
    146     */
    147    public void setInstructionalMethodName(String instrMethodName) { iInstrMethodName = instrMethodName; }
    148
    149    /**
    150     * Return instructional method reference
    151     * @return instructional method reference
    152     */
    153    public String getInstructionalMethodReference() { return iInstrMethodReference; }
    154
    155    /**
    156     * Set instructional method reference
    157     * @param instrMethodReference instructional method reference
    158     */
    159    public void setInstructionalMethodReference(String instrMethodReference) { iInstrMethodReference = instrMethodReference; }
    160
    161    @Override
    162    public String toString() {
    163        return getName();
    164    }
    165
    166    /** Average minimal penalty from {@link Subpart#getMinPenalty()}
    167     * @return minimal penalty
    168     **/
    169    public double getMinPenalty() {
    170        double min = 0.0;
    171        for (Subpart subpart : getSubparts()) {
    172            min += subpart.getMinPenalty();
    173        }
    174        return min / getSubparts().size();
    175    }
    176
    177    /** Average maximal penalty from {@link Subpart#getMaxPenalty()}
    178     * @return maximal penalty
    179     **/
    180    public double getMaxPenalty() {
    181        double max = 0.0;
    182        for (Subpart subpart : getSubparts()) {
    183            max += subpart.getMinPenalty();
    184        }
    185        return max / getSubparts().size();
    186    }
    187
    188    /**
    189     * Available space in the configuration that is not reserved by any config reservation
    190     * @param assignment current assignment
    191     * @param excludeRequest excluding given request (if not null)
    192     * @return available space
    193     **/
    194    public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
    195        // configuration is unlimited -> there is unreserved space unless there is an unlimited reservation too
    196        // (in which case there is no unreserved space)
    197        if (getLimit() < 0) {
    198            // exclude reservations that are not directly set on this section
    199            for (Reservation r: getConfigReservations()) {
    200                // ignore expired reservations
    201                if (r.isExpired()) continue;
    202                // there is an unlimited reservation -> no unreserved space
    203                if (r.getLimit() < 0) return 0.0;
    204            }
    205            return Double.MAX_VALUE;
    206        }
    207
    208        double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
    209        // exclude reservations that are not directly set on this section
    210        for (Reservation r: getConfigReservations()) {
    211            // ignore expired reservations
    212            if (r.isExpired()) continue;
    213            // unlimited reservation -> all the space is reserved
    214            if (r.getLimit() < 0.0) return 0.0;
    215            // compute space that can be potentially taken by this reservation
    216            double reserved = r.getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest);
    217            // deduct the space from available space
    218            available -= Math.max(0.0, reserved);
    219        }
    220
    221        return available;
    222    }
    223
    224    /**
    225     * Total space in the configuration that cannot be reserved by any config reservation
    226     * @return total unreserved space
    227     **/
    228    public synchronized double getTotalUnreservedSpace() {
    229        if (iTotalUnreservedSpace == null)
    230            iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
    231        return iTotalUnreservedSpace;
    232    }
    233    private Double iTotalUnreservedSpace = null;
    234    private double getTotalUnreservedSpaceNoCache() {
    235        // configuration is unlimited -> there is unreserved space unless there is an unlimited reservation too
    236        // (in which case there is no unreserved space)
    237        if (getLimit() < 0) {
    238            // exclude reservations that are not directly set on this section
    239            for (Reservation r: getConfigReservations()) {
    240                // ignore expired reservations
    241                if (r.isExpired()) continue;
    242                // there is an unlimited reservation -> no unreserved space
    243                if (r.getLimit() < 0) return 0.0;
    244            }
    245            return Double.MAX_VALUE;
    246        }
    247
    248        // we need to check all reservations linked with this section
    249        double available = getLimit(), reserved = 0, exclusive = 0;
    250        Set<Config> configs = new HashSet<Config>();
    251        reservations: for (Reservation r: getConfigReservations()) {
    252            // ignore expired reservations
    253            if (r.isExpired()) continue;
    254            // unlimited reservation -> no unreserved space
    255            if (r.getLimit() < 0) return 0.0;
    256            for (Config s: r.getConfigs()) {
    257                if (s.equals(this)) continue;
    258                if (s.getLimit() < 0) continue reservations;
    259                if (configs.add(s))
    260                    available += s.getLimit();
    261            }
    262            reserved += r.getLimit();
    263            if (r.getConfigs().size() == 1)
    264                exclusive += r.getLimit();
    265        }
    266
    267        return Math.min(available - reserved, getLimit() - exclusive);
    268    }
    269
    270    /**
    271     * Get reservations for this configuration
    272     * @return related reservations
    273     */
    274    public synchronized List<Reservation> getReservations() {
    275        if (iReservations == null) {
    276            iReservations = new ArrayList<Reservation>();
    277            for (Reservation r: getOffering().getReservations()) {
    278                if (r.getConfigs().isEmpty() || r.getConfigs().contains(this))
    279                    iReservations.add(r);
    280            }
    281        }
    282        return iReservations;
    283    }
    284    List<Reservation> iReservations = null;
    285
    286    /**
    287     * Get reservations that require this configuration
    288     * @return related reservations
    289     */
    290    public synchronized List<Reservation> getConfigReservations() {
    291        if (iConfigReservations == null) {
    292            iConfigReservations = new ArrayList<Reservation>();
    293            for (Reservation r: getOffering().getReservations()) {
    294                if (!r.getConfigs().isEmpty() && r.getConfigs().contains(this))
    295                    iConfigReservations.add(r);
    296            }
    297        }
    298        return iConfigReservations;
    299    }
    300    List<Reservation> iConfigReservations = null;
    301
    302    /**
    303     * Clear reservation information that was cached on this configuration or below
    304     */
    305    public synchronized void clearReservationCache() {
    306        for (Subpart s: getSubparts())
    307            s.clearReservationCache();
    308        iReservations = null;
    309        iConfigReservations = null;
    310        iTotalUnreservedSpace = null;
    311    }
    312
    313    @Override
    314    public boolean equals(Object o) {
    315        if (o == null || !(o instanceof Config)) return false;
    316        return getId() == ((Config)o).getId();
    317    }
    318
    319    @Override
    320    public int hashCode() {
    321        return new Long(getId()).hashCode();
    322    }
    323
    324    @Override
    325    public Model<Request, Enrollment> getModel() {
    326        return getOffering().getModel();
    327    }
    328
    329    /** Set of assigned enrollments
    330     * @param assignment current assignment
    331     * @return enrollments in this configuration
    332     **/
    333    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
    334        return getContext(assignment).getEnrollments();
    335    }
    336
    337    /**
    338     * Enrollment weight -- weight of all requests which have an enrollment that
    339     * contains this config, excluding the given one. See
    340     * {@link Request#getWeight()}.
    341     * @param assignment current assignment
    342     * @param excludeRequest request to exclude, null if all requests are to be included
    343     * @return enrollment weight
    344     */
    345    public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
    346        return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
    347    }
    348
    349    /**
    350     * Enrollment weight including over the limit enrollments.
    351     * That is enrollments that have reservation with {@link Reservation#canBatchAssignOverLimit()} set to true.
    352     * {@link Request#getWeight()}.
    353     * @param assignment current assignment
    354     * @param excludeRequest request to exclude, null if all requests are to be included
    355     * @return enrollment weight
    356     */
    357    public double getEnrollmentTotalWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
    358        return getContext(assignment).getEnrollmentTotalWeight(assignment, excludeRequest);
    359    }
    360
    361    /**
    362     * Maximal weight of a single enrollment in the config
    363     * @param assignment current assignment
    364     * @return maximal enrollment weight
    365     */
    366    public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
    367        return getContext(assignment).getMaxEnrollmentWeight();
    368    }
    369
    370    /**
    371     * Minimal weight of a single enrollment in the config
    372     * @param assignment current assignment
    373     * @return minimal enrollment weight
    374     */
    375    public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
    376        return getContext(assignment).getMinEnrollmentWeight();
    377    }
    378
    379    @Override
    380    public ConfigContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
    381        return new ConfigContext(assignment);
    382    }
    383
    384
    385    @Override
    386    public ConfigContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, ConfigContext parentContext) {
    387        return new ConfigContext(parentContext);
    388    }
    389
    390    public class ConfigContext implements AssignmentConstraintContext<Request, Enrollment> {
    391        private double iEnrollmentWeight = 0.0;
    392        private double iEnrollmentTotalWeight = 0.0;
    393        private double iMaxEnrollmentWeight = 0.0;
    394        private double iMinEnrollmentWeight = 0.0;
    395        private Set<Enrollment> iEnrollments = null;
    396        private boolean iReadOnly = false;
    397
    398        public ConfigContext(Assignment<Request, Enrollment> assignment) {
    399            iEnrollments = new HashSet<Enrollment>();
    400            for (Course course: getOffering().getCourses()) {
    401                for (CourseRequest request: course.getRequests()) {
    402                    Enrollment enrollment = assignment.getValue(request);
    403                    if (enrollment != null && Config.this.equals(enrollment.getConfig()))
    404                        assigned(assignment, enrollment);
    405                }
    406            }
    407        }
    408
    409        public ConfigContext(ConfigContext parent) {
    410            iEnrollmentWeight = parent.iEnrollmentWeight;
    411            iEnrollmentTotalWeight = parent.iEnrollmentTotalWeight;
    412            iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight;
    413            iMinEnrollmentWeight = parent.iMinEnrollmentWeight;
    414            iEnrollments = parent.iEnrollments;
    415            iReadOnly = true;
    416        }
    417
    418        /** Called when an enrollment with this config is assigned to a request */
    419        @Override
    420        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    421            if (iReadOnly) {
    422                iEnrollments = new HashSet<Enrollment>(iEnrollments);
    423                iReadOnly = false;
    424            }
    425            if (iEnrollments.isEmpty()) {
    426                iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
    427            } else {
    428                iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
    429                iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
    430            }
    431            if (iEnrollments.add(enrollment)) {
    432                iEnrollmentTotalWeight += enrollment.getRequest().getWeight();
    433                if (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())
    434                    iEnrollmentWeight += enrollment.getRequest().getWeight();
    435            }
    436        }
    437
    438        /** Called when an enrollment with this config is unassigned from a request */
    439        @Override
    440        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    441            if (iReadOnly) {
    442                iEnrollments = new HashSet<Enrollment>(iEnrollments);
    443                iReadOnly = false;
    444            }
    445            if (iEnrollments.remove(enrollment)) {
    446                iEnrollmentTotalWeight -= enrollment.getRequest().getWeight();
    447                if (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())
    448                    iEnrollmentWeight -= enrollment.getRequest().getWeight();
    449            }
    450            if (iEnrollments.isEmpty()) {
    451                iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
    452            } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) {
    453                if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) {
    454                    double newMinEnrollmentWeight = Double.MAX_VALUE;
    455                    for (Enrollment e : iEnrollments) {
    456                        if (e.getRequest().getWeight() == iMinEnrollmentWeight) {
    457                            newMinEnrollmentWeight = iMinEnrollmentWeight;
    458                            break;
    459                        } else {
    460                            newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
    461                        }
    462                    }
    463                    iMinEnrollmentWeight = newMinEnrollmentWeight;
    464                }
    465                if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) {
    466                    double newMaxEnrollmentWeight = Double.MIN_VALUE;
    467                    for (Enrollment e : iEnrollments) {
    468                        if (e.getRequest().getWeight() == iMaxEnrollmentWeight) {
    469                            newMaxEnrollmentWeight = iMaxEnrollmentWeight;
    470                            break;
    471                        } else {
    472                            newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
    473                        }
    474                    }
    475                    iMaxEnrollmentWeight = newMaxEnrollmentWeight;
    476                }
    477            }
    478        }
    479
    480        /**
    481         * Enrollment weight -- weight of all requests which have an enrollment that
    482         * contains this config, excluding the given one. See
    483         * {@link Request#getWeight()}.
    484         * @param assignment current assignment
    485         * @param excludeRequest request to exclude
    486         * @return enrollment weight
    487         */
    488        public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
    489            double weight = iEnrollmentWeight;
    490            if (excludeRequest != null) {
    491                Enrollment enrollment = assignment.getValue(excludeRequest);
    492                if (enrollment != null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
    493                    weight -= excludeRequest.getWeight();
    494            }
    495            return weight;
    496        }
    497
    498        /**
    499         * Enrollment weight including over the limit enrollments.
    500         * That is enrollments that have reservation with {@link Reservation#canBatchAssignOverLimit()} set to true.
    501         * @param assignment current assignment
    502         * @param excludeRequest request to exclude
    503         * @return enrollment weight
    504         */
    505        public double getEnrollmentTotalWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
    506            double weight = iEnrollmentTotalWeight;
    507            if (excludeRequest != null) {
    508                Enrollment enrollment = assignment.getValue(excludeRequest);
    509                if (enrollment != null && iEnrollments.contains(enrollment))
    510                    weight -= excludeRequest.getWeight();
    511            }
    512            return weight;
    513        }
    514
    515        /** Set of assigned enrollments
    516         * @return assigned enrollments (using this configuration)
    517         **/
    518        public Set<Enrollment> getEnrollments() {
    519            return iEnrollments;
    520        }
    521
    522        /**
    523         * Maximal weight of a single enrollment in the config
    524         * @return maximal enrollment weight
    525         */
    526        public double getMaxEnrollmentWeight() {
    527            return iMaxEnrollmentWeight;
    528        }
    529
    530        /**
    531         * Minimal weight of a single enrollment in the config
    532         * @return minimal enrollment weight
    533         */
    534        public double getMinEnrollmentWeight() {
    535            return iMinEnrollmentWeight;
    536        }
    537    }
    538
    539    /**
    540     * True if at least one subpart of this config has a credit value set
    541     * @return true if a there is a subpart credit
    542     */
    543    public boolean hasCreditValue() {
    544        for (Subpart subpart: getSubparts())
    545            if (subpart.hasCreditValue()) return true;
    546        return false;
    547    }
    548
    549    /**
    550     * Sum of subpart credit of this config
    551     * return config credit value
    552     */
    553    public Float getCreditValue() {
    554        float credit = 0f; boolean hasCredit = false;
    555        for (Subpart subpart: getSubparts())
    556            if (subpart.hasCreditValue()) {
    557                hasCredit = true;
    558                credit += subpart.getCreditValue();
    559            }
    560        return (hasCredit ? new Float(credit) : null);
    561    }
    562
    563    public int getNrOnline() {
    564        int online = 0;
    565        for (Subpart subpart: getSubparts())
    566            if (subpart.isOnline()) online ++;
    567        return online;
    568    }
    569
    570    public int getNrArrHours() {
    571        int arrHrs = 0;
    572        for (Subpart subpart: getSubparts())
    573            if (!subpart.hasTime()) arrHrs ++;
    574        return arrHrs;
    575    }
    576
    577    public int getNrPast() {
    578        int past = 0;
    579        for (Subpart subpart: getSubparts())
    580            if (subpart.isPast()) past ++;
    581        return past;
    582    }
    583}