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