001package org.cpsolver.studentsct.check;
002
003import java.text.DecimalFormat;
004
005import org.cpsolver.ifs.util.CSVFile;
006import org.cpsolver.studentsct.StudentSectioningModel;
007import org.cpsolver.studentsct.model.Config;
008import org.cpsolver.studentsct.model.Course;
009import org.cpsolver.studentsct.model.CourseRequest;
010import org.cpsolver.studentsct.model.Offering;
011import org.cpsolver.studentsct.model.Request;
012import org.cpsolver.studentsct.model.Section;
013import org.cpsolver.studentsct.model.Subpart;
014
015
016/**
017 * This class looks and reports cases when there are more students requesting a
018 * course than the course limit.
019 * 
020 * <br>
021 * <br>
022 * 
023 * Usage:
024 * <pre><code>
025 * &nbsp;&nbsp;&nbsp;&nbsp; CourseLimitCheck ch = new CourseLimitCheck(model);
026 * &nbsp;&nbsp;&nbsp;&nbsp; if (!ch.check()) ch.getCSVFile().save(new File("limits.csv"));
027 * </code></pre>
028 * 
029 * <br>
030 * Parameters:
031 * <table border='1'><caption>Related Solver Parameters</caption>
032 * <tr>
033 * <th>Parameter</th>
034 * <th>Type</th>
035 * <th>Comment</th>
036 * </tr>
037 * <tr>
038 * <td>CourseLimitCheck.FixUnlimited</td>
039 * <td>{@link Boolean}</td>
040 * <td>
041 * If true, courses with zero or positive limit, but with unlimited sections,
042 * are made unlimited (course limit is set to -1).</td>
043 * </tr>
044 * <tr>
045 * <td>CourseLimitCheck.UpZeroLimits</td>
046 * <td>{@link Boolean}</td>
047 * <td>
048 * If true, courses with zero limit, requested by one or more students are
049 * increased in limit in order to accomodate all students that request the
050 * course. Section limits are increased to ( total weight of all requests for
051 * the offering / sections in subpart).</td>
052 * </tr>
053 * <tr>
054 * <td>CourseLimitCheck.UpNonZeroLimits</td>
055 * <td>{@link Boolean}</td>
056 * <td>
057 * If true, courses with positive limit, requested by more students than allowed
058 * by the limit are increased in limit in order to accomodate all students that
059 * requests the course. Section limits are increased proportionally by ( total
060 * weight of all requests in the offering / current offering limit), where
061 * offering limit is the sum of limits of courses of the offering.</td>
062 * </tr>
063 * </table>
064 * 
065 * <br>
066 * <br>
067 * 
068 * @author  Tomáš Müller
069 * @version StudentSct 1.3 (Student Sectioning)<br>
070 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
071 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
072 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
073 * <br>
074 *          This library is free software; you can redistribute it and/or modify
075 *          it under the terms of the GNU Lesser General Public License as
076 *          published by the Free Software Foundation; either version 3 of the
077 *          License, or (at your option) any later version. <br>
078 * <br>
079 *          This library is distributed in the hope that it will be useful, but
080 *          WITHOUT ANY WARRANTY; without even the implied warranty of
081 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
082 *          Lesser General Public License for more details. <br>
083 * <br>
084 *          You should have received a copy of the GNU Lesser General Public
085 *          License along with this library; if not see
086 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
087 */
088public class CourseLimitCheck {
089    private static org.apache.logging.log4j.Logger sLog = org.apache.logging.log4j.LogManager.getLogger(CourseLimitCheck.class);
090    private static DecimalFormat sDF = new DecimalFormat("0.0");
091    private StudentSectioningModel iModel;
092    private CSVFile iCSVFile = null;
093    private boolean iFixUnlimited = false;
094    private boolean iUpZeroLimits = false;
095    private boolean iUpNonZeroLimits = false;
096
097    /**
098     * Constructor
099     * 
100     * @param model
101     *            student sectioning model
102     */
103    public CourseLimitCheck(StudentSectioningModel model) {
104        iModel = model;
105        iCSVFile = new CSVFile();
106        iCSVFile.setHeader(new CSVFile.CSVField[] { new CSVFile.CSVField("Course"), new CSVFile.CSVField("Limit"),
107                new CSVFile.CSVField("Students"), new CSVFile.CSVField("Real"), new CSVFile.CSVField("Last-like") });
108        iFixUnlimited = model.getProperties().getPropertyBoolean("CourseLimitCheck.FixUnlimited", iFixUnlimited);
109        iUpZeroLimits = model.getProperties().getPropertyBoolean("CourseLimitCheck.UpZeroLimits", iUpZeroLimits);
110        iUpNonZeroLimits = model.getProperties().getPropertyBoolean("CourseLimitCheck.UpNonZeroLimits",
111                iUpNonZeroLimits);
112    }
113
114    /** Return student sectioning model 
115     * @return problem model
116     **/
117    public StudentSectioningModel getModel() {
118        return iModel;
119    }
120
121    /** Return report
122     * @return generated report
123     **/
124    public CSVFile getCSVFile() {
125        return iCSVFile;
126    }
127
128    /**
129     * Check for courses where the limit is below the number of students that
130     * request the course
131     * 
132     * @return false, if there is such a case
133     */
134    public boolean check() {
135        sLog.info("Checking for course limits...");
136        boolean ret = true;
137        for (Offering offering : getModel().getOfferings()) {
138            if (offering.isDummy()) continue;
139            boolean hasUnlimitedSection = false;
140            if (iFixUnlimited)
141                for (Config config : offering.getConfigs()) {
142                    for (Subpart subpart : config.getSubparts()) {
143                        for (Section section : subpart.getSections()) {
144                            if (section.getLimit() < 0)
145                                hasUnlimitedSection = true;
146                        }
147                    }
148                }
149            int offeringLimit = 0;
150            int nrStudents = 0;
151            for (Course course : offering.getCourses()) {
152                if (course.getLimit() < 0) {
153                    offeringLimit = -1;
154                    continue;
155                }
156                if (iFixUnlimited && hasUnlimitedSection) {
157                    sLog.info("Course " + course + " made unlimited.");
158                    course.setLimit(-1);
159                    offeringLimit = -1;
160                    continue;
161                }
162                double total = 0;
163                double lastLike = 0, real = 0;
164                for (Request request : getModel().variables()) {
165                    if (request instanceof CourseRequest && ((CourseRequest) request).getCourses().contains(course)) {
166                        total += request.getWeight();
167                        if (request.getStudent().isDummy())
168                            lastLike += request.getWeight();
169                        else
170                            real += request.getWeight();
171                    }
172                }
173                nrStudents += Math.round(total);
174                offeringLimit += course.getLimit();
175                if (Math.round(total) > course.getLimit()) {
176                    sLog.error("Course " + course + " is requested by " + sDF.format(total)
177                            + " students, but its limit is only " + course.getLimit());
178                    ret = false;
179                    iCSVFile.addLine(new CSVFile.CSVField[] { new CSVFile.CSVField(course.getName()),
180                            new CSVFile.CSVField(course.getLimit()), new CSVFile.CSVField(total),
181                            new CSVFile.CSVField(real), new CSVFile.CSVField(lastLike) });
182                    if (iUpZeroLimits && course.getLimit() == 0) {
183                        int oldLimit = course.getLimit();
184                        course.setLimit((int) Math.round(total));
185                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
186                                + oldLimit + ")");
187                    } else if (iUpNonZeroLimits && course.getLimit() > 0) {
188                        int oldLimit = course.getLimit();
189                        course.setLimit((int) Math.round(total));
190                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
191                                + oldLimit + ")");
192                    }
193                }
194            }
195            if (iUpZeroLimits && offeringLimit == 0 && nrStudents > 0) {
196                for (Config config : offering.getConfigs()) {
197                    for (Subpart subpart : config.getSubparts()) {
198                        for (Section section : subpart.getSections()) {
199                            int oldLimit = section.getLimit();
200                            section.setLimit(Math.max(section.getLimit(), (int) Math.ceil(nrStudents
201                                    / subpart.getSections().size())));
202                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
203                                    + " (was " + oldLimit + ")");
204                        }
205                    }
206                }
207            } else if (iUpNonZeroLimits && offeringLimit >= 0 && nrStudents > offeringLimit) {
208                double fact = ((double) nrStudents) / offeringLimit;
209                for (Config config : offering.getConfigs()) {
210                    for (Subpart subpart : config.getSubparts()) {
211                        for (Section section : subpart.getSections()) {
212                            int oldLimit = section.getLimit();
213                            section.setLimit((int) Math.ceil(fact * section.getLimit()));
214                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
215                                    + " (was " + oldLimit + ")");
216                        }
217                    }
218                }
219            }
220
221            if (offeringLimit >= 0) {
222                int totalSectionLimit = 0;
223                for (Config config : offering.getConfigs()) {
224                    int configLimit = -1;
225                    for (Subpart subpart : config.getSubparts()) {
226                        int subpartLimit = 0;
227                        for (Section section : subpart.getSections()) {
228                            subpartLimit += section.getLimit();
229                        }
230                        if (configLimit < 0)
231                            configLimit = subpartLimit;
232                        else
233                            configLimit = Math.min(configLimit, subpartLimit);
234                    }
235                    totalSectionLimit += configLimit;
236                }
237                if (totalSectionLimit < offeringLimit) {
238                    sLog.error("Offering limit of " + offering + " is " + offeringLimit
239                            + ", but total section limit is only " + totalSectionLimit);
240                    if (iUpZeroLimits && totalSectionLimit == 0) {
241                        for (Config config : offering.getConfigs()) {
242                            for (Subpart subpart : config.getSubparts()) {
243                                for (Section section : subpart.getSections()) {
244                                    int oldLimit = section.getLimit();
245                                    section.setLimit(Math.max(section.getLimit(), (int) Math
246                                            .ceil(((double) offeringLimit) / subpart.getSections().size())));
247                                    sLog.info("    -- limit of section " + section + " increased to "
248                                            + section.getLimit() + " (was " + oldLimit + ")");
249                                }
250                            }
251                        }
252                    } else if (iUpNonZeroLimits && totalSectionLimit > 0) {
253                        double fact = ((double) offeringLimit) / totalSectionLimit;
254                        for (Config config : offering.getConfigs()) {
255                            for (Subpart subpart : config.getSubparts()) {
256                                for (Section section : subpart.getSections()) {
257                                    int oldLimit = section.getLimit();
258                                    section.setLimit((int) Math.ceil(fact * section.getLimit()));
259                                    sLog.info("    -- limit of section " + section + " increased to "
260                                            + section.getLimit() + " (was " + oldLimit + ")");
261                                }
262                            }
263                        }
264                    }
265                }
266            }
267        }
268        return ret;
269    }
270
271}