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