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' summary='Related Solver Parameters'>
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 * @version StudentSct 1.3 (Student Sectioning)<br>
069 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
070 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
071 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
072 * <br>
073 *          This library is free software; you can redistribute it and/or modify
074 *          it under the terms of the GNU Lesser General Public License as
075 *          published by the Free Software Foundation; either version 3 of the
076 *          License, or (at your option) any later version. <br>
077 * <br>
078 *          This library is distributed in the hope that it will be useful, but
079 *          WITHOUT ANY WARRANTY; without even the implied warranty of
080 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
081 *          Lesser General Public License for more details. <br>
082 * <br>
083 *          You should have received a copy of the GNU Lesser General Public
084 *          License along with this library; if not see
085 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
086 */
087public class CourseLimitCheck {
088    private static org.apache.logging.log4j.Logger sLog = org.apache.logging.log4j.LogManager.getLogger(CourseLimitCheck.class);
089    private static DecimalFormat sDF = new DecimalFormat("0.0");
090    private StudentSectioningModel iModel;
091    private CSVFile iCSVFile = null;
092    private boolean iFixUnlimited = false;
093    private boolean iUpZeroLimits = false;
094    private boolean iUpNonZeroLimits = false;
095
096    /**
097     * Constructor
098     * 
099     * @param model
100     *            student sectioning model
101     */
102    public CourseLimitCheck(StudentSectioningModel model) {
103        iModel = model;
104        iCSVFile = new CSVFile();
105        iCSVFile.setHeader(new CSVFile.CSVField[] { new CSVFile.CSVField("Course"), new CSVFile.CSVField("Limit"),
106                new CSVFile.CSVField("Students"), new CSVFile.CSVField("Real"), new CSVFile.CSVField("Last-like") });
107        iFixUnlimited = model.getProperties().getPropertyBoolean("CourseLimitCheck.FixUnlimited", iFixUnlimited);
108        iUpZeroLimits = model.getProperties().getPropertyBoolean("CourseLimitCheck.UpZeroLimits", iUpZeroLimits);
109        iUpNonZeroLimits = model.getProperties().getPropertyBoolean("CourseLimitCheck.UpNonZeroLimits",
110                iUpNonZeroLimits);
111    }
112
113    /** Return student sectioning model 
114     * @return problem model
115     **/
116    public StudentSectioningModel getModel() {
117        return iModel;
118    }
119
120    /** Return report
121     * @return generated report
122     **/
123    public CSVFile getCSVFile() {
124        return iCSVFile;
125    }
126
127    /**
128     * Check for courses where the limit is below the number of students that
129     * request the course
130     * 
131     * @return false, if there is such a case
132     */
133    public boolean check() {
134        sLog.info("Checking for course limits...");
135        boolean ret = true;
136        for (Offering offering : getModel().getOfferings()) {
137            if (offering.isDummy()) continue;
138            boolean hasUnlimitedSection = false;
139            if (iFixUnlimited)
140                for (Config config : offering.getConfigs()) {
141                    for (Subpart subpart : config.getSubparts()) {
142                        for (Section section : subpart.getSections()) {
143                            if (section.getLimit() < 0)
144                                hasUnlimitedSection = true;
145                        }
146                    }
147                }
148            int offeringLimit = 0;
149            int nrStudents = 0;
150            for (Course course : offering.getCourses()) {
151                if (course.getLimit() < 0) {
152                    offeringLimit = -1;
153                    continue;
154                }
155                if (iFixUnlimited && hasUnlimitedSection) {
156                    sLog.info("Course " + course + " made unlimited.");
157                    course.setLimit(-1);
158                    offeringLimit = -1;
159                    continue;
160                }
161                double total = 0;
162                double lastLike = 0, real = 0;
163                for (Request request : getModel().variables()) {
164                    if (request instanceof CourseRequest && ((CourseRequest) request).getCourses().contains(course)) {
165                        total += request.getWeight();
166                        if (request.getStudent().isDummy())
167                            lastLike += request.getWeight();
168                        else
169                            real += request.getWeight();
170                    }
171                }
172                nrStudents += Math.round(total);
173                offeringLimit += course.getLimit();
174                if (Math.round(total) > course.getLimit()) {
175                    sLog.error("Course " + course + " is requested by " + sDF.format(total)
176                            + " students, but its limit is only " + course.getLimit());
177                    ret = false;
178                    iCSVFile.addLine(new CSVFile.CSVField[] { new CSVFile.CSVField(course.getName()),
179                            new CSVFile.CSVField(course.getLimit()), new CSVFile.CSVField(total),
180                            new CSVFile.CSVField(real), new CSVFile.CSVField(lastLike) });
181                    if (iUpZeroLimits && course.getLimit() == 0) {
182                        int oldLimit = course.getLimit();
183                        course.setLimit((int) Math.round(total));
184                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
185                                + oldLimit + ")");
186                    } else if (iUpNonZeroLimits && course.getLimit() > 0) {
187                        int oldLimit = course.getLimit();
188                        course.setLimit((int) Math.round(total));
189                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
190                                + oldLimit + ")");
191                    }
192                }
193            }
194            if (iUpZeroLimits && offeringLimit == 0 && nrStudents > 0) {
195                for (Config config : offering.getConfigs()) {
196                    for (Subpart subpart : config.getSubparts()) {
197                        for (Section section : subpart.getSections()) {
198                            int oldLimit = section.getLimit();
199                            section.setLimit(Math.max(section.getLimit(), (int) Math.ceil(nrStudents
200                                    / subpart.getSections().size())));
201                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
202                                    + " (was " + oldLimit + ")");
203                        }
204                    }
205                }
206            } else if (iUpNonZeroLimits && offeringLimit >= 0 && nrStudents > offeringLimit) {
207                double fact = ((double) nrStudents) / offeringLimit;
208                for (Config config : offering.getConfigs()) {
209                    for (Subpart subpart : config.getSubparts()) {
210                        for (Section section : subpart.getSections()) {
211                            int oldLimit = section.getLimit();
212                            section.setLimit((int) Math.ceil(fact * section.getLimit()));
213                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
214                                    + " (was " + oldLimit + ")");
215                        }
216                    }
217                }
218            }
219
220            if (offeringLimit >= 0) {
221                int totalSectionLimit = 0;
222                for (Config config : offering.getConfigs()) {
223                    int configLimit = -1;
224                    for (Subpart subpart : config.getSubparts()) {
225                        int subpartLimit = 0;
226                        for (Section section : subpart.getSections()) {
227                            subpartLimit += section.getLimit();
228                        }
229                        if (configLimit < 0)
230                            configLimit = subpartLimit;
231                        else
232                            configLimit = Math.min(configLimit, subpartLimit);
233                    }
234                    totalSectionLimit += configLimit;
235                }
236                if (totalSectionLimit < offeringLimit) {
237                    sLog.error("Offering limit of " + offering + " is " + offeringLimit
238                            + ", but total section limit is only " + totalSectionLimit);
239                    if (iUpZeroLimits && totalSectionLimit == 0) {
240                        for (Config config : offering.getConfigs()) {
241                            for (Subpart subpart : config.getSubparts()) {
242                                for (Section section : subpart.getSections()) {
243                                    int oldLimit = section.getLimit();
244                                    section.setLimit(Math.max(section.getLimit(), (int) Math
245                                            .ceil(((double) offeringLimit) / subpart.getSections().size())));
246                                    sLog.info("    -- limit of section " + section + " increased to "
247                                            + section.getLimit() + " (was " + oldLimit + ")");
248                                }
249                            }
250                        }
251                    } else if (iUpNonZeroLimits && totalSectionLimit > 0) {
252                        double fact = ((double) offeringLimit) / totalSectionLimit;
253                        for (Config config : offering.getConfigs()) {
254                            for (Subpart subpart : config.getSubparts()) {
255                                for (Section section : subpart.getSections()) {
256                                    int oldLimit = section.getLimit();
257                                    section.setLimit((int) Math.ceil(fact * section.getLimit()));
258                                    sLog.info("    -- limit of section " + section + " increased to "
259                                            + section.getLimit() + " (was " + oldLimit + ")");
260                                }
261                            }
262                        }
263                    }
264                }
265            }
266        }
267        return ret;
268    }
269
270}