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