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