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 * 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 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}