001package org.cpsolver.studentsct.online.expectations;
002
003import org.cpsolver.ifs.assignment.Assignment;
004import org.cpsolver.ifs.util.DataProperties;
005import org.cpsolver.studentsct.model.Config;
006import org.cpsolver.studentsct.model.Enrollment;
007import org.cpsolver.studentsct.model.Request;
008import org.cpsolver.studentsct.model.Section;
009import org.cpsolver.studentsct.model.Subpart;
010import org.cpsolver.studentsct.online.OnlineConfig;
011import org.cpsolver.studentsct.online.OnlineSection;
012
013/**
014 * A class is considered over-expected, when there less space available than expected. The
015 * expectations can be increased by the given percentage (parameter OverExpected.Percentage,
016 * defaults to 1.0).
017 * Expectation rounding can be defined by OverExpected.Rounding parameter, defaults to round
018 * (other values are none, ceil, and floor).<br><br>
019 * Unlimited classes are never over-expected. A class is over-expected when the number of
020 * enrolled students (including the student in question) + expectations (multiplied by
021 * OverExpected.Percentage) is greater or equal the section limit.
022 *  
023 * 
024 * @author  Tomáš Müller
025 * @version StudentSct 1.3 (Student Sectioning)<br>
026 *          Copyright (C) 2014 Tomáš Müller<br>
027 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
028 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
029 * <br>
030 *          This library is free software; you can redistribute it and/or modify
031 *          it under the terms of the GNU Lesser General Public License as
032 *          published by the Free Software Foundation; either version 3 of the
033 *          License, or (at your option) any later version. <br>
034 * <br>
035 *          This library is distributed in the hope that it will be useful, but
036 *          WITHOUT ANY WARRANTY; without even the implied warranty of
037 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
038 *          Lesser General Public License for more details. <br>
039 * <br>
040 *          You should have received a copy of the GNU Lesser General Public
041 *          License along with this library; if not see <a
042 *          href='http://www.gnu.org/licenses'>http://www.gnu.org/licenses</a>.
043 * 
044 */
045public class PercentageOverExpected implements OverExpectedCriterion {
046    private Double iPercentage = null;
047    private Rounding iRounding = Rounding.ROUND;
048
049    /**
050     * Expectations rounding
051     */
052    public static enum Rounding {
053        /** no rounding */
054        NONE,
055        /** ceiling, using {@link Math#ceil(double)} */
056        CEIL,
057        /** floor, using {@link Math#floor(double)} */
058        FLOOR,
059        /** rounding, using {@link Math#round(double)} */
060        ROUND,
061    }
062
063    public PercentageOverExpected(DataProperties config) {
064        iPercentage = config.getPropertyDouble("OverExpected.Percentage", iPercentage);
065        iRounding = Rounding.valueOf(config.getProperty("OverExpected.Rounding", iRounding.name()).toUpperCase());
066    }
067
068    public PercentageOverExpected(Double percentage) {
069        super();
070        iPercentage = percentage;
071    }
072
073    public PercentageOverExpected() {
074        this((Double) null);
075    }
076
077    /**
078     * Over-expected percentage, defaults to 1.0
079     * @return expectations adjustment
080     */
081    public double getPercentage() {
082        return iPercentage == null ? 1.0 : iPercentage;
083    }
084
085    /**
086     * Over-expected percentage, defaults to 1.0
087     * @param percentage expectations adjustment
088     */
089    public void setPercentage(Double percentage) {
090        iPercentage = percentage;
091    }
092
093    /**
094     * Round the given value using the rounding from OverExpected.Rounding parameter
095     * @param value given value
096     * @return rounded value
097     */
098    protected double round(double value) {
099        switch (iRounding) {
100            case CEIL:
101                return Math.ceil(value);
102            case FLOOR:
103                return Math.floor(value);
104            case ROUND:
105                return Math.round(value);
106            default:
107                return value;
108        }
109    }
110
111    /**
112     * Check if there are expectations on any of the sections of the given subpart
113     * @param subpart given subpart
114     * @return true if there is at least one section with positive {@link Section#getSpaceExpected()}
115     */
116    protected boolean hasExpectations(Subpart subpart) {
117        for (Section section : subpart.getSections())
118            if (round(section.getSpaceExpected()) > 0.0)
119                return true;
120        return false;
121    }
122
123    /**
124     * Config enrollment (using {@link OnlineConfig#getEnrollment()} if applicable}, {@link Config#getEnrollmentWeight(Assignment, Request)} otherwise)
125     * @param assignment current assignment
126     * @param config given configuration
127     * @param request given request
128     * @return current enrollment of the section, excluding the request
129     */
130    protected double getEnrollment(Assignment<Request, Enrollment> assignment, Config config, Request request) {
131        if (config instanceof OnlineConfig) {
132            return ((OnlineConfig) config).getEnrollment();
133        } else {
134            return config.getEnrollmentWeight(assignment, request);
135        }
136    }
137
138    /**
139     * Section enrollment (using {@link OnlineSection#getEnrollment()} if applicable}, {@link Section#getEnrollmentWeight(Assignment, Request)} otherwise)
140     * @param assignment current assignment
141     * @param section given section
142     * @param request given request
143     * @return current enrollment of the section, excluding the request
144     */
145    protected double getEnrollment(Assignment<Request, Enrollment> assignment, Section section, Request request) {
146        if (section instanceof OnlineSection) {
147            return ((OnlineSection) section).getEnrollment();
148        } else {
149            return section.getEnrollmentWeight(assignment, request);
150        }
151    }
152
153    /**
154     * Section limit (using {@link OnlineSection#getEnrollment()} if applicable}, {@link Section#getLimit()} otherwise)
155     * @param section given section
156     * @return limit of the given section
157     */
158    protected int getLimit(Section section) {
159        if (section.getLimit() < 0)
160            return section.getLimit();
161        if (section instanceof OnlineSection) {
162            return section.getLimit() + ((OnlineSection) section).getEnrollment();
163        } else {
164            return section.getLimit();
165        }
166    }
167
168    /**
169     * Subpart limit (using {@link OnlineConfig#getEnrollment()} if applicable}, {@link Subpart#getLimit()} otherwise)
170     * @param subpart given subpart
171     * @return limit of the given subpart
172     */
173    protected int getLimit(Subpart subpart) {
174        int limit = subpart.getLimit();
175        if (limit < 0)
176            return limit;
177        if (subpart.getConfig() instanceof OnlineConfig)
178            limit += ((OnlineConfig) subpart.getConfig()).getEnrollment();
179        return limit;
180    }
181
182    @Override
183    public double getOverExpected(Assignment<Request, Enrollment> assignment, Section section, Request request) {
184        if (section.getLimit() <= 0)
185            return 0.0; // ignore unlimited & not available
186
187        double expected = round(getPercentage() * section.getSpaceExpected());
188        double enrolled = getEnrollment(assignment, section, request) + request.getWeight();
189        double limit = getLimit(section);
190        int subparts = section.getSubpart().getConfig().getSubparts().size();
191
192        return expected + enrolled > limit ? 1.0 / subparts : 0.0;
193    }
194
195    @Override
196    public Integer getExpected(int sectionLimit, double expectedSpace) {
197        if (sectionLimit <= 0)
198            return null;
199
200        double expected = round(getPercentage() * expectedSpace);
201        if (expected > 0.0)
202            return (int) Math.floor(expected);
203
204        return null;
205    }
206
207    @Override
208    public String toString() {
209        return "perc(" + getPercentage() + ")";
210    }
211
212}