    001package net.sf.cpsolver.exam;
    002
    003import java.io.File;
    004import java.io.FileInputStream;
    005import java.io.FileOutputStream;
    006import java.io.FileWriter;
    007import java.io.IOException;
    008import java.io.PrintWriter;
    009import java.text.DecimalFormat;
    010import java.util.Collection;
    011import java.util.Map;
    012
    013import net.sf.cpsolver.exam.criteria.DistributionPenalty;
    014import net.sf.cpsolver.exam.criteria.ExamRotationPenalty;
    015import net.sf.cpsolver.exam.criteria.InstructorBackToBackConflicts;
    016import net.sf.cpsolver.exam.criteria.InstructorDirectConflicts;
    017import net.sf.cpsolver.exam.criteria.InstructorDistanceBackToBackConflicts;
    018import net.sf.cpsolver.exam.criteria.InstructorMoreThan2ADayConflicts;
    019import net.sf.cpsolver.exam.criteria.InstructorNotAvailableConflicts;
    020import net.sf.cpsolver.exam.criteria.LargeExamsPenalty;
    021import net.sf.cpsolver.exam.criteria.PeriodIndexPenalty;
    022import net.sf.cpsolver.exam.criteria.PeriodPenalty;
    023import net.sf.cpsolver.exam.criteria.PeriodSizePenalty;
    024import net.sf.cpsolver.exam.criteria.PerturbationPenalty;
    025import net.sf.cpsolver.exam.criteria.RoomPenalty;
    026import net.sf.cpsolver.exam.criteria.RoomPerturbationPenalty;
    027import net.sf.cpsolver.exam.criteria.RoomSizePenalty;
    028import net.sf.cpsolver.exam.criteria.RoomSplitDistancePenalty;
    029import net.sf.cpsolver.exam.criteria.RoomSplitPenalty;
    030import net.sf.cpsolver.exam.criteria.StudentBackToBackConflicts;
    031import net.sf.cpsolver.exam.criteria.StudentDirectConflicts;
    032import net.sf.cpsolver.exam.criteria.StudentDistanceBackToBackConflicts;
    033import net.sf.cpsolver.exam.criteria.StudentMoreThan2ADayConflicts;
    034import net.sf.cpsolver.exam.criteria.StudentNotAvailableConflicts;
    035import net.sf.cpsolver.exam.criteria.additional.DistanceToStronglyPreferredRoom;
    036import net.sf.cpsolver.exam.criteria.additional.DistributionViolation;
    037import net.sf.cpsolver.exam.criteria.additional.PeriodViolation;
    038import net.sf.cpsolver.exam.criteria.additional.RoomViolation;
    039import net.sf.cpsolver.exam.model.Exam;
    040import net.sf.cpsolver.exam.model.ExamInstructor;
    041import net.sf.cpsolver.exam.model.ExamModel;
    042import net.sf.cpsolver.exam.model.ExamPlacement;
    043import net.sf.cpsolver.exam.model.ExamStudent;
    044import net.sf.cpsolver.exam.reports.ExamAssignments;
    045import net.sf.cpsolver.exam.reports.ExamCourseSectionAssignments;
    046import net.sf.cpsolver.exam.reports.ExamInstructorConflicts;
    047import net.sf.cpsolver.exam.reports.ExamNbrMeetingsPerDay;
    048import net.sf.cpsolver.exam.reports.ExamPeriodUsage;
    049import net.sf.cpsolver.exam.reports.ExamRoomSchedule;
    050import net.sf.cpsolver.exam.reports.ExamRoomSplit;
    051import net.sf.cpsolver.exam.reports.ExamStudentBackToBackConflicts;
    052import net.sf.cpsolver.exam.reports.ExamStudentConflicts;
    053import net.sf.cpsolver.exam.reports.ExamStudentConflictsBySectionCourse;
    054import net.sf.cpsolver.exam.reports.ExamStudentConflictsPerExam;
    055import net.sf.cpsolver.exam.reports.ExamStudentDirectConflicts;
    056import net.sf.cpsolver.exam.reports.ExamStudentMoreTwoADay;
    057import net.sf.cpsolver.exam.split.ExamSplitter;
    058import net.sf.cpsolver.ifs.solution.Solution;
    059import net.sf.cpsolver.ifs.solution.SolutionListener;
    060import net.sf.cpsolver.ifs.solver.Solver;
    061import net.sf.cpsolver.ifs.util.DataProperties;
    062import net.sf.cpsolver.ifs.util.Progress;
    063import net.sf.cpsolver.ifs.util.ToolBox;
    064
    065import org.apache.log4j.ConsoleAppender;
    066import org.apache.log4j.FileAppender;
    067import org.apache.log4j.Level;
    068import org.apache.log4j.Logger;
    069import org.apache.log4j.PatternLayout;
    070import org.dom4j.Document;
    071import org.dom4j.io.OutputFormat;
    072import org.dom4j.io.SAXReader;
    073import org.dom4j.io.XMLWriter;
    074
    075/**
    076 * An examination timetabling test program. The following steps are performed:
    077 * <ul>
    078 * <li>Input properties are loaded
    079 * <li>Input problem is loaded (General.Input property)
    080 * <li>Problem is solved (using the given properties)
    081 * <li>Solution is save (General.OutputFile property)
    082 * </ul>
    083 * <br>
    084 * <br>
    085 * Usage: <code>
    086 * &nbsp;&nbsp;&nbsp;java -Xmx1024m -jar examtt-1.1.jar exam.properties input.xml output.xml
    087 * </code> <br>
    088 * <br>
    089 *
    090 * @version ExamTT 1.2 (Examination Timetabling)<br>
    091 *          Copyright (C) 2007 - 2010 Tomáš Müller<br>
    092 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    093 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    094 * <br>
    095 *          This library is free software; you can redistribute it and/or modify
    096 *          it under the terms of the GNU Lesser General Public License as
    097 *          published by the Free Software Foundation; either version 3 of the
    098 *          License, or (at your option) any later version. <br>
    099 * <br>
    100 *          This library is distributed in the hope that it will be useful, but
    101 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    102 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    103 *          Lesser General Public License for more details. <br>
    104 * <br>
    105 *          You should have received a copy of the GNU Lesser General Public
    106 *          License along with this library; if not see
    107 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    108 */
    109public class Test {
    110    private static org.apache.log4j.Logger sLog = org.apache.log4j.Logger.getLogger(Test.class);
    111
    112    /**
    113     * Setup log4j logging
    114     *
    115     * @param logFile
    116     *            log file
    117     * @param debug
    118     *            true if debug messages should be logged (use -Ddebug=true to
    119     *            enable debug message)
    120     */
    121    public static void setupLogging(File logFile, boolean debug) {
    122        Logger root = Logger.getRootLogger();
    123        ConsoleAppender console = new ConsoleAppender(new PatternLayout("%m%n"));// %-5p
    124                                                                                 // %c{1}>
    125                                                                                 // %m%n
    126        console.setThreshold(Level.INFO);
    127        root.addAppender(console);
    128        if (logFile != null) {
    129            try {
    130                FileAppender file = new FileAppender(new PatternLayout(
    131                        "%d{dd-MMM-yy HH:mm:ss.SSS} [%t] %-5p %c{2}> %m%n"), logFile.getPath(), false);
    132                file.setThreshold(Level.DEBUG);
    133                root.addAppender(file);
    134            } catch (IOException e) {
    135                sLog.fatal("Unable to configure logging, reason: " + e.getMessage(), e);
    136            }
    137        }
    138        if (!debug)
    139            root.setLevel(Level.INFO);
    140    }
    141
    142    /** Generate exam reports */
    143    public static void createReports(ExamModel model, File outDir, String outName) throws IOException {
    144        new ExamAssignments(model).report().save(new File(outDir, outName + ".schdex.csv"));
    145
    146        new ExamCourseSectionAssignments(model).report().save(new File(outDir, outName + ".schdcs.csv"));
    147
    148        new ExamStudentConflicts(model).report().save(new File(outDir, outName + ".sconf.csv"));
    149
    150        new ExamInstructorConflicts(model).report().save(new File(outDir, outName + ".iconf.csv"));
    151
    152        new ExamStudentConflictsPerExam(model).report().save(new File(outDir, outName + ".sconfex.csv"));
    153
    154        new ExamStudentDirectConflicts(model).report().save(new File(outDir, outName + ".sdir.csv"));
    155
    156        new ExamStudentBackToBackConflicts(model).report().save(new File(outDir, outName + ".sbtb.csv"));
    157
    158        new ExamStudentMoreTwoADay(model).report().save(new File(outDir, outName + ".sm2d.csv"));
    159
    160        new ExamPeriodUsage(model).report().save(new File(outDir, outName + ".per.csv"));
    161
    162        new ExamRoomSchedule(model).report().save(new File(outDir, outName + ".schdr.csv"));
    163
    164        new ExamRoomSplit(model).report().save(new File(outDir, outName + ".rsplit.csv"));
    165
    166        new ExamNbrMeetingsPerDay(model).report().save(new File(outDir, outName + ".distmpd.csv"));
    167
    168        new ExamStudentConflictsBySectionCourse(model).report().save(new File(outDir, outName + ".sconfcs.csv"));
    169    }
    170
    171    public static class ShutdownHook extends Thread {
    172        Solver<Exam, ExamPlacement> iSolver = null;
    173
    174        public ShutdownHook(Solver<Exam, ExamPlacement> solver) {
    175            setName("ShutdownHook");
    176            iSolver = solver;
    177        }
    178
    179        @Override
    180        public void run() {
    181            try {
    182                if (iSolver.isRunning())
    183                    iSolver.stopSolver();
    184                Solution<Exam, ExamPlacement> solution = iSolver.lastSolution();
    185                Progress.removeInstance(solution.getModel());
    186                if (solution.getBestInfo() == null) {
    187                    sLog.error("No best solution found.");
    188                } else
    189                    solution.restoreBest();
    190
    191                sLog.info("Best solution:" + ToolBox.dict2string(solution.getExtendedInfo(), 1));
    192
    193                sLog.info("Best solution found after " + solution.getBestTime() + " seconds ("
    194                        + solution.getBestIteration() + " iterations).");
    195                sLog.info("Number of assigned variables is " + solution.getModel().nrAssignedVariables());
    196                sLog.info("Total value of the solution is " + solution.getModel().getTotalValue());
    197
    198                File outFile = new File(iSolver.getProperties().getProperty("General.OutputFile",
    199                        iSolver.getProperties().getProperty("General.Output") + File.separator + "solution.xml"));
    200                FileOutputStream fos = new FileOutputStream(outFile);
    201                (new XMLWriter(fos, OutputFormat.createPrettyPrint())).write(((ExamModel) solution.getModel()).save());
    202                fos.flush();
    203                fos.close();
    204
    205                if ("true".equals(System.getProperty("reports", "false")))
    206                    createReports((ExamModel) solution.getModel(), outFile.getParentFile(), outFile.getName()
    207                            .substring(0, outFile.getName().lastIndexOf('.')));
    208
    209                String baseName = new File(iSolver.getProperties().getProperty("General.Input")).getName();
    210                if (baseName.indexOf('.') > 0)
    211                    baseName = baseName.substring(0, baseName.lastIndexOf('.'));
    212                addCSVLine(new File(outFile.getParentFile(), baseName + ".csv"), outFile.getName(), iSolver.getProperties().getProperty("General.Config"), solution);
    213
    214            } catch (Exception e) {
    215                e.printStackTrace();
    216            }
    217        }
    218    }
    219
    220    private static void addCSVLine(File file, String instance, String config, Solution<Exam, ExamPlacement> solution) {
    221        try {
    222            ExamModel model = (ExamModel) solution.getModel();
    223            boolean ex = file.exists();
    224            PrintWriter pw = new PrintWriter(new FileWriter(file, true));
    225            boolean mpp = ((PerturbationPenalty)model.getCriterion(PerturbationPenalty.class)).isMPP();
    226            int largeSize = ((LargeExamsPenalty)model.getCriterion(LargeExamsPenalty.class)).getLargeSize();
    227            RoomSplitDistancePenalty splitDistance = (RoomSplitDistancePenalty)model.getCriterion(RoomSplitDistancePenalty.class);
    228            ExamSplitter splitter = (ExamSplitter)model.getCriterion(ExamSplitter.class);
    229            PeriodViolation violPer = (PeriodViolation)model.getCriterion(PeriodViolation.class);
    230            RoomViolation violRoom = (RoomViolation)model.getCriterion(RoomViolation.class);
    231            DistributionViolation violDist = (DistributionViolation)model.getCriterion(DistributionViolation.class);
    232            DistanceToStronglyPreferredRoom distStrPref = (DistanceToStronglyPreferredRoom)model.getCriterion(DistanceToStronglyPreferredRoom.class);
    233            ExamRotationPenalty rotation = (ExamRotationPenalty)model.getCriterion(ExamRotationPenalty.class);
    234            DecimalFormat df = new DecimalFormat("0.00");
    235            if (!ex) {
    236                pw.println("SEED"
    237                        + ",NA,DC,M2D,BTB" + (model.getBackToBackDistance() < 0 ? "" : ",dBTB")
    238                        + ",iNA,iDC,iM2D,iBTB" + (model.getBackToBackDistance() < 0 ? "" : ",diBTB")
    239                        + ",PP,RP,DP"
    240                        + ",PI,@P,PS" // Period Index, Rotation Penalty, Period Size
    241                        + ",RSz,RSp,RD" // Room Size, Room Split, Room Split Distance
    242                        + (largeSize >= 0 ? ",LP" : "")
    243                        + (mpp ? ",IP,IRP" : "")
    244                        + (distStrPref == null ? "" : ",@D")
    245                        + (splitter == null ? "" : ",XX")
    246                        + (violPer == null ? "" : ",!P")
    247                        + (violRoom == null ? "" : ",!R")
    248                        + (violDist == null ? "" : ",!D")
    249                        + ",INSTANCE,CONFIG");
    250                int nrStudentExams = 0;
    251                for (ExamStudent student : model.getStudents()) {
    252                    nrStudentExams += student.variables().size();
    253                }
    254                int nrInstructorExams = 0;
    255                for (ExamInstructor instructor : model.getInstructors()) {
    256                    nrInstructorExams += instructor.variables().size();
    257                }
    258                pw.println("MIN"
    259                        + ",#EX,#RM,#PER," + (model.getBackToBackDistance() < 0 ? "" : ",")
    260                        + ",#STD,#STDX,#INS,#INSX" + (model.getBackToBackDistance() < 0 ? "" : ",")
    261                        + "," + model.getCriterion(PeriodPenalty.class).getBounds()[0]
    262                        + "," + model.getCriterion(RoomPenalty.class).getBounds()[0]
    263                        + "," + model.getCriterion(DistributionPenalty.class).getBounds()[0]
    264                        + ",," + df.format(rotation.averagePeriod()) + ","
    265                        + ",,,"
    266                        + (largeSize >= 0 ? ",0" : "")
    267                        + (mpp ? ",," : "")
    268                        + (distStrPref == null ? "" : ",")
    269                        + (splitter == null ? "" : ",")
    270                        + (violPer == null ? "" : ",")
    271                        + (violRoom == null ? "" : ",")
    272                        + (violDist == null ? "" : ",")
    273                        + ",,");
    274                pw.println("MAX"
    275                        + "," + model.variables().size() + "," + model.getRooms().size() + "," + model.getPeriods().size() + "," + (model.getBackToBackDistance() < 0 ? "" : ",")
    276                        + "," + model.getStudents().size() + "," + nrStudentExams + "," + model.getInstructors().size() + "," + nrInstructorExams + (model.getBackToBackDistance() < 0 ? "" : ",")
    277                        + "," + model.getCriterion(PeriodPenalty.class).getBounds()[1]
    278                        + "," + model.getCriterion(RoomPenalty.class).getBounds()[1]
    279                        + "," + model.getCriterion(DistributionPenalty.class).getBounds()[1]
    280                        + ",," + rotation.nrAssignedExamsWithAvgPeriod() + ","
    281                        + ",,,"
    282                        + (largeSize >= 0 ? "," + model.getCriterion(LargeExamsPenalty.class).getBounds()[1] : "")
    283                        + (mpp ? ",," : "")
    284                        + (distStrPref == null ? "" : ",")
    285                        + (splitter == null ? "" : ",")
    286                        + (violPer == null ? "" : "," + model.getCriterion(PeriodViolation.class).getBounds()[1])
    287                        + (violRoom == null ? "" : "," + model.getCriterion(RoomViolation.class).getBounds()[1])
    288                        + (violDist == null ? "" : "," + model.getCriterion(DistributionViolation.class).getBounds()[1])
    289                        + ",,");
    290            }
    291            pw.println(ToolBox.getSeed()
    292                    + "," + model.getCriterion(StudentNotAvailableConflicts.class).getValue()
    293                    + "," + model.getCriterion(StudentDirectConflicts.class).getValue()
    294                    + "," + model.getCriterion(StudentMoreThan2ADayConflicts.class).getValue()
    295                    + "," + model.getCriterion(StudentBackToBackConflicts.class).getValue()
    296                    + (model.getBackToBackDistance() < 0 ? "" : "," + model.getCriterion(StudentDistanceBackToBackConflicts.class).getValue())
    297                    + "," + model.getCriterion(InstructorNotAvailableConflicts.class).getValue()
    298                    + "," + model.getCriterion(InstructorDirectConflicts.class).getValue()
    299                    + "," + model.getCriterion(InstructorMoreThan2ADayConflicts.class).getValue()
    300                    + "," + model.getCriterion(InstructorBackToBackConflicts.class).getValue()
    301                    + (model.getBackToBackDistance() < 0 ? "" : "," + model.getCriterion(InstructorDistanceBackToBackConflicts.class).getValue())
    302                    + "," + model.getCriterion(PeriodPenalty.class).getValue()
    303                    + "," + model.getCriterion(RoomPenalty.class).getValue()
    304                    + "," + model.getCriterion(DistributionPenalty.class).getValue()
    305                    + "," + df.format(model.getCriterion(PeriodIndexPenalty.class).getValue() / model.nrAssignedVariables())
    306                    + "," + df.format(Math.sqrt(rotation.getValue() / rotation.nrAssignedExamsWithAvgPeriod()) - 1)
    307                    + "," + df.format(model.getCriterion(PeriodSizePenalty.class).getValue() / model.nrAssignedVariables())
    308                    + "," + df.format(model.getCriterion(RoomSizePenalty.class).getValue() / model.nrAssignedVariables())
    309                    + "," + model.getCriterion(RoomSplitPenalty.class).getValue()
    310                    + "," + df.format(splitDistance.nrRoomSplits() <= 0 ? 0.0 : splitDistance.getValue() / splitDistance.nrRoomSplits())
    311                    + (largeSize >= 0 ? "," + model.getCriterion(LargeExamsPenalty.class).getValue() : "")
    312                    + (mpp ? "," + df.format(model.getCriterion(PerturbationPenalty.class).getValue() / model.nrAssignedVariables())
    313                           + "," + df.format(model.getCriterion(RoomPerturbationPenalty.class).getValue() / model.nrAssignedVariables()): "")
    314                    + (distStrPref == null ? "" : "," + df.format(distStrPref.getValue() / model.nrAssignedVariables()))
    315                    + (splitter == null ? "" : "," + df.format(splitter.getValue()))
    316                    + (violPer == null ? "" : "," + df.format(violPer.getValue()))
    317                    + (violRoom == null ? "" : "," + df.format(violRoom.getValue()))
    318                    + (violDist == null ? "" : "," + df.format(violDist.getValue()))
    319                    + "," + instance + "," + config);
    320            pw.flush();
    321            pw.close();
    322        } catch (Exception e) {
    323            sLog.error("Unable to add CSV line to " + file, e);
    324        }
    325    }
    326
    327    /**
    328     * Main program
    329     *
    330     * @param args
    331     *            problem property file, input file (optional, may be set by
    332     *            General.Input property), output file (optional, may be set by
    333     *            General.OutputFile property)
    334     */
    335    public static void main(String[] args) {
    336        try {
    337            DataProperties cfg = new DataProperties();
    338            cfg.setProperty("Termination.StopWhenComplete", "false");
    339            cfg.setProperty("Termination.TimeOut", "1800");
    340            cfg.setProperty("General.SaveBestUnassigned", "-1");
    341            cfg.setProperty("Neighbour.Class", "net.sf.cpsolver.exam.heuristics.ExamNeighbourSelection");
    342            if (args.length >= 1) {
    343                cfg.load(new FileInputStream(args[0]));
    344                cfg.setProperty("General.Config", new File(args[0]).getName());
    345            }
    346            cfg.putAll(System.getProperties());
    347
    348            File inputFile = new File("c:\\test\\exam\\exam1070.xml");
    349            if (args.length >= 2) {
    350                inputFile = new File(args[1]);
    351            }
    352            ToolBox.setSeed(cfg.getPropertyLong("General.Seed", Math.round(Long.MAX_VALUE * Math.random())));
    353
    354            cfg.setProperty("General.Input", inputFile.toString());
    355
    356            String outName = inputFile.getName();
    357            if (outName.indexOf('.') >= 0)
    358                outName = outName.substring(0, outName.lastIndexOf('.')) + "s.xml";
    359            File outFile = new File(inputFile.getParentFile(), outName);
    360            if (args.length >= 3) {
    361                outFile = new File(args[2]);
    362                if (outFile.exists() && outFile.isDirectory())
    363                    outFile = new File(outFile, outName);
    364                if (!outFile.exists() && !outFile.getName().endsWith(".xml"))
    365                    outFile = new File(outFile, outName);
    366            }
    367            if (outFile.getParentFile() != null)
    368                outFile.getParentFile().mkdirs();
    369            cfg.setProperty("General.OutputFile", outFile.toString());
    370            cfg.setProperty("General.Output", outFile.getParent());
    371
    372            String logName = outFile.getName();
    373            if (logName.indexOf('.') >= 0)
    374                logName = logName.substring(0, logName.lastIndexOf('.')) + ".log";
    375            setupLogging(new File(outFile.getParent(), logName), "true".equals(System.getProperty("debug", "false")));
    376
    377            ExamModel model = new ExamModel(cfg);
    378
    379            Document document = (new SAXReader()).read(new File(cfg.getProperty("General.Input")));
    380            model.load(document);
    381
    382            Solver<Exam, ExamPlacement> solver = new Solver<Exam, ExamPlacement>(cfg);
    383            solver.setInitalSolution(new Solution<Exam, ExamPlacement>(model));
    384
    385            solver.currentSolution().addSolutionListener(new SolutionListener<Exam, ExamPlacement>() {
    386                @Override
    387                public void solutionUpdated(Solution<Exam, ExamPlacement> solution) {
    388                }
    389
    390                @Override
    391                public void getInfo(Solution<Exam, ExamPlacement> solution, Map<String, String> info) {
    392                }
    393
    394                @Override
    395                public void getInfo(Solution<Exam, ExamPlacement> solution, Map<String, String> info,
    396                        Collection<Exam> variables) {
    397                }
    398
    399                @Override
    400                public void bestCleared(Solution<Exam, ExamPlacement> solution) {
    401                }
    402
    403                @Override
    404                public void bestSaved(Solution<Exam, ExamPlacement> solution) {
    405                    ExamModel m = (ExamModel) solution.getModel();
    406                    if (sLog.isInfoEnabled()) {
    407                        sLog.info("**BEST[" + solution.getIteration() + "]** "
    408                                + (m.nrUnassignedVariables() > 0 ? "V:" + m.nrAssignedVariables() + "/" + m.variables().size() + " - " : "") +
    409                                "T:" + new DecimalFormat("0.00").format(m.getTotalValue()) +
    410                                " " + m);
    411                    }
    412                }
    413
    414                @Override
    415                public void bestRestored(Solution<Exam, ExamPlacement> solution) {
    416                }
    417            });
    418
    419            Runtime.getRuntime().addShutdownHook(new ShutdownHook(solver));
    420
    421            solver.start();
    422            try {
    423                solver.getSolverThread().join();
    424            } catch (InterruptedException e) {
    425            }
    426        } catch (Exception e) {
    427            e.printStackTrace();
    428        }
    429    }
    430}