001    package net.sf.cpsolver.exam;
002    
003    import java.io.File;
004    import java.io.FileInputStream;
005    import java.io.FileOutputStream;
006    import java.io.FileWriter;
007    import java.io.IOException;
008    import java.io.PrintWriter;
009    import java.text.DecimalFormat;
010    import java.util.Collection;
011    import java.util.Map;
012    
013    import net.sf.cpsolver.exam.criteria.DistributionPenalty;
014    import net.sf.cpsolver.exam.criteria.ExamRotationPenalty;
015    import net.sf.cpsolver.exam.criteria.InstructorBackToBackConflicts;
016    import net.sf.cpsolver.exam.criteria.InstructorDirectConflicts;
017    import net.sf.cpsolver.exam.criteria.InstructorDistanceBackToBackConflicts;
018    import net.sf.cpsolver.exam.criteria.InstructorMoreThan2ADayConflicts;
019    import net.sf.cpsolver.exam.criteria.InstructorNotAvailableConflicts;
020    import net.sf.cpsolver.exam.criteria.LargeExamsPenalty;
021    import net.sf.cpsolver.exam.criteria.PeriodIndexPenalty;
022    import net.sf.cpsolver.exam.criteria.PeriodPenalty;
023    import net.sf.cpsolver.exam.criteria.PeriodSizePenalty;
024    import net.sf.cpsolver.exam.criteria.PerturbationPenalty;
025    import net.sf.cpsolver.exam.criteria.RoomPenalty;
026    import net.sf.cpsolver.exam.criteria.RoomPerturbationPenalty;
027    import net.sf.cpsolver.exam.criteria.RoomSizePenalty;
028    import net.sf.cpsolver.exam.criteria.RoomSplitDistancePenalty;
029    import net.sf.cpsolver.exam.criteria.RoomSplitPenalty;
030    import net.sf.cpsolver.exam.criteria.StudentBackToBackConflicts;
031    import net.sf.cpsolver.exam.criteria.StudentDirectConflicts;
032    import net.sf.cpsolver.exam.criteria.StudentDistanceBackToBackConflicts;
033    import net.sf.cpsolver.exam.criteria.StudentMoreThan2ADayConflicts;
034    import net.sf.cpsolver.exam.criteria.StudentNotAvailableConflicts;
035    import net.sf.cpsolver.exam.criteria.additional.DistanceToStronglyPreferredRoom;
036    import net.sf.cpsolver.exam.criteria.additional.DistributionViolation;
037    import net.sf.cpsolver.exam.criteria.additional.PeriodViolation;
038    import net.sf.cpsolver.exam.criteria.additional.RoomViolation;
039    import net.sf.cpsolver.exam.model.Exam;
040    import net.sf.cpsolver.exam.model.ExamInstructor;
041    import net.sf.cpsolver.exam.model.ExamModel;
042    import net.sf.cpsolver.exam.model.ExamPlacement;
043    import net.sf.cpsolver.exam.model.ExamStudent;
044    import net.sf.cpsolver.exam.reports.ExamAssignments;
045    import net.sf.cpsolver.exam.reports.ExamCourseSectionAssignments;
046    import net.sf.cpsolver.exam.reports.ExamInstructorConflicts;
047    import net.sf.cpsolver.exam.reports.ExamNbrMeetingsPerDay;
048    import net.sf.cpsolver.exam.reports.ExamPeriodUsage;
049    import net.sf.cpsolver.exam.reports.ExamRoomSchedule;
050    import net.sf.cpsolver.exam.reports.ExamRoomSplit;
051    import net.sf.cpsolver.exam.reports.ExamStudentBackToBackConflicts;
052    import net.sf.cpsolver.exam.reports.ExamStudentConflicts;
053    import net.sf.cpsolver.exam.reports.ExamStudentConflictsBySectionCourse;
054    import net.sf.cpsolver.exam.reports.ExamStudentConflictsPerExam;
055    import net.sf.cpsolver.exam.reports.ExamStudentDirectConflicts;
056    import net.sf.cpsolver.exam.reports.ExamStudentMoreTwoADay;
057    import net.sf.cpsolver.exam.split.ExamSplitter;
058    import net.sf.cpsolver.ifs.solution.Solution;
059    import net.sf.cpsolver.ifs.solution.SolutionListener;
060    import net.sf.cpsolver.ifs.solver.Solver;
061    import net.sf.cpsolver.ifs.util.DataProperties;
062    import net.sf.cpsolver.ifs.util.Progress;
063    import net.sf.cpsolver.ifs.util.ToolBox;
064    
065    import org.apache.log4j.ConsoleAppender;
066    import org.apache.log4j.FileAppender;
067    import org.apache.log4j.Level;
068    import org.apache.log4j.Logger;
069    import org.apache.log4j.PatternLayout;
070    import org.dom4j.Document;
071    import org.dom4j.io.OutputFormat;
072    import org.dom4j.io.SAXReader;
073    import 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 Tomas Muller<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     */
109    public 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    }