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