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