001 package net.sf.cpsolver.studentsct; 002 003 import java.io.BufferedReader; 004 import java.io.File; 005 import java.io.FileInputStream; 006 import java.io.FileOutputStream; 007 import java.io.FileReader; 008 import java.io.FileWriter; 009 import java.io.IOException; 010 import java.io.PrintWriter; 011 import java.text.DecimalFormat; 012 import java.util.Collections; 013 import java.util.Comparator; 014 import java.util.Date; 015 import java.util.Enumeration; 016 import java.util.HashSet; 017 import java.util.Hashtable; 018 import java.util.Iterator; 019 import java.util.Map; 020 import java.util.StringTokenizer; 021 import java.util.TreeSet; 022 import java.util.Vector; 023 024 import org.apache.log4j.Level; 025 import org.apache.log4j.Logger; 026 import org.dom4j.Document; 027 import org.dom4j.DocumentHelper; 028 import org.dom4j.Element; 029 import org.dom4j.io.OutputFormat; 030 import org.dom4j.io.SAXReader; 031 import org.dom4j.io.XMLWriter; 032 033 import net.sf.cpsolver.ifs.heuristics.BacktrackNeighbourSelection; 034 import net.sf.cpsolver.ifs.model.Neighbour; 035 import net.sf.cpsolver.ifs.model.Value; 036 import net.sf.cpsolver.ifs.model.Variable; 037 import net.sf.cpsolver.ifs.solution.Solution; 038 import net.sf.cpsolver.ifs.solution.SolutionListener; 039 import net.sf.cpsolver.ifs.solver.Solver; 040 import net.sf.cpsolver.ifs.solver.SolverListener; 041 import net.sf.cpsolver.ifs.util.DataProperties; 042 import net.sf.cpsolver.ifs.util.JProf; 043 import net.sf.cpsolver.ifs.util.ToolBox; 044 import net.sf.cpsolver.studentsct.check.CourseLimitCheck; 045 import net.sf.cpsolver.studentsct.check.InevitableStudentConflicts; 046 import net.sf.cpsolver.studentsct.check.OverlapCheck; 047 import net.sf.cpsolver.studentsct.check.SectionLimitCheck; 048 import net.sf.cpsolver.studentsct.extension.DistanceConflict; 049 import net.sf.cpsolver.studentsct.filter.CombinedStudentFilter; 050 import net.sf.cpsolver.studentsct.filter.FreshmanStudentFilter; 051 import net.sf.cpsolver.studentsct.filter.RandomStudentFilter; 052 import net.sf.cpsolver.studentsct.filter.ReverseStudentFilter; 053 import net.sf.cpsolver.studentsct.filter.StudentFilter; 054 import net.sf.cpsolver.studentsct.heuristics.StudentSctNeighbourSelection; 055 import net.sf.cpsolver.studentsct.heuristics.selection.BranchBoundSelection; 056 import net.sf.cpsolver.studentsct.heuristics.selection.OnlineSelection; 057 import net.sf.cpsolver.studentsct.heuristics.selection.SwapStudentSelection; 058 import net.sf.cpsolver.studentsct.heuristics.selection.BranchBoundSelection.BranchBoundNeighbour; 059 import net.sf.cpsolver.studentsct.heuristics.studentord.StudentOrder; 060 import net.sf.cpsolver.studentsct.heuristics.studentord.StudentRandomOrder; 061 import net.sf.cpsolver.studentsct.model.AcademicAreaCode; 062 import net.sf.cpsolver.studentsct.model.Course; 063 import net.sf.cpsolver.studentsct.model.CourseRequest; 064 import net.sf.cpsolver.studentsct.model.Enrollment; 065 import net.sf.cpsolver.studentsct.model.Offering; 066 import net.sf.cpsolver.studentsct.model.Request; 067 import net.sf.cpsolver.studentsct.model.Student; 068 import net.sf.cpsolver.studentsct.report.CourseConflictTable; 069 import net.sf.cpsolver.studentsct.report.DistanceConflictTable; 070 071 /** 072 * A main class for running of the student sectioning solver from command line. 073 * <br><br> 074 * Usage:<br> 075 * java -Xmx1024m -jar studentsct-1.1.jar config.properties [input_file] [output_folder] [batch|online|simple]<br> 076 * <br> 077 * Modes:<br> 078 * batch ... batch sectioning mode (default mode -- IFS solver with {@link StudentSctNeighbourSelection} is used)<br> 079 * online ... online sectioning mode (students are sectioned one by one, sectioning info (expected/held space) is used)<br> 080 * simple ... simple sectioning mode (students are sectioned one by one, sectioning info is not used)<br> 081 * See http://www.unitime.org for example configuration files and benchmark data sets.<br><br> 082 * 083 * The test does the following steps:<ul> 084 * <li>Provided property file is loaded (see {@link DataProperties}). 085 * <li>Output folder is created (General.Output property) and logging is setup (using log4j). 086 * <li>Input data are loaded from the given XML file (calling {@link StudentSectioningXMLLoader#load()}). 087 * <li>Solver is executed (see {@link Solver}). 088 * <li>Resultant solution is saved to an XML file (calling {@link StudentSectioningXMLSaver#save()}. 089 * </ul> 090 * Also, a log and some reports (e.g., {@link CourseConflictTable} and {@link DistanceConflictTable}) are created in the output folder. 091 * 092 * <br><br> 093 * Parameters: 094 * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr> 095 * <tr><td>Test.LastLikeCourseDemands</td><td>{@link String}</td><td>Load last-like course demands from the given XML file (in the format that is being used for last like course demand table in the timetabling application)</td></tr> 096 * <tr><td>Test.StudentInfos</td><td>{@link String}</td><td>Load last-like course demands from the given XML file (in the format that is being used for last like course demand table in the timetabling application)</td></tr> 097 * <tr><td>Test.CrsReq</td><td>{@link String}</td><td>Load student requests from the given semi-colon separated list files (in the format that is being used by the old MSF system)</td></tr> 098 * <tr><td>Test.EtrChk</td><td>{@link String}</td><td>Load student information (academic area, classification, major, minor) from the given semi-colon separated list files (in the format that is being used by the old MSF system)</td></tr> 099 * <tr><td>Sectioning.UseStudentPreferencePenalties</td><td>{@link Boolean}</td><td>If true, {@link StudentPreferencePenalties} are used (applicable only for online sectioning)</td></tr> 100 * <tr><td>Test.StudentOrder</td><td>{@link String}</td><td>A class that is used for ordering of students (must be an interface of {@link StudentOrder}, default is {@link StudentRandomOrder}, not applicable only for batch sectioning)</td></tr> 101 * <tr><td>Test.CombineStudents</td><td>{@link File}</td><td>If provided, students are combined from the input file (last-like students) and the provided file (real students). Real non-freshmen students are taken from real data, last-like data are loaded on top of the real data (all students, but weighted to occupy only the remaining space).</td></tr> 102 * <tr><td>Test.CombineStudentsLastLike</td><td>{@link File}</td><td>If provided (together with Test.CombineStudents), students are combined from the this file (last-like students) and Test.CombineStudents file (real students). Real non-freshmen students are taken from real data, last-like data are loaded on top of the real data (all students, but weighted to occupy only the remaining space).</td></tr> 103 * <tr><td>Test.CombineAcceptProb</td><td>{@link Double}</td><td>Used in combining students, probability of a non-freshmen real student to be taken into the combined file (default is 1.0 -- all real non-freshmen students are taken).</td></tr> 104 * <tr><td>Test.FixPriorities</td><td>{@link Boolean}</td><td>If true, course/free time request priorities are corrected (to go from zero, without holes or duplicates).</td></tr> 105 * <tr><td>Test.ExtraStudents</td><td>{@link File}</td><td>If provided, students are loaded from the given file on top of the students loaded from the ordinary input file (students with the same id are skipped).</td></tr> 106 * </table> 107 * <br><br> 108 * 109 * @version 110 * StudentSct 1.1 (Student Sectioning)<br> 111 * Copyright (C) 2007 Tomáš Müller<br> 112 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 113 * Lazenska 391, 76314 Zlin, Czech Republic<br> 114 * <br> 115 * This library is free software; you can redistribute it and/or 116 * modify it under the terms of the GNU Lesser General Public 117 * License as published by the Free Software Foundation; either 118 * version 2.1 of the License, or (at your option) any later version. 119 * <br><br> 120 * This library is distributed in the hope that it will be useful, 121 * but WITHOUT ANY WARRANTY; without even the implied warranty of 122 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 123 * Lesser General Public License for more details. 124 * <br><br> 125 * You should have received a copy of the GNU Lesser General Public 126 * License along with this library; if not, write to the Free Software 127 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 128 */ 129 130 public class Test { 131 private static org.apache.log4j.Logger sLog = org.apache.log4j.Logger.getLogger(Test.class); 132 private static java.text.SimpleDateFormat sDateFormat = new java.text.SimpleDateFormat("yyMMdd_HHmmss",java.util.Locale.US); 133 private static DecimalFormat sDF = new DecimalFormat("0.000"); 134 135 /** Load student sectioning model */ 136 public static StudentSectioningModel loadModel(DataProperties cfg) { 137 StudentSectioningModel model = null; 138 try { 139 if (cfg.getProperty("Test.CombineStudents")==null) { 140 model = new StudentSectioningModel(cfg); 141 new StudentSectioningXMLLoader(model).load(); 142 } else { 143 model = combineStudents(cfg, 144 new File(cfg.getProperty("Test.CombineStudentsLastLike",cfg.getProperty("General.Input","."+File.separator+"solution.xml"))), 145 new File(cfg.getProperty("Test.CombineStudents"))); 146 } 147 if (cfg.getProperty("Test.ExtraStudents")!=null) { 148 StudentSectioningXMLLoader extra = new StudentSectioningXMLLoader(model); 149 extra.setInputFile(new File(cfg.getProperty("Test.ExtraStudents"))); 150 extra.setLoadOfferings(false); 151 extra.setLoadStudents(true); 152 extra.setStudentFilter(new ExtraStudentFilter(model)); 153 extra.load(); 154 } 155 if (cfg.getProperty("Test.LastLikeCourseDemands")!=null) 156 loadLastLikeCourseDemandsXml(model, new File(cfg.getProperty("Test.LastLikeCourseDemands"))); 157 if (cfg.getProperty("Test.StudentInfos")!=null) 158 loadStudentInfoXml(model, new File(cfg.getProperty("Test.StudentInfos"))); 159 if (cfg.getProperty("Test.CrsReq")!=null) 160 loadCrsReqFiles(model, cfg.getProperty("Test.CrsReq")); 161 } catch (Exception e) { 162 sLog.error("Unable to load model, reason: "+e.getMessage(), e); 163 return null; 164 } 165 if (cfg.getPropertyBoolean("Debug.DistanceConflict",false)) 166 DistanceConflict.sDebug=true; 167 if (cfg.getPropertyBoolean("Debug.BranchBoundSelection",false)) 168 BranchBoundSelection.sDebug=true; 169 if (cfg.getPropertyBoolean("Debug.SwapStudentsSelection",false)) 170 SwapStudentSelection.sDebug=true; 171 if (cfg.getProperty("CourseRequest.SameTimePrecise")!=null) 172 CourseRequest.sSameTimePrecise=cfg.getPropertyBoolean("CourseRequest.SameTimePrecise", false); 173 Logger.getLogger(BacktrackNeighbourSelection.class).setLevel(cfg.getPropertyBoolean("Debug.BacktrackNeighbourSelection",false)?Level.DEBUG:Level.INFO); 174 if (cfg.getPropertyBoolean("Test.FixPriorities", false)) 175 fixPriorities(model); 176 if (cfg.getProperty("Student.DummyStudentWeight")!=null) 177 Student.sDummyStudentWeight = cfg.getPropertyDouble("Student.DummyStudentWeight", Student.sDummyStudentWeight); 178 return model; 179 } 180 181 /** Batch sectioning test */ 182 public static Solution batchSectioning(DataProperties cfg) { 183 StudentSectioningModel model = loadModel(cfg); 184 if (model==null) return null; 185 186 if (cfg.getPropertyBoolean("Test.ComputeSectioningInfo", true)) 187 model.clearOnlineSectioningInfos(); 188 189 Solution solution = solveModel(model, cfg); 190 191 printInfo(solution, 192 cfg.getPropertyBoolean("Test.CreateReports", true), 193 cfg.getPropertyBoolean("Test.ComputeSectioningInfo", true), 194 cfg.getPropertyBoolean("Test.RunChecks", true)); 195 196 try { 197 Solver solver = new Solver(cfg); 198 solver.setInitalSolution(solution); 199 new StudentSectioningXMLSaver(solver).save(new File(new File(cfg.getProperty("General.Output",".")),"solution.xml")); 200 } catch (Exception e) { 201 sLog.error("Unable to save solution, reason: "+e.getMessage(),e); 202 } 203 204 saveInfoToXML(solution, null, new File(new File(cfg.getProperty("General.Output",".")),"info.xml")); 205 206 return solution; 207 } 208 209 /** Online sectioning test */ 210 public static Solution onlineSectioning(DataProperties cfg) throws Exception { 211 StudentSectioningModel model = loadModel(cfg); 212 if (model==null) return null; 213 214 Solution solution = new Solution(model,0,0); 215 solution.addSolutionListener(new TestSolutionListener()); 216 double startTime = JProf.currentTimeSec(); 217 218 219 Solver solver = new Solver(cfg); 220 solver.setInitalSolution(solution); 221 solver.initSolver(); 222 223 OnlineSelection onlineSelection = new OnlineSelection(cfg); 224 onlineSelection.init(solver); 225 226 double totalPenalty = 0, minPenalty = 0, maxPenalty = 0; 227 double minAvEnrlPenalty = 0, maxAvEnrlPenalty = 0; 228 double totalPrefPenalty = 0, minPrefPenalty = 0, maxPrefPenalty = 0; 229 double minAvEnrlPrefPenalty = 0, maxAvEnrlPrefPenalty = 0; 230 int nrChoices = 0, nrEnrollments = 0, nrCourseRequests = 0; 231 int chChoices = 0, chCourseRequests = 0, chStudents = 0; 232 233 int choiceLimit = model.getProperties().getPropertyInt("Test.ChoicesLimit", -1); 234 235 File outDir = new File(model.getProperties().getProperty("General.Output",".")); 236 outDir.mkdirs(); 237 PrintWriter pw = new PrintWriter(new FileWriter(new File(outDir,"choices.csv"))); 238 239 Vector students = model.getStudents(); 240 try { 241 Class studentOrdClass = Class.forName(model.getProperties().getProperty("Test.StudentOrder", StudentRandomOrder.class.getName())); 242 StudentOrder studentOrd = (StudentOrder)studentOrdClass.getConstructor(new Class[] {DataProperties.class}).newInstance(new Object[] {model.getProperties()}); 243 students = studentOrd.order(model.getStudents()); 244 } catch (Exception e) { 245 sLog.error("Unable to reorder students, reason: "+e.getMessage(),e); 246 } 247 248 for (Enumeration e=students.elements();e.hasMoreElements();) { 249 Student student = (Student)e.nextElement(); 250 if (student.nrAssignedRequests()>0) continue; //skip students with assigned courses (i.e., students already assigned by a batch sectioning process) 251 sLog.info("Sectioning student: "+student); 252 253 BranchBoundSelection.Selection selection = onlineSelection.getSelection(student); 254 BranchBoundNeighbour neighbour = selection.select(); 255 if (neighbour!=null) { 256 StudentPreferencePenalties penalties = null; 257 if (selection instanceof OnlineSelection.EpsilonSelection) { 258 OnlineSelection.EpsilonSelection epsSelection = (OnlineSelection.EpsilonSelection)selection; 259 penalties = epsSelection.getPenalties(); 260 for (int i=0;i<neighbour.getAssignment().length;i++) { 261 Request r = (Request)student.getRequests().elementAt(i); 262 if (r instanceof CourseRequest) { 263 nrCourseRequests++;chCourseRequests++; 264 int chChoicesThisRq = 0; 265 CourseRequest request = (CourseRequest)r; 266 for (Enumeration f=request.getAvaiableEnrollments().elements();f.hasMoreElements();) { 267 Enrollment x = (Enrollment)f.nextElement(); 268 nrEnrollments++; 269 if (epsSelection.isAllowed(i, x)) { 270 nrChoices++; 271 if (choiceLimit<=0 || chChoicesThisRq<choiceLimit) { 272 chChoices++;chChoicesThisRq++; 273 } 274 } 275 } 276 } 277 } 278 chStudents++; 279 if (chStudents==100) { 280 pw.println(sDF.format(((double)chChoices)/chCourseRequests)); pw.flush(); 281 chStudents = 0; chChoices = 0; chCourseRequests = 0; 282 } 283 } 284 for (int i=0;i<neighbour.getAssignment().length;i++) { 285 if (neighbour.getAssignment()[i]==null) continue; 286 Enrollment enrollment = neighbour.getAssignment()[i]; 287 if (enrollment.getRequest() instanceof CourseRequest) { 288 CourseRequest request = (CourseRequest)enrollment.getRequest(); 289 double[] avEnrlMinMax = getMinMaxAvailableEnrollmentPenalty(request); 290 minAvEnrlPenalty += avEnrlMinMax[0]; 291 maxAvEnrlPenalty += avEnrlMinMax[1]; 292 totalPenalty += enrollment.getPenalty(); 293 minPenalty += request.getMinPenalty(); 294 maxPenalty += request.getMaxPenalty(); 295 if (penalties!=null) { 296 double[] avEnrlPrefMinMax = penalties.getMinMaxAvailableEnrollmentPenalty(enrollment.getRequest()); 297 minAvEnrlPrefPenalty += avEnrlPrefMinMax[0]; 298 maxAvEnrlPrefPenalty += avEnrlPrefMinMax[1]; 299 totalPrefPenalty += penalties.getPenalty(enrollment); 300 minPrefPenalty += penalties.getMinPenalty(enrollment.getRequest()); 301 maxPrefPenalty += penalties.getMaxPenalty(enrollment.getRequest()); 302 } 303 } 304 } 305 neighbour.assign(solution.getIteration()); 306 sLog.info("Student "+student+" enrolls into "+neighbour); 307 onlineSelection.updateSpace(student); 308 } else { 309 sLog.warn("No solution found."); 310 } 311 solution.update(JProf.currentTimeSec()-startTime); 312 } 313 314 if (chCourseRequests>0) 315 pw.println(sDF.format(((double)chChoices)/chCourseRequests)); 316 317 pw.flush(); pw.close(); 318 319 solution.saveBest(); 320 321 printInfo(solution, 322 cfg.getPropertyBoolean("Test.CreateReports", true), 323 false, 324 cfg.getPropertyBoolean("Test.RunChecks", true)); 325 326 Hashtable extra = new Hashtable(); 327 sLog.info("Overall penalty is "+getPerc(totalPenalty, minPenalty, maxPenalty)+"% ("+sDF.format(totalPenalty)+"/"+sDF.format(minPenalty)+".."+sDF.format(maxPenalty)+")"); 328 extra.put("Overall penalty", getPerc(totalPenalty, minPenalty, maxPenalty)+"% ("+sDF.format(totalPenalty)+"/"+sDF.format(minPenalty)+".."+sDF.format(maxPenalty)+")"); 329 extra.put("Overall available enrollment penalty", getPerc(totalPenalty, minAvEnrlPenalty, maxAvEnrlPenalty)+"% ("+sDF.format(totalPenalty)+"/"+sDF.format(minAvEnrlPenalty)+".."+sDF.format(maxAvEnrlPenalty)+")"); 330 if (onlineSelection.isUseStudentPrefPenalties()) { 331 sLog.info("Overall preference penalty is "+getPerc(totalPrefPenalty, minPrefPenalty, maxPrefPenalty)+"% ("+sDF.format(totalPrefPenalty)+"/"+sDF.format(minPrefPenalty)+".."+sDF.format(maxPrefPenalty)+")"); 332 extra.put("Overall preference penalty", getPerc(totalPrefPenalty, minPrefPenalty, maxPrefPenalty)+"% ("+sDF.format(totalPrefPenalty)+"/"+sDF.format(minPrefPenalty)+".."+sDF.format(maxPrefPenalty)+")"); 333 extra.put("Overall preference available enrollment penalty", getPerc(totalPrefPenalty, minAvEnrlPrefPenalty, maxAvEnrlPrefPenalty)+"% ("+sDF.format(totalPrefPenalty)+"/"+sDF.format(minAvEnrlPrefPenalty)+".."+sDF.format(maxAvEnrlPrefPenalty)+")"); 334 extra.put("Average number of choices", sDF.format(((double)nrChoices)/nrCourseRequests)+" ("+nrChoices+"/"+nrCourseRequests+")"); 335 extra.put("Average number of enrollments", sDF.format(((double)nrEnrollments)/nrCourseRequests)+" ("+nrEnrollments+"/"+nrCourseRequests+")"); 336 } 337 338 try { 339 new StudentSectioningXMLSaver(solver).save(new File(new File(cfg.getProperty("General.Output",".")),"solution.xml")); 340 } catch (Exception e) { 341 sLog.error("Unable to save solution, reason: "+e.getMessage(),e); 342 } 343 344 saveInfoToXML(solution, extra, new File(new File(cfg.getProperty("General.Output",".")),"info.xml")); 345 346 return solution; 347 } 348 349 /** Minimum and maximum enrollment penalty, i.e., {@link Enrollment#getPenalty()} of all enrollments */ 350 public static double[] getMinMaxEnrollmentPenalty(CourseRequest request) { 351 Vector enrollments = request.values(); 352 if (enrollments.isEmpty()) return new double[] {0,0}; 353 double min = Double.MAX_VALUE, max = Double.MIN_VALUE; 354 for (Enumeration e=enrollments.elements();e.hasMoreElements();) { 355 Enrollment enrollment = (Enrollment)e.nextElement(); 356 double penalty = enrollment.getPenalty(); 357 min = Math.min(min, penalty); 358 max = Math.max(max, penalty); 359 } 360 return new double[] {min, max}; 361 } 362 363 /** Minimum and maximum available enrollment penalty, i.e., {@link Enrollment#getPenalty()} of all available enrollments */ 364 public static double[] getMinMaxAvailableEnrollmentPenalty(CourseRequest request) { 365 Vector enrollments = request.getAvaiableEnrollments(); 366 if (enrollments.isEmpty()) return new double[] {0,0}; 367 double min = Double.MAX_VALUE, max = Double.MIN_VALUE; 368 for (Enumeration e=enrollments.elements();e.hasMoreElements();) { 369 Enrollment enrollment = (Enrollment)e.nextElement(); 370 double penalty = enrollment.getPenalty(); 371 min = Math.min(min, penalty); 372 max = Math.max(max, penalty); 373 } 374 return new double[] {min, max}; 375 } 376 377 /** 378 * Compute percentage 379 * @param value current value 380 * @param min minimal bound 381 * @param max maximal bound 382 * @return (value-min)/(max-min) 383 */ 384 public static String getPerc(double value, double min, double max) { 385 if (max==min) return sDF.format(100.0); 386 return sDF.format(100.0 - 100.0*(value-min)/(max-min)); 387 } 388 389 /** 390 * Print some information about the solution 391 * @param solution given solution 392 * @param computeTables true, if reports {@link CourseConflictTable} and {@link DistanceConflictTable} are to be computed as well 393 * @param computeSectInfos true, if online sectioning infou is to be computed as well (see {@link StudentSectioningModel#computeOnlineSectioningInfos()}) 394 * @param runChecks true, if checks {@link OverlapCheck} and {@link SectionLimitCheck} are to be performed as well 395 */ 396 public static void printInfo(Solution solution, boolean computeTables, boolean computeSectInfos, boolean runChecks) { 397 StudentSectioningModel model = (StudentSectioningModel)solution.getModel(); 398 399 if (computeTables) { 400 if (solution.getModel().assignedVariables().size()>0) { 401 try { 402 File outDir = new File(model.getProperties().getProperty("General.Output",".")); 403 outDir.mkdirs(); 404 CourseConflictTable cct = new CourseConflictTable((StudentSectioningModel)solution.getModel()); 405 cct.createTable(true, false).save(new File(outDir, "conflicts-lastlike.csv")); 406 cct.createTable(false, true).save(new File(outDir, "conflicts-real.csv")); 407 408 DistanceConflictTable dct = new DistanceConflictTable((StudentSectioningModel)solution.getModel()); 409 dct.createTable(true, false).save(new File(outDir, "distances-lastlike.csv")); 410 dct.createTable(false, true).save(new File(outDir, "distances-real.csv")); 411 } catch (IOException e) { 412 sLog.error(e.getMessage(),e); 413 } 414 } 415 416 solution.saveBest(); 417 } 418 419 if (computeSectInfos) 420 model.computeOnlineSectioningInfos(); 421 422 if (runChecks) { 423 try { 424 if (model.getProperties().getPropertyBoolean("Test.InevitableStudentConflictsCheck", false)) { 425 InevitableStudentConflicts ch = new InevitableStudentConflicts(model); 426 if (!ch.check()) ch.getCSVFile().save(new File(new File(model.getProperties().getProperty("General.Output",".")), "inevitable-conflicts.csv")); 427 } 428 } catch (IOException e) { 429 sLog.error(e.getMessage(),e); 430 } 431 new OverlapCheck(model).check(); 432 new SectionLimitCheck(model).check(); 433 try { 434 CourseLimitCheck ch = new CourseLimitCheck(model); 435 if (!ch.check()) ch.getCSVFile().save(new File(new File(model.getProperties().getProperty("General.Output",".")), "course-limits.csv")); 436 } catch (IOException e) { 437 sLog.error(e.getMessage(),e); 438 } 439 } 440 441 sLog.info("Best solution found after "+solution.getBestTime()+" seconds ("+solution.getBestIteration()+" iterations)."); 442 sLog.info("Info: "+ToolBox.dict2string(solution.getExtendedInfo(),2)); 443 } 444 445 /** Solve the student sectioning problem using IFS solver */ 446 public static Solution solveModel(StudentSectioningModel model, DataProperties cfg) { 447 Solver solver = new Solver(cfg); 448 Solution solution = new Solution(model,0,0); 449 solver.setInitalSolution(solution); 450 if (cfg.getPropertyBoolean("Test.Verbose", false)) { 451 solver.addSolverListener(new SolverListener() { 452 public boolean variableSelected(long iteration, Variable variable) { 453 return true; 454 } 455 public boolean valueSelected(long iteration, Variable variable, Value value) { 456 return true; 457 } 458 public boolean neighbourSelected(long iteration, Neighbour neighbour) { 459 sLog.debug("Select["+iteration+"]: "+neighbour); 460 return true; 461 } 462 }); 463 } 464 solution.addSolutionListener(new TestSolutionListener()); 465 466 solver.start(); 467 try { 468 solver.getSolverThread().join(); 469 } catch (InterruptedException e) {} 470 471 solution = solver.lastSolution(); 472 solution.restoreBest(); 473 474 printInfo(solution, false, false, false); 475 476 return solution; 477 } 478 479 480 /** 481 * Compute last-like student weight for the given course 482 * @param course given course 483 * @param real number of real students for the course 484 * @param lastLike number of last-like students for the course 485 * @return weight of a student request for the given course 486 */ 487 public static double getLastLikeStudentWeight(Course course, int real, int lastLike) { 488 int projected = course.getProjected(); 489 int limit = course.getLimit(); 490 if (course.getLimit()<0) { 491 sLog.debug(" -- Course "+course.getName()+" is unlimited."); 492 return 1.0; 493 } 494 if (projected<=0) { 495 sLog.warn(" -- No projected demand for course "+course.getName()+", using course limit ("+limit+")"); 496 projected = limit; 497 } else if (limit<projected) { 498 sLog.warn(" -- Projected number of students is over course limit for course "+course.getName()+" ("+Math.round(projected)+">"+limit+")"); 499 projected = limit; 500 } 501 if (lastLike==0) { 502 sLog.warn(" -- No last like info for course "+course.getName()); 503 return 1.0; 504 } 505 double weight = ((double)Math.max(0, projected - real)) / lastLike; 506 sLog.debug(" -- last like student weight for "+course.getName()+" is "+weight+" (lastLike="+lastLike+", real="+real+", projected="+projected+")"); 507 return weight; 508 } 509 510 511 /** 512 * Load last-like students from an XML file (the one that is used to load last like course demands table 513 * in the timetabling application) 514 */ 515 public static void loadLastLikeCourseDemandsXml(StudentSectioningModel model, File xml) { 516 try { 517 Document document = (new SAXReader()).read(xml); 518 Element root = document.getRootElement(); 519 Hashtable requests = new Hashtable(); 520 long reqId = 0; 521 for (Iterator i=root.elementIterator("student");i.hasNext();) { 522 Element studentEl = (Element)i.next(); 523 Student student = new Student(Long.parseLong(studentEl.attributeValue("externalId"))); 524 student.setDummy(true); 525 int priority = 0; 526 HashSet reqCourses = new HashSet(); 527 for (Iterator j=studentEl.elementIterator("studentCourse");j.hasNext();) { 528 Element courseEl = (Element)j.next(); 529 String subjectArea = courseEl.attributeValue("subject"); 530 String courseNbr = courseEl.attributeValue("courseNumber"); 531 Course course = null; 532 for (Enumeration e=model.getOfferings().elements();course==null && e.hasMoreElements();) { 533 Offering offering = (Offering)e.nextElement(); 534 for (Enumeration f=offering.getCourses().elements();course==null && f.hasMoreElements();) { 535 Course c = (Course)f.nextElement(); 536 if (c.getSubjectArea().equals(subjectArea) && c.getCourseNumber().equals(courseNbr)) 537 course = c; 538 } 539 } 540 if (course==null && courseNbr.charAt(courseNbr.length()-1)>='A' && courseNbr.charAt(courseNbr.length()-1)<='Z') { 541 String courseNbrNoSfx = courseNbr.substring(0, courseNbr.length()-1); 542 for (Enumeration e=model.getOfferings().elements();course==null && e.hasMoreElements();) { 543 Offering offering = (Offering)e.nextElement(); 544 for (Enumeration f=offering.getCourses().elements();course==null && f.hasMoreElements();) { 545 Course c = (Course)f.nextElement(); 546 if (c.getSubjectArea().equals(subjectArea) && c.getCourseNumber().equals(courseNbrNoSfx)) 547 course = c; 548 } 549 } 550 } 551 if (course==null) { 552 sLog.warn("Course "+subjectArea+" "+courseNbr+" not found."); 553 } else { 554 if (!reqCourses.add(course)) { 555 sLog.warn("Course "+subjectArea+" "+courseNbr+" already requested."); 556 } else { 557 Vector courses = new Vector(1); 558 courses.add(course); 559 CourseRequest request = new CourseRequest(reqId++, priority++, false, student, courses, false); 560 Vector requestsThisCourse = (Vector)requests.get(course); 561 if (requestsThisCourse==null) { 562 requestsThisCourse = new Vector(); 563 requests.put(course, requestsThisCourse); 564 } 565 requestsThisCourse.add(request); 566 } 567 } 568 } 569 if (!student.getRequests().isEmpty()) 570 model.addStudent(student); 571 } 572 for (Iterator i=requests.entrySet().iterator();i.hasNext();) { 573 Map.Entry entry = (Map.Entry)i.next(); 574 Course course = (Course)entry.getKey(); 575 Vector requestsThisCourse = (Vector)entry.getValue(); 576 double weight = getLastLikeStudentWeight(course, 0, requestsThisCourse.size()); 577 for (Enumeration e=requestsThisCourse.elements();e.hasMoreElements();) { 578 CourseRequest request = (CourseRequest)e.nextElement(); 579 request.setWeight(weight); 580 } 581 } 582 } catch (Exception e) { 583 sLog.error(e.getMessage(),e); 584 } 585 } 586 587 /** 588 * Load course request from the given files (in the format being used by the old MSF system) 589 * @param model student sectioning model (with offerings loaded) 590 * @param files semi-colon separated list of files to be loaded 591 */ 592 public static void loadCrsReqFiles(StudentSectioningModel model, String files) { 593 try { 594 boolean lastLike = model.getProperties().getPropertyBoolean("Test.CrsReqIsLastLike", true); 595 boolean shuffleIds = model.getProperties().getPropertyBoolean("Test.CrsReqShuffleStudentIds", true); 596 boolean tryWithoutSuffix = model.getProperties().getPropertyBoolean("Test.CrsReqTryWithoutSuffix", false); 597 Hashtable students = new Hashtable(); 598 long reqId = 0; 599 for (StringTokenizer stk=new StringTokenizer(files,";");stk.hasMoreTokens();) { 600 String file = stk.nextToken(); 601 sLog.debug("Loading "+file+" ..."); 602 BufferedReader in = new BufferedReader(new FileReader(file)); 603 String line; int lineIndex=0; 604 while ((line=in.readLine())!=null) { 605 lineIndex++; 606 if (line.length()<=150) continue; 607 char code = line.charAt(13); 608 if (code=='H' || code=='T') continue; //skip header and tail 609 long studentId = Long.parseLong(line.substring(14,23)); 610 Student student = (Student)students.get(new Long(studentId)); 611 if (student==null) { 612 student = new Student(studentId); 613 if (lastLike) student.setDummy(true); 614 students.put(new Long(studentId), student); 615 sLog.debug(" -- loading student "+studentId+" ..."); 616 } else 617 sLog.debug(" -- updating student "+studentId+" ..."); 618 line = line.substring(150); 619 while (line.length()>=20) { 620 String subjectArea = line.substring(0,4).trim(); 621 String courseNbr = line.substring(4,8).trim(); 622 if (subjectArea.length()==0 || courseNbr.length()==0) { 623 line = line.substring(20); 624 continue; 625 } 626 String instrSel = line.substring(8,10); //ZZ - Remove previous instructor selection 627 char reqPDiv = line.charAt(10); //P - Personal preference; C - Conflict resolution; 628 //0 - (Zero) used by program only, for change requests to reschedule division 629 // (used to reschedule canceled division) 630 String reqDiv = line.substring(11,13); //00 - Reschedule division 631 String reqSect = line.substring(13,15); //Contains designator for designator-required courses 632 String credit = line.substring(15,19); 633 char nameRaise = line.charAt(19); //N - Name raise 634 char action =line.charAt(19); //A - Add; D - Drop; C - Change 635 sLog.debug(" -- requesting "+subjectArea+" "+courseNbr+" (action:"+action+") ..."); 636 Course course = null; 637 for (Enumeration e=model.getOfferings().elements();course==null && e.hasMoreElements();) { 638 Offering offering = (Offering)e.nextElement(); 639 for (Enumeration f=offering.getCourses().elements();course==null && f.hasMoreElements();) { 640 Course c = (Course)f.nextElement(); 641 if (c.getSubjectArea().equals(subjectArea) && c.getCourseNumber().equals(courseNbr)) 642 course = c; 643 } 644 } 645 if (course==null && tryWithoutSuffix && courseNbr.charAt(courseNbr.length()-1)>='A' && courseNbr.charAt(courseNbr.length()-1)<='Z') { 646 String courseNbrNoSfx = courseNbr.substring(0, courseNbr.length()-1); 647 for (Enumeration e=model.getOfferings().elements();course==null && e.hasMoreElements();) { 648 Offering offering = (Offering)e.nextElement(); 649 for (Enumeration f=offering.getCourses().elements();course==null && f.hasMoreElements();) { 650 Course c = (Course)f.nextElement(); 651 if (c.getSubjectArea().equals(subjectArea) && c.getCourseNumber().equals(courseNbrNoSfx)) 652 course = c; 653 } 654 } 655 } 656 if (course==null) { 657 if (courseNbr.charAt(courseNbr.length()-1)>='A' && courseNbr.charAt(courseNbr.length()-1)<='Z') { 658 } else { 659 sLog.warn(" -- course "+subjectArea+" "+courseNbr+" not found (file "+file+", line "+lineIndex+")"); 660 } 661 } else { 662 CourseRequest courseRequest = null; 663 for (Enumeration e=student.getRequests().elements();courseRequest==null && e.hasMoreElements();) { 664 Request request = (Request)e.nextElement(); 665 if (request instanceof CourseRequest && ((CourseRequest)request).getCourses().contains(course)) 666 courseRequest = (CourseRequest)request; 667 } 668 if (action=='A') { 669 if (courseRequest==null) { 670 Vector courses = new Vector(1); courses.add(course); 671 courseRequest = new CourseRequest(reqId++, student.getRequests().size(), false, student, courses, false); 672 } else { 673 sLog.warn(" -- request for course "+course+" is already present"); 674 } 675 } else if (action=='D') { 676 if (courseRequest==null) { 677 sLog.warn(" -- request for course "+course+" is not present -- cannot be dropped"); 678 } else { 679 student.getRequests().remove(courseRequest); 680 } 681 } else if (action=='C') { 682 if (courseRequest==null) { 683 sLog.warn(" -- request for course "+course+" is not present -- cannot be changed"); 684 } else { 685 //? 686 } 687 } else { 688 sLog.warn(" -- unknown action "+action); 689 } 690 } 691 line = line.substring(20); 692 } 693 } 694 in.close(); 695 } 696 Hashtable requests = new Hashtable(); 697 HashSet studentIds = new HashSet(); 698 for (Enumeration e=students.elements();e.hasMoreElements();) { 699 Student student = (Student)e.nextElement(); 700 if (!student.getRequests().isEmpty()) 701 model.addStudent(student); 702 if (shuffleIds) { 703 long newId = -1; 704 while (true) { 705 newId = 1+(long)(999999999L * Math.random()); 706 if (studentIds.add(new Long(newId))) break; 707 } 708 student.setId(newId); 709 } 710 if (student.isDummy()) { 711 for (Enumeration f=student.getRequests().elements();f.hasMoreElements();) { 712 Request request = (Request)f.nextElement(); 713 if (request instanceof CourseRequest) { 714 Course course = (Course)((CourseRequest)request).getCourses().firstElement(); 715 Vector requestsThisCourse = (Vector)requests.get(course); 716 if (requestsThisCourse==null) { 717 requestsThisCourse = new Vector(); 718 requests.put(course, requestsThisCourse); 719 } 720 requestsThisCourse.add(request); 721 } 722 } 723 } 724 } 725 Collections.sort(model.getStudents(), new Comparator() { 726 public int compare(Object o1, Object o2) { 727 return Double.compare(((Student)o1).getId(),((Student)o2).getId()); 728 } 729 }); 730 for (Iterator i=requests.entrySet().iterator();i.hasNext();) { 731 Map.Entry entry = (Map.Entry)i.next(); 732 Course course = (Course)entry.getKey(); 733 Vector requestsThisCourse = (Vector)entry.getValue(); 734 double weight = getLastLikeStudentWeight(course, 0, requestsThisCourse.size()); 735 for (Enumeration e=requestsThisCourse.elements();e.hasMoreElements();) { 736 CourseRequest request = (CourseRequest)e.nextElement(); 737 request.setWeight(weight); 738 } 739 } 740 if (model.getProperties().getProperty("Test.EtrChk")!=null) { 741 for (StringTokenizer stk=new StringTokenizer(model.getProperties().getProperty("Test.EtrChk"),";");stk.hasMoreTokens();) { 742 String file = stk.nextToken(); 743 sLog.debug("Loading "+file+" ..."); 744 BufferedReader in = new BufferedReader(new FileReader(file)); 745 String line; int lineIndex=0; 746 while ((line=in.readLine())!=null) { 747 lineIndex++; 748 if (line.length()<55) continue; 749 char code = line.charAt(12); 750 if (code=='H' || code=='T') continue; //skip header and tail 751 if (code=='D' || code=='K') continue; //skip delete nad cancel 752 long studentId = Long.parseLong(line.substring(2,11)); 753 Student student = (Student)students.get(new Long(studentId)); 754 if (student==null) { 755 sLog.info(" -- student "+studentId+" not found"); 756 continue; 757 } 758 sLog.info(" -- reading student "+studentId); 759 String area = line.substring(15,18).trim(); 760 if (area.length()==0) continue; 761 String clasf = line.substring(18,20).trim(); 762 String major = line.substring(21, 24).trim(); 763 String minor = line.substring(24, 27).trim(); 764 student.getAcademicAreaClasiffications().clear(); student.getMajors().clear(); student.getMinors().clear(); 765 student.getAcademicAreaClasiffications().add(new AcademicAreaCode(area, clasf)); 766 if (major.length()>0) student.getMajors().add(new AcademicAreaCode(area, major)); 767 if (minor.length()>0) student.getMinors().add(new AcademicAreaCode(area, minor)); 768 } 769 } 770 } 771 int without = 0; 772 for (Enumeration e=students.elements();e.hasMoreElements();) { 773 Student student = (Student)e.nextElement(); 774 if (student.getAcademicAreaClasiffications().isEmpty()) without++; 775 } 776 fixPriorities(model); 777 sLog.info("Students without academic area: "+without); 778 } catch (Exception e) { 779 sLog.error(e.getMessage(),e); 780 } 781 } 782 783 public static void fixPriorities(StudentSectioningModel model) { 784 for (Enumeration e=model.getStudents().elements();e.hasMoreElements();) { 785 Student student = (Student)e.nextElement(); 786 Collections.sort(student.getRequests(), new Comparator() { 787 public int compare(Object o1, Object o2) { 788 Request r1 = (Request)o1; 789 Request r2 = (Request)o2; 790 int cmp = Double.compare(r1.getPriority(), r2.getPriority()); 791 if (cmp!=0) return cmp; 792 return Double.compare(r1.getId(), r2.getId()); 793 } 794 }); 795 int priority = 0; 796 for (Enumeration f=student.getRequests().elements();f.hasMoreElements();priority++) { 797 Request request = (Request)f.nextElement(); 798 if (priority!=request.getPriority()) { 799 sLog.debug("Change priority of "+request+" to "+priority); 800 request.setPriority(priority); 801 } 802 } 803 } 804 } 805 806 /** Load student infos from a given XML file. */ 807 public static void loadStudentInfoXml(StudentSectioningModel model, File xml) { 808 try { 809 sLog.info("Loading student infos from "+xml); 810 Document document = (new SAXReader()).read(xml); 811 Element root = document.getRootElement(); 812 Hashtable requests = new Hashtable(); 813 long reqId = 0; 814 Hashtable studentTable = new Hashtable(); 815 for (Enumeration e=model.getStudents().elements();e.hasMoreElements();) { 816 Student student = (Student)e.nextElement(); 817 studentTable.put(new Long(student.getId()), student); 818 } 819 for (Iterator i=root.elementIterator("student");i.hasNext();) { 820 Element studentEl = (Element)i.next(); 821 Student student = (Student)studentTable.get(Long.valueOf(studentEl.attributeValue("externalId"))); 822 if (student==null) { 823 sLog.debug(" -- student "+studentEl.attributeValue("externalId")+" not found"); 824 continue; 825 } 826 sLog.debug(" -- loading info for student "+student); 827 student.getAcademicAreaClasiffications().clear(); 828 if (studentEl.element("studentAcadAreaClass")!=null) 829 for (Iterator j=studentEl.element("studentAcadAreaClass").elementIterator("acadAreaClass");j.hasNext();) { 830 Element studentAcadAreaClassElement = (Element)j.next(); 831 student.getAcademicAreaClasiffications().add( 832 new AcademicAreaCode( 833 studentAcadAreaClassElement.attributeValue("academicArea"), 834 studentAcadAreaClassElement.attributeValue("academicClass")) 835 ); 836 } 837 sLog.debug(" -- acad areas classifs "+student.getAcademicAreaClasiffications()); 838 student.getMajors().clear(); 839 if (studentEl.element("studentMajors")!=null) 840 for (Iterator j=studentEl.element("studentMajors").elementIterator("major");j.hasNext();) { 841 Element studentMajorElement = (Element)j.next(); 842 student.getMajors().add( 843 new AcademicAreaCode( 844 studentMajorElement.attributeValue("academicArea"), 845 studentMajorElement.attributeValue("code")) 846 ); 847 } 848 sLog.debug(" -- majors "+student.getMajors()); 849 student.getMinors().clear(); 850 if (studentEl.element("studentMinors")!=null) 851 for (Iterator j=studentEl.element("studentMinors").elementIterator("minor");j.hasNext();) { 852 Element studentMinorElement = (Element)j.next(); 853 student.getMinors().add( 854 new AcademicAreaCode( 855 studentMinorElement.attributeValue("academicArea",""), 856 studentMinorElement.attributeValue("code","")) 857 ); 858 } 859 sLog.debug(" -- minors "+student.getMinors()); 860 } 861 } catch (Exception e) { 862 sLog.error(e.getMessage(),e); 863 } 864 } 865 866 /** Save solution info as XML */ 867 public static void saveInfoToXML(Solution solution, Hashtable extra, File file) { 868 FileOutputStream fos = null; 869 try { 870 Document document = DocumentHelper.createDocument(); 871 document.addComment("Solution Info"); 872 873 Element root = document.addElement("info"); 874 TreeSet entrySet = new TreeSet(new Comparator() { 875 public int compare(Object o1, Object o2) { 876 Map.Entry e1 = (Map.Entry)o1; 877 Map.Entry e2 = (Map.Entry)o2; 878 return ((Comparable)e1.getKey()).compareTo(e2.getKey()); 879 } 880 }); 881 entrySet.addAll(solution.getExtendedInfo().entrySet()); 882 if (extra!=null) entrySet.addAll(extra.entrySet()); 883 for (Iterator i=entrySet.iterator();i.hasNext();) { 884 Map.Entry entry = (Map.Entry)i.next(); 885 root.addElement("property").addAttribute("name", entry.getKey().toString()).setText(entry.getValue().toString()); 886 } 887 888 fos = new FileOutputStream(file); 889 (new XMLWriter(fos,OutputFormat.createPrettyPrint())).write(document); 890 fos.flush();fos.close();fos=null; 891 } catch (Exception e) { 892 sLog.error("Unable to save info, reason: "+e.getMessage(),e); 893 } finally { 894 try { 895 if (fos!=null) fos.close(); 896 } catch (IOException e) {} 897 } 898 } 899 900 private static void fixWeights(StudentSectioningModel model) { 901 Hashtable lastLike = new Hashtable(); 902 Hashtable real = new Hashtable(); 903 HashSet lastLikeIds = new HashSet(); 904 HashSet realIds = new HashSet(); 905 for (Enumeration e=model.getStudents().elements();e.hasMoreElements();) { 906 Student student = (Student)e.nextElement(); 907 if (student.isDummy()) { 908 if (!lastLikeIds.add(new Long(student.getId()))){ 909 sLog.error("Two last-like student with id "+student.getId()); 910 } 911 } else { 912 if (!realIds.add(new Long(student.getId()))){ 913 sLog.error("Two real student with id "+student.getId()); 914 } 915 } 916 for (Enumeration f=student.getRequests().elements();f.hasMoreElements();) { 917 Request request = (Request)f.nextElement(); 918 if (request instanceof CourseRequest) { 919 CourseRequest courseRequest = (CourseRequest)request; 920 Course course = (Course)courseRequest.getCourses().firstElement(); 921 Integer cnt = (Integer)(student.isDummy()?lastLike:real).get(course); 922 (student.isDummy()?lastLike:real).put(course, new Integer((cnt==null?0:cnt.intValue())+1)); 923 } 924 } 925 } 926 for (Enumeration e=new Vector(model.getStudents()).elements();e.hasMoreElements();) { 927 Student student = (Student)e.nextElement(); 928 if (student.isDummy() && realIds.contains(new Long(student.getId()))) { 929 sLog.warn("There is both last-like and real student with id "+student.getId()); 930 long newId = -1; 931 while (true) { 932 newId = 1+(long)(999999999L * Math.random()); 933 if (!realIds.contains(new Long(newId)) && !lastLikeIds.contains(new Long(newId))) break; 934 } 935 lastLikeIds.remove(new Long(student.getId())); 936 lastLikeIds.add(new Long(newId)); 937 student.setId(newId); 938 sLog.warn(" -- last-like student id changed to "+student.getId()); 939 } 940 for (Enumeration f=new Vector(student.getRequests()).elements();f.hasMoreElements();) { 941 Request request = (Request)f.nextElement(); 942 if (!student.isDummy()) { 943 request.setWeight(1.0); continue; 944 } 945 if (request instanceof CourseRequest) { 946 CourseRequest courseRequest = (CourseRequest)request; 947 Course course = (Course)courseRequest.getCourses().firstElement(); 948 Integer lastLikeCnt = (Integer)lastLike.get(course); 949 Integer realCnt = (Integer)real.get(course); 950 courseRequest.setWeight(getLastLikeStudentWeight(course, realCnt==null?0:realCnt.intValue(), lastLikeCnt==null?0:lastLikeCnt.intValue())); 951 } else request.setWeight(1.0); 952 if (request.getWeight()<=0.0) { 953 model.removeVariable(request); 954 student.getRequests().remove(request); 955 } 956 } 957 if (student.getRequests().isEmpty()) { 958 model.getStudents().remove(student); 959 } 960 } 961 } 962 963 /** Combine students from the provided two files */ 964 public static StudentSectioningModel combineStudents(DataProperties cfg, File lastLikeStudentData, File realStudentData) { 965 try { 966 RandomStudentFilter rnd = new RandomStudentFilter(1.0); 967 968 StudentSectioningModel model = null; 969 970 for (StringTokenizer stk = new StringTokenizer(cfg.getProperty("Test.CombineAcceptProb", "1.0"),",");stk.hasMoreTokens();) { 971 double acceptProb = Double.parseDouble(stk.nextToken()); 972 sLog.info("Test.CombineAcceptProb="+acceptProb); 973 rnd.setProbability(acceptProb); 974 975 StudentFilter batchFilter = 976 new CombinedStudentFilter( 977 new ReverseStudentFilter(new FreshmanStudentFilter()), 978 rnd, 979 CombinedStudentFilter.OP_AND 980 ); 981 StudentFilter onlineFilter = new ReverseStudentFilter(batchFilter); 982 983 model = new StudentSectioningModel(cfg); 984 StudentSectioningXMLLoader loader = new StudentSectioningXMLLoader(model); 985 loader.setLoadStudents(false); 986 loader.load(); 987 988 StudentSectioningXMLLoader lastLikeLoader = new StudentSectioningXMLLoader(model); 989 lastLikeLoader.setInputFile(lastLikeStudentData); 990 lastLikeLoader.setLoadOfferings(false); 991 lastLikeLoader.setLoadStudents(true); 992 lastLikeLoader.load(); 993 994 StudentSectioningXMLLoader realLoader = new StudentSectioningXMLLoader(model); 995 realLoader.setInputFile(realStudentData); 996 realLoader.setLoadOfferings(false); 997 realLoader.setLoadStudents(true); 998 realLoader.setStudentFilter(batchFilter); 999 realLoader.load(); 1000 1001 fixWeights(model); 1002 1003 fixPriorities(model); 1004 1005 Solver solver = new Solver(model.getProperties()); 1006 solver.setInitalSolution(model); 1007 new StudentSectioningXMLSaver(solver).save(new File(new File(model.getProperties().getProperty("General.Output",".")),"solution-r"+((int)(100.0*acceptProb))+".xml")); 1008 1009 } 1010 1011 return model; 1012 1013 } catch (Exception e) { 1014 sLog.error("Unable to combine students, reason: "+e.getMessage(),e); 1015 return null; 1016 } 1017 } 1018 1019 /** Main */ 1020 public static void main(String[] args) { 1021 try { 1022 DataProperties cfg = new DataProperties(); 1023 cfg.setProperty("Termination.Class","net.sf.cpsolver.ifs.termination.GeneralTerminationCondition"); 1024 cfg.setProperty("Termination.StopWhenComplete","true"); 1025 cfg.setProperty("Termination.TimeOut","600"); 1026 cfg.setProperty("Comparator.Class","net.sf.cpsolver.ifs.solution.GeneralSolutionComparator"); 1027 cfg.setProperty("Value.Class","net.sf.cpsolver.studentsct.heuristics.EnrollmentSelection");//net.sf.cpsolver.ifs.heuristics.GeneralValueSelection 1028 cfg.setProperty("Value.WeightConflicts", "1.0"); 1029 cfg.setProperty("Value.WeightNrAssignments", "0.0"); 1030 cfg.setProperty("Variable.Class","net.sf.cpsolver.ifs.heuristics.GeneralVariableSelection"); 1031 cfg.setProperty("Neighbour.Class","net.sf.cpsolver.studentsct.heuristics.StudentSctNeighbourSelection"); 1032 cfg.setProperty("General.SaveBestUnassigned", "-1"); 1033 cfg.setProperty("Extensions.Classes","net.sf.cpsolver.ifs.extension.ConflictStatistics;net.sf.cpsolver.studentsct.extension.DistanceConflict"); 1034 cfg.setProperty("Data.Initiative","puWestLafayetteTrdtn"); 1035 cfg.setProperty("Data.Term","Fal"); 1036 cfg.setProperty("Data.Year","2007"); 1037 cfg.setProperty("General.Input","pu-sectll-fal07-s.xml"); 1038 if (args.length>=1) { 1039 cfg.load(new FileInputStream(args[0])); 1040 } 1041 cfg.putAll(System.getProperties()); 1042 1043 if (args.length>=2) { 1044 cfg.setProperty("General.Input", args[1]); 1045 } 1046 1047 if (args.length>=3) { 1048 File logFile = new File(ToolBox.configureLogging(args[2]+File.separator+(sDateFormat.format(new Date())), cfg, false, false)); 1049 cfg.setProperty("General.Output", logFile.getParentFile().getAbsolutePath()); 1050 } else if (cfg.getProperty("General.Output")!=null) { 1051 cfg.setProperty("General.Output", cfg.getProperty("General.Output",".")+File.separator+(sDateFormat.format(new Date()))); 1052 File logFile = new File(ToolBox.configureLogging(cfg.getProperty("General.Output","."), cfg, false, false)); 1053 } else { 1054 ToolBox.configureLogging(); 1055 cfg.setProperty("General.Output", System.getProperty("user.home", ".")+File.separator+"Sectioning-Test"+File.separator+(sDateFormat.format(new Date()))); 1056 } 1057 1058 if (args.length>=4 && "online".equals(args[3])) { 1059 onlineSectioning(cfg); 1060 } else if (args.length>=4 && "simple".equals(args[3])) { 1061 cfg.setProperty("Sectioning.UseOnlinePenalties", "false"); 1062 onlineSectioning(cfg); 1063 } else { 1064 batchSectioning(cfg); 1065 } 1066 } catch (Exception e) { 1067 sLog.error(e.getMessage(),e); 1068 e.printStackTrace(); 1069 } 1070 } 1071 1072 public static class ExtraStudentFilter implements StudentFilter { 1073 HashSet iIds = new HashSet(); 1074 public ExtraStudentFilter(StudentSectioningModel model) { 1075 for (Enumeration e=model.getStudents().elements();e.hasMoreElements();) { 1076 Student student = (Student)e.nextElement(); 1077 iIds.add(new Long(student.getId())); 1078 } 1079 } 1080 public boolean accept(Student student) { 1081 return !iIds.contains(new Long(student.getId())); 1082 } 1083 } 1084 1085 public static class TestSolutionListener implements SolutionListener { 1086 public void solutionUpdated(Solution solution) {} 1087 public void getInfo(Solution solution, java.util.Dictionary info) {} 1088 public void getInfo(Solution solution, java.util.Dictionary info, java.util.Vector variables) {} 1089 public void bestCleared(Solution solution) {} 1090 public void bestSaved(Solution solution) { 1091 StudentSectioningModel m = (StudentSectioningModel)solution.getModel(); 1092 sLog.debug("**BEST** "+ 1093 (m.getNrRealStudents(false)>0?"RRq:"+m.getNrAssignedRealRequests(false)+"/"+m.getNrRealRequests(false)+", ":"")+ 1094 (m.getNrLastLikeStudents(false)>0?"DRq:"+m.getNrAssignedLastLikeRequests(false)+"/"+m.getNrLastLikeRequests(false)+", ":"")+ 1095 (m.getNrRealStudents(false)>0?"RS:"+m.getNrCompleteRealStudents(false)+"/"+m.getNrRealStudents(false)+", ":"")+ 1096 (m.getNrLastLikeStudents(false)>0?"DS:"+m.getNrCompleteLastLikeStudents(false)+"/"+m.getNrLastLikeStudents(false)+", ":"")+ 1097 "V:"+sDF.format(m.getTotalValue())+ 1098 (m.getDistanceConflict()==null?"":", DC:"+sDF.format(m.getDistanceConflict().getTotalNrConflicts()))); 1099 } 1100 public void bestRestored(Solution solution) {} 1101 } 1102 }