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 Tomáš Müller<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.Variable, 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.Variable, 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,Same-Days,Same-Room,Different Lecture,Overlap [h]"); 099 double diffRoomWeight = getProperties().getPropertyDouble("BackToBack.DifferentRoomWeight", 0.8); 100 double diffTypeWeight = getProperties().getPropertyDouble("BackToBack.DifferentTypeWeight", 0.5); 101 double diffRoomWeightSD = getProperties().getPropertyDouble("SameDays.DifferentRoomWeight", 0.8); 102 double diffTypeWeightSD = getProperties().getPropertyDouble("SameDays.DifferentTypeWeight", 0.5); 103 double diffTypeWeightSR = getProperties().getPropertyDouble("SameRoom.DifferentTypeWeight", 0.5); 104 for (TeachingRequest.Variable request : variables()) { 105 out.print(request.getCourse().getCourseName()); 106 String sect = "", time = "", room = ""; 107 for (Iterator<Section> i = request.getSections().iterator(); i.hasNext(); ) { 108 Section section = i.next(); 109 sect += section.getSectionName(); 110 time += (section.getTime() == null ? "-" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader(true)); 111 room += (section.getRoom() == null ? "-" : section.getRoom()); 112 if (i.hasNext()) { sect += ", "; time += ", "; room += ", "; } 113 } 114 out.print(",\"" + sect + "\",\"" + time + "\",\"" + room + "\""); 115 out.print("," + new DecimalFormat("0.0").format(request.getRequest().getLoad())); 116 TeachingAssignment ta = assignment.getValue(request); 117 if (ta != null) { 118 Instructor instructor = ta.getInstructor(); 119 out.print("," + instructor.getExternalId()); 120 out.print(",\"" + instructor.getName() + "\""); 121 out.print("," + (ta.getInstructorPreference() == 0 ? "" : ta.getInstructorPreference())); 122 out.print("," + (ta.getCoursePreference() == 0 ? "" : ta.getCoursePreference())); 123 out.print("," + (ta.getAttributePreference() == 0 ? "" : ta.getAttributePreference())); 124 out.print("," + (ta.getTimePreference() == 0 ? "" : ta.getTimePreference())); 125 double b2b = instructor.countBackToBacks(assignment, ta, diffRoomWeight, diffTypeWeight); 126 out.print("," + (b2b == 0.0 ? "" : new DecimalFormat("0.0").format(b2b))); 127 double sd = instructor.countSameDays(assignment, ta, diffRoomWeightSD, diffTypeWeightSD); 128 out.print("," + (sd == 0.0 ? "" : new DecimalFormat("0.0").format(sd))); 129 double sr = instructor.countSameRooms(assignment, ta, diffTypeWeightSR); 130 out.print("," + (sr == 0.0 ? "" : new DecimalFormat("0.0").format(sr))); 131 double dl = instructor.differentLectures(assignment, ta); 132 out.print("," + (dl == 0.0 ? "" : new DecimalFormat("0.0").format(dl))); 133 double sh = instructor.share(assignment, ta); 134 out.print("," + (sh == 0 ? "" : new DecimalFormat("0.0").format(sh / 12.0))); 135 } 136 out.println(); 137 } 138 out.flush(); 139 out.close(); 140 141 out = new PrintWriter(new File(outputDir, "solution-students.csv")); 142 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"); 143 for (Instructor instructor: getInstructors()) { 144 out.print(instructor.getExternalId()); 145 out.print(",\"" + instructor.getName() + "\""); 146 out.print("," + (instructor.getPreference() == 0 ? "" : instructor.getPreference())); 147 out.print(",\"" + instructor.getAvailable() + "\""); 148 String timePref = ""; 149 for (Preference<TimeLocation> p: instructor.getTimePreferences()) { 150 if (!p.isProhibited()) { 151 if (!timePref.isEmpty()) timePref += ", "; 152 timePref += p.getTarget().getLongName(true).trim() + ": " + (p.isRequired() ? "R" : p.isProhibited() ? "P" : p.getPreference()); 153 } 154 } 155 out.print(",\"" + timePref + "\""); 156 String coursePref = ""; 157 for (Preference<Course> p: instructor.getCoursePreferences()) { 158 if (!coursePref.isEmpty()) coursePref += ", "; 159 coursePref += p.getTarget().getCourseName() + ": " + (p.isRequired() ? "R" : p.isProhibited() ? "P" : p.getPreference()); 160 } 161 out.print(",\"" + coursePref + "\""); 162 out.print("," + (instructor.getBackToBackPreference() == 0 ? "" : instructor.getBackToBackPreference())); 163 out.print("," + (instructor.getSameDaysPreference() == 0 ? "" : instructor.getSameDaysPreference())); 164 out.print("," + (instructor.getSameRoomPreference() == 0 ? "" : instructor.getSameRoomPreference())); 165 out.print("," + new DecimalFormat("0.0").format(instructor.getMaxLoad())); 166 167 Instructor.Context context = instructor.getContext(assignment); 168 out.print("," + new DecimalFormat("0.0").format(context.getLoad())); 169 out.print("," + (context.countBackToBackPercentage() == 0.0 ? "" : new DecimalFormat("0.0").format(100.0 * context.countBackToBackPercentage()))); 170 out.print("," + (context.countSameDaysPercentage() == 0.0 ? "" : new DecimalFormat("0.0").format(100.0 * context.countSameDaysPercentage()))); 171 out.print("," + (context.countSameRoomPercentage() == 0.0 ? "" : new DecimalFormat("0.0").format(100.0 * context.countSameRoomPercentage()))); 172 out.print("," + (context.countDifferentLectures() == 0.0 ? "" : new DecimalFormat("0.0").format(100.0 * context.countDifferentLectures()))); 173 out.print("," + (context.countTimeOverlaps() == 0.0 ? "" : new DecimalFormat("0.0").format(context.countTimeOverlaps() / 12.0))); 174 for (TeachingAssignment ta : context.getAssignments()) { 175 String sect = ""; 176 for (Iterator<Section> i = ta.variable().getSections().iterator(); i.hasNext(); ) { 177 Section section = i.next(); 178 sect += section.getSectionName() + (section.getTime() == null ? "" : " " + section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader(true)); 179 if (i.hasNext()) sect += ", "; 180 } 181 out.print(",\"" + ta.variable().getCourse() + " " + sect + "\""); 182 } 183 out.println(); 184 } 185 out.flush(); 186 out.close(); 187 } 188 189 /** 190 * Save the problem and the resulting assignment 191 * @param outputDir output directory 192 * @param assignment final assignment 193 */ 194 protected void save(File outputDir, Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) { 195 try { 196 File outFile = new File(outputDir, "solution.xml"); 197 FileOutputStream fos = new FileOutputStream(outFile); 198 try { 199 (new XMLWriter(fos, OutputFormat.createPrettyPrint())).write(save(assignment)); 200 fos.flush(); 201 } finally { 202 fos.close(); 203 } 204 } catch (Exception e) { 205 sLog.error("Failed to save solution: " + e.getMessage(), e); 206 } 207 } 208 209 /** 210 * Run the problem 211 */ 212 public void execute() { 213 int nrSolvers = getProperties().getPropertyInt("Parallel.NrSolvers", 1); 214 Solver<TeachingRequest.Variable, TeachingAssignment> solver = (nrSolvers == 1 ? new Solver<TeachingRequest.Variable, TeachingAssignment>(getProperties()) : new ParallelSolver<TeachingRequest.Variable, TeachingAssignment>(getProperties())); 215 216 Assignment<TeachingRequest.Variable, TeachingAssignment> assignment = (nrSolvers <= 1 ? new DefaultSingleAssignment<TeachingRequest.Variable, TeachingAssignment>() : new DefaultParallelAssignment<TeachingRequest.Variable, TeachingAssignment>()); 217 if (!load(new File(getProperties().getProperty("input", "input/solution.xml")), assignment)) 218 return; 219 220 solver.setInitalSolution(new Solution<TeachingRequest.Variable, TeachingAssignment>(this, assignment)); 221 222 solver.currentSolution().addSolutionListener(new SolutionListener<TeachingRequest.Variable, TeachingAssignment>() { 223 @Override 224 public void solutionUpdated(Solution<TeachingRequest.Variable, TeachingAssignment> solution) { 225 } 226 227 @Override 228 public void getInfo(Solution<TeachingRequest.Variable, TeachingAssignment> solution, Map<String, String> info) { 229 } 230 231 @Override 232 public void getInfo(Solution<TeachingRequest.Variable, TeachingAssignment> solution, Map<String, String> info, Collection<TeachingRequest.Variable> variables) { 233 } 234 235 @Override 236 public void bestCleared(Solution<TeachingRequest.Variable, TeachingAssignment> solution) { 237 } 238 239 @Override 240 public void bestSaved(Solution<TeachingRequest.Variable, TeachingAssignment> solution) { 241 Model<TeachingRequest.Variable, TeachingAssignment> m = solution.getModel(); 242 Assignment<TeachingRequest.Variable, TeachingAssignment> a = solution.getAssignment(); 243 System.out.println("**BEST[" + solution.getIteration() + "]** " + m.toString(a)); 244 } 245 246 @Override 247 public void bestRestored(Solution<TeachingRequest.Variable, TeachingAssignment> solution) { 248 } 249 }); 250 251 solver.start(); 252 try { 253 solver.getSolverThread().join(); 254 } catch (InterruptedException e) { 255 } 256 257 Solution<TeachingRequest.Variable, TeachingAssignment> solution = solver.lastSolution(); 258 solution.restoreBest(); 259 260 sLog.info("Best solution found after " + solution.getBestTime() + " seconds (" + solution.getBestIteration() + " iterations)."); 261 sLog.info("Number of assigned variables is " + solution.getModel().assignedVariables(solution.getAssignment()).size()); 262 sLog.info("Total value of the solution is " + solution.getModel().getTotalValue(solution.getAssignment())); 263 264 sLog.info("Info: " + ToolBox.dict2string(solution.getExtendedInfo(), 2)); 265 266 File outDir = new File(getProperties().getProperty("output", "output")); 267 outDir.mkdirs(); 268 269 save(outDir, solution.getAssignment()); 270 271 try { 272 generateReports(outDir, assignment); 273 } catch (IOException e) { 274 sLog.error("Failed to write reports: " + e.getMessage(), e); 275 } 276 277 ConflictStatistics<TeachingRequest.Variable, TeachingAssignment> cbs = null; 278 for (Extension<TeachingRequest.Variable, TeachingAssignment> extension : solver.getExtensions()) { 279 if (ConflictStatistics.class.isInstance(extension)) { 280 cbs = (ConflictStatistics<TeachingRequest.Variable, TeachingAssignment>) extension; 281 } 282 } 283 284 if (cbs != null) { 285 PrintWriter out = null; 286 try { 287 out = new PrintWriter(new FileWriter(new File(outDir, "cbs.txt"))); 288 out.println(cbs.toString()); 289 out.flush(); out.close(); 290 } catch (IOException e) { 291 sLog.error("Failed to write CBS: " + e.getMessage(), e); 292 } finally { 293 if (out != null) out.close(); 294 } 295 } 296 } 297 298 public static void main(String[] args) throws Exception { 299 ToolBox.configureLogging(); 300 301 DataProperties config = new DataProperties(); 302 if (System.getProperty("config") == null) { 303 config.load(Test.class.getClass().getResourceAsStream("/org/cpsolver/instructor/default.properties")); 304 } else { 305 config.load(new FileInputStream(System.getProperty("config"))); 306 } 307 config.putAll(System.getProperties()); 308 309 new Test(config).execute(); 310 } 311 312}