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}