001 package net.sf.cpsolver.exam;
002
003 import java.io.File;
004 import java.io.FileInputStream;
005 import java.io.FileOutputStream;
006 import java.io.FileWriter;
007 import java.io.IOException;
008 import java.io.PrintWriter;
009 import java.text.DecimalFormat;
010 import java.util.Collection;
011 import java.util.Map;
012
013 import net.sf.cpsolver.exam.criteria.DistributionPenalty;
014 import net.sf.cpsolver.exam.criteria.ExamRotationPenalty;
015 import net.sf.cpsolver.exam.criteria.InstructorBackToBackConflicts;
016 import net.sf.cpsolver.exam.criteria.InstructorDirectConflicts;
017 import net.sf.cpsolver.exam.criteria.InstructorDistanceBackToBackConflicts;
018 import net.sf.cpsolver.exam.criteria.InstructorMoreThan2ADayConflicts;
019 import net.sf.cpsolver.exam.criteria.InstructorNotAvailableConflicts;
020 import net.sf.cpsolver.exam.criteria.LargeExamsPenalty;
021 import net.sf.cpsolver.exam.criteria.PeriodIndexPenalty;
022 import net.sf.cpsolver.exam.criteria.PeriodPenalty;
023 import net.sf.cpsolver.exam.criteria.PeriodSizePenalty;
024 import net.sf.cpsolver.exam.criteria.PerturbationPenalty;
025 import net.sf.cpsolver.exam.criteria.RoomPenalty;
026 import net.sf.cpsolver.exam.criteria.RoomPerturbationPenalty;
027 import net.sf.cpsolver.exam.criteria.RoomSizePenalty;
028 import net.sf.cpsolver.exam.criteria.RoomSplitDistancePenalty;
029 import net.sf.cpsolver.exam.criteria.RoomSplitPenalty;
030 import net.sf.cpsolver.exam.criteria.StudentBackToBackConflicts;
031 import net.sf.cpsolver.exam.criteria.StudentDirectConflicts;
032 import net.sf.cpsolver.exam.criteria.StudentDistanceBackToBackConflicts;
033 import net.sf.cpsolver.exam.criteria.StudentMoreThan2ADayConflicts;
034 import net.sf.cpsolver.exam.criteria.StudentNotAvailableConflicts;
035 import net.sf.cpsolver.exam.criteria.additional.DistanceToStronglyPreferredRoom;
036 import net.sf.cpsolver.exam.criteria.additional.DistributionViolation;
037 import net.sf.cpsolver.exam.criteria.additional.PeriodViolation;
038 import net.sf.cpsolver.exam.criteria.additional.RoomViolation;
039 import net.sf.cpsolver.exam.model.Exam;
040 import net.sf.cpsolver.exam.model.ExamInstructor;
041 import net.sf.cpsolver.exam.model.ExamModel;
042 import net.sf.cpsolver.exam.model.ExamPlacement;
043 import net.sf.cpsolver.exam.model.ExamStudent;
044 import net.sf.cpsolver.exam.reports.ExamAssignments;
045 import net.sf.cpsolver.exam.reports.ExamCourseSectionAssignments;
046 import net.sf.cpsolver.exam.reports.ExamInstructorConflicts;
047 import net.sf.cpsolver.exam.reports.ExamNbrMeetingsPerDay;
048 import net.sf.cpsolver.exam.reports.ExamPeriodUsage;
049 import net.sf.cpsolver.exam.reports.ExamRoomSchedule;
050 import net.sf.cpsolver.exam.reports.ExamRoomSplit;
051 import net.sf.cpsolver.exam.reports.ExamStudentBackToBackConflicts;
052 import net.sf.cpsolver.exam.reports.ExamStudentConflicts;
053 import net.sf.cpsolver.exam.reports.ExamStudentConflictsBySectionCourse;
054 import net.sf.cpsolver.exam.reports.ExamStudentConflictsPerExam;
055 import net.sf.cpsolver.exam.reports.ExamStudentDirectConflicts;
056 import net.sf.cpsolver.exam.reports.ExamStudentMoreTwoADay;
057 import net.sf.cpsolver.exam.split.ExamSplitter;
058 import net.sf.cpsolver.ifs.solution.Solution;
059 import net.sf.cpsolver.ifs.solution.SolutionListener;
060 import net.sf.cpsolver.ifs.solver.Solver;
061 import net.sf.cpsolver.ifs.util.DataProperties;
062 import net.sf.cpsolver.ifs.util.Progress;
063 import net.sf.cpsolver.ifs.util.ToolBox;
064
065 import org.apache.log4j.ConsoleAppender;
066 import org.apache.log4j.FileAppender;
067 import org.apache.log4j.Level;
068 import org.apache.log4j.Logger;
069 import org.apache.log4j.PatternLayout;
070 import org.dom4j.Document;
071 import org.dom4j.io.OutputFormat;
072 import org.dom4j.io.SAXReader;
073 import org.dom4j.io.XMLWriter;
074
075 /**
076 * An examination timetabling test program. The following steps are performed:
077 * <ul>
078 * <li>Input properties are loaded
079 * <li>Input problem is loaded (General.Input property)
080 * <li>Problem is solved (using the given properties)
081 * <li>Solution is save (General.OutputFile property)
082 * </ul>
083 * <br>
084 * <br>
085 * Usage: <code>
086 * java -Xmx1024m -jar examtt-1.1.jar exam.properties input.xml output.xml
087 * </code> <br>
088 * <br>
089 *
090 * @version ExamTT 1.2 (Examination Timetabling)<br>
091 * Copyright (C) 2007 - 2010 Tomas Muller<br>
092 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
093 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
094 * <br>
095 * This library is free software; you can redistribute it and/or modify
096 * it under the terms of the GNU Lesser General Public License as
097 * published by the Free Software Foundation; either version 3 of the
098 * License, or (at your option) any later version. <br>
099 * <br>
100 * This library is distributed in the hope that it will be useful, but
101 * WITHOUT ANY WARRANTY; without even the implied warranty of
102 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
103 * Lesser General Public License for more details. <br>
104 * <br>
105 * You should have received a copy of the GNU Lesser General Public
106 * License along with this library; if not see
107 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
108 */
109 public class Test {
110 private static org.apache.log4j.Logger sLog = org.apache.log4j.Logger.getLogger(Test.class);
111
112 /**
113 * Setup log4j logging
114 *
115 * @param logFile
116 * log file
117 * @param debug
118 * true if debug messages should be logged (use -Ddebug=true to
119 * enable debug message)
120 */
121 public static void setupLogging(File logFile, boolean debug) {
122 Logger root = Logger.getRootLogger();
123 ConsoleAppender console = new ConsoleAppender(new PatternLayout("%m%n"));// %-5p
124 // %c{1}>
125 // %m%n
126 console.setThreshold(Level.INFO);
127 root.addAppender(console);
128 if (logFile != null) {
129 try {
130 FileAppender file = new FileAppender(new PatternLayout(
131 "%d{dd-MMM-yy HH:mm:ss.SSS} [%t] %-5p %c{2}> %m%n"), logFile.getPath(), false);
132 file.setThreshold(Level.DEBUG);
133 root.addAppender(file);
134 } catch (IOException e) {
135 sLog.fatal("Unable to configure logging, reason: " + e.getMessage(), e);
136 }
137 }
138 if (!debug)
139 root.setLevel(Level.INFO);
140 }
141
142 /** Generate exam reports */
143 public static void createReports(ExamModel model, File outDir, String outName) throws IOException {
144 new ExamAssignments(model).report().save(new File(outDir, outName + ".schdex.csv"));
145
146 new ExamCourseSectionAssignments(model).report().save(new File(outDir, outName + ".schdcs.csv"));
147
148 new ExamStudentConflicts(model).report().save(new File(outDir, outName + ".sconf.csv"));
149
150 new ExamInstructorConflicts(model).report().save(new File(outDir, outName + ".iconf.csv"));
151
152 new ExamStudentConflictsPerExam(model).report().save(new File(outDir, outName + ".sconfex.csv"));
153
154 new ExamStudentDirectConflicts(model).report().save(new File(outDir, outName + ".sdir.csv"));
155
156 new ExamStudentBackToBackConflicts(model).report().save(new File(outDir, outName + ".sbtb.csv"));
157
158 new ExamStudentMoreTwoADay(model).report().save(new File(outDir, outName + ".sm2d.csv"));
159
160 new ExamPeriodUsage(model).report().save(new File(outDir, outName + ".per.csv"));
161
162 new ExamRoomSchedule(model).report().save(new File(outDir, outName + ".schdr.csv"));
163
164 new ExamRoomSplit(model).report().save(new File(outDir, outName + ".rsplit.csv"));
165
166 new ExamNbrMeetingsPerDay(model).report().save(new File(outDir, outName + ".distmpd.csv"));
167
168 new ExamStudentConflictsBySectionCourse(model).report().save(new File(outDir, outName + ".sconfcs.csv"));
169 }
170
171 public static class ShutdownHook extends Thread {
172 Solver<Exam, ExamPlacement> iSolver = null;
173
174 public ShutdownHook(Solver<Exam, ExamPlacement> solver) {
175 setName("ShutdownHook");
176 iSolver = solver;
177 }
178
179 @Override
180 public void run() {
181 try {
182 if (iSolver.isRunning())
183 iSolver.stopSolver();
184 Solution<Exam, ExamPlacement> solution = iSolver.lastSolution();
185 Progress.removeInstance(solution.getModel());
186 if (solution.getBestInfo() == null) {
187 sLog.error("No best solution found.");
188 } else
189 solution.restoreBest();
190
191 sLog.info("Best solution:" + ToolBox.dict2string(solution.getExtendedInfo(), 1));
192
193 sLog.info("Best solution found after " + solution.getBestTime() + " seconds ("
194 + solution.getBestIteration() + " iterations).");
195 sLog.info("Number of assigned variables is " + solution.getModel().nrAssignedVariables());
196 sLog.info("Total value of the solution is " + solution.getModel().getTotalValue());
197
198 File outFile = new File(iSolver.getProperties().getProperty("General.OutputFile",
199 iSolver.getProperties().getProperty("General.Output") + File.separator + "solution.xml"));
200 FileOutputStream fos = new FileOutputStream(outFile);
201 (new XMLWriter(fos, OutputFormat.createPrettyPrint())).write(((ExamModel) solution.getModel()).save());
202 fos.flush();
203 fos.close();
204
205 if ("true".equals(System.getProperty("reports", "false")))
206 createReports((ExamModel) solution.getModel(), outFile.getParentFile(), outFile.getName()
207 .substring(0, outFile.getName().lastIndexOf('.')));
208
209 String baseName = new File(iSolver.getProperties().getProperty("General.Input")).getName();
210 if (baseName.indexOf('.') > 0)
211 baseName = baseName.substring(0, baseName.lastIndexOf('.'));
212 addCSVLine(new File(outFile.getParentFile(), baseName + ".csv"), outFile.getName(), iSolver.getProperties().getProperty("General.Config"), solution);
213
214 } catch (Exception e) {
215 e.printStackTrace();
216 }
217 }
218 }
219
220 private static void addCSVLine(File file, String instance, String config, Solution<Exam, ExamPlacement> solution) {
221 try {
222 ExamModel model = (ExamModel) solution.getModel();
223 boolean ex = file.exists();
224 PrintWriter pw = new PrintWriter(new FileWriter(file, true));
225 boolean mpp = ((PerturbationPenalty)model.getCriterion(PerturbationPenalty.class)).isMPP();
226 int largeSize = ((LargeExamsPenalty)model.getCriterion(LargeExamsPenalty.class)).getLargeSize();
227 RoomSplitDistancePenalty splitDistance = (RoomSplitDistancePenalty)model.getCriterion(RoomSplitDistancePenalty.class);
228 ExamSplitter splitter = (ExamSplitter)model.getCriterion(ExamSplitter.class);
229 PeriodViolation violPer = (PeriodViolation)model.getCriterion(PeriodViolation.class);
230 RoomViolation violRoom = (RoomViolation)model.getCriterion(RoomViolation.class);
231 DistributionViolation violDist = (DistributionViolation)model.getCriterion(DistributionViolation.class);
232 DistanceToStronglyPreferredRoom distStrPref = (DistanceToStronglyPreferredRoom)model.getCriterion(DistanceToStronglyPreferredRoom.class);
233 ExamRotationPenalty rotation = (ExamRotationPenalty)model.getCriterion(ExamRotationPenalty.class);
234 DecimalFormat df = new DecimalFormat("0.00");
235 if (!ex) {
236 pw.println("SEED"
237 + ",NA,DC,M2D,BTB" + (model.getBackToBackDistance() < 0 ? "" : ",dBTB")
238 + ",iNA,iDC,iM2D,iBTB" + (model.getBackToBackDistance() < 0 ? "" : ",diBTB")
239 + ",PP,RP,DP"
240 + ",PI,@P,PS" // Period Index, Rotation Penalty, Period Size
241 + ",RSz,RSp,RD" // Room Size, Room Split, Room Split Distance
242 + (largeSize >= 0 ? ",LP" : "")
243 + (mpp ? ",IP,IRP" : "")
244 + (distStrPref == null ? "" : ",@D")
245 + (splitter == null ? "" : ",XX")
246 + (violPer == null ? "" : ",!P")
247 + (violRoom == null ? "" : ",!R")
248 + (violDist == null ? "" : ",!D")
249 + ",INSTANCE,CONFIG");
250 int nrStudentExams = 0;
251 for (ExamStudent student : model.getStudents()) {
252 nrStudentExams += student.variables().size();
253 }
254 int nrInstructorExams = 0;
255 for (ExamInstructor instructor : model.getInstructors()) {
256 nrInstructorExams += instructor.variables().size();
257 }
258 pw.println("MIN"
259 + ",#EX,#RM,#PER," + (model.getBackToBackDistance() < 0 ? "" : ",")
260 + ",#STD,#STDX,#INS,#INSX" + (model.getBackToBackDistance() < 0 ? "" : ",")
261 + "," + model.getCriterion(PeriodPenalty.class).getBounds()[0]
262 + "," + model.getCriterion(RoomPenalty.class).getBounds()[0]
263 + "," + model.getCriterion(DistributionPenalty.class).getBounds()[0]
264 + ",," + df.format(rotation.averagePeriod()) + ","
265 + ",,,"
266 + (largeSize >= 0 ? ",0" : "")
267 + (mpp ? ",," : "")
268 + (distStrPref == null ? "" : ",")
269 + (splitter == null ? "" : ",")
270 + (violPer == null ? "" : ",")
271 + (violRoom == null ? "" : ",")
272 + (violDist == null ? "" : ",")
273 + ",,");
274 pw.println("MAX"
275 + "," + model.variables().size() + "," + model.getRooms().size() + "," + model.getPeriods().size() + "," + (model.getBackToBackDistance() < 0 ? "" : ",")
276 + "," + model.getStudents().size() + "," + nrStudentExams + "," + model.getInstructors().size() + "," + nrInstructorExams + (model.getBackToBackDistance() < 0 ? "" : ",")
277 + "," + model.getCriterion(PeriodPenalty.class).getBounds()[1]
278 + "," + model.getCriterion(RoomPenalty.class).getBounds()[1]
279 + "," + model.getCriterion(DistributionPenalty.class).getBounds()[1]
280 + ",," + rotation.nrAssignedExamsWithAvgPeriod() + ","
281 + ",,,"
282 + (largeSize >= 0 ? "," + model.getCriterion(LargeExamsPenalty.class).getBounds()[1] : "")
283 + (mpp ? ",," : "")
284 + (distStrPref == null ? "" : ",")
285 + (splitter == null ? "" : ",")
286 + (violPer == null ? "" : "," + model.getCriterion(PeriodViolation.class).getBounds()[1])
287 + (violRoom == null ? "" : "," + model.getCriterion(RoomViolation.class).getBounds()[1])
288 + (violDist == null ? "" : "," + model.getCriterion(DistributionViolation.class).getBounds()[1])
289 + ",,");
290 }
291 pw.println(ToolBox.getSeed()
292 + "," + model.getCriterion(StudentNotAvailableConflicts.class).getValue()
293 + "," + model.getCriterion(StudentDirectConflicts.class).getValue()
294 + "," + model.getCriterion(StudentMoreThan2ADayConflicts.class).getValue()
295 + "," + model.getCriterion(StudentBackToBackConflicts.class).getValue()
296 + (model.getBackToBackDistance() < 0 ? "" : "," + model.getCriterion(StudentDistanceBackToBackConflicts.class).getValue())
297 + "," + model.getCriterion(InstructorNotAvailableConflicts.class).getValue()
298 + "," + model.getCriterion(InstructorDirectConflicts.class).getValue()
299 + "," + model.getCriterion(InstructorMoreThan2ADayConflicts.class).getValue()
300 + "," + model.getCriterion(InstructorBackToBackConflicts.class).getValue()
301 + (model.getBackToBackDistance() < 0 ? "" : "," + model.getCriterion(InstructorDistanceBackToBackConflicts.class).getValue())
302 + "," + model.getCriterion(PeriodPenalty.class).getValue()
303 + "," + model.getCriterion(RoomPenalty.class).getValue()
304 + "," + model.getCriterion(DistributionPenalty.class).getValue()
305 + "," + df.format(model.getCriterion(PeriodIndexPenalty.class).getValue() / model.nrAssignedVariables())
306 + "," + df.format(Math.sqrt(rotation.getValue() / rotation.nrAssignedExamsWithAvgPeriod()) - 1)
307 + "," + df.format(model.getCriterion(PeriodSizePenalty.class).getValue() / model.nrAssignedVariables())
308 + "," + df.format(model.getCriterion(RoomSizePenalty.class).getValue() / model.nrAssignedVariables())
309 + "," + model.getCriterion(RoomSplitPenalty.class).getValue()
310 + "," + df.format(splitDistance.nrRoomSplits() <= 0 ? 0.0 : splitDistance.getValue() / splitDistance.nrRoomSplits())
311 + (largeSize >= 0 ? "," + model.getCriterion(LargeExamsPenalty.class).getValue() : "")
312 + (mpp ? "," + df.format(model.getCriterion(PerturbationPenalty.class).getValue() / model.nrAssignedVariables())
313 + "," + df.format(model.getCriterion(RoomPerturbationPenalty.class).getValue() / model.nrAssignedVariables()): "")
314 + (distStrPref == null ? "" : "," + df.format(distStrPref.getValue() / model.nrAssignedVariables()))
315 + (splitter == null ? "" : "," + df.format(splitter.getValue()))
316 + (violPer == null ? "" : "," + df.format(violPer.getValue()))
317 + (violRoom == null ? "" : "," + df.format(violRoom.getValue()))
318 + (violDist == null ? "" : "," + df.format(violDist.getValue()))
319 + "," + instance + "," + config);
320 pw.flush();
321 pw.close();
322 } catch (Exception e) {
323 sLog.error("Unable to add CSV line to " + file, e);
324 }
325 }
326
327 /**
328 * Main program
329 *
330 * @param args
331 * problem property file, input file (optional, may be set by
332 * General.Input property), output file (optional, may be set by
333 * General.OutputFile property)
334 */
335 public static void main(String[] args) {
336 try {
337 DataProperties cfg = new DataProperties();
338 cfg.setProperty("Termination.StopWhenComplete", "false");
339 cfg.setProperty("Termination.TimeOut", "1800");
340 cfg.setProperty("General.SaveBestUnassigned", "-1");
341 cfg.setProperty("Neighbour.Class", "net.sf.cpsolver.exam.heuristics.ExamNeighbourSelection");
342 if (args.length >= 1) {
343 cfg.load(new FileInputStream(args[0]));
344 cfg.setProperty("General.Config", new File(args[0]).getName());
345 }
346 cfg.putAll(System.getProperties());
347
348 File inputFile = new File("c:\\test\\exam\\exam1070.xml");
349 if (args.length >= 2) {
350 inputFile = new File(args[1]);
351 }
352 ToolBox.setSeed(cfg.getPropertyLong("General.Seed", Math.round(Long.MAX_VALUE * Math.random())));
353
354 cfg.setProperty("General.Input", inputFile.toString());
355
356 String outName = inputFile.getName();
357 if (outName.indexOf('.') >= 0)
358 outName = outName.substring(0, outName.lastIndexOf('.')) + "s.xml";
359 File outFile = new File(inputFile.getParentFile(), outName);
360 if (args.length >= 3) {
361 outFile = new File(args[2]);
362 if (outFile.exists() && outFile.isDirectory())
363 outFile = new File(outFile, outName);
364 if (!outFile.exists() && !outFile.getName().endsWith(".xml"))
365 outFile = new File(outFile, outName);
366 }
367 if (outFile.getParentFile() != null)
368 outFile.getParentFile().mkdirs();
369 cfg.setProperty("General.OutputFile", outFile.toString());
370 cfg.setProperty("General.Output", outFile.getParent());
371
372 String logName = outFile.getName();
373 if (logName.indexOf('.') >= 0)
374 logName = logName.substring(0, logName.lastIndexOf('.')) + ".log";
375 setupLogging(new File(outFile.getParent(), logName), "true".equals(System.getProperty("debug", "false")));
376
377 ExamModel model = new ExamModel(cfg);
378
379 Document document = (new SAXReader()).read(new File(cfg.getProperty("General.Input")));
380 model.load(document);
381
382 Solver<Exam, ExamPlacement> solver = new Solver<Exam, ExamPlacement>(cfg);
383 solver.setInitalSolution(new Solution<Exam, ExamPlacement>(model));
384
385 solver.currentSolution().addSolutionListener(new SolutionListener<Exam, ExamPlacement>() {
386 @Override
387 public void solutionUpdated(Solution<Exam, ExamPlacement> solution) {
388 }
389
390 @Override
391 public void getInfo(Solution<Exam, ExamPlacement> solution, Map<String, String> info) {
392 }
393
394 @Override
395 public void getInfo(Solution<Exam, ExamPlacement> solution, Map<String, String> info,
396 Collection<Exam> variables) {
397 }
398
399 @Override
400 public void bestCleared(Solution<Exam, ExamPlacement> solution) {
401 }
402
403 @Override
404 public void bestSaved(Solution<Exam, ExamPlacement> solution) {
405 ExamModel m = (ExamModel) solution.getModel();
406 if (sLog.isInfoEnabled()) {
407 sLog.info("**BEST[" + solution.getIteration() + "]** "
408 + (m.nrUnassignedVariables() > 0 ? "V:" + m.nrAssignedVariables() + "/" + m.variables().size() + " - " : "") +
409 "T:" + new DecimalFormat("0.00").format(m.getTotalValue()) +
410 " " + m);
411 }
412 }
413
414 @Override
415 public void bestRestored(Solution<Exam, ExamPlacement> solution) {
416 }
417 });
418
419 Runtime.getRuntime().addShutdownHook(new ShutdownHook(solver));
420
421 solver.start();
422 try {
423 solver.getSolverThread().join();
424 } catch (InterruptedException e) {
425 }
426 } catch (Exception e) {
427 e.printStackTrace();
428 }
429 }
430 }