001package org.cpsolver.coursett; 002 003import java.io.File; 004import java.io.FileWriter; 005import java.io.IOException; 006import java.io.PrintWriter; 007import java.text.DecimalFormat; 008import java.util.ArrayList; 009import java.util.Collection; 010import java.util.Date; 011import java.util.HashSet; 012import java.util.HashMap; 013import java.util.List; 014import java.util.Locale; 015import java.util.Map; 016import java.util.TreeSet; 017 018import org.apache.log4j.ConsoleAppender; 019import org.apache.log4j.FileAppender; 020import org.apache.log4j.Level; 021import org.apache.log4j.Logger; 022import org.apache.log4j.PatternLayout; 023import org.cpsolver.coursett.constraint.DepartmentSpreadConstraint; 024import org.cpsolver.coursett.constraint.GroupConstraint; 025import org.cpsolver.coursett.constraint.InstructorConstraint; 026import org.cpsolver.coursett.constraint.JenrlConstraint; 027import org.cpsolver.coursett.constraint.RoomConstraint; 028import org.cpsolver.coursett.constraint.SpreadConstraint; 029import org.cpsolver.coursett.criteria.BackToBackInstructorPreferences; 030import org.cpsolver.coursett.criteria.BrokenTimePatterns; 031import org.cpsolver.coursett.criteria.DepartmentBalancingPenalty; 032import org.cpsolver.coursett.criteria.DistributionPreferences; 033import org.cpsolver.coursett.criteria.Perturbations; 034import org.cpsolver.coursett.criteria.RoomPreferences; 035import org.cpsolver.coursett.criteria.SameSubpartBalancingPenalty; 036import org.cpsolver.coursett.criteria.StudentCommittedConflict; 037import org.cpsolver.coursett.criteria.StudentConflict; 038import org.cpsolver.coursett.criteria.StudentDistanceConflict; 039import org.cpsolver.coursett.criteria.StudentHardConflict; 040import org.cpsolver.coursett.criteria.TimePreferences; 041import org.cpsolver.coursett.criteria.TooBigRooms; 042import org.cpsolver.coursett.criteria.UselessHalfHours; 043import org.cpsolver.coursett.heuristics.UniversalPerturbationsCounter; 044import org.cpsolver.coursett.model.Lecture; 045import org.cpsolver.coursett.model.Placement; 046import org.cpsolver.coursett.model.RoomLocation; 047import org.cpsolver.coursett.model.Student; 048import org.cpsolver.coursett.model.TimeLocation; 049import org.cpsolver.coursett.model.TimetableModel; 050import org.cpsolver.ifs.assignment.Assignment; 051import org.cpsolver.ifs.assignment.DefaultParallelAssignment; 052import org.cpsolver.ifs.assignment.DefaultSingleAssignment; 053import org.cpsolver.ifs.extension.ConflictStatistics; 054import org.cpsolver.ifs.extension.Extension; 055import org.cpsolver.ifs.extension.MacPropagation; 056import org.cpsolver.ifs.model.Constraint; 057import org.cpsolver.ifs.solution.Solution; 058import org.cpsolver.ifs.solution.SolutionListener; 059import org.cpsolver.ifs.solver.ParallelSolver; 060import org.cpsolver.ifs.solver.Solver; 061import org.cpsolver.ifs.util.DataProperties; 062import org.cpsolver.ifs.util.Progress; 063import org.cpsolver.ifs.util.ProgressWriter; 064import org.cpsolver.ifs.util.ToolBox; 065 066 067/** 068 * A main class for running of the solver from command line. <br> 069 * <br> 070 * Usage:<br> 071 * java -Xmx1024m -jar coursett1.1.jar config.properties [input_file] 072 * [output_folder]<br> 073 * <br> 074 * See http://www.unitime.org for example configuration files and banchmark data 075 * sets.<br> 076 * <br> 077 * 078 * The test does the following steps: 079 * <ul> 080 * <li>Provided property file is loaded (see {@link DataProperties}). 081 * <li>Output folder is created (General.Output property) and loggings is setup 082 * (using log4j). 083 * <li>Input data are loaded (calling {@link TimetableLoader#load()}). 084 * <li>Solver is executed (see {@link Solver}). 085 * <li>Resultant solution is saved (calling {@link TimetableSaver#save()}, when 086 * General.Save property is set to true. 087 * </ul> 088 * Also, a log and a CSV (comma separated text file) is created in the output 089 * folder. 090 * 091 * @version CourseTT 1.3 (University Course Timetabling)<br> 092 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 093 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 094 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 095 * <br> 096 * This library is free software; you can redistribute it and/or modify 097 * it under the terms of the GNU Lesser General Public License as 098 * published by the Free Software Foundation; either version 3 of the 099 * License, or (at your option) any later version. <br> 100 * <br> 101 * This library is distributed in the hope that it will be useful, but 102 * WITHOUT ANY WARRANTY; without even the implied warranty of 103 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 104 * Lesser General Public License for more details. <br> 105 * <br> 106 * You should have received a copy of the GNU Lesser General Public 107 * License along with this library; if not see 108 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 109 */ 110 111public class Test implements SolutionListener<Lecture, Placement> { 112 private static java.text.SimpleDateFormat sDateFormat = new java.text.SimpleDateFormat("yyMMdd_HHmmss", 113 java.util.Locale.US); 114 private static java.text.DecimalFormat sDoubleFormat = new java.text.DecimalFormat("0.000", 115 new java.text.DecimalFormatSymbols(Locale.US)); 116 private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(Test.class); 117 118 private PrintWriter iCSVFile = null; 119 120 private MacPropagation<Lecture, Placement> iProp = null; 121 private ConflictStatistics<Lecture, Placement> iStat = null; 122 private int iLastNotified = -1; 123 124 private boolean initialized = false; 125 private Solver<Lecture, Placement> iSolver = null; 126 127 /** Current version 128 * @return version string 129 **/ 130 public static String getVersionString() { 131 return "IFS Timetable Solver v" + Constants.getVersion() + " build" + Constants.getBuildNumber() + ", " 132 + Constants.getReleaseDate(); 133 } 134 135 /** Solver initialization 136 * @param solver current solver 137 **/ 138 public void init(Solver<Lecture, Placement> solver) { 139 iSolver = solver; 140 solver.currentSolution().addSolutionListener(this); 141 } 142 143 /** 144 * Setup log4j logging 145 * 146 * @param logFile log file 147 * @param debug true if debug messages should be logged (use -Ddebug=true to enable debug message) 148 */ 149 public static void setupLogging(File logFile, boolean debug) { 150 Logger root = Logger.getRootLogger(); 151 ConsoleAppender console = new ConsoleAppender(new PatternLayout("[%t] %m%n")); 152 console.setThreshold(Level.INFO); 153 root.addAppender(console); 154 if (logFile != null) { 155 try { 156 FileAppender file = new FileAppender(new PatternLayout("%d{dd-MMM-yy HH:mm:ss.SSS} [%t] %-5p %c{2}> %m%n"), logFile.getPath(), false); 157 file.setThreshold(Level.DEBUG); 158 root.addAppender(file); 159 } catch (IOException e) { 160 sLogger.fatal("Unable to configure logging, reason: " + e.getMessage(), e); 161 } 162 } 163 if (!debug) 164 root.setLevel(Level.INFO); 165 } 166 167 /** 168 * Return name of the class that is used for loading the data. This class 169 * needs to extend class {@link TimetableLoader}. It can be also defined in 170 * configuration, using TimetableLoader property. 171 **/ 172 private String getTimetableLoaderClass(DataProperties properties) { 173 String loader = properties.getProperty("TimetableLoader"); 174 if (loader != null) 175 return loader; 176 if (properties.getPropertyInt("General.InputVersion", -1) >= 0) 177 return "org.unitime.timetable.solver.TimetableDatabaseLoader"; 178 else 179 return "org.cpsolver.coursett.TimetableXMLLoader"; 180 } 181 182 /** 183 * Return name of the class that is used for loading the data. This class 184 * needs to extend class {@link TimetableSaver}. It can be also defined in 185 * configuration, using TimetableSaver property. 186 **/ 187 private String getTimetableSaverClass(DataProperties properties) { 188 String saver = properties.getProperty("TimetableSaver"); 189 if (saver != null) 190 return saver; 191 if (properties.getPropertyInt("General.InputVersion", -1) >= 0) 192 return "org.unitime.timetable.solver.TimetableDatabaseSaver"; 193 else 194 return "org.cpsolver.coursett.TimetableXMLSaver"; 195 } 196 197 /** 198 * Solver Test 199 * 200 * @param args 201 * command line arguments 202 */ 203 public Test(String[] args) { 204 try { 205 DataProperties properties = ToolBox.loadProperties(new java.io.File(args[0])); 206 properties.putAll(System.getProperties()); 207 properties.setProperty("General.Output", properties.getProperty("General.Output", ".") + File.separator + sDateFormat.format(new Date())); 208 if (args.length > 1) 209 properties.setProperty("General.Input", args[1]); 210 if (args.length > 2) 211 properties.setProperty("General.Output", args[2] + File.separator + (sDateFormat.format(new Date()))); 212 System.out.println("Output folder: " + properties.getProperty("General.Output")); 213 File outDir = new File(properties.getProperty("General.Output", ".")); 214 outDir.mkdirs(); 215 setupLogging(new File(outDir, "debug.log"), "true".equals(System.getProperty("debug", "false"))); 216 217 TimetableModel model = new TimetableModel(properties); 218 int nrSolvers = properties.getPropertyInt("Parallel.NrSolvers", 1); 219 Assignment<Lecture, Placement> assignment = (nrSolvers <= 1 ? new DefaultSingleAssignment<Lecture, Placement>() : new DefaultParallelAssignment<Lecture, Placement>()); 220 Progress.getInstance(model).addProgressListener(new ProgressWriter(System.out)); 221 Solver<Lecture, Placement> solver = (nrSolvers == 1 ? new Solver<Lecture, Placement>(properties) : new ParallelSolver<Lecture, Placement>(properties)); 222 223 TimetableLoader loader = null; 224 try { 225 loader = (TimetableLoader) Class.forName(getTimetableLoaderClass(properties)) 226 .getConstructor(new Class[] { TimetableModel.class, Assignment.class }).newInstance(new Object[] { model, assignment }); 227 } catch (ClassNotFoundException e) { 228 System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage()); 229 loader = new TimetableXMLLoader(model, assignment); 230 } 231 loader.load(); 232 233 solver.setInitalSolution(new Solution<Lecture, Placement>(model, assignment)); 234 init(solver); 235 236 iCSVFile = new PrintWriter(new FileWriter(outDir.toString() + File.separator + "stat.csv")); 237 String colSeparator = ";"; 238 iCSVFile.println("Assigned" 239 + colSeparator 240 + "Assigned[%]" 241 + colSeparator 242 + "Time[min]" 243 + colSeparator 244 + "Iter" 245 + colSeparator 246 + "IterYield[%]" 247 + colSeparator 248 + "Speed[it/s]" 249 + colSeparator 250 + "AddedPert" 251 + colSeparator 252 + "AddedPert[%]" 253 + colSeparator 254 + "HardStudentConf" 255 + colSeparator 256 + "StudentConf" 257 + colSeparator 258 + "DistStudentConf" 259 + colSeparator 260 + "CommitStudentConf" 261 + colSeparator 262 + "TimePref" 263 + colSeparator 264 + "RoomPref" 265 + colSeparator 266 + "DistInstrPref" 267 + colSeparator 268 + "GrConstPref" 269 + colSeparator 270 + "UselessHalfHours" 271 + colSeparator 272 + "BrokenTimePat" 273 + colSeparator 274 + "TooBigRooms" 275 + (iProp != null ? colSeparator + "GoodVars" + colSeparator + "GoodVars[%]" + colSeparator 276 + "GoodVals" + colSeparator + "GoodVals[%]" : "")); 277 iCSVFile.flush(); 278 279 Runtime.getRuntime().addShutdownHook(new ShutdownHook(solver)); 280 281 solver.start(); 282 try { 283 solver.getSolverThread().join(); 284 } catch (InterruptedException e) { 285 } 286 } catch (Throwable t) { 287 sLogger.error("Test failed.", t); 288 } 289 } 290 291 public static void main(String[] args) { 292 new Test(args); 293 } 294 295 @Override 296 public void bestCleared(Solution<Lecture, Placement> solution) { 297 } 298 299 @Override 300 public void bestRestored(Solution<Lecture, Placement> solution) { 301 } 302 303 @Override 304 public void bestSaved(Solution<Lecture, Placement> solution) { 305 notify(solution); 306 if (sLogger.isInfoEnabled()) 307 sLogger.info("**BEST[" + solution.getIteration() + "]** " + ((TimetableModel)solution.getModel()).toString(solution.getAssignment()) + 308 (solution.getFailedIterations() > 0 ? ", F:" + sDoubleFormat.format(100.0 * solution.getFailedIterations() / solution.getIteration()) + "%" : "")); 309 } 310 311 @Override 312 public void getInfo(Solution<Lecture, Placement> solution, Map<String, String> info) { 313 } 314 315 @Override 316 public void getInfo(Solution<Lecture, Placement> solution, Map<String, String> info, Collection<Lecture> variables) { 317 } 318 319 @Override 320 public void solutionUpdated(Solution<Lecture, Placement> solution) { 321 if (!initialized) { 322 for (Extension<Lecture, Placement> extension : iSolver.getExtensions()) { 323 if (MacPropagation.class.isInstance(extension)) 324 iProp = (MacPropagation<Lecture, Placement>) extension; 325 if (ConflictStatistics.class.isInstance(extension)) { 326 iStat = (ConflictStatistics<Lecture, Placement>) extension; 327 } 328 } 329 } 330 } 331 332 /** Add a line into the output CSV file when a enw best solution is found. 333 * @param solution current solution 334 **/ 335 public void notify(Solution<Lecture, Placement> solution) { 336 String colSeparator = ";"; 337 Assignment<Lecture, Placement> assignment = solution.getAssignment(); 338 if (assignment.nrAssignedVariables() < solution.getModel().countVariables() && iLastNotified == assignment.nrAssignedVariables()) 339 return; 340 iLastNotified = assignment.nrAssignedVariables(); 341 if (iCSVFile != null) { 342 TimetableModel model = (TimetableModel) solution.getModel(); 343 iCSVFile.print(model.variables().size() - model.nrUnassignedVariables(assignment)); 344 iCSVFile.print(colSeparator); 345 iCSVFile.print(sDoubleFormat.format(100.0 * assignment.nrAssignedVariables() / model.variables().size())); 346 iCSVFile.print(colSeparator); 347 iCSVFile.print(sDoubleFormat.format((solution.getTime()) / 60.0)); 348 iCSVFile.print(colSeparator); 349 iCSVFile.print(solution.getIteration()); 350 iCSVFile.print(colSeparator); 351 iCSVFile.print(sDoubleFormat.format(100.0 * assignment.nrAssignedVariables() / solution.getIteration())); 352 iCSVFile.print(colSeparator); 353 iCSVFile.print(sDoubleFormat.format((solution.getIteration()) / solution.getTime())); 354 iCSVFile.print(colSeparator); 355 iCSVFile.print(model.perturbVariables(assignment).size()); 356 iCSVFile.print(colSeparator); 357 iCSVFile.print(sDoubleFormat.format(100.0 * model.perturbVariables(assignment).size() / model.variables().size())); 358 iCSVFile.print(colSeparator); 359 iCSVFile.print(Math.round(solution.getModel().getCriterion(StudentHardConflict.class).getValue(assignment))); 360 iCSVFile.print(colSeparator); 361 iCSVFile.print(Math.round(solution.getModel().getCriterion(StudentConflict.class).getValue(assignment))); 362 iCSVFile.print(colSeparator); 363 iCSVFile.print(Math.round(solution.getModel().getCriterion(StudentDistanceConflict.class).getValue(assignment))); 364 iCSVFile.print(colSeparator); 365 iCSVFile.print(Math.round(solution.getModel().getCriterion(StudentCommittedConflict.class).getValue(assignment))); 366 iCSVFile.print(colSeparator); 367 iCSVFile.print(sDoubleFormat.format(solution.getModel().getCriterion(TimePreferences.class).getValue(assignment))); 368 iCSVFile.print(colSeparator); 369 iCSVFile.print(Math.round(solution.getModel().getCriterion(RoomPreferences.class).getValue(assignment))); 370 iCSVFile.print(colSeparator); 371 iCSVFile.print(Math.round(solution.getModel().getCriterion(BackToBackInstructorPreferences.class).getValue(assignment))); 372 iCSVFile.print(colSeparator); 373 iCSVFile.print(Math.round(solution.getModel().getCriterion(DistributionPreferences.class).getValue(assignment))); 374 iCSVFile.print(colSeparator); 375 iCSVFile.print(Math.round(solution.getModel().getCriterion(UselessHalfHours.class).getValue(assignment))); 376 iCSVFile.print(colSeparator); 377 iCSVFile.print(Math.round(solution.getModel().getCriterion(BrokenTimePatterns.class).getValue(assignment))); 378 iCSVFile.print(colSeparator); 379 iCSVFile.print(Math.round(solution.getModel().getCriterion(TooBigRooms.class).getValue(assignment))); 380 if (iProp != null) { 381 if (solution.getModel().nrUnassignedVariables(assignment) > 0) { 382 int goodVariables = 0; 383 long goodValues = 0; 384 long allValues = 0; 385 for (Lecture variable : ((TimetableModel) solution.getModel()).unassignedVariables(assignment)) { 386 goodValues += iProp.goodValues(assignment, variable).size(); 387 allValues += variable.values(solution.getAssignment()).size(); 388 if (!iProp.goodValues(assignment, variable).isEmpty()) 389 goodVariables++; 390 } 391 iCSVFile.print(colSeparator); 392 iCSVFile.print(goodVariables); 393 iCSVFile.print(colSeparator); 394 iCSVFile.print(sDoubleFormat.format(100.0 * goodVariables / solution.getModel().nrUnassignedVariables(assignment))); 395 iCSVFile.print(colSeparator); 396 iCSVFile.print(goodValues); 397 iCSVFile.print(colSeparator); 398 iCSVFile.print(sDoubleFormat.format(100.0 * goodValues / allValues)); 399 } else { 400 iCSVFile.print(colSeparator); 401 iCSVFile.print(colSeparator); 402 iCSVFile.print(colSeparator); 403 iCSVFile.print(colSeparator); 404 } 405 } 406 iCSVFile.println(); 407 iCSVFile.flush(); 408 } 409 } 410 411 /** Print room utilization 412 * @param pw writer 413 * @param model problem model 414 * @param assignment current assignment 415 **/ 416 public static void printRoomInfo(PrintWriter pw, TimetableModel model, Assignment<Lecture, Placement> assignment) { 417 pw.println("Room info:"); 418 pw.println("id, name, size, used_day, used_total"); 419 int firstDaySlot = model.getProperties().getPropertyInt("General.FirstDaySlot", Constants.DAY_SLOTS_FIRST); 420 int lastDaySlot = model.getProperties().getPropertyInt("General.LastDaySlot", Constants.DAY_SLOTS_LAST); 421 int firstWorkDay = model.getProperties().getPropertyInt("General.FirstWorkDay", 0); 422 int lastWorkDay = model.getProperties().getPropertyInt("General.LastWorkDay", Constants.NR_DAYS_WEEK - 1); 423 if (lastWorkDay < firstWorkDay) lastWorkDay += 7; 424 for (RoomConstraint rc : model.getRoomConstraints()) { 425 int used_day = 0; 426 int used_total = 0; 427 for (int day = firstWorkDay; day <= lastWorkDay; day++) { 428 for (int time = firstDaySlot; time <= lastDaySlot; time++) { 429 if (!rc.getContext(assignment).getPlacements((day % 7) * Constants.SLOTS_PER_DAY + time).isEmpty()) 430 used_day++; 431 } 432 } 433 for (int day = 0; day < Constants.DAY_CODES.length; day++) { 434 for (int time = 0; time < Constants.SLOTS_PER_DAY; time++) { 435 if (!rc.getContext(assignment).getPlacements((day % 7) * Constants.SLOTS_PER_DAY + time).isEmpty()) 436 used_total++; 437 } 438 } 439 pw.println(rc.getResourceId() + "," + rc.getName() + "," + rc.getCapacity() + "," + used_day + "," + used_total); 440 } 441 } 442 443 /** Class information 444 * @param pw writer 445 * @param model problem model 446 **/ 447 public static void printClassInfo(PrintWriter pw, TimetableModel model) { 448 pw.println("Class info:"); 449 pw.println("id, name, min_class_limit, max_class_limit, room2limit_ratio, half_hours"); 450 for (Lecture lecture : model.variables()) { 451 if (lecture.timeLocations().isEmpty()) { 452 pw.println(lecture.getClassId() + "," + lecture.getName() + "," + lecture.minClassLimit() + "," 453 + lecture.maxClassLimit() + "," + lecture.roomToLimitRatio() + "," 454 + "NO TIMES"); 455 sLogger.error(lecture.getName() + " has no times."); 456 continue; 457 } 458 TimeLocation time = lecture.timeLocations().get(0); 459 pw.println(lecture.getClassId() + "," + lecture.getName() + "," + lecture.minClassLimit() + "," 460 + lecture.maxClassLimit() + "," + lecture.roomToLimitRatio() + "," 461 + (time.getNrSlotsPerMeeting() * time.getNrMeetings())); 462 } 463 } 464 465 /** Create info.txt with some more information about the problem 466 * @param solution current solution 467 * @throws IOException an exception that may be thrown 468 **/ 469 public static void printSomeStuff(Solution<Lecture, Placement> solution) throws IOException { 470 TimetableModel model = (TimetableModel) solution.getModel(); 471 Assignment<Lecture, Placement> assignment = solution.getAssignment(); 472 File outDir = new File(model.getProperties().getProperty("General.Output", ".")); 473 PrintWriter pw = new PrintWriter(new FileWriter(outDir.toString() + File.separator + "info.txt")); 474 PrintWriter pwi = new PrintWriter(new FileWriter(outDir.toString() + File.separator + "info.csv")); 475 String name = new File(model.getProperties().getProperty("General.Input")).getName(); 476 pwi.println("Instance," + name.substring(0, name.lastIndexOf('.'))); 477 pw.println("Solution info: " + ToolBox.dict2string(solution.getInfo(), 1)); 478 pw.println("Bounds: " + ToolBox.dict2string(model.getBounds(assignment), 1)); 479 Map<String, String> info = solution.getInfo(); 480 for (String key : new TreeSet<String>(info.keySet())) { 481 if (key.equals("Memory usage")) 482 continue; 483 if (key.equals("Iteration")) 484 continue; 485 if (key.equals("Time")) 486 continue; 487 String value = info.get(key); 488 if (value.indexOf(' ') > 0) 489 value = value.substring(0, value.indexOf(' ')); 490 pwi.println(key + "," + value); 491 } 492 printRoomInfo(pw, model, assignment); 493 printClassInfo(pw, model); 494 long nrValues = 0; 495 long nrTimes = 0; 496 long nrRooms = 0; 497 double totalMaxNormTimePref = 0.0; 498 double totalMinNormTimePref = 0.0; 499 double totalNormTimePref = 0.0; 500 int totalMaxRoomPref = 0; 501 int totalMinRoomPref = 0; 502 int totalRoomPref = 0; 503 long nrStudentEnrls = 0; 504 long nrInevitableStudentConflicts = 0; 505 long nrJenrls = 0; 506 int nrHalfHours = 0; 507 int nrMeetings = 0; 508 int totalMinLimit = 0; 509 int totalMaxLimit = 0; 510 long nrReqRooms = 0; 511 int nrSingleValueVariables = 0; 512 int nrSingleTimeVariables = 0; 513 int nrSingleRoomVariables = 0; 514 long totalAvailableMinRoomSize = 0; 515 long totalAvailableMaxRoomSize = 0; 516 long totalRoomSize = 0; 517 long nrOneOrMoreRoomVariables = 0; 518 long nrOneRoomVariables = 0; 519 HashSet<Student> students = new HashSet<Student>(); 520 HashSet<Long> offerings = new HashSet<Long>(); 521 HashSet<Long> configs = new HashSet<Long>(); 522 HashSet<Long> subparts = new HashSet<Long>(); 523 int[] sizeLimits = new int[] { 0, 25, 50, 75, 100, 150, 200, 400 }; 524 int[] nrRoomsOfSize = new int[sizeLimits.length]; 525 int[] minRoomOfSize = new int[sizeLimits.length]; 526 int[] maxRoomOfSize = new int[sizeLimits.length]; 527 int[] totalUsedSlots = new int[sizeLimits.length]; 528 int[] totalUsedSeats = new int[sizeLimits.length]; 529 int[] totalUsedSeats2 = new int[sizeLimits.length]; 530 int firstDaySlot = model.getProperties().getPropertyInt("General.FirstDaySlot", Constants.DAY_SLOTS_FIRST); 531 int lastDaySlot = model.getProperties().getPropertyInt("General.LastDaySlot", Constants.DAY_SLOTS_LAST); 532 int firstWorkDay = model.getProperties().getPropertyInt("General.FirstWorkDay", 0); 533 int lastWorkDay = model.getProperties().getPropertyInt("General.LastWorkDay", Constants.NR_DAYS_WEEK - 1); 534 if (lastWorkDay < firstWorkDay) lastWorkDay += 7; 535 for (Lecture lect : model.variables()) { 536 if (lect.getConfiguration() != null) { 537 offerings.add(lect.getConfiguration().getOfferingId()); 538 configs.add(lect.getConfiguration().getConfigId()); 539 } 540 subparts.add(lect.getSchedulingSubpartId()); 541 nrStudentEnrls += (lect.students() == null ? 0 : lect.students().size()); 542 students.addAll(lect.students()); 543 nrValues += lect.values(solution.getAssignment()).size(); 544 nrReqRooms += lect.getNrRooms(); 545 for (RoomLocation room: lect.roomLocations()) 546 if (room.getPreference() < Constants.sPreferenceLevelProhibited / 2) 547 nrRooms++; 548 for (TimeLocation time: lect.timeLocations()) 549 if (time.getPreference() < Constants.sPreferenceLevelProhibited / 2) 550 nrTimes ++; 551 totalMinLimit += lect.minClassLimit(); 552 totalMaxLimit += lect.maxClassLimit(); 553 if (!lect.values(solution.getAssignment()).isEmpty()) { 554 Placement p = lect.values(solution.getAssignment()).get(0); 555 nrMeetings += p.getTimeLocation().getNrMeetings(); 556 nrHalfHours += p.getTimeLocation().getNrMeetings() * p.getTimeLocation().getNrSlotsPerMeeting(); 557 totalMaxNormTimePref += lect.getMinMaxTimePreference()[1]; 558 totalMinNormTimePref += lect.getMinMaxTimePreference()[0]; 559 totalNormTimePref += Math.abs(lect.getMinMaxTimePreference()[1] - lect.getMinMaxTimePreference()[0]); 560 totalMaxRoomPref += lect.getMinMaxRoomPreference()[1]; 561 totalMinRoomPref += lect.getMinMaxRoomPreference()[0]; 562 totalRoomPref += Math.abs(lect.getMinMaxRoomPreference()[1] - lect.getMinMaxRoomPreference()[0]); 563 TimeLocation time = p.getTimeLocation(); 564 boolean hasRoomConstraint = false; 565 for (RoomLocation roomLocation : lect.roomLocations()) { 566 if (roomLocation.getRoomConstraint().getConstraint()) 567 hasRoomConstraint = true; 568 } 569 if (hasRoomConstraint && lect.getNrRooms() > 0) { 570 for (int d = firstWorkDay; d <= lastWorkDay; d++) { 571 if ((time.getDayCode() & Constants.DAY_CODES[d % 7]) == 0) 572 continue; 573 for (int t = Math.max(time.getStartSlot(), firstDaySlot); t <= Math.min(time.getStartSlot() + time.getLength() - 1, lastDaySlot); t++) { 574 for (int l = 0; l < sizeLimits.length; l++) { 575 if (sizeLimits[l] <= lect.minRoomSize()) { 576 totalUsedSlots[l] += lect.getNrRooms(); 577 totalUsedSeats[l] += lect.classLimit(assignment); 578 totalUsedSeats2[l] += lect.minRoomSize() * lect.getNrRooms(); 579 } 580 } 581 } 582 } 583 } 584 } 585 if (lect.values(solution.getAssignment()).size() == 1) { 586 nrSingleValueVariables++; 587 } 588 if (lect.timeLocations().size() == 1) { 589 nrSingleTimeVariables++; 590 } 591 if (lect.roomLocations().size() == 1) { 592 nrSingleRoomVariables++; 593 } 594 if (lect.getNrRooms() == 1) { 595 nrOneRoomVariables++; 596 } 597 if (lect.getNrRooms() > 0) { 598 nrOneOrMoreRoomVariables++; 599 } 600 if (!lect.roomLocations().isEmpty()) { 601 int minRoomSize = Integer.MAX_VALUE; 602 int maxRoomSize = Integer.MIN_VALUE; 603 for (RoomLocation rl : lect.roomLocations()) { 604 minRoomSize = Math.min(minRoomSize, rl.getRoomSize()); 605 maxRoomSize = Math.max(maxRoomSize, rl.getRoomSize()); 606 totalRoomSize += rl.getRoomSize(); 607 } 608 totalAvailableMinRoomSize += minRoomSize; 609 totalAvailableMaxRoomSize += maxRoomSize; 610 } 611 } 612 for (JenrlConstraint jenrl : model.getJenrlConstraints()) { 613 nrJenrls += jenrl.getJenrl(); 614 if ((jenrl.first()).timeLocations().size() == 1 && (jenrl.second()).timeLocations().size() == 1) { 615 TimeLocation t1 = jenrl.first().timeLocations().get(0); 616 TimeLocation t2 = jenrl.second().timeLocations().get(0); 617 if (t1.hasIntersection(t2)) { 618 nrInevitableStudentConflicts += jenrl.getJenrl(); 619 pw.println("Inevitable " + jenrl.getJenrl() + " student conflicts between " + jenrl.first() + " " 620 + t1 + " and " + jenrl.second() + " " + t2); 621 } else if (jenrl.first().values(solution.getAssignment()).size() == 1 && jenrl.second().values(solution.getAssignment()).size() == 1) { 622 Placement p1 = jenrl.first().values(solution.getAssignment()).get(0); 623 Placement p2 = jenrl.second().values(solution.getAssignment()).get(0); 624 if (JenrlConstraint.isInConflict(p1, p2, ((TimetableModel)p1.variable().getModel()).getDistanceMetric(), ((TimetableModel)p1.variable().getModel()).getStudentWorkDayLimit())) { 625 nrInevitableStudentConflicts += jenrl.getJenrl(); 626 pw.println("Inevitable " + jenrl.getJenrl() 627 + (p1.getTimeLocation().hasIntersection(p2.getTimeLocation()) ? "" : " distance") 628 + " student conflicts between " + p1 + " and " + p2); 629 } 630 } 631 } 632 } 633 int totalCommitedPlacements = 0; 634 for (Student student : students) { 635 if (student.getCommitedPlacements() != null) 636 totalCommitedPlacements += student.getCommitedPlacements().size(); 637 } 638 pw.println("Total number of classes: " + model.variables().size()); 639 pwi.println("Number of classes," + model.variables().size()); 640 pw.println("Total number of instructional offerings: " + offerings.size() + " (" 641 + sDoubleFormat.format(100.0 * offerings.size() / model.variables().size()) + "%)"); 642 // pwi.println("Number of instructional offerings,"+offerings.size()); 643 pw.println("Total number of configurations: " + configs.size() + " (" 644 + sDoubleFormat.format(100.0 * configs.size() / model.variables().size()) + "%)"); 645 pw.println("Total number of scheduling subparts: " + subparts.size() + " (" 646 + sDoubleFormat.format(100.0 * subparts.size() / model.variables().size()) + "%)"); 647 // pwi.println("Number of scheduling subparts,"+subparts.size()); 648 pw.println("Average number classes per subpart: " 649 + sDoubleFormat.format(1.0 * model.variables().size() / subparts.size())); 650 pwi.println("Avg. classes per instruction," 651 + sDoubleFormat.format(1.0 * model.variables().size() / subparts.size())); 652 pw.println("Average number classes per config: " 653 + sDoubleFormat.format(1.0 * model.variables().size() / configs.size())); 654 pw.println("Average number classes per offering: " 655 + sDoubleFormat.format(1.0 * model.variables().size() / offerings.size())); 656 pw.println("Total number of classes with only one value: " + nrSingleValueVariables + " (" 657 + sDoubleFormat.format(100.0 * nrSingleValueVariables / model.variables().size()) + "%)"); 658 pw.println("Total number of classes with only one time: " + nrSingleTimeVariables + " (" 659 + sDoubleFormat.format(100.0 * nrSingleTimeVariables / model.variables().size()) + "%)"); 660 pw.println("Total number of classes with only one room: " + nrSingleRoomVariables + " (" 661 + sDoubleFormat.format(100.0 * nrSingleRoomVariables / model.variables().size()) + "%)"); 662 pwi.println("Classes with single value," + nrSingleValueVariables); 663 // pwi.println("Classes with only one time/room,"+nrSingleTimeVariables+"/"+nrSingleRoomVariables); 664 pw.println("Total number of classes requesting no room: " 665 + (model.variables().size() - nrOneOrMoreRoomVariables) 666 + " (" 667 + sDoubleFormat.format(100.0 * (model.variables().size() - nrOneOrMoreRoomVariables) 668 / model.variables().size()) + "%)"); 669 pw.println("Total number of classes requesting one room: " + nrOneRoomVariables + " (" 670 + sDoubleFormat.format(100.0 * nrOneRoomVariables / model.variables().size()) + "%)"); 671 pw.println("Total number of classes requesting one or more rooms: " + nrOneOrMoreRoomVariables + " (" 672 + sDoubleFormat.format(100.0 * nrOneOrMoreRoomVariables / model.variables().size()) + "%)"); 673 // pwi.println("% classes requesting no room,"+sDoubleFormat.format(100.0*(model.variables().size()-nrOneOrMoreRoomVariables)/model.variables().size())+"%"); 674 // pwi.println("% classes requesting one room,"+sDoubleFormat.format(100.0*nrOneRoomVariables/model.variables().size())+"%"); 675 // pwi.println("% classes requesting two or more rooms,"+sDoubleFormat.format(100.0*(nrOneOrMoreRoomVariables-nrOneRoomVariables)/model.variables().size())+"%"); 676 pw.println("Average number of requested rooms: " 677 + sDoubleFormat.format(1.0 * nrReqRooms / model.variables().size())); 678 pw.println("Average minimal class limit: " 679 + sDoubleFormat.format(1.0 * totalMinLimit / model.variables().size())); 680 pw.println("Average maximal class limit: " 681 + sDoubleFormat.format(1.0 * totalMaxLimit / model.variables().size())); 682 // pwi.println("Average class limit,"+sDoubleFormat.format(1.0*(totalMinLimit+totalMaxLimit)/(2*model.variables().size()))); 683 pw.println("Average number of placements: " + sDoubleFormat.format(1.0 * nrValues / model.variables().size())); 684 // pwi.println("Average domain size,"+sDoubleFormat.format(1.0*nrValues/model.variables().size())); 685 pwi.println("Avg. domain size," + sDoubleFormat.format(1.0 * nrValues / model.variables().size())); 686 pw.println("Average number of time locations: " 687 + sDoubleFormat.format(1.0 * nrTimes / model.variables().size())); 688 pwi.println("Avg. number of avail. times/rooms," 689 + sDoubleFormat.format(1.0 * nrTimes / model.variables().size()) + "/" 690 + sDoubleFormat.format(1.0 * nrRooms / model.variables().size())); 691 pw.println("Average number of room locations: " 692 + sDoubleFormat.format(1.0 * nrRooms / model.variables().size())); 693 pw.println("Average minimal requested room size: " 694 + sDoubleFormat.format(1.0 * totalAvailableMinRoomSize / nrOneOrMoreRoomVariables)); 695 pw.println("Average maximal requested room size: " 696 + sDoubleFormat.format(1.0 * totalAvailableMaxRoomSize / nrOneOrMoreRoomVariables)); 697 pw.println("Average requested room sizes: " + sDoubleFormat.format(1.0 * totalRoomSize / nrRooms)); 698 pwi.println("Average requested room size," + sDoubleFormat.format(1.0 * totalRoomSize / nrRooms)); 699 pw.println("Average maximum normalized time preference: " 700 + sDoubleFormat.format(totalMaxNormTimePref / model.variables().size())); 701 pw.println("Average minimum normalized time preference: " 702 + sDoubleFormat.format(totalMinNormTimePref / model.variables().size())); 703 pw.println("Average normalized time preference," 704 + sDoubleFormat.format(totalNormTimePref / model.variables().size())); 705 pw.println("Average maximum room preferences: " 706 + sDoubleFormat.format(1.0 * totalMaxRoomPref / nrOneOrMoreRoomVariables)); 707 pw.println("Average minimum room preferences: " 708 + sDoubleFormat.format(1.0 * totalMinRoomPref / nrOneOrMoreRoomVariables)); 709 pw.println("Average room preferences," + sDoubleFormat.format(1.0 * totalRoomPref / nrOneOrMoreRoomVariables)); 710 pw.println("Total number of students:" + students.size()); 711 pwi.println("Number of students," + students.size()); 712 pwi.println("Number of inevitable student conflicts," + nrInevitableStudentConflicts); 713 pw.println("Total amount of student enrollments: " + nrStudentEnrls); 714 pwi.println("Number of student enrollments," + nrStudentEnrls); 715 pw.println("Total amount of joined enrollments: " + nrJenrls); 716 pwi.println("Number of joint student enrollments," + nrJenrls); 717 pw.println("Average number of students: " 718 + sDoubleFormat.format(1.0 * students.size() / model.variables().size())); 719 pw.println("Average number of enrollemnts (per student): " 720 + sDoubleFormat.format(1.0 * nrStudentEnrls / students.size())); 721 pwi.println("Avg. number of classes per student," 722 + sDoubleFormat.format(1.0 * nrStudentEnrls / students.size())); 723 pwi.println("Avg. number of committed classes per student," 724 + sDoubleFormat.format(1.0 * totalCommitedPlacements / students.size())); 725 pw.println("Total amount of inevitable student conflicts: " + nrInevitableStudentConflicts + " (" 726 + sDoubleFormat.format(100.0 * nrInevitableStudentConflicts / nrStudentEnrls) + "%)"); 727 pw.println("Average number of meetings (per class): " 728 + sDoubleFormat.format(1.0 * nrMeetings / model.variables().size())); 729 pw.println("Average number of hours per class: " 730 + sDoubleFormat.format(1.0 * nrHalfHours / model.variables().size() / 12.0)); 731 pwi.println("Avg. number of meetings per class," 732 + sDoubleFormat.format(1.0 * nrMeetings / model.variables().size())); 733 pwi.println("Avg. number of hours per class," 734 + sDoubleFormat.format(1.0 * nrHalfHours / model.variables().size() / 12.0)); 735 int minRoomSize = Integer.MAX_VALUE; 736 int maxRoomSize = Integer.MIN_VALUE; 737 int nrDistancePairs = 0; 738 double maxRoomDistance = Double.MIN_VALUE; 739 double totalRoomDistance = 0.0; 740 int[] totalAvailableSlots = new int[sizeLimits.length]; 741 int[] totalAvailableSeats = new int[sizeLimits.length]; 742 int nrOfRooms = 0; 743 totalRoomSize = 0; 744 for (RoomConstraint rc : model.getRoomConstraints()) { 745 if (rc.variables().isEmpty()) continue; 746 nrOfRooms++; 747 minRoomSize = Math.min(minRoomSize, rc.getCapacity()); 748 maxRoomSize = Math.max(maxRoomSize, rc.getCapacity()); 749 for (int l = 0; l < sizeLimits.length; l++) { 750 if (sizeLimits[l] <= rc.getCapacity() 751 && (l + 1 == sizeLimits.length || rc.getCapacity() < sizeLimits[l + 1])) { 752 nrRoomsOfSize[l]++; 753 if (minRoomOfSize[l] == 0) 754 minRoomOfSize[l] = rc.getCapacity(); 755 else 756 minRoomOfSize[l] = Math.min(minRoomOfSize[l], rc.getCapacity()); 757 if (maxRoomOfSize[l] == 0) 758 maxRoomOfSize[l] = rc.getCapacity(); 759 else 760 maxRoomOfSize[l] = Math.max(maxRoomOfSize[l], rc.getCapacity()); 761 } 762 } 763 totalRoomSize += rc.getCapacity(); 764 if (rc.getPosX() != null && rc.getPosY() != null) { 765 for (RoomConstraint rc2 : model.getRoomConstraints()) { 766 if (rc2.getResourceId().compareTo(rc.getResourceId()) > 0 && rc2.getPosX() != null && rc2.getPosY() != null) { 767 double distance = ((TimetableModel)solution.getModel()).getDistanceMetric().getDistanceInMinutes(rc.getId(), rc.getPosX(), rc.getPosY(), rc2.getId(), rc2.getPosX(), rc2.getPosY()); 768 totalRoomDistance += distance; 769 nrDistancePairs++; 770 maxRoomDistance = Math.max(maxRoomDistance, distance); 771 } 772 } 773 } 774 for (int d = firstWorkDay; d <= lastWorkDay; d++) { 775 for (int t = firstDaySlot; t <= lastDaySlot; t++) { 776 if (rc.isAvailable((d % 7) * Constants.SLOTS_PER_DAY + t)) { 777 for (int l = 0; l < sizeLimits.length; l++) { 778 if (sizeLimits[l] <= rc.getCapacity()) { 779 totalAvailableSlots[l]++; 780 totalAvailableSeats[l] += rc.getCapacity(); 781 } 782 } 783 } 784 } 785 } 786 } 787 pw.println("Total number of rooms: " + nrOfRooms); 788 pwi.println("Number of rooms," + nrOfRooms); 789 pw.println("Minimal room size: " + minRoomSize); 790 pw.println("Maximal room size: " + maxRoomSize); 791 pwi.println("Room size min/max," + minRoomSize + "/" + maxRoomSize); 792 pw.println("Average room size: " 793 + sDoubleFormat.format(1.0 * totalRoomSize / model.getRoomConstraints().size())); 794 pw.println("Maximal distance between two rooms: " + sDoubleFormat.format(maxRoomDistance)); 795 pw.println("Average distance between two rooms: " 796 + sDoubleFormat.format(totalRoomDistance / nrDistancePairs)); 797 pwi.println("Average distance between two rooms [min]," 798 + sDoubleFormat.format(totalRoomDistance / nrDistancePairs)); 799 pwi.println("Maximal distance between two rooms [min]," + sDoubleFormat.format(maxRoomDistance)); 800 for (int l = 0; l < sizeLimits.length; l++) {// sizeLimits.length;l++) { 801 pwi.println("\"Room frequency (size>=" + sizeLimits[l] + ", used/avaiable times)\"," 802 + sDoubleFormat.format(100.0 * totalUsedSlots[l] / totalAvailableSlots[l]) + "%"); 803 pwi.println("\"Room utilization (size>=" + sizeLimits[l] + ", used/available seats)\"," 804 + sDoubleFormat.format(100.0 * totalUsedSeats[l] / totalAvailableSeats[l]) + "%"); 805 pwi.println("\"Number of rooms (size>=" + sizeLimits[l] + ")\"," + nrRoomsOfSize[l]); 806 pwi.println("\"Min/max room size (size>=" + sizeLimits[l] + ")\"," + minRoomOfSize[l] + "-" 807 + maxRoomOfSize[l]); 808 // pwi.println("\"Room utilization (size>="+sizeLimits[l]+", minRoomSize)\","+sDoubleFormat.format(100.0*totalUsedSeats2[l]/totalAvailableSeats[l])+"%"); 809 } 810 pw.println("Average hours available: " 811 + sDoubleFormat.format(1.0 * totalAvailableSlots[0] / nrOfRooms / 12.0)); 812 int totalInstructedClasses = 0; 813 for (InstructorConstraint ic : model.getInstructorConstraints()) { 814 totalInstructedClasses += ic.variables().size(); 815 } 816 pw.println("Total number of instructors: " + model.getInstructorConstraints().size()); 817 pwi.println("Number of instructors," + model.getInstructorConstraints().size()); 818 pw.println("Total class-instructor assignments: " + totalInstructedClasses + " (" 819 + sDoubleFormat.format(100.0 * totalInstructedClasses / model.variables().size()) + "%)"); 820 pwi.println("Number of class-instructor assignments," + totalInstructedClasses); 821 pw.println("Average classes per instructor: " 822 + sDoubleFormat.format(1.0 * totalInstructedClasses / model.getInstructorConstraints().size())); 823 pwi.println("Average classes per instructor," 824 + sDoubleFormat.format(1.0 * totalInstructedClasses / model.getInstructorConstraints().size())); 825 // pw.println("Average hours available: "+sDoubleFormat.format(1.0*totalAvailableSlots/model.getInstructorConstraints().size()/12.0)); 826 // pwi.println("Instructor availability [h],"+sDoubleFormat.format(1.0*totalAvailableSlots/model.getInstructorConstraints().size()/12.0)); 827 int nrGroupConstraints = model.getGroupConstraints().size() + model.getSpreadConstraints().size(); 828 int nrHardGroupConstraints = 0; 829 int nrVarsInGroupConstraints = 0; 830 for (GroupConstraint gc : model.getGroupConstraints()) { 831 if (gc.isHard()) 832 nrHardGroupConstraints++; 833 nrVarsInGroupConstraints += gc.variables().size(); 834 } 835 for (SpreadConstraint sc : model.getSpreadConstraints()) { 836 nrVarsInGroupConstraints += sc.variables().size(); 837 } 838 pw.println("Total number of group constraints: " + nrGroupConstraints + " (" 839 + sDoubleFormat.format(100.0 * nrGroupConstraints / model.variables().size()) + "%)"); 840 // pwi.println("Number of group constraints,"+nrGroupConstraints); 841 pw.println("Total number of hard group constraints: " + nrHardGroupConstraints + " (" 842 + sDoubleFormat.format(100.0 * nrHardGroupConstraints / model.variables().size()) + "%)"); 843 // pwi.println("Number of hard group constraints,"+nrHardGroupConstraints); 844 pw.println("Average classes per group constraint: " 845 + sDoubleFormat.format(1.0 * nrVarsInGroupConstraints / nrGroupConstraints)); 846 // pwi.println("Average classes per group constraint,"+sDoubleFormat.format(1.0*nrVarsInGroupConstraints/nrGroupConstraints)); 847 pwi.println("Avg. number distribution constraints per class," 848 + sDoubleFormat.format(1.0 * nrVarsInGroupConstraints / model.variables().size())); 849 pwi.println("Joint enrollment constraints," + model.getJenrlConstraints().size()); 850 pw.flush(); 851 pw.close(); 852 pwi.flush(); 853 pwi.close(); 854 } 855 856 public static void saveOutputCSV(Solution<Lecture, Placement> s, File file) { 857 try { 858 DecimalFormat dx = new DecimalFormat("000"); 859 PrintWriter w = new PrintWriter(new FileWriter(file)); 860 TimetableModel m = (TimetableModel) s.getModel(); 861 int firstDaySlot = m.getProperties().getPropertyInt("General.FirstDaySlot", Constants.DAY_SLOTS_FIRST); 862 int lastDaySlot = m.getProperties().getPropertyInt("General.LastDaySlot", Constants.DAY_SLOTS_LAST); 863 int firstWorkDay = m.getProperties().getPropertyInt("General.FirstWorkDay", 0); 864 int lastWorkDay = m.getProperties().getPropertyInt("General.LastWorkDay", Constants.NR_DAYS_WEEK - 1); 865 if (lastWorkDay < firstWorkDay) lastWorkDay += 7; 866 Assignment<Lecture, Placement> a = s.getAssignment(); 867 int idx = 1; 868 w.println("000." + dx.format(idx++) + " Assigned variables," + a.nrAssignedVariables()); 869 w.println("000." + dx.format(idx++) + " Time [sec]," + sDoubleFormat.format(s.getBestTime())); 870 w.println("000." + dx.format(idx++) + " Hard student conflicts," + Math.round(m.getCriterion(StudentHardConflict.class).getValue(a))); 871 if (m.getProperties().getPropertyBoolean("General.UseDistanceConstraints", true)) 872 w.println("000." + dx.format(idx++) + " Distance student conf.," + Math.round(m.getCriterion(StudentDistanceConflict.class).getValue(a))); 873 w.println("000." + dx.format(idx++) + " Student conflicts," + Math.round(m.getCriterion(StudentConflict.class).getValue(a))); 874 w.println("000." + dx.format(idx++) + " Committed student conflicts," + Math.round(m.getCriterion(StudentCommittedConflict.class).getValue(a))); 875 w.println("000." + dx.format(idx++) + " All Student conflicts," 876 + Math.round(m.getCriterion(StudentConflict.class).getValue(a) + m.getCriterion(StudentCommittedConflict.class).getValue(a))); 877 w.println("000." + dx.format(idx++) + " Time preferences," 878 + sDoubleFormat.format( m.getCriterion(TimePreferences.class).getValue(a))); 879 w.println("000." + dx.format(idx++) + " Room preferences," + Math.round(m.getCriterion(RoomPreferences.class).getValue(a))); 880 w.println("000." + dx.format(idx++) + " Useless half-hours," + Math.round(m.getCriterion(UselessHalfHours.class).getValue(a))); 881 w.println("000." + dx.format(idx++) + " Broken time patterns," + Math.round(m.getCriterion(BrokenTimePatterns.class).getValue(a))); 882 w.println("000." + dx.format(idx++) + " Too big room," + Math.round(m.getCriterion(TooBigRooms.class).getValue(a))); 883 w.println("000." + dx.format(idx++) + " Distribution preferences," + sDoubleFormat.format(m.getCriterion(DistributionPreferences.class).getValue(a))); 884 if (m.getProperties().getPropertyBoolean("General.UseDistanceConstraints", true)) 885 w.println("000." + dx.format(idx++) + " Back-to-back instructor pref.," + Math.round(m.getCriterion(BackToBackInstructorPreferences.class).getValue(a))); 886 if (m.getProperties().getPropertyBoolean("General.DeptBalancing", true)) { 887 w.println("000." + dx.format(idx++) + " Dept. balancing penalty," + sDoubleFormat.format(m.getCriterion(DepartmentBalancingPenalty.class).getValue(a))); 888 } 889 w.println("000." + dx.format(idx++) + " Same subpart balancing penalty," + sDoubleFormat.format(m.getCriterion(SameSubpartBalancingPenalty.class).getValue(a))); 890 if (m.getProperties().getPropertyBoolean("General.MPP", false)) { 891 Map<String, Double> mppInfo = ((UniversalPerturbationsCounter)((Perturbations)m.getCriterion(Perturbations.class)).getPerturbationsCounter()).getCompactInfo(a, m, false, false); 892 int pidx = 51; 893 w.println("000." + dx.format(pidx++) + " Perturbation penalty," + sDoubleFormat.format(m.getCriterion(Perturbations.class).getValue(a))); 894 w.println("000." + dx.format(pidx++) + " Additional perturbations," + m.perturbVariables(a).size()); 895 int nrPert = 0, nrStudentPert = 0; 896 for (Lecture lecture : m.variables()) { 897 if (lecture.getInitialAssignment() != null) 898 continue; 899 nrPert++; 900 nrStudentPert += lecture.classLimit(a); 901 } 902 w.println("000." + dx.format(pidx++) + " Given perturbations," + nrPert); 903 w.println("000." + dx.format(pidx++) + " Given student perturbations," + nrStudentPert); 904 for (String key : new TreeSet<String>(mppInfo.keySet())) { 905 Double value = mppInfo.get(key); 906 w.println("000." + dx.format(pidx++) + " " + key + "," + sDoubleFormat.format(value)); 907 } 908 } 909 HashSet<Student> students = new HashSet<Student>(); 910 int enrls = 0; 911 int minRoomPref = 0, maxRoomPref = 0; 912 int minGrPref = 0, maxGrPref = 0; 913 int minTimePref = 0, maxTimePref = 0; 914 int worstInstrPref = 0; 915 HashSet<Constraint<Lecture, Placement>> used = new HashSet<Constraint<Lecture, Placement>>(); 916 for (Lecture lecture : m.variables()) { 917 enrls += (lecture.students() == null ? 0 : lecture.students().size()); 918 students.addAll(lecture.students()); 919 920 int[] minMaxRoomPref = lecture.getMinMaxRoomPreference(); 921 maxRoomPref += minMaxRoomPref[1] - minMaxRoomPref[0]; 922 923 double[] minMaxTimePref = lecture.getMinMaxTimePreference(); 924 maxTimePref += minMaxTimePref[1] - minMaxTimePref[0]; 925 for (Constraint<Lecture, Placement> c : lecture.constraints()) { 926 if (!used.add(c)) 927 continue; 928 929 if (c instanceof InstructorConstraint) { 930 InstructorConstraint ic = (InstructorConstraint) c; 931 worstInstrPref += ic.getWorstPreference(); 932 } 933 934 if (c instanceof GroupConstraint) { 935 GroupConstraint gc = (GroupConstraint) c; 936 if (gc.isHard()) 937 continue; 938 maxGrPref += Math.abs(gc.getPreference()) * (1 + (gc.variables().size() * (gc.variables().size() - 1)) / 2); 939 } 940 } 941 } 942 int totalCommitedPlacements = 0; 943 for (Student student : students) { 944 if (student.getCommitedPlacements() != null) 945 totalCommitedPlacements += student.getCommitedPlacements().size(); 946 } 947 HashMap<Long, List<Lecture>> subs = new HashMap<Long, List<Lecture>>(); 948 for (Lecture lecture : m.variables()) { 949 if (lecture.isCommitted() || lecture.getScheduler() == null) 950 continue; 951 List<Lecture> vars = subs.get(lecture.getScheduler()); 952 if (vars == null) { 953 vars = new ArrayList<Lecture>(); 954 subs.put(lecture.getScheduler(), vars); 955 } 956 vars.add(lecture); 957 } 958 int bidx = 101; 959 w.println("000." + dx.format(bidx++) + " Assigned variables max," + m.variables().size()); 960 w.println("000." + dx.format(bidx++) + " Student enrollments," + enrls); 961 w.println("000." + dx.format(bidx++) + " Student commited enrollments," + totalCommitedPlacements); 962 w.println("000." + dx.format(bidx++) + " All student enrollments," + (enrls + totalCommitedPlacements)); 963 w.println("000." + dx.format(bidx++) + " Time preferences min," + minTimePref); 964 w.println("000." + dx.format(bidx++) + " Time preferences max," + maxTimePref); 965 w.println("000." + dx.format(bidx++) + " Room preferences min," + minRoomPref); 966 w.println("000." + dx.format(bidx++) + " Room preferences max," + maxRoomPref); 967 w.println("000." + dx.format(bidx++) + " Useless half-hours max," + 968 (Constants.sPreferenceLevelStronglyDiscouraged * m.getRoomConstraints().size() * (lastDaySlot - firstDaySlot + 1) * (lastWorkDay - firstWorkDay + 1))); 969 w.println("000." + dx.format(bidx++) + " Too big room max," + (Constants.sPreferenceLevelStronglyDiscouraged * m.variables().size())); 970 w.println("000." + dx.format(bidx++) + " Distribution preferences min," + minGrPref); 971 w.println("000." + dx.format(bidx++) + " Distribution preferences max," + maxGrPref); 972 w.println("000." + dx.format(bidx++) + " Back-to-back instructor pref max," + worstInstrPref); 973 TooBigRooms tbr = (TooBigRooms)m.getCriterion(TooBigRooms.class); 974 for (Long scheduler: new TreeSet<Long>(subs.keySet())) { 975 List<Lecture> vars = subs.get(scheduler); 976 idx = 001; 977 bidx = 101; 978 int nrAssg = 0; 979 enrls = 0; 980 int roomPref = 0; 981 minRoomPref = 0; 982 maxRoomPref = 0; 983 double timePref = 0; 984 minTimePref = 0; 985 maxTimePref = 0; 986 double grPref = 0; 987 minGrPref = 0; 988 maxGrPref = 0; 989 long allSC = 0, hardSC = 0, distSC = 0; 990 int instPref = 0; 991 worstInstrPref = 0; 992 int spreadPen = 0, deptSpreadPen = 0; 993 int tooBigRooms = 0; 994 int rcs = 0, uselessSlots = 0; 995 used = new HashSet<Constraint<Lecture, Placement>>(); 996 for (Lecture lecture : vars) { 997 if (lecture.isCommitted()) 998 continue; 999 enrls += lecture.students().size(); 1000 Placement placement = a.getValue(lecture); 1001 if (placement != null) { 1002 nrAssg++; 1003 } 1004 1005 int[] minMaxRoomPref = lecture.getMinMaxRoomPreference(); 1006 minRoomPref += minMaxRoomPref[0]; 1007 maxRoomPref += minMaxRoomPref[1]; 1008 1009 double[] minMaxTimePref = lecture.getMinMaxTimePreference(); 1010 minTimePref += minMaxTimePref[0]; 1011 maxTimePref += minMaxTimePref[1]; 1012 1013 if (placement != null) { 1014 roomPref += placement.getRoomPreference(); 1015 timePref += placement.getTimeLocation().getNormalizedPreference(); 1016 if (tbr != null) tooBigRooms += tbr.getPreference(placement); 1017 } 1018 1019 for (Constraint<Lecture, Placement> c : lecture.constraints()) { 1020 if (!used.add(c)) 1021 continue; 1022 1023 if (c instanceof InstructorConstraint) { 1024 InstructorConstraint ic = (InstructorConstraint) c; 1025 instPref += ic.getPreference(a); 1026 worstInstrPref += ic.getWorstPreference(); 1027 } 1028 1029 if (c instanceof DepartmentSpreadConstraint) { 1030 DepartmentSpreadConstraint dsc = (DepartmentSpreadConstraint) c; 1031 deptSpreadPen += dsc.getPenalty(a); 1032 } else if (c instanceof SpreadConstraint) { 1033 SpreadConstraint sc = (SpreadConstraint) c; 1034 spreadPen += sc.getPenalty(a); 1035 } 1036 1037 if (c instanceof GroupConstraint) { 1038 GroupConstraint gc = (GroupConstraint) c; 1039 if (gc.isHard()) 1040 continue; 1041 minGrPref -= Math.abs(gc.getPreference()); 1042 maxGrPref += 0; 1043 grPref += Math.min(0, gc.getCurrentPreference(a)); 1044 // minGrPref += Math.min(gc.getPreference(), 0); 1045 // maxGrPref += Math.max(gc.getPreference(), 0); 1046 // grPref += gc.getCurrentPreference(); 1047 } 1048 1049 if (c instanceof JenrlConstraint) { 1050 JenrlConstraint jc = (JenrlConstraint) c; 1051 if (!jc.isInConflict(a) || !jc.isOfTheSameProblem()) 1052 continue; 1053 Lecture l1 = jc.first(); 1054 Lecture l2 = jc.second(); 1055 allSC += jc.getJenrl(); 1056 if (l1.areStudentConflictsHard(l2)) 1057 hardSC += jc.getJenrl(); 1058 Placement p1 = a.getValue(l1); 1059 Placement p2 = a.getValue(l2); 1060 if (!p1.getTimeLocation().hasIntersection(p2.getTimeLocation())) 1061 distSC += jc.getJenrl(); 1062 } 1063 1064 if (c instanceof RoomConstraint) { 1065 RoomConstraint rc = (RoomConstraint) c; 1066 uselessSlots += UselessHalfHours.countUselessSlotsHalfHours(rc.getContext(a)) + BrokenTimePatterns.countUselessSlotsBrokenTimePatterns(rc.getContext(a)); 1067 rcs++; 1068 } 1069 } 1070 } 1071 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Assigned variables," + nrAssg); 1072 w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Assigned variables max," + vars.size()); 1073 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Hard student conflicts," + hardSC); 1074 w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Student enrollments," + enrls); 1075 if (m.getProperties().getPropertyBoolean("General.UseDistanceConstraints", true)) 1076 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Distance student conf.," + distSC); 1077 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Student conflicts," + allSC); 1078 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Time preferences," + timePref); 1079 w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Time preferences min," + minTimePref); 1080 w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Time preferences max," + maxTimePref); 1081 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Room preferences," + roomPref); 1082 w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Room preferences min," + minRoomPref); 1083 w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Room preferences max," + maxRoomPref); 1084 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Useless half-hours," + uselessSlots); 1085 w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Useless half-hours max," + 1086 (Constants.sPreferenceLevelStronglyDiscouraged * rcs * (lastDaySlot - firstDaySlot + 1) * (lastWorkDay - firstWorkDay + 1))); 1087 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Too big room," + tooBigRooms); 1088 w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Too big room max," + (Constants.sPreferenceLevelStronglyDiscouraged * vars.size())); 1089 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Distribution preferences," + grPref); 1090 w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Distribution preferences min," + minGrPref); 1091 w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Distribution preferences max," + maxGrPref); 1092 if (m.getProperties().getPropertyBoolean("General.UseDistanceConstraints", true)) 1093 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Back-to-back instructor pref," + instPref); 1094 w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Back-to-back instructor pref max," + worstInstrPref); 1095 if (m.getProperties().getPropertyBoolean("General.DeptBalancing", true)) { 1096 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Department balancing penalty," + sDoubleFormat.format((deptSpreadPen) / 12.0)); 1097 } 1098 w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Same subpart balancing penalty," + sDoubleFormat.format((spreadPen) / 12.0)); 1099 } 1100 w.flush(); 1101 w.close(); 1102 } catch (java.io.IOException io) { 1103 sLogger.error(io.getMessage(), io); 1104 } 1105 } 1106 1107 private class ShutdownHook extends Thread { 1108 Solver<Lecture, Placement> iSolver = null; 1109 1110 private ShutdownHook(Solver<Lecture, Placement> solver) { 1111 setName("ShutdownHook"); 1112 iSolver = solver; 1113 } 1114 1115 @Override 1116 public void run() { 1117 try { 1118 if (iSolver.isRunning()) iSolver.stopSolver(); 1119 Solution<Lecture, Placement> solution = iSolver.lastSolution(); 1120 long lastIt = solution.getIteration(); 1121 double lastTime = solution.getTime(); 1122 DataProperties properties = iSolver.getProperties(); 1123 TimetableModel model = (TimetableModel) solution.getModel(); 1124 File outDir = new File(properties.getProperty("General.Output", ".")); 1125 1126 if (solution.getBestInfo() != null) { 1127 Solution<Lecture, Placement> bestSolution = solution;// .cloneBest(); 1128 sLogger.info("Last solution: " + ToolBox.dict2string(bestSolution.getExtendedInfo(), 1)); 1129 sLogger.info("Best solution (before restore): " + ToolBox.dict2string(bestSolution.getBestInfo(), 1)); 1130 bestSolution.restoreBest(); 1131 sLogger.info("Best solution: " + ToolBox.dict2string(bestSolution.getExtendedInfo(), 1)); 1132 if (properties.getPropertyBoolean("General.SwitchStudents", true)) 1133 ((TimetableModel) bestSolution.getModel()).switchStudents(bestSolution.getAssignment()); 1134 sLogger.info("Best solution: " + ToolBox.dict2string(bestSolution.getExtendedInfo(), 1)); 1135 saveOutputCSV(bestSolution, new File(outDir, "output.csv")); 1136 1137 printSomeStuff(bestSolution); 1138 1139 if (properties.getPropertyBoolean("General.Save", true)) { 1140 TimetableSaver saver = null; 1141 try { 1142 saver = (TimetableSaver) Class.forName(getTimetableSaverClass(properties)) 1143 .getConstructor(new Class[] { Solver.class }).newInstance(new Object[] { iSolver }); 1144 } catch (ClassNotFoundException e) { 1145 System.err.println(e.getClass().getSimpleName() + ": " + e.getMessage()); 1146 saver = new TimetableXMLSaver(iSolver); 1147 } 1148 if ((saver instanceof TimetableXMLSaver) && properties.getProperty("General.SolutionFile") != null) 1149 ((TimetableXMLSaver) saver).save(new File(properties.getProperty("General.SolutionFile"))); 1150 else 1151 saver.save(); 1152 } 1153 } else { 1154 sLogger.info("Last solution:" + ToolBox.dict2string(solution.getExtendedInfo(), 1)); 1155 } 1156 1157 iCSVFile.close(); 1158 1159 sLogger.info("Total number of done iteration steps:" + lastIt); 1160 sLogger.info("Achieved speed: " + sDoubleFormat.format(lastIt / lastTime) + " iterations/second"); 1161 1162 PrintWriter out = new PrintWriter(new FileWriter(new File(outDir, "solver.html"))); 1163 out.println("<html><title>Save log</title><body>"); 1164 out.println(Progress.getInstance(model).getHtmlLog(Progress.MSGLEVEL_TRACE, true)); 1165 out.println("</html>"); 1166 out.flush(); 1167 out.close(); 1168 Progress.removeInstance(model); 1169 1170 if (iStat != null) { 1171 PrintWriter cbs = new PrintWriter(new FileWriter(new File(outDir, "cbs.txt"))); 1172 cbs.println(iStat.toString()); 1173 cbs.flush(); cbs.close(); 1174 } 1175 1176 System.out.println("Unassigned variables: " + model.nrUnassignedVariables(solution.getAssignment())); 1177 } catch (Throwable t) { 1178 sLogger.error("Test failed.", t); 1179 } 1180 } 1181 } 1182}