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 * CourseLimitCheck ch = new CourseLimitCheck(model); 026 * 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}