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 }