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