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