    001package org.cpsolver.instructor;
    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.Iterator;
    012import java.util.Map;
    013
    014import org.apache.logging.log4j.Logger;
    015import org.cpsolver.coursett.model.TimeLocation;
    016import org.cpsolver.ifs.assignment.Assignment;
    017import org.cpsolver.ifs.assignment.DefaultParallelAssignment;
    018import org.cpsolver.ifs.assignment.DefaultSingleAssignment;
    019import org.cpsolver.ifs.extension.ConflictStatistics;
    020import org.cpsolver.ifs.extension.Extension;
    021import org.cpsolver.ifs.model.Model;
    022import org.cpsolver.ifs.solution.Solution;
    023import org.cpsolver.ifs.solution.SolutionListener;
    024import org.cpsolver.ifs.solver.ParallelSolver;
    025import org.cpsolver.ifs.solver.Solver;
    026import org.cpsolver.ifs.util.DataProperties;
    027import org.cpsolver.ifs.util.ToolBox;
    028import org.cpsolver.instructor.model.Course;
    029import org.cpsolver.instructor.model.Instructor;
    030import org.cpsolver.instructor.model.InstructorSchedulingModel;
    031import org.cpsolver.instructor.model.Preference;
    032import org.cpsolver.instructor.model.Section;
    033import org.cpsolver.instructor.model.TeachingAssignment;
    034import org.cpsolver.instructor.model.TeachingRequest;
    035import org.dom4j.Document;
    036import org.dom4j.io.OutputFormat;
    037import org.dom4j.io.SAXReader;
    038import org.dom4j.io.XMLWriter;
    039/**
    040 * A main class for running of the instructor scheduling solver from command line. <br>
    041 * Instructor scheduling is a process of assigning instructors (typically teaching assistants) to classes
    042 * after the course timetabling and student scheduling is done.
    043 *
    044 * @author  Tomáš Müller
    045 * @version IFS 1.3 (Instructor Sectioning)<br>
    046 *          Copyright (C) 2016 Tomáš Müller<br>
    047 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    048 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    049 * <br>
    050 *          This library is free software; you can redistribute it and/or modify
    051 *          it under the terms of the GNU Lesser General Public License as
    052 *          published by the Free Software Foundation; either version 3 of the
    053 *          License, or (at your option) any later version. <br>
    054 * <br>
    055 *          This library is distributed in the hope that it will be useful, but
    056 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    057 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    058 *          Lesser General Public License for more details. <br>
    059 * <br>
    060 *          You should have received a copy of the GNU Lesser General Public
    061 *          License along with this library; if not see
    062 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    063 */
    064public class Test extends InstructorSchedulingModel {
    065    private static Logger sLog = org.apache.logging.log4j.LogManager.getLogger(Test.class);
    066
    067    /**
    068     * Constructor
    069     * @param properties data properties
    070     */
    071    public Test(DataProperties properties) {
    072        super(properties);
    073    }
    074
    075    /**
    076     * Load input problem
    077     * @param inputFile input file (or folder)
    078     * @param assignment current assignments
    079     * @return true if the problem was successfully loaded in
    080     */
    081    protected boolean load(File inputFile, Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
    082        try {
    083            Document document = (new SAXReader()).read(inputFile);
    084            return load(document, assignment);
    085        } catch (Exception e) {
    086            sLog.error("Failed to load model from " + inputFile + ": " + e.getMessage(), e);
    087            return false;
    088        }
    089    }
    090
    091    /**
    092     * Generate a few reports
    093     * @param outputDir output directory
    094     * @param assignment current assignments
    095     * @throws IOException when there is an IO error
    096     */
    097    protected void generateReports(File outputDir, Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) throws IOException {
    098        PrintWriter out = new PrintWriter(new File(outputDir, "solution-assignments.csv"));
    099        out.println("Course,Section,Time,Room,Load,Student,Name,Instructor Pref,Course Pref,Attribute Pref,Time Pref,Back-To-Back,Same-Days,Same-Room,Different Lecture,Overlap [h]");
    100        double diffRoomWeight = getProperties().getPropertyDouble("BackToBack.DifferentRoomWeight", 0.8);
    101        double diffTypeWeight = getProperties().getPropertyDouble("BackToBack.DifferentTypeWeight", 0.5);
    102        double diffRoomWeightSD = getProperties().getPropertyDouble("SameDays.DifferentRoomWeight", 0.8);
    103        double diffTypeWeightSD = getProperties().getPropertyDouble("SameDays.DifferentTypeWeight", 0.5);
    104        double diffTypeWeightSR = getProperties().getPropertyDouble("SameRoom.DifferentTypeWeight", 0.5);
    105        for (TeachingRequest.Variable request : variables()) {
    106            out.print(request.getCourse().getCourseName());
    107            String sect = "", time = "", room = "";
    108            for (Iterator<Section> i = request.getSections().iterator(); i.hasNext(); ) {
    109                Section section = i.next();
    110                sect += section.getSectionName();
    111                time += (section.getTime() == null ? "-" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader(true));
    112                room += (section.getRoom() == null ? "-" : section.getRoom());
    113                if (i.hasNext()) { sect += ", "; time += ", "; room += ", "; }
    114            }
    115            out.print(",\"" + sect + "\",\"" + time + "\",\"" + room + "\"");
    116            out.print("," + new DecimalFormat("0.0").format(request.getRequest().getLoad()));
    117            TeachingAssignment ta = assignment.getValue(request);
    118            if (ta != null) {
    119                Instructor instructor = ta.getInstructor();
    120                out.print("," + instructor.getExternalId());
    121                out.print(",\"" + instructor.getName() + "\"");
    122                out.print("," + (ta.getInstructorPreference() == 0 ? "" : ta.getInstructorPreference()));
    123                out.print("," + (ta.getCoursePreference() == 0 ? "" : ta.getCoursePreference()));
    124                out.print("," + (ta.getAttributePreference() == 0 ? "" : ta.getAttributePreference()));
    125                out.print("," + (ta.getTimePreference() == 0 ? "" : ta.getTimePreference()));
    126                double b2b = instructor.countBackToBacks(assignment, ta, diffRoomWeight, diffTypeWeight);
    127                out.print("," + (b2b == 0.0 ? "" : new DecimalFormat("0.0").format(b2b)));
    128                double sd = instructor.countSameDays(assignment, ta, diffRoomWeightSD, diffTypeWeightSD);
    129                out.print("," + (sd == 0.0 ? "" : new DecimalFormat("0.0").format(sd)));
    130                double sr = instructor.countSameRooms(assignment, ta, diffTypeWeightSR);
    131                out.print("," + (sr == 0.0 ? "" : new DecimalFormat("0.0").format(sr)));
    132                double dl = instructor.differentLectures(assignment, ta);
    133                out.print("," + (dl == 0.0 ? "" : new DecimalFormat("0.0").format(dl)));
    134                double sh = instructor.share(assignment, ta);
    135                out.print("," + (sh == 0 ? "" : new DecimalFormat("0.0").format(sh / 12.0)));
    136            }
    137            out.println();
    138        }
    139        out.flush();
    140        out.close();
    141
    142        out = new PrintWriter(new File(outputDir, "solution-students.csv"));
    143        out.println("Student,Name,Preference,Not Available,Time Pref,Course Pref,Back-to-Back,Same-Days,Same-Room,Max Load,Assigned Load,Back-To-Back,Same-Days,Same-Room,Different Lecture,Overlap [h],1st Assignment,2nd Assignment, 3rd Assignment");
    144        for (Instructor instructor: getInstructors()) {
    145            out.print(instructor.getExternalId());
    146            out.print(",\"" + instructor.getName() + "\"");
    147            out.print("," + (instructor.getPreference() == 0 ? "" : instructor.getPreference()));
    148            out.print(",\"" + instructor.getAvailable() + "\"");
    149            String timePref = "";
    150            for (Preference<TimeLocation> p: instructor.getTimePreferences()) {
    151                if (!p.isProhibited()) {
    152                    if (!timePref.isEmpty()) timePref += ", ";
    153                    timePref += p.getTarget().getLongName(true).trim() + ": " + (p.isRequired() ? "R" : p.isProhibited() ? "P" : p.getPreference());
    154                }
    155            }
    156            out.print(",\"" + timePref + "\"");
    157            String coursePref = "";
    158            for (Preference<Course> p: instructor.getCoursePreferences()) {
    159                if (!coursePref.isEmpty()) coursePref += ", ";
    160                coursePref += p.getTarget().getCourseName() + ": " + (p.isRequired() ? "R" : p.isProhibited() ? "P" : p.getPreference());
    161            }
    162            out.print(",\"" + coursePref + "\"");
    163            out.print("," + (instructor.getBackToBackPreference() == 0 ? "" : instructor.getBackToBackPreference()));
    164            out.print("," + (instructor.getSameDaysPreference() == 0 ? "" : instructor.getSameDaysPreference()));
    165            out.print("," + (instructor.getSameRoomPreference() == 0 ? "" : instructor.getSameRoomPreference()));
    166            out.print("," + new DecimalFormat("0.0").format(instructor.getMaxLoad()));
    167
    168            Instructor.Context context = instructor.getContext(assignment);
    169            out.print("," + new DecimalFormat("0.0").format(context.getLoad()));
    170            out.print("," + (context.countBackToBackPercentage() == 0.0 ? "" : new DecimalFormat("0.0").format(100.0 * context.countBackToBackPercentage())));
    171            out.print("," + (context.countSameDaysPercentage() == 0.0 ? "" : new DecimalFormat("0.0").format(100.0 * context.countSameDaysPercentage())));
    172            out.print("," + (context.countSameRoomPercentage() == 0.0 ? "" : new DecimalFormat("0.0").format(100.0 * context.countSameRoomPercentage())));
    173            out.print("," + (context.countDifferentLectures() == 0.0 ? "" : new DecimalFormat("0.0").format(100.0 * context.countDifferentLectures())));
    174            out.print("," + (context.countTimeOverlaps() == 0.0 ? "" : new DecimalFormat("0.0").format(context.countTimeOverlaps() / 12.0)));
    175            for (TeachingAssignment ta : context.getAssignments()) {
    176                String sect = "";
    177                for (Iterator<Section> i = ta.variable().getSections().iterator(); i.hasNext(); ) {
    178                    Section section = i.next();
    179                    sect += section.getSectionName() + (section.getTime() == null ? "" : " " + section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader(true));
    180                    if (i.hasNext()) sect += ", ";
    181                }
    182                out.print(",\"" + ta.variable().getCourse() + " " + sect + "\"");
    183            }
    184            out.println();
    185        }
    186        out.flush();
    187        out.close();
    188    }
    189
    190    /**
    191     * Save the problem and the resulting assignment
    192     * @param outputDir output directory
    193     * @param assignment final assignment
    194     */
    195    protected void save(File outputDir, Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
    196        try {
    197            File outFile = new File(outputDir, "solution.xml");
    198            FileOutputStream fos = new FileOutputStream(outFile);
    199            try {
    200                (new XMLWriter(fos, OutputFormat.createPrettyPrint())).write(save(assignment));
    201                fos.flush();
    202            } finally {
    203                fos.close();
    204            }
    205        } catch (Exception e) {
    206            sLog.error("Failed to save solution: " + e.getMessage(), e);
    207        }
    208    }
    209
    210    /**
    211     * Run the problem
    212     */
    213    public void execute() {
    214        int nrSolvers = getProperties().getPropertyInt("Parallel.NrSolvers", 1);
    215        Solver<TeachingRequest.Variable, TeachingAssignment> solver = (nrSolvers == 1 ? new Solver<TeachingRequest.Variable, TeachingAssignment>(getProperties()) : new ParallelSolver<TeachingRequest.Variable, TeachingAssignment>(getProperties()));
    216
    217        Assignment<TeachingRequest.Variable, TeachingAssignment> assignment = (nrSolvers <= 1 ? new DefaultSingleAssignment<TeachingRequest.Variable, TeachingAssignment>() : new DefaultParallelAssignment<TeachingRequest.Variable, TeachingAssignment>());
    218        if (!load(new File(getProperties().getProperty("input", "input/solution.xml")), assignment))
    219            return;
    220
    221        solver.setInitalSolution(new Solution<TeachingRequest.Variable, TeachingAssignment>(this, assignment));
    222
    223        solver.currentSolution().addSolutionListener(new SolutionListener<TeachingRequest.Variable, TeachingAssignment>() {
    224            @Override
    225            public void solutionUpdated(Solution<TeachingRequest.Variable, TeachingAssignment> solution) {
    226            }
    227
    228            @Override
    229            public void getInfo(Solution<TeachingRequest.Variable, TeachingAssignment> solution, Map<String, String> info) {
    230            }
    231
    232            @Override
    233            public void getInfo(Solution<TeachingRequest.Variable, TeachingAssignment> solution, Map<String, String> info, Collection<TeachingRequest.Variable> variables) {
    234            }
    235
    236            @Override
    237            public void bestCleared(Solution<TeachingRequest.Variable, TeachingAssignment> solution) {
    238            }
    239
    240            @Override
    241            public void bestSaved(Solution<TeachingRequest.Variable, TeachingAssignment> solution) {
    242                Model<TeachingRequest.Variable, TeachingAssignment> m = solution.getModel();
    243                Assignment<TeachingRequest.Variable, TeachingAssignment> a = solution.getAssignment();
    244                System.out.println("**BEST[" + solution.getIteration() + "]** " + m.toString(a));
    245            }
    246
    247            @Override
    248            public void bestRestored(Solution<TeachingRequest.Variable, TeachingAssignment> solution) {
    249            }
    250        });
    251
    252        solver.start();
    253        try {
    254            solver.getSolverThread().join();
    255        } catch (InterruptedException e) {
    256        }
    257
    258        Solution<TeachingRequest.Variable, TeachingAssignment> solution = solver.lastSolution();
    259        solution.restoreBest();
    260
    261        sLog.info("Best solution found after " + solution.getBestTime() + " seconds (" + solution.getBestIteration() + " iterations).");
    262        sLog.info("Number of assigned variables is " + solution.getModel().assignedVariables(solution.getAssignment()).size());
    263        sLog.info("Total value of the solution is " + solution.getModel().getTotalValue(solution.getAssignment()));
    264
    265        sLog.info("Info: " + ToolBox.dict2string(solution.getExtendedInfo(), 2));
    266
    267        File outDir = new File(getProperties().getProperty("output", "output"));
    268        outDir.mkdirs();
    269
    270        save(outDir, solution.getAssignment());
    271
    272        try {
    273            generateReports(outDir, assignment);
    274        } catch (IOException e) {
    275            sLog.error("Failed to write reports: " + e.getMessage(), e);
    276        }
    277
    278        ConflictStatistics<TeachingRequest.Variable, TeachingAssignment> cbs = null;
    279        for (Extension<TeachingRequest.Variable, TeachingAssignment> extension : solver.getExtensions()) {
    280            if (ConflictStatistics.class.isInstance(extension)) {
    281                cbs = (ConflictStatistics<TeachingRequest.Variable, TeachingAssignment>) extension;
    282            }
    283        }
    284
    285        if (cbs != null) {
    286            PrintWriter out = null;
    287            try {
    288                out = new PrintWriter(new FileWriter(new File(outDir, "cbs.txt")));
    289                out.println(cbs.toString());
    290                out.flush(); out.close();
    291            } catch (IOException e) {
    292                sLog.error("Failed to write CBS: " + e.getMessage(), e);
    293            } finally {
    294                if (out != null) out.close();
    295            }
    296        }
    297    }
    298
    299    public static void main(String[] args) throws Exception {
    300        ToolBox.configureLogging();
    301
    302        DataProperties config = new DataProperties();
    303        if (System.getProperty("config") == null) {
    304            config.load(Test.class.getClass().getResourceAsStream("/org/cpsolver/instructor/default.properties"));
    305        } else {
    306            config.load(new FileInputStream(System.getProperty("config")));
    307        }
    308        config.putAll(System.getProperties());
    309
    310        new Test(config).execute();
    311    }
    312
    313}