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