001 package net.sf.cpsolver.studentsct.check;
002
003 import java.text.DecimalFormat;
004
005 import net.sf.cpsolver.ifs.util.CSVFile;
006 import net.sf.cpsolver.studentsct.StudentSectioningModel;
007 import net.sf.cpsolver.studentsct.model.Config;
008 import net.sf.cpsolver.studentsct.model.Course;
009 import net.sf.cpsolver.studentsct.model.CourseRequest;
010 import net.sf.cpsolver.studentsct.model.Offering;
011 import net.sf.cpsolver.studentsct.model.Request;
012 import net.sf.cpsolver.studentsct.model.Section;
013 import 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 * CourseLimitCheck ch = new CourseLimitCheck(model);<br>
025 * 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 Tomas Muller<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 */
087 public 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 }