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 }