001package net.sf.cpsolver.studentsct.check;
002
003import java.text.DecimalFormat;
004
005import net.sf.cpsolver.ifs.util.CSVFile;
006import net.sf.cpsolver.studentsct.StudentSectioningModel;
007import net.sf.cpsolver.studentsct.model.Config;
008import net.sf.cpsolver.studentsct.model.Course;
009import net.sf.cpsolver.studentsct.model.CourseRequest;
010import net.sf.cpsolver.studentsct.model.Offering;
011import net.sf.cpsolver.studentsct.model.Request;
012import net.sf.cpsolver.studentsct.model.Section;
013import net.sf.cpsolver.studentsct.model.Subpart;
014
015/**
016 * This class looks and reports cases when there are more students requesting a
017 * course than the course limit.
018 * 
019 * <br>
020 * <br>
021 * 
022 * Usage:<br>
023 * <code>
024 * &nbsp;&nbsp;&nbsp;&nbsp; CourseLimitCheck ch = new CourseLimitCheck(model);<br>
025 * &nbsp;&nbsp;&nbsp;&nbsp; if (!ch.check()) ch.getCSVFile().save(new File("limits.csv"));
026 * </code>
027 * 
028 * <br>
029 * <br>
030 * Parameters:
031 * <table border='1'>
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.2 (Student Sectioning)<br>
069 *          Copyright (C) 2007 - 2010 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    public StudentSectioningModel getModel() {
115        return iModel;
116    }
117
118    /** Return report */
119    public CSVFile getCSVFile() {
120        return iCSVFile;
121    }
122
123    /**
124     * Check for courses where the limit is below the number of students that
125     * request the course
126     * 
127     * @return false, if there is such a case
128     */
129    public boolean check() {
130        sLog.info("Checking for course limits...");
131        boolean ret = true;
132        for (Offering offering : getModel().getOfferings()) {
133            boolean hasUnlimitedSection = false;
134            if (iFixUnlimited)
135                for (Config config : offering.getConfigs()) {
136                    for (Subpart subpart : config.getSubparts()) {
137                        for (Section section : subpart.getSections()) {
138                            if (section.getLimit() < 0)
139                                hasUnlimitedSection = true;
140                        }
141                    }
142                }
143            int offeringLimit = 0;
144            int nrStudents = 0;
145            for (Course course : offering.getCourses()) {
146                if (course.getLimit() < 0) {
147                    offeringLimit = -1;
148                    continue;
149                }
150                if (iFixUnlimited && hasUnlimitedSection) {
151                    sLog.info("Course " + course + " made unlimited.");
152                    course.setLimit(-1);
153                    offeringLimit = -1;
154                    continue;
155                }
156                double total = 0;
157                double lastLike = 0, real = 0;
158                for (Request request : getModel().variables()) {
159                    if (request instanceof CourseRequest && ((CourseRequest) request).getCourses().contains(course)) {
160                        total += request.getWeight();
161                        if (request.getStudent().isDummy())
162                            lastLike += request.getWeight();
163                        else
164                            real += request.getWeight();
165                    }
166                }
167                nrStudents += Math.round(total);
168                offeringLimit += course.getLimit();
169                if (Math.round(total) > course.getLimit()) {
170                    sLog.error("Course " + course + " is requested by " + sDF.format(total)
171                            + " students, but its limit is only " + course.getLimit());
172                    ret = false;
173                    iCSVFile.addLine(new CSVFile.CSVField[] { new CSVFile.CSVField(course.getName()),
174                            new CSVFile.CSVField(course.getLimit()), new CSVFile.CSVField(total),
175                            new CSVFile.CSVField(real), new CSVFile.CSVField(lastLike) });
176                    if (iUpZeroLimits && course.getLimit() == 0) {
177                        int oldLimit = course.getLimit();
178                        course.setLimit((int) Math.round(total));
179                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
180                                + oldLimit + ")");
181                    } else if (iUpNonZeroLimits && 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                    }
187                }
188            }
189            if (iUpZeroLimits && offeringLimit == 0 && nrStudents > 0) {
190                for (Config config : offering.getConfigs()) {
191                    for (Subpart subpart : config.getSubparts()) {
192                        for (Section section : subpart.getSections()) {
193                            int oldLimit = section.getLimit();
194                            section.setLimit(Math.max(section.getLimit(), (int) Math.ceil(nrStudents
195                                    / subpart.getSections().size())));
196                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
197                                    + " (was " + oldLimit + ")");
198                        }
199                    }
200                }
201            } else if (iUpNonZeroLimits && offeringLimit >= 0 && nrStudents > offeringLimit) {
202                double fact = ((double) nrStudents) / offeringLimit;
203                for (Config config : offering.getConfigs()) {
204                    for (Subpart subpart : config.getSubparts()) {
205                        for (Section section : subpart.getSections()) {
206                            int oldLimit = section.getLimit();
207                            section.setLimit((int) Math.ceil(fact * section.getLimit()));
208                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
209                                    + " (was " + oldLimit + ")");
210                        }
211                    }
212                }
213            }
214
215            if (offeringLimit >= 0) {
216                int totalSectionLimit = 0;
217                for (Config config : offering.getConfigs()) {
218                    int configLimit = -1;
219                    for (Subpart subpart : config.getSubparts()) {
220                        int subpartLimit = 0;
221                        for (Section section : subpart.getSections()) {
222                            subpartLimit += section.getLimit();
223                        }
224                        if (configLimit < 0)
225                            configLimit = subpartLimit;
226                        else
227                            configLimit = Math.min(configLimit, subpartLimit);
228                    }
229                    totalSectionLimit += configLimit;
230                }
231                if (totalSectionLimit < offeringLimit) {
232                    sLog.error("Offering limit of " + offering + " is " + offeringLimit
233                            + ", but total section limit is only " + totalSectionLimit);
234                    if (iUpZeroLimits && totalSectionLimit == 0) {
235                        for (Config config : offering.getConfigs()) {
236                            for (Subpart subpart : config.getSubparts()) {
237                                for (Section section : subpart.getSections()) {
238                                    int oldLimit = section.getLimit();
239                                    section.setLimit(Math.max(section.getLimit(), (int) Math
240                                            .ceil(((double) offeringLimit) / subpart.getSections().size())));
241                                    sLog.info("    -- limit of section " + section + " increased to "
242                                            + section.getLimit() + " (was " + oldLimit + ")");
243                                }
244                            }
245                        }
246                    } else if (iUpNonZeroLimits && totalSectionLimit > 0) {
247                        double fact = ((double) offeringLimit) / totalSectionLimit;
248                        for (Config config : offering.getConfigs()) {
249                            for (Subpart subpart : config.getSubparts()) {
250                                for (Section section : subpart.getSections()) {
251                                    int oldLimit = section.getLimit();
252                                    section.setLimit((int) Math.ceil(fact * section.getLimit()));
253                                    sLog.info("    -- limit of section " + section + " increased to "
254                                            + section.getLimit() + " (was " + oldLimit + ")");
255                                }
256                            }
257                        }
258                    }
259                }
260            }
261        }
262        return ret;
263    }
264
265}