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.log4j.Logger sLog = org.apache.log4j.Logger.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            boolean hasUnlimitedSection = false;
138            if (iFixUnlimited)
139                for (Config config : offering.getConfigs()) {
140                    for (Subpart subpart : config.getSubparts()) {
141                        for (Section section : subpart.getSections()) {
142                            if (section.getLimit() < 0)
143                                hasUnlimitedSection = true;
144                        }
145                    }
146                }
147            int offeringLimit = 0;
148            int nrStudents = 0;
149            for (Course course : offering.getCourses()) {
150                if (course.getLimit() < 0) {
151                    offeringLimit = -1;
152                    continue;
153                }
154                if (iFixUnlimited && hasUnlimitedSection) {
155                    sLog.info("Course " + course + " made unlimited.");
156                    course.setLimit(-1);
157                    offeringLimit = -1;
158                    continue;
159                }
160                double total = 0;
161                double lastLike = 0, real = 0;
162                for (Request request : getModel().variables()) {
163                    if (request instanceof CourseRequest && ((CourseRequest) request).getCourses().contains(course)) {
164                        total += request.getWeight();
165                        if (request.getStudent().isDummy())
166                            lastLike += request.getWeight();
167                        else
168                            real += request.getWeight();
169                    }
170                }
171                nrStudents += Math.round(total);
172                offeringLimit += course.getLimit();
173                if (Math.round(total) > course.getLimit()) {
174                    sLog.error("Course " + course + " is requested by " + sDF.format(total)
175                            + " students, but its limit is only " + course.getLimit());
176                    ret = false;
177                    iCSVFile.addLine(new CSVFile.CSVField[] { new CSVFile.CSVField(course.getName()),
178                            new CSVFile.CSVField(course.getLimit()), new CSVFile.CSVField(total),
179                            new CSVFile.CSVField(real), new CSVFile.CSVField(lastLike) });
180                    if (iUpZeroLimits && course.getLimit() == 0) {
181                        int oldLimit = course.getLimit();
182                        course.setLimit((int) Math.round(total));
183                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
184                                + oldLimit + ")");
185                    } else if (iUpNonZeroLimits && course.getLimit() > 0) {
186                        int oldLimit = course.getLimit();
187                        course.setLimit((int) Math.round(total));
188                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
189                                + oldLimit + ")");
190                    }
191                }
192            }
193            if (iUpZeroLimits && offeringLimit == 0 && nrStudents > 0) {
194                for (Config config : offering.getConfigs()) {
195                    for (Subpart subpart : config.getSubparts()) {
196                        for (Section section : subpart.getSections()) {
197                            int oldLimit = section.getLimit();
198                            section.setLimit(Math.max(section.getLimit(), (int) Math.ceil(nrStudents
199                                    / subpart.getSections().size())));
200                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
201                                    + " (was " + oldLimit + ")");
202                        }
203                    }
204                }
205            } else if (iUpNonZeroLimits && offeringLimit >= 0 && nrStudents > offeringLimit) {
206                double fact = ((double) nrStudents) / offeringLimit;
207                for (Config config : offering.getConfigs()) {
208                    for (Subpart subpart : config.getSubparts()) {
209                        for (Section section : subpart.getSections()) {
210                            int oldLimit = section.getLimit();
211                            section.setLimit((int) Math.ceil(fact * section.getLimit()));
212                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
213                                    + " (was " + oldLimit + ")");
214                        }
215                    }
216                }
217            }
218
219            if (offeringLimit >= 0) {
220                int totalSectionLimit = 0;
221                for (Config config : offering.getConfigs()) {
222                    int configLimit = -1;
223                    for (Subpart subpart : config.getSubparts()) {
224                        int subpartLimit = 0;
225                        for (Section section : subpart.getSections()) {
226                            subpartLimit += section.getLimit();
227                        }
228                        if (configLimit < 0)
229                            configLimit = subpartLimit;
230                        else
231                            configLimit = Math.min(configLimit, subpartLimit);
232                    }
233                    totalSectionLimit += configLimit;
234                }
235                if (totalSectionLimit < offeringLimit) {
236                    sLog.error("Offering limit of " + offering + " is " + offeringLimit
237                            + ", but total section limit is only " + totalSectionLimit);
238                    if (iUpZeroLimits && totalSectionLimit == 0) {
239                        for (Config config : offering.getConfigs()) {
240                            for (Subpart subpart : config.getSubparts()) {
241                                for (Section section : subpart.getSections()) {
242                                    int oldLimit = section.getLimit();
243                                    section.setLimit(Math.max(section.getLimit(), (int) Math
244                                            .ceil(((double) offeringLimit) / subpart.getSections().size())));
245                                    sLog.info("    -- limit of section " + section + " increased to "
246                                            + section.getLimit() + " (was " + oldLimit + ")");
247                                }
248                            }
249                        }
250                    } else if (iUpNonZeroLimits && totalSectionLimit > 0) {
251                        double fact = ((double) offeringLimit) / totalSectionLimit;
252                        for (Config config : offering.getConfigs()) {
253                            for (Subpart subpart : config.getSubparts()) {
254                                for (Section section : subpart.getSections()) {
255                                    int oldLimit = section.getLimit();
256                                    section.setLimit((int) Math.ceil(fact * section.getLimit()));
257                                    sLog.info("    -- limit of section " + section + " increased to "
258                                            + section.getLimit() + " (was " + oldLimit + ")");
259                                }
260                            }
261                        }
262                    }
263                }
264            }
265        }
266        return ret;
267    }
268
269}