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