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