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