001package net.sf.cpsolver.exam; 002 003import java.io.File; 004import java.io.FileInputStream; 005import java.io.FileOutputStream; 006import java.io.FileWriter; 007import java.io.IOException; 008import java.io.PrintWriter; 009import java.text.DecimalFormat; 010import java.util.Collection; 011import java.util.Map; 012 013import net.sf.cpsolver.exam.criteria.DistributionPenalty; 014import net.sf.cpsolver.exam.criteria.ExamRotationPenalty; 015import net.sf.cpsolver.exam.criteria.InstructorBackToBackConflicts; 016import net.sf.cpsolver.exam.criteria.InstructorDirectConflicts; 017import net.sf.cpsolver.exam.criteria.InstructorDistanceBackToBackConflicts; 018import net.sf.cpsolver.exam.criteria.InstructorMoreThan2ADayConflicts; 019import net.sf.cpsolver.exam.criteria.InstructorNotAvailableConflicts; 020import net.sf.cpsolver.exam.criteria.LargeExamsPenalty; 021import net.sf.cpsolver.exam.criteria.PeriodIndexPenalty; 022import net.sf.cpsolver.exam.criteria.PeriodPenalty; 023import net.sf.cpsolver.exam.criteria.PeriodSizePenalty; 024import net.sf.cpsolver.exam.criteria.PerturbationPenalty; 025import net.sf.cpsolver.exam.criteria.RoomPenalty; 026import net.sf.cpsolver.exam.criteria.RoomPerturbationPenalty; 027import net.sf.cpsolver.exam.criteria.RoomSizePenalty; 028import net.sf.cpsolver.exam.criteria.RoomSplitDistancePenalty; 029import net.sf.cpsolver.exam.criteria.RoomSplitPenalty; 030import net.sf.cpsolver.exam.criteria.StudentBackToBackConflicts; 031import net.sf.cpsolver.exam.criteria.StudentDirectConflicts; 032import net.sf.cpsolver.exam.criteria.StudentDistanceBackToBackConflicts; 033import net.sf.cpsolver.exam.criteria.StudentMoreThan2ADayConflicts; 034import net.sf.cpsolver.exam.criteria.StudentNotAvailableConflicts; 035import net.sf.cpsolver.exam.criteria.additional.DistanceToStronglyPreferredRoom; 036import net.sf.cpsolver.exam.criteria.additional.DistributionViolation; 037import net.sf.cpsolver.exam.criteria.additional.PeriodViolation; 038import net.sf.cpsolver.exam.criteria.additional.RoomViolation; 039import net.sf.cpsolver.exam.model.Exam; 040import net.sf.cpsolver.exam.model.ExamInstructor; 041import net.sf.cpsolver.exam.model.ExamModel; 042import net.sf.cpsolver.exam.model.ExamPlacement; 043import net.sf.cpsolver.exam.model.ExamStudent; 044import net.sf.cpsolver.exam.reports.ExamAssignments; 045import net.sf.cpsolver.exam.reports.ExamCourseSectionAssignments; 046import net.sf.cpsolver.exam.reports.ExamInstructorConflicts; 047import net.sf.cpsolver.exam.reports.ExamNbrMeetingsPerDay; 048import net.sf.cpsolver.exam.reports.ExamPeriodUsage; 049import net.sf.cpsolver.exam.reports.ExamRoomSchedule; 050import net.sf.cpsolver.exam.reports.ExamRoomSplit; 051import net.sf.cpsolver.exam.reports.ExamStudentBackToBackConflicts; 052import net.sf.cpsolver.exam.reports.ExamStudentConflicts; 053import net.sf.cpsolver.exam.reports.ExamStudentConflictsBySectionCourse; 054import net.sf.cpsolver.exam.reports.ExamStudentConflictsPerExam; 055import net.sf.cpsolver.exam.reports.ExamStudentDirectConflicts; 056import net.sf.cpsolver.exam.reports.ExamStudentMoreTwoADay; 057import net.sf.cpsolver.exam.split.ExamSplitter; 058import net.sf.cpsolver.ifs.solution.Solution; 059import net.sf.cpsolver.ifs.solution.SolutionListener; 060import net.sf.cpsolver.ifs.solver.Solver; 061import net.sf.cpsolver.ifs.util.DataProperties; 062import net.sf.cpsolver.ifs.util.Progress; 063import net.sf.cpsolver.ifs.util.ToolBox; 064 065import org.apache.log4j.ConsoleAppender; 066import org.apache.log4j.FileAppender; 067import org.apache.log4j.Level; 068import org.apache.log4j.Logger; 069import org.apache.log4j.PatternLayout; 070import org.dom4j.Document; 071import org.dom4j.io.OutputFormat; 072import org.dom4j.io.SAXReader; 073import 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 Tomáš Müller<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 */ 109public 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}