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}