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                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().isEmpty()) {
525                    Placement p = lect.values().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 = 0; d < Constants.NR_DAYS_WEEK; d++) {
542                            if ((time.getDayCode() & Constants.DAY_CODES[d]) == 0)
543                                continue;
544                            for (int t = Math.max(time.getStartSlot(), Constants.DAY_SLOTS_FIRST); t <= Math.min(time
545                                    .getStartSlot()
546                                    + time.getLength() - 1, Constants.DAY_SLOTS_LAST); t++) {
547                                for (int l = 0; l < sizeLimits.length; l++) {
548                                    if (sizeLimits[l] <= lect.minRoomSize()) {
549                                        totalUsedSlots[l] += lect.getNrRooms();
550                                        totalUsedSeats[l] += lect.classLimit();
551                                        totalUsedSeats2[l] += lect.minRoomSize() * lect.getNrRooms();
552                                    }
553                                }
554                            }
555                        }
556                    }
557                }
558                if (lect.values().size() == 1) {
559                    nrSingleValueVariables++;
560                }
561                if (lect.timeLocations().size() == 1) {
562                    nrSingleTimeVariables++;
563                }
564                if (lect.roomLocations().size() == 1) {
565                    nrSingleRoomVariables++;
566                }
567                if (lect.getNrRooms() == 1) {
568                    nrOneRoomVariables++;
569                }
570                if (lect.getNrRooms() > 0) {
571                    nrOneOrMoreRoomVariables++;
572                }
573                if (!lect.roomLocations().isEmpty()) {
574                    int minRoomSize = Integer.MAX_VALUE;
575                    int maxRoomSize = Integer.MIN_VALUE;
576                    for (RoomLocation rl : lect.roomLocations()) {
577                        minRoomSize = Math.min(minRoomSize, rl.getRoomSize());
578                        maxRoomSize = Math.max(maxRoomSize, rl.getRoomSize());
579                        totalRoomSize += rl.getRoomSize();
580                    }
581                    totalAvailableMinRoomSize += minRoomSize;
582                    totalAvailableMaxRoomSize += maxRoomSize;
583                }
584            }
585            for (JenrlConstraint jenrl : model.getJenrlConstraints()) {
586                nrJenrls += jenrl.getJenrl();
587                if ((jenrl.first()).timeLocations().size() == 1 && (jenrl.second()).timeLocations().size() == 1) {
588                    TimeLocation t1 = jenrl.first().timeLocations().get(0);
589                    TimeLocation t2 = jenrl.second().timeLocations().get(0);
590                    if (t1.hasIntersection(t2)) {
591                        nrInevitableStudentConflicts += jenrl.getJenrl();
592                        pw.println("Inevitable " + jenrl.getJenrl() + " student conflicts between " + jenrl.first() + " "
593                                + t1 + " and " + jenrl.second() + " " + t2);
594                    } else if (jenrl.first().values().size() == 1 && jenrl.second().values().size() == 1) {
595                        Placement p1 = jenrl.first().values().get(0);
596                        Placement p2 = jenrl.second().values().get(0);
597                        if (JenrlConstraint.isInConflict(p1, p2, ((TimetableModel)p1.variable().getModel()).getDistanceMetric())) {
598                            nrInevitableStudentConflicts += jenrl.getJenrl();
599                            pw.println("Inevitable " + jenrl.getJenrl()
600                                    + (p1.getTimeLocation().hasIntersection(p2.getTimeLocation()) ? "" : " distance")
601                                    + " student conflicts between " + p1 + " and " + p2);
602                        }
603                    }
604                }
605            }
606            int totalCommitedPlacements = 0;
607            for (Student student : students) {
608                if (student.getCommitedPlacements() != null)
609                    totalCommitedPlacements += student.getCommitedPlacements().size();
610            }
611            pw.println("Total number of classes: " + model.variables().size());
612            pwi.println("Number of classes," + model.variables().size());
613            pw.println("Total number of instructional offerings: " + offerings.size() + " ("
614                    + sDoubleFormat.format(100.0 * offerings.size() / model.variables().size()) + "%)");
615            // pwi.println("Number of instructional offerings,"+offerings.size());
616            pw.println("Total number of configurations: " + configs.size() + " ("
617                    + sDoubleFormat.format(100.0 * configs.size() / model.variables().size()) + "%)");
618            pw.println("Total number of scheduling subparts: " + subparts.size() + " ("
619                    + sDoubleFormat.format(100.0 * subparts.size() / model.variables().size()) + "%)");
620            // pwi.println("Number of scheduling subparts,"+subparts.size());
621            pw.println("Average number classes per subpart: "
622                    + sDoubleFormat.format(1.0 * model.variables().size() / subparts.size()));
623            pwi.println("Avg. classes per instruction,"
624                    + sDoubleFormat.format(1.0 * model.variables().size() / subparts.size()));
625            pw.println("Average number classes per config: "
626                    + sDoubleFormat.format(1.0 * model.variables().size() / configs.size()));
627            pw.println("Average number classes per offering: "
628                    + sDoubleFormat.format(1.0 * model.variables().size() / offerings.size()));
629            pw.println("Total number of classes with only one value: " + nrSingleValueVariables + " ("
630                    + sDoubleFormat.format(100.0 * nrSingleValueVariables / model.variables().size()) + "%)");
631            pw.println("Total number of classes with only one time: " + nrSingleTimeVariables + " ("
632                    + sDoubleFormat.format(100.0 * nrSingleTimeVariables / model.variables().size()) + "%)");
633            pw.println("Total number of classes with only one room: " + nrSingleRoomVariables + " ("
634                    + sDoubleFormat.format(100.0 * nrSingleRoomVariables / model.variables().size()) + "%)");
635            pwi.println("Classes with single value," + nrSingleValueVariables);
636            // pwi.println("Classes with only one time/room,"+nrSingleTimeVariables+"/"+nrSingleRoomVariables);
637            pw.println("Total number of classes requesting no room: "
638                    + (model.variables().size() - nrOneOrMoreRoomVariables)
639                    + " ("
640                    + sDoubleFormat.format(100.0 * (model.variables().size() - nrOneOrMoreRoomVariables)
641                            / model.variables().size()) + "%)");
642            pw.println("Total number of classes requesting one room: " + nrOneRoomVariables + " ("
643                    + sDoubleFormat.format(100.0 * nrOneRoomVariables / model.variables().size()) + "%)");
644            pw.println("Total number of classes requesting one or more rooms: " + nrOneOrMoreRoomVariables + " ("
645                    + sDoubleFormat.format(100.0 * nrOneOrMoreRoomVariables / model.variables().size()) + "%)");
646            // pwi.println("% classes requesting no room,"+sDoubleFormat.format(100.0*(model.variables().size()-nrOneOrMoreRoomVariables)/model.variables().size())+"%");
647            // pwi.println("% classes requesting one room,"+sDoubleFormat.format(100.0*nrOneRoomVariables/model.variables().size())+"%");
648            // pwi.println("% classes requesting two or more rooms,"+sDoubleFormat.format(100.0*(nrOneOrMoreRoomVariables-nrOneRoomVariables)/model.variables().size())+"%");
649            pw.println("Average number of requested rooms: "
650                    + sDoubleFormat.format(1.0 * nrReqRooms / model.variables().size()));
651            pw.println("Average minimal class limit: "
652                    + sDoubleFormat.format(1.0 * totalMinLimit / model.variables().size()));
653            pw.println("Average maximal class limit: "
654                    + sDoubleFormat.format(1.0 * totalMaxLimit / model.variables().size()));
655            // pwi.println("Average class limit,"+sDoubleFormat.format(1.0*(totalMinLimit+totalMaxLimit)/(2*model.variables().size())));
656            pw.println("Average number of placements: " + sDoubleFormat.format(1.0 * nrValues / model.variables().size()));
657            // pwi.println("Average domain size,"+sDoubleFormat.format(1.0*nrValues/model.variables().size()));
658            pwi.println("Avg. domain size," + sDoubleFormat.format(1.0 * nrValues / model.variables().size()));
659            pw.println("Average number of time locations: "
660                    + sDoubleFormat.format(1.0 * nrTimes / model.variables().size()));
661            pwi.println("Avg. number of avail. times/rooms,"
662                    + sDoubleFormat.format(1.0 * nrTimes / model.variables().size()) + "/"
663                    + sDoubleFormat.format(1.0 * nrRooms / model.variables().size()));
664            pw.println("Average number of room locations: "
665                    + sDoubleFormat.format(1.0 * nrRooms / model.variables().size()));
666            pw.println("Average minimal requested room size: "
667                    + sDoubleFormat.format(1.0 * totalAvailableMinRoomSize / nrOneOrMoreRoomVariables));
668            pw.println("Average maximal requested room size: "
669                    + sDoubleFormat.format(1.0 * totalAvailableMaxRoomSize / nrOneOrMoreRoomVariables));
670            pw.println("Average requested room sizes: " + sDoubleFormat.format(1.0 * totalRoomSize / nrRooms));
671            pwi.println("Average requested room size," + sDoubleFormat.format(1.0 * totalRoomSize / nrRooms));
672            pw.println("Average maximum normalized time preference: "
673                    + sDoubleFormat.format(totalMaxNormTimePref / model.variables().size()));
674            pw.println("Average minimum normalized time preference: "
675                    + sDoubleFormat.format(totalMinNormTimePref / model.variables().size()));
676            pw.println("Average normalized time preference,"
677                    + sDoubleFormat.format(totalNormTimePref / model.variables().size()));
678            pw.println("Average maximum room preferences: "
679                    + sDoubleFormat.format(1.0 * totalMaxRoomPref / nrOneOrMoreRoomVariables));
680            pw.println("Average minimum room preferences: "
681                    + sDoubleFormat.format(1.0 * totalMinRoomPref / nrOneOrMoreRoomVariables));
682            pw.println("Average room preferences," + sDoubleFormat.format(1.0 * totalRoomPref / nrOneOrMoreRoomVariables));
683            pw.println("Total number of students:" + students.size());
684            pwi.println("Number of students," + students.size());
685            pwi.println("Number of inevitable student conflicts," + nrInevitableStudentConflicts);
686            pw.println("Total amount of student enrollments: " + nrStudentEnrls);
687            pwi.println("Number of student enrollments," + nrStudentEnrls);
688            pw.println("Total amount of joined enrollments: " + nrJenrls);
689            pwi.println("Number of joint student enrollments," + nrJenrls);
690            pw.println("Average number of students: "
691                    + sDoubleFormat.format(1.0 * students.size() / model.variables().size()));
692            pw.println("Average number of enrollemnts (per student): "
693                    + sDoubleFormat.format(1.0 * nrStudentEnrls / students.size()));
694            pwi.println("Avg. number of classes per student,"
695                    + sDoubleFormat.format(1.0 * nrStudentEnrls / students.size()));
696            pwi.println("Avg. number of committed classes per student,"
697                    + sDoubleFormat.format(1.0 * totalCommitedPlacements / students.size()));
698            pw.println("Total amount of inevitable student conflicts: " + nrInevitableStudentConflicts + " ("
699                    + sDoubleFormat.format(100.0 * nrInevitableStudentConflicts / nrStudentEnrls) + "%)");
700            pw.println("Average number of meetings (per class): "
701                    + sDoubleFormat.format(1.0 * nrMeetings / model.variables().size()));
702            pw.println("Average number of hours per class: "
703                    + sDoubleFormat.format(1.0 * nrHalfHours / model.variables().size() / 12.0));
704            pwi.println("Avg. number of meetings per class,"
705                    + sDoubleFormat.format(1.0 * nrMeetings / model.variables().size()));
706            pwi.println("Avg. number of hours per class,"
707                    + sDoubleFormat.format(1.0 * nrHalfHours / model.variables().size() / 12.0));
708            int minRoomSize = Integer.MAX_VALUE;
709            int maxRoomSize = Integer.MIN_VALUE;
710            int nrDistancePairs = 0;
711            double maxRoomDistance = Double.MIN_VALUE;
712            double totalRoomDistance = 0.0;
713            int[] totalAvailableSlots = new int[sizeLimits.length];
714            int[] totalAvailableSeats = new int[sizeLimits.length];
715            int nrOfRooms = 0;
716            totalRoomSize = 0;
717            for (RoomConstraint rc : model.getRoomConstraints()) {
718                if (rc.variables().isEmpty()) continue;
719                nrOfRooms++;
720                minRoomSize = Math.min(minRoomSize, rc.getCapacity());
721                maxRoomSize = Math.max(maxRoomSize, rc.getCapacity());
722                for (int l = 0; l < sizeLimits.length; l++) {
723                    if (sizeLimits[l] <= rc.getCapacity()
724                            && (l + 1 == sizeLimits.length || rc.getCapacity() < sizeLimits[l + 1])) {
725                        nrRoomsOfSize[l]++;
726                        if (minRoomOfSize[l] == 0)
727                            minRoomOfSize[l] = rc.getCapacity();
728                        else
729                            minRoomOfSize[l] = Math.min(minRoomOfSize[l], rc.getCapacity());
730                        if (maxRoomOfSize[l] == 0)
731                            maxRoomOfSize[l] = rc.getCapacity();
732                        else
733                            maxRoomOfSize[l] = Math.max(maxRoomOfSize[l], rc.getCapacity());
734                    }
735                }
736                totalRoomSize += rc.getCapacity();
737                if (rc.getPosX() != null && rc.getPosY() != null) {
738                    for (RoomConstraint rc2 : model.getRoomConstraints()) {
739                        if (rc2.getResourceId().compareTo(rc.getResourceId()) > 0 && rc2.getPosX() != null && rc2.getPosY() != null) {
740                            double distance = ((TimetableModel)solution.getModel()).getDistanceMetric().getDistanceInMinutes(rc.getId(), rc.getPosX(), rc.getPosY(), rc2.getId(), rc2.getPosX(), rc2.getPosY());
741                            totalRoomDistance += distance;
742                            nrDistancePairs++;
743                            maxRoomDistance = Math.max(maxRoomDistance, distance);
744                        }
745                    }
746                }
747                for (int d = 0; d < Constants.NR_DAYS_WEEK; d++) {
748                    for (int t = Constants.DAY_SLOTS_FIRST; t <= Constants.DAY_SLOTS_LAST; t++) {
749                        if (rc.isAvailable(d * Constants.SLOTS_PER_DAY + t)) {
750                            for (int l = 0; l < sizeLimits.length; l++) {
751                                if (sizeLimits[l] <= rc.getCapacity()) {
752                                    totalAvailableSlots[l]++;
753                                    totalAvailableSeats[l] += rc.getCapacity();
754                                }
755                            }
756                        }
757                    }
758                }
759            }
760            pw.println("Total number of rooms: " + nrOfRooms);
761            pwi.println("Number of rooms," + nrOfRooms);
762            pw.println("Minimal room size: " + minRoomSize);
763            pw.println("Maximal room size: " + maxRoomSize);
764            pwi.println("Room size min/max," + minRoomSize + "/" + maxRoomSize);
765            pw.println("Average room size: "
766                    + sDoubleFormat.format(1.0 * totalRoomSize / model.getRoomConstraints().size()));
767            pw.println("Maximal distance between two rooms: " + sDoubleFormat.format(maxRoomDistance));
768            pw.println("Average distance between two rooms: "
769                    + sDoubleFormat.format(totalRoomDistance / nrDistancePairs));
770            pwi.println("Average distance between two rooms [min],"
771                    + sDoubleFormat.format(totalRoomDistance / nrDistancePairs));
772            pwi.println("Maximal distance between two rooms [min]," + sDoubleFormat.format(maxRoomDistance));
773            for (int l = 0; l < sizeLimits.length; l++) {// sizeLimits.length;l++) {
774                pwi.println("\"Room frequency (size>=" + sizeLimits[l] + ", used/avaiable times)\","
775                        + sDoubleFormat.format(100.0 * totalUsedSlots[l] / totalAvailableSlots[l]) + "%");
776                pwi.println("\"Room utilization (size>=" + sizeLimits[l] + ", used/available seats)\","
777                        + sDoubleFormat.format(100.0 * totalUsedSeats[l] / totalAvailableSeats[l]) + "%");
778                pwi.println("\"Number of rooms (size>=" + sizeLimits[l] + ")\"," + nrRoomsOfSize[l]);
779                pwi.println("\"Min/max room size (size>=" + sizeLimits[l] + ")\"," + minRoomOfSize[l] + "-"
780                        + maxRoomOfSize[l]);
781                // pwi.println("\"Room utilization (size>="+sizeLimits[l]+", minRoomSize)\","+sDoubleFormat.format(100.0*totalUsedSeats2[l]/totalAvailableSeats[l])+"%");
782            }
783            pw.println("Average hours available: "
784                    + sDoubleFormat.format(1.0 * totalAvailableSlots[0] / nrOfRooms / 12.0));
785            int totalInstructedClasses = 0;
786            for (InstructorConstraint ic : model.getInstructorConstraints()) {
787                totalInstructedClasses += ic.variables().size();
788            }
789            pw.println("Total number of instructors: " + model.getInstructorConstraints().size());
790            pwi.println("Number of instructors," + model.getInstructorConstraints().size());
791            pw.println("Total class-instructor assignments: " + totalInstructedClasses + " ("
792                    + sDoubleFormat.format(100.0 * totalInstructedClasses / model.variables().size()) + "%)");
793            pwi.println("Number of class-instructor assignments," + totalInstructedClasses);
794            pw.println("Average classes per instructor: "
795                    + sDoubleFormat.format(1.0 * totalInstructedClasses / model.getInstructorConstraints().size()));
796            pwi.println("Average classes per instructor,"
797                    + sDoubleFormat.format(1.0 * totalInstructedClasses / model.getInstructorConstraints().size()));
798            // pw.println("Average hours available: "+sDoubleFormat.format(1.0*totalAvailableSlots/model.getInstructorConstraints().size()/12.0));
799            // pwi.println("Instructor availability [h],"+sDoubleFormat.format(1.0*totalAvailableSlots/model.getInstructorConstraints().size()/12.0));
800            int nrGroupConstraints = model.getGroupConstraints().size() + model.getSpreadConstraints().size();
801            int nrHardGroupConstraints = 0;
802            int nrVarsInGroupConstraints = 0;
803            for (GroupConstraint gc : model.getGroupConstraints()) {
804                if (gc.isHard())
805                    nrHardGroupConstraints++;
806                nrVarsInGroupConstraints += gc.variables().size();
807            }
808            for (SpreadConstraint sc : model.getSpreadConstraints()) {
809                nrVarsInGroupConstraints += sc.variables().size();
810            }
811            pw.println("Total number of group constraints: " + nrGroupConstraints + " ("
812                    + sDoubleFormat.format(100.0 * nrGroupConstraints / model.variables().size()) + "%)");
813            // pwi.println("Number of group constraints,"+nrGroupConstraints);
814            pw.println("Total number of hard group constraints: " + nrHardGroupConstraints + " ("
815                    + sDoubleFormat.format(100.0 * nrHardGroupConstraints / model.variables().size()) + "%)");
816            // pwi.println("Number of hard group constraints,"+nrHardGroupConstraints);
817            pw.println("Average classes per group constraint: "
818                    + sDoubleFormat.format(1.0 * nrVarsInGroupConstraints / nrGroupConstraints));
819            // pwi.println("Average classes per group constraint,"+sDoubleFormat.format(1.0*nrVarsInGroupConstraints/nrGroupConstraints));
820            pwi.println("Avg. number distribution constraints per class,"
821                    + sDoubleFormat.format(1.0 * nrVarsInGroupConstraints / model.variables().size()));
822            pwi.println("Joint enrollment constraints," + model.getJenrlConstraints().size());
823            pw.flush();
824            pw.close();
825            pwi.flush();
826            pwi.close();
827        }
828    
829        public static void saveOutputCSV(Solution<Lecture, Placement> s, File file) {
830            try {
831                DecimalFormat dx = new DecimalFormat("000");
832                PrintWriter w = new PrintWriter(new FileWriter(file));
833                TimetableModel m = (TimetableModel) s.getModel();
834                int idx = 1;
835                w.println("000." + dx.format(idx++) + " Assigned variables," + m.assignedVariables().size());
836                w.println("000." + dx.format(idx++) + " Time [sec]," + sDoubleFormat.format(s.getBestTime()));
837                w.println("000." + dx.format(idx++) + " Hard student conflicts," + Math.round(m.getCriterion(StudentHardConflict.class).getValue()));
838                if (m.getProperties().getPropertyBoolean("General.UseDistanceConstraints", true))
839                    w.println("000." + dx.format(idx++) + " Distance student conf.," + Math.round(m.getCriterion(StudentDistanceConflict.class).getValue()));
840                w.println("000." + dx.format(idx++) + " Student conflicts," + Math.round(m.getCriterion(StudentConflict.class).getValue()));
841                w.println("000." + dx.format(idx++) + " Committed student conflicts," + Math.round(m.getCriterion(StudentCommittedConflict.class).getValue()));
842                w.println("000." + dx.format(idx++) + " All Student conflicts,"
843                        + Math.round(m.getCriterion(StudentConflict.class).getValue() + m.getCriterion(StudentCommittedConflict.class).getValue()));
844                w.println("000." + dx.format(idx++) + " Time preferences,"
845                        + sDoubleFormat.format( m.getCriterion(TimePreferences.class).getValue()));
846                w.println("000." + dx.format(idx++) + " Room preferences," + Math.round(m.getCriterion(RoomPreferences.class).getValue()));
847                w.println("000." + dx.format(idx++) + " Useless half-hours," + Math.round(m.getCriterion(UselessHalfHours.class).getValue()));
848                w.println("000." + dx.format(idx++) + " Broken time patterns," + Math.round(m.getCriterion(BrokenTimePatterns.class).getValue()));
849                w.println("000." + dx.format(idx++) + " Too big room," + Math.round(m.getCriterion(TooBigRooms.class).getValue()));
850                w.println("000." + dx.format(idx++) + " Distribution preferences," + sDoubleFormat.format(m.getCriterion(DistributionPreferences.class).getValue()));
851                if (m.getProperties().getPropertyBoolean("General.UseDistanceConstraints", true))
852                    w.println("000." + dx.format(idx++) + " Back-to-back instructor pref.," + Math.round(m.getCriterion(BackToBackInstructorPreferences.class).getValue()));
853                if (m.getProperties().getPropertyBoolean("General.DeptBalancing", true)) {
854                    w.println("000." + dx.format(idx++) + " Dept. balancing penalty," + sDoubleFormat.format(m.getCriterion(DepartmentBalancingPenalty.class).getValue()));
855                }
856                w.println("000." + dx.format(idx++) + " Same subpart balancing penalty," + sDoubleFormat.format(m.getCriterion(SameSubpartBalancingPenalty.class).getValue()));
857                if (m.getProperties().getPropertyBoolean("General.MPP", false)) {
858                    Map<String, Double> mppInfo = ((UniversalPerturbationsCounter)((Perturbations)m.getCriterion(Perturbations.class)).getPerturbationsCounter()).getCompactInfo(m, false, false);
859                    int pidx = 51;
860                    w.println("000." + dx.format(pidx++) + " Perturbation penalty," + sDoubleFormat.format(m.getCriterion(Perturbations.class).getValue()));
861                    w.println("000." + dx.format(pidx++) + " Additional perturbations," + m.perturbVariables().size());
862                    int nrPert = 0, nrStudentPert = 0;
863                    for (Lecture lecture : m.variables()) {
864                        if (lecture.getInitialAssignment() != null)
865                            continue;
866                        nrPert++;
867                        nrStudentPert += lecture.classLimit();
868                    }
869                    w.println("000." + dx.format(pidx++) + " Given perturbations," + nrPert);
870                    w.println("000." + dx.format(pidx++) + " Given student perturbations," + nrStudentPert);
871                    for (String key : new TreeSet<String>(mppInfo.keySet())) {
872                        Double value = mppInfo.get(key);
873                        w.println("000." + dx.format(pidx++) + " " + key + "," + sDoubleFormat.format(value));
874                    }
875                }
876                HashSet<Student> students = new HashSet<Student>();
877                int enrls = 0;
878                int minRoomPref = 0, maxRoomPref = 0;
879                int minGrPref = 0, maxGrPref = 0;
880                int minTimePref = 0, maxTimePref = 0;
881                int worstInstrPref = 0;
882                HashSet<Constraint<Lecture, Placement>> used = new HashSet<Constraint<Lecture, Placement>>();
883                for (Lecture lecture : m.variables()) {
884                    enrls += (lecture.students() == null ? 0 : lecture.students().size());
885                    students.addAll(lecture.students());
886    
887                    int[] minMaxRoomPref = lecture.getMinMaxRoomPreference();
888                    minRoomPref += minMaxRoomPref[0];
889                    maxRoomPref += minMaxRoomPref[1];
890    
891                    double[] minMaxTimePref = lecture.getMinMaxTimePreference();
892                    minTimePref += minMaxTimePref[0];
893                    maxTimePref += minMaxTimePref[1];
894                    for (Constraint<Lecture, Placement> c : lecture.constraints()) {
895                        if (!used.add(c))
896                            continue;
897    
898                        if (c instanceof InstructorConstraint) {
899                            InstructorConstraint ic = (InstructorConstraint) c;
900                            worstInstrPref += ic.getWorstPreference();
901                        }
902    
903                        if (c instanceof GroupConstraint) {
904                            GroupConstraint gc = (GroupConstraint) c;
905                            if (gc.isHard())
906                                continue;
907                            minGrPref -= Math.abs(gc.getPreference());
908                            maxGrPref += 0;
909                            // minGrPref += Math.min(gc.getPreference(), 0);
910                            // maxGrPref += Math.max(gc.getPreference(), 0);
911                        }
912                    }
913                }
914                int totalCommitedPlacements = 0;
915                for (Student student : students) {
916                    if (student.getCommitedPlacements() != null)
917                        totalCommitedPlacements += student.getCommitedPlacements().size();
918                }
919                HashMap<Long, List<Lecture>> subs = new HashMap<Long, List<Lecture>>();
920                for (Lecture lecture : m.variables()) {
921                    if (lecture.isCommitted() || lecture.getScheduler() == null)
922                        continue;
923                    List<Lecture> vars = subs.get(lecture.getScheduler());
924                    if (vars == null) {
925                        vars = new ArrayList<Lecture>();
926                        subs.put(lecture.getScheduler(), vars);
927                    }
928                    vars.add(lecture);
929                }
930                int bidx = 101;
931                w.println("000." + dx.format(bidx++) + " Assigned variables max," + m.variables().size());
932                w.println("000." + dx.format(bidx++) + " Student enrollments," + enrls);
933                w.println("000." + dx.format(bidx++) + " Student commited enrollments," + totalCommitedPlacements);
934                w.println("000." + dx.format(bidx++) + " All student enrollments," + (enrls + totalCommitedPlacements));
935                w.println("000." + dx.format(bidx++) + " Time preferences min," + minTimePref);
936                w.println("000." + dx.format(bidx++) + " Time preferences max," + maxTimePref);
937                w.println("000." + dx.format(bidx++) + " Room preferences min," + minRoomPref);
938                w.println("000." + dx.format(bidx++) + " Room preferences max," + maxRoomPref);
939                w.println("000."
940                        + dx.format(bidx++)
941                        + " Useless half-hours max,"
942                        + (Constants.sPreferenceLevelStronglyDiscouraged * m.getRoomConstraints().size()
943                                * Constants.SLOTS_PER_DAY_NO_EVENINGS * Constants.NR_DAYS_WEEK));
944                w.println("000." + dx.format(bidx++) + " Too big room max,"
945                        + (Constants.sPreferenceLevelStronglyDiscouraged * m.variables().size()));
946                w.println("000." + dx.format(bidx++) + " Distribution preferences min," + minGrPref);
947                w.println("000." + dx.format(bidx++) + " Distribution preferences max," + maxGrPref);
948                w.println("000." + dx.format(bidx++) + " Back-to-back instructor pref max," + worstInstrPref);
949                for (Long scheduler: new TreeSet<Long>(subs.keySet())) {
950                    List<Lecture> vars = subs.get(scheduler);
951                    idx = 001;
952                    bidx = 101;
953                    int nrAssg = 0;
954                    enrls = 0;
955                    int roomPref = 0;
956                    minRoomPref = 0;
957                    maxRoomPref = 0;
958                    double timePref = 0;
959                    minTimePref = 0;
960                    maxTimePref = 0;
961                    double grPref = 0;
962                    minGrPref = 0;
963                    maxGrPref = 0;
964                    long allSC = 0, hardSC = 0, distSC = 0;
965                    int instPref = 0;
966                    worstInstrPref = 0;
967                    int spreadPen = 0, deptSpreadPen = 0;
968                    int tooBigRooms = 0;
969                    int rcs = 0, uselessSlots = 0;
970                    used = new HashSet<Constraint<Lecture, Placement>>();
971                    for (Lecture lecture : vars) {
972                        if (lecture.isCommitted())
973                            continue;
974                        enrls += lecture.students().size();
975                        Placement placement = lecture.getAssignment();
976                        if (placement != null) {
977                            nrAssg++;
978                        }
979    
980                        int[] minMaxRoomPref = lecture.getMinMaxRoomPreference();
981                        minRoomPref += minMaxRoomPref[0];
982                        maxRoomPref += minMaxRoomPref[1];
983    
984                        double[] minMaxTimePref = lecture.getMinMaxTimePreference();
985                        minTimePref += minMaxTimePref[0];
986                        maxTimePref += minMaxTimePref[1];
987    
988                        if (placement != null) {
989                            roomPref += placement.getRoomPreference();
990                            timePref += placement.getTimeLocation().getNormalizedPreference();
991                            tooBigRooms += TooBigRooms.getTooBigRoomPreference(placement);
992                        }
993    
994                        for (Constraint<Lecture, Placement> c : lecture.constraints()) {
995                            if (!used.add(c))
996                                continue;
997    
998                            if (c instanceof InstructorConstraint) {
999                                InstructorConstraint ic = (InstructorConstraint) c;
1000                                instPref += ic.getPreference();
1001                                worstInstrPref += ic.getWorstPreference();
1002                            }
1003    
1004                            if (c instanceof DepartmentSpreadConstraint) {
1005                                DepartmentSpreadConstraint dsc = (DepartmentSpreadConstraint) c;
1006                                deptSpreadPen += dsc.getPenalty();
1007                            } else if (c instanceof SpreadConstraint) {
1008                                SpreadConstraint sc = (SpreadConstraint) c;
1009                                spreadPen += sc.getPenalty();
1010                            }
1011    
1012                            if (c instanceof GroupConstraint) {
1013                                GroupConstraint gc = (GroupConstraint) c;
1014                                if (gc.isHard())
1015                                    continue;
1016                                minGrPref -= Math.abs(gc.getPreference());
1017                                maxGrPref += 0;
1018                                grPref += Math.min(0, gc.getCurrentPreference());
1019                                // minGrPref += Math.min(gc.getPreference(), 0);
1020                                // maxGrPref += Math.max(gc.getPreference(), 0);
1021                                // grPref += gc.getCurrentPreference();
1022                            }
1023    
1024                            if (c instanceof JenrlConstraint) {
1025                                JenrlConstraint jc = (JenrlConstraint) c;
1026                                if (!jc.isInConflict() || !jc.isOfTheSameProblem())
1027                                    continue;
1028                                Lecture l1 = jc.first();
1029                                Lecture l2 = jc.second();
1030                                allSC += jc.getJenrl();
1031                                if (l1.areStudentConflictsHard(l2))
1032                                    hardSC += jc.getJenrl();
1033                                Placement p1 = l1.getAssignment();
1034                                Placement p2 = l2.getAssignment();
1035                                if (!p1.getTimeLocation().hasIntersection(p2.getTimeLocation()))
1036                                    distSC += jc.getJenrl();
1037                            }
1038    
1039                            if (c instanceof RoomConstraint) {
1040                                RoomConstraint rc = (RoomConstraint) c;
1041                                uselessSlots += UselessHalfHours.countUselessSlotsHalfHours(rc) + BrokenTimePatterns.countUselessSlotsBrokenTimePatterns(rc);
1042                                rcs++;
1043                            }
1044                        }
1045                    }
1046                    w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Assigned variables," + nrAssg);
1047                    w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Assigned variables max," + vars.size());
1048                    w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Hard student conflicts," + hardSC);
1049                    w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Student enrollments," + enrls);
1050                    if (m.getProperties().getPropertyBoolean("General.UseDistanceConstraints", true))
1051                        w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Distance student conf.," + distSC);
1052                    w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Student conflicts," + allSC);
1053                    w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Time preferences," + timePref);
1054                    w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Time preferences min," + minTimePref);
1055                    w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Time preferences max," + maxTimePref);
1056                    w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Room preferences," + roomPref);
1057                    w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Room preferences min," + minRoomPref);
1058                    w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Room preferences max," + maxRoomPref);
1059                    w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Useless half-hours," + uselessSlots);
1060                    w
1061                            .println(dx.format(scheduler)
1062                                    + "."
1063                                    + dx.format(bidx++)
1064                                    + " Useless half-hours max,"
1065                                    + (Constants.sPreferenceLevelStronglyDiscouraged * rcs
1066                                            * Constants.SLOTS_PER_DAY_NO_EVENINGS * Constants.NR_DAYS_WEEK));
1067                    w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Too big room," + tooBigRooms);
1068                    w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Too big room max,"
1069                            + (Constants.sPreferenceLevelStronglyDiscouraged * vars.size()));
1070                    w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Distribution preferences," + grPref);
1071                    w
1072                            .println(dx.format(scheduler) + "." + dx.format(bidx++) + " Distribution preferences min,"
1073                                    + minGrPref);
1074                    w
1075                            .println(dx.format(scheduler) + "." + dx.format(bidx++) + " Distribution preferences max,"
1076                                    + maxGrPref);
1077                    if (m.getProperties().getPropertyBoolean("General.UseDistanceConstraints", true))
1078                        w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Back-to-back instructor pref,"
1079                                + instPref);
1080                    w.println(dx.format(scheduler) + "." + dx.format(bidx++) + " Back-to-back instructor pref max,"
1081                            + worstInstrPref);
1082                    if (m.getProperties().getPropertyBoolean("General.DeptBalancing", true)) {
1083                        w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Department balancing penalty,"
1084                                + sDoubleFormat.format((deptSpreadPen) / 12.0));
1085                    }
1086                    w.println(dx.format(scheduler) + "." + dx.format(idx++) + " Same subpart balancing penalty,"
1087                            + sDoubleFormat.format((spreadPen) / 12.0));
1088                }
1089                w.flush();
1090                w.close();
1091            } catch (java.io.IOException io) {
1092                sLogger.error(io.getMessage(), io);
1093            }
1094        }
1095    }