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