001package org.cpsolver.instructor.model; 002 003import java.text.DecimalFormat; 004import java.util.ArrayList; 005import java.util.BitSet; 006import java.util.Collection; 007import java.util.Date; 008import java.util.HashMap; 009import java.util.HashSet; 010import java.util.Iterator; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014import java.util.TreeSet; 015 016import org.apache.logging.log4j.Logger; 017import org.cpsolver.coursett.Constants; 018import org.cpsolver.coursett.model.TimeLocation; 019import org.cpsolver.ifs.assignment.Assignment; 020import org.cpsolver.ifs.criteria.Criterion; 021import org.cpsolver.ifs.model.Constraint; 022import org.cpsolver.ifs.model.Model; 023import org.cpsolver.ifs.util.DataProperties; 024import org.cpsolver.ifs.util.ToolBox; 025import org.cpsolver.instructor.constraints.GroupConstraint; 026import org.cpsolver.instructor.constraints.GroupConstraint.Distribution; 027import org.cpsolver.instructor.constraints.InstructorConstraint; 028import org.cpsolver.instructor.constraints.SameInstructorConstraint; 029import org.cpsolver.instructor.constraints.SameLinkConstraint; 030import org.cpsolver.instructor.criteria.UnusedInstructorLoad; 031import org.cpsolver.instructor.criteria.AttributePreferences; 032import org.cpsolver.instructor.criteria.BackToBack; 033import org.cpsolver.instructor.criteria.CoursePreferences; 034import org.cpsolver.instructor.criteria.InstructorPreferences; 035import org.cpsolver.instructor.criteria.SameInstructor; 036import org.cpsolver.instructor.criteria.DifferentLecture; 037import org.cpsolver.instructor.criteria.Distributions; 038import org.cpsolver.instructor.criteria.OriginalInstructor; 039import org.cpsolver.instructor.criteria.SameCommon; 040import org.cpsolver.instructor.criteria.SameCourse; 041import org.cpsolver.instructor.criteria.SameDays; 042import org.cpsolver.instructor.criteria.SameLink; 043import org.cpsolver.instructor.criteria.SameRoom; 044import org.cpsolver.instructor.criteria.TeachingPreferences; 045import org.cpsolver.instructor.criteria.TimeOverlaps; 046import org.cpsolver.instructor.criteria.TimePreferences; 047import org.dom4j.Document; 048import org.dom4j.DocumentHelper; 049import org.dom4j.Element; 050 051/** 052 * Instructor Scheduling Model. Variables are {@link org.cpsolver.instructor.model.TeachingRequest}, values are {@link org.cpsolver.instructor.model.TeachingAssignment}. 053 * Each teaching request has a course (see {@link org.cpsolver.instructor.model.Course}) and one or more sections (see {link {@link org.cpsolver.instructor.model.Section}}). 054 * Each assignment assigns one instructor (see {@link org.cpsolver.instructor.model.Instructor}) to a single teaching request. 055 * 056 * @author Tomáš Müller 057 * @version IFS 1.3 (Instructor Sectioning)<br> 058 * Copyright (C) 2016 Tomáš Müller<br> 059 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 060 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 061 * <br> 062 * This library is free software; you can redistribute it and/or modify 063 * it under the terms of the GNU Lesser General Public License as 064 * published by the Free Software Foundation; either version 3 of the 065 * License, or (at your option) any later version. <br> 066 * <br> 067 * This library is distributed in the hope that it will be useful, but 068 * WITHOUT ANY WARRANTY; without even the implied warranty of 069 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 070 * Lesser General Public License for more details. <br> 071 * <br> 072 * You should have received a copy of the GNU Lesser General Public 073 * License along with this library; if not see 074 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 075 */ 076public class InstructorSchedulingModel extends Model<TeachingRequest.Variable, TeachingAssignment> { 077 private static Logger sLog = org.apache.logging.log4j.LogManager.getLogger(InstructorSchedulingModel.class); 078 private DataProperties iProperties; 079 private Set<Attribute.Type> iTypes = new HashSet<Attribute.Type>(); 080 private List<Instructor> iInstructors = new ArrayList<Instructor>(); 081 private List<TeachingRequest> iRequests = new ArrayList<TeachingRequest>(); 082 083 /** 084 * Constructor 085 * @param properties data properties 086 */ 087 public InstructorSchedulingModel(DataProperties properties) { 088 super(); 089 iProperties = properties; 090 addCriterion(new AttributePreferences()); 091 addCriterion(new InstructorPreferences()); 092 addCriterion(new TeachingPreferences()); 093 addCriterion(new TimePreferences()); 094 addCriterion(new CoursePreferences()); 095 addCriterion(new BackToBack()); 096 addCriterion(new SameInstructor()); 097 addCriterion(new TimeOverlaps()); 098 addCriterion(new DifferentLecture()); 099 addCriterion(new SameLink()); 100 addCriterion(new OriginalInstructor()); 101 addCriterion(new UnusedInstructorLoad()); 102 addCriterion(new SameCourse()); 103 addCriterion(new SameCommon()); 104 addCriterion(new SameDays()); 105 addCriterion(new SameRoom()); 106 addCriterion(new Distributions()); 107 addGlobalConstraint(new InstructorConstraint()); 108 addGlobalConstraint(new GroupConstraint()); 109 } 110 111 /** 112 * Return solver configuration 113 * @return data properties given in the constructor 114 */ 115 public DataProperties getProperties() { 116 return iProperties; 117 } 118 119 /** 120 * Add instructor 121 * @param instructor an instructor 122 */ 123 public void addInstructor(Instructor instructor) { 124 instructor.setModel(this); 125 iInstructors.add(instructor); 126 for (Attribute attribute: instructor.getAttributes()) 127 addAttributeType(attribute.getType()); 128 } 129 130 /** 131 * All instructors 132 * @return all instructors in the model 133 */ 134 public List<Instructor> getInstructors() { 135 return iInstructors; 136 } 137 138 /** 139 * Add teaching request and the related variables 140 * @param request teaching request 141 */ 142 public void addRequest(TeachingRequest request) { 143 iRequests.add(request); 144 for (TeachingRequest.Variable variable: request.getVariables()) 145 addVariable(variable); 146 } 147 148 /** 149 * All teaching requests 150 * @return all teaching requests in the model 151 */ 152 public List<TeachingRequest> getRequests() { 153 return iRequests; 154 } 155 156 157 /** 158 * Return registered attribute types 159 * @return attribute types in the problem 160 */ 161 public Set<Attribute.Type> getAttributeTypes() { return iTypes; } 162 163 /** 164 * Register an attribute type 165 * @param type attribute type 166 */ 167 public void addAttributeType(Attribute.Type type) { iTypes.add(type); } 168 169 @Override 170 public Map<String, String> getInfo(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) { 171 Map<String, String> info = super.getInfo(assignment); 172 173 double totalLoad = 0.0; 174 double assignedLoad = 0.0; 175 for (TeachingRequest.Variable clazz : variables()) { 176 totalLoad += clazz.getRequest().getLoad(); 177 if (assignment.getValue(clazz) != null) 178 assignedLoad += clazz.getRequest().getLoad(); 179 } 180 info.put("Assigned Load", getPerc(assignedLoad, totalLoad, 0) + "% (" + sDoubleFormat.format(assignedLoad) + " / " + sDoubleFormat.format(totalLoad) + ")"); 181 182 return info; 183 } 184 185 @Override 186 public double getTotalValue(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) { 187 double ret = 0; 188 for (Criterion<TeachingRequest.Variable, TeachingAssignment> criterion : getCriteria()) 189 ret += criterion.getWeightedValue(assignment); 190 return ret; 191 } 192 193 @Override 194 public double getTotalValue(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, Collection<TeachingRequest.Variable> variables) { 195 double ret = 0; 196 for (Criterion<TeachingRequest.Variable, TeachingAssignment> criterion : getCriteria()) 197 ret += criterion.getWeightedValue(assignment, variables); 198 return ret; 199 } 200 201 /** 202 * Store the problem (together with its solution) in an XML format 203 * @param assignment current assignment 204 * @return XML document with the problem 205 */ 206 public Document save(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) { 207 DecimalFormat sDF7 = new DecimalFormat("0000000"); 208 boolean saveInitial = getProperties().getPropertyBoolean("Xml.SaveInitial", false); 209 boolean saveBest = getProperties().getPropertyBoolean("Xml.SaveBest", false); 210 boolean saveSolution = getProperties().getPropertyBoolean("Xml.SaveSolution", true); 211 Document document = DocumentHelper.createDocument(); 212 if (assignment != null && assignment.nrAssignedVariables() > 0) { 213 StringBuffer comments = new StringBuffer("Solution Info:\n"); 214 Map<String, String> solutionInfo = (getProperties().getPropertyBoolean("Xml.ExtendedInfo", true) ? getExtendedInfo(assignment) : getInfo(assignment)); 215 for (String key : new TreeSet<String>(solutionInfo.keySet())) { 216 String value = solutionInfo.get(key); 217 comments.append(" " + key + ": " + value + "\n"); 218 } 219 document.addComment(comments.toString()); 220 } 221 Element root = document.addElement("instructor-schedule"); 222 root.addAttribute("version", "1.0"); 223 root.addAttribute("created", String.valueOf(new Date())); 224 Element typesEl = root.addElement("attributes"); 225 for (Attribute.Type type: getAttributeTypes()) { 226 Element typeEl = typesEl.addElement("type"); 227 if (type.getTypeId() != null) 228 typeEl.addAttribute("id", String.valueOf(type.getTypeId())); 229 typeEl.addAttribute("name", type.getTypeName()); 230 typeEl.addAttribute("conjunctive", type.isConjunctive() ? "true" : "false"); 231 typeEl.addAttribute("required", type.isRequired() ? "true": "false"); 232 Set<Attribute> attributes = new HashSet<Attribute>(); 233 for (TeachingRequest request: getRequests()) { 234 for (Preference<Attribute> pref: request.getAttributePreferences()) { 235 Attribute attribute = pref.getTarget(); 236 if (type.equals(attribute.getType()) && attributes.add(attribute)) { 237 Element attributeEl = typeEl.addElement("attribute"); 238 if (attribute.getAttributeId() != null) 239 attributeEl.addAttribute("id", String.valueOf(attribute.getAttributeId())); 240 attributeEl.addAttribute("name", attribute.getAttributeName()); 241 if (attribute.getParentAttribute() != null && attribute.getParentAttribute().getAttributeId() != null) 242 attributeEl.addAttribute("parent", String.valueOf(attribute.getParentAttribute().getAttributeId())); 243 } 244 } 245 for (Instructor instructor: getInstructors()) { 246 for (Attribute attribute: instructor.getAttributes()) { 247 if (type.equals(attribute.getType()) && attributes.add(attribute)) { 248 Element attributeEl = typeEl.addElement("attribute"); 249 if (attribute.getAttributeId() != null) 250 attributeEl.addAttribute("id", String.valueOf(attribute.getAttributeId())); 251 attributeEl.addAttribute("name", attribute.getAttributeName()); 252 if (attribute.getParentAttribute() != null && attribute.getParentAttribute().getAttributeId() != null) 253 attributeEl.addAttribute("parent", String.valueOf(attribute.getParentAttribute().getAttributeId())); 254 } 255 } 256 } 257 } 258 } 259 Element requestsEl = root.addElement("teaching-requests"); 260 for (TeachingRequest request: getRequests()) { 261 Element requestEl = requestsEl.addElement("request"); 262 requestEl.addAttribute("id", String.valueOf(request.getRequestId())); 263 if (request.getNrInstructors() != 1) 264 requestEl.addAttribute("nrInstructors", String.valueOf(request.getNrInstructors())); 265 Course course = request.getCourse(); 266 Element courseEl = requestEl.addElement("course"); 267 if (course.getCourseId() != null) 268 courseEl.addAttribute("id", String.valueOf(course.getCourseId())); 269 if (course.getCourseName() != null) 270 courseEl.addAttribute("name", String.valueOf(course.getCourseName())); 271 for (Section section: request.getSections()) { 272 Element sectionEl = requestEl.addElement("section"); 273 sectionEl.addAttribute("id", String.valueOf(section.getSectionId())); 274 if (section.getExternalId() != null && !section.getExternalId().isEmpty()) sectionEl.addAttribute("externalId", section.getExternalId()); 275 if (section.getSectionType() != null && !section.getSectionType().isEmpty()) sectionEl.addAttribute("type", section.getSectionType()); 276 if (section.getSectionName() != null && !section.getSectionName().isEmpty()) sectionEl.addAttribute("name", section.getSectionName()); 277 if (section.hasTime()) { 278 TimeLocation tl = section.getTime(); 279 Element timeEl = sectionEl.addElement("time"); 280 timeEl.addAttribute("days", sDF7.format(Long.parseLong(Integer.toBinaryString(tl.getDayCode())))); 281 timeEl.addAttribute("start", String.valueOf(tl.getStartSlot())); 282 timeEl.addAttribute("length", String.valueOf(tl.getLength())); 283 if (tl.getBreakTime() != 0) 284 timeEl.addAttribute("breakTime", String.valueOf(tl.getBreakTime())); 285 if (tl.getTimePatternId() != null) 286 timeEl.addAttribute("pattern", tl.getTimePatternId().toString()); 287 if (tl.getDatePatternId() != null) 288 timeEl.addAttribute("datePattern", tl.getDatePatternId().toString()); 289 if (tl.getDatePatternName() != null && !tl.getDatePatternName().isEmpty()) 290 timeEl.addAttribute("datePatternName", tl.getDatePatternName()); 291 if (tl.getWeekCode() != null) 292 timeEl.addAttribute("dates", bitset2string(tl.getWeekCode())); 293 timeEl.setText(tl.getLongName(false)); 294 } 295 if (section.hasRoom()) sectionEl.addAttribute("room", section.getRoom()); 296 if (section.isAllowOverlap()) sectionEl.addAttribute("canOverlap", "true"); 297 if (section.isCommon()) sectionEl.addAttribute("common", "true"); 298 } 299 requestEl.addAttribute("load", sDoubleFormat.format(request.getLoad())); 300 requestEl.addAttribute("sameCourse", Constants.preferenceLevel2preference(request.getSameCoursePreference())); 301 requestEl.addAttribute("sameCommon", Constants.preferenceLevel2preference(request.getSameCommonPreference())); 302 for (Preference<Attribute> pref: request.getAttributePreferences()) { 303 Element attributeEl = requestEl.addElement("attribute"); 304 if (pref.getTarget().getAttributeId() != null) 305 attributeEl.addAttribute("id", String.valueOf(pref.getTarget().getAttributeId())); 306 attributeEl.addAttribute("name", pref.getTarget().getAttributeName()); 307 attributeEl.addAttribute("type", pref.getTarget().getType().getTypeName()); 308 attributeEl.addAttribute("preference", (pref.isRequired() ? "R" : pref.isProhibited() ? "P" : String.valueOf(pref.getPreference()))); 309 if (pref.getTarget().getParentAttribute() != null && pref.getTarget().getParentAttribute().getAttributeId() != null) 310 attributeEl.addAttribute("parent", String.valueOf(pref.getTarget().getParentAttribute().getAttributeId())); 311 } 312 for (Preference<Instructor> pref: request.getInstructorPreferences()) { 313 Element instructorEl = requestEl.addElement("instructor"); 314 instructorEl.addAttribute("id", String.valueOf(pref.getTarget().getInstructorId())); 315 if (pref.getTarget().hasExternalId()) 316 instructorEl.addAttribute("externalId", pref.getTarget().getExternalId()); 317 if (pref.getTarget().hasName()) 318 instructorEl.addAttribute("name", pref.getTarget().getName()); 319 instructorEl.addAttribute("preference", (pref.isRequired() ? "R" : pref.isProhibited() ? "P" : String.valueOf(pref.getPreference()))); 320 } 321 if (saveBest) 322 for (TeachingRequest.Variable variable: request.getVariables()) { 323 if (variable.getBestAssignment() != null) { 324 Instructor instructor = variable.getBestAssignment().getInstructor(); 325 Element instructorEl = requestEl.addElement("best-instructor"); 326 instructorEl.addAttribute("id", String.valueOf(instructor.getInstructorId())); 327 if (request.getNrInstructors() != 1) 328 instructorEl.addAttribute("index", String.valueOf(variable.getInstructorIndex())); 329 if (instructor.hasExternalId()) 330 instructorEl.addAttribute("externalId", instructor.getExternalId()); 331 if (instructor.hasName()) 332 instructorEl.addAttribute("name", instructor.getName()); 333 } 334 } 335 if (saveInitial) 336 for (TeachingRequest.Variable variable: request.getVariables()) { 337 if (variable.getInitialAssignment() != null) { 338 Instructor instructor = variable.getInitialAssignment().getInstructor(); 339 Element instructorEl = requestEl.addElement("initial-instructor"); 340 instructorEl.addAttribute("id", String.valueOf(instructor.getInstructorId())); 341 if (request.getNrInstructors() != 1) 342 instructorEl.addAttribute("index", String.valueOf(variable.getInstructorIndex())); 343 if (instructor.hasExternalId()) 344 instructorEl.addAttribute("externalId", instructor.getExternalId()); 345 if (instructor.hasName()) 346 instructorEl.addAttribute("name", instructor.getName()); 347 } 348 } 349 if (saveSolution) 350 for (TeachingRequest.Variable variable: request.getVariables()) { 351 TeachingAssignment ta = assignment.getValue(variable); 352 if (ta != null) { 353 Instructor instructor = ta.getInstructor(); 354 Element instructorEl = requestEl.addElement("assigned-instructor"); 355 instructorEl.addAttribute("id", String.valueOf(instructor.getInstructorId())); 356 if (request.getNrInstructors() != 1) 357 instructorEl.addAttribute("index", String.valueOf(variable.getInstructorIndex())); 358 if (instructor.hasExternalId()) 359 instructorEl.addAttribute("externalId", instructor.getExternalId()); 360 if (instructor.hasName()) 361 instructorEl.addAttribute("name", instructor.getName()); 362 } 363 } 364 } 365 Element instructorsEl = root.addElement("instructors"); 366 for (Instructor instructor: getInstructors()) { 367 Element instructorEl = instructorsEl.addElement("instructor"); 368 instructorEl.addAttribute("id", String.valueOf(instructor.getInstructorId())); 369 if (instructor.hasExternalId()) 370 instructorEl.addAttribute("externalId", instructor.getExternalId()); 371 if (instructor.hasName()) 372 instructorEl.addAttribute("name", instructor.getName()); 373 if (instructor.getPreference() != 0) 374 instructorEl.addAttribute("preference", String.valueOf(instructor.getPreference())); 375 if (instructor.getBackToBackPreference() != 0) 376 instructorEl.addAttribute("btb", String.valueOf(instructor.getBackToBackPreference())); 377 if (instructor.getSameDaysPreference() != 0) 378 instructorEl.addAttribute("same-days", String.valueOf(instructor.getSameDaysPreference())); 379 if (instructor.getSameRoomPreference() != 0) 380 instructorEl.addAttribute("same-room", String.valueOf(instructor.getSameRoomPreference())); 381 for (Attribute attribute: instructor.getAttributes()) { 382 Element attributeEl = instructorEl.addElement("attribute"); 383 if (attribute.getAttributeId() != null) 384 attributeEl.addAttribute("id", String.valueOf(attribute.getAttributeId())); 385 attributeEl.addAttribute("name", attribute.getAttributeName()); 386 attributeEl.addAttribute("type", attribute.getType().getTypeName()); 387 if (attribute.getParentAttribute() != null && attribute.getParentAttribute().getAttributeId() != null) 388 attributeEl.addAttribute("parent", String.valueOf(attribute.getParentAttribute().getAttributeId())); 389 } 390 instructorEl.addAttribute("maxLoad", sDoubleFormat.format(instructor.getMaxLoad())); 391 for (Distribution d: instructor.getDistributions()) { 392 Element dEl = instructorEl.addElement("distribution"); 393 dEl.addAttribute("type", d.getType().reference()); 394 dEl.addAttribute("preference", d.getPreference()); 395 dEl.addAttribute("name", d.getType().getName()); 396 } 397 for (Preference<TimeLocation> tp: instructor.getTimePreferences()) { 398 399 Element timeEl = instructorEl.addElement("time"); 400 TimeLocation tl = tp.getTarget(); 401 timeEl.addAttribute("days", sDF7.format(Long.parseLong(Integer.toBinaryString(tl.getDayCode())))); 402 timeEl.addAttribute("start", String.valueOf(tl.getStartSlot())); 403 timeEl.addAttribute("length", String.valueOf(tl.getLength())); 404 if (tl.getBreakTime() != 0) 405 timeEl.addAttribute("breakTime", String.valueOf(tl.getBreakTime())); 406 if (tl.getTimePatternId() != null) 407 timeEl.addAttribute("pattern", tl.getTimePatternId().toString()); 408 if (tl.getDatePatternId() != null) 409 timeEl.addAttribute("datePattern", tl.getDatePatternId().toString()); 410 if (tl.getDatePatternName() != null && !tl.getDatePatternName().isEmpty()) 411 timeEl.addAttribute("datePatternName", tl.getDatePatternName()); 412 if (tl.getWeekCode() != null) 413 timeEl.addAttribute("dates", bitset2string(tl.getWeekCode())); 414 timeEl.addAttribute("preference", tp.isProhibited() ? "P" : tp.isRequired() ? "R" : String.valueOf(tp.getPreference())); 415 if (tp.getTarget() instanceof EnrolledClass) { 416 Element classEl = timeEl.addElement("section"); 417 Element courseEl = null; 418 EnrolledClass ec = (EnrolledClass)tp.getTarget(); 419 if (ec.getCourseId() != null || ec.getCourse() != null) { 420 courseEl = timeEl.addElement("course"); 421 if (ec.getCourseId() != null) courseEl.addAttribute("id", String.valueOf(ec.getCourseId())); 422 if (ec.getCourse() != null) courseEl.addAttribute("name", ec.getCourse()); 423 } 424 if (ec.getClassId() != null) classEl.addAttribute("id", String.valueOf(ec.getClassId())); 425 if (ec.getType() != null) classEl.addAttribute("type", ec.getType()); 426 if (ec.getSection() != null) classEl.addAttribute("name", ec.getSection()); 427 if (ec.getExternalId() != null) classEl.addAttribute("externalId", ec.getExternalId()); 428 if (ec.getRoom() != null) classEl.addAttribute("room", ec.getRoom()); 429 classEl.addAttribute("role", ec.isInstructor() ? "instructor" : "student"); 430 } else { 431 timeEl.setText(tl.getLongName(false)); 432 } 433 } 434 for (Preference<Course> cp: instructor.getCoursePreferences()) { 435 Element courseEl = instructorEl.addElement("course"); 436 Course course = cp.getTarget(); 437 if (course.getCourseId() != null) 438 courseEl.addAttribute("id", String.valueOf(course.getCourseId())); 439 if (course.getCourseName() != null) 440 courseEl.addAttribute("name", String.valueOf(course.getCourseName())); 441 courseEl.addAttribute("preference", cp.isProhibited() ? "P" : cp.isRequired() ? "R" : String.valueOf(cp.getPreference())); 442 } 443 } 444 Element constraintsEl = root.addElement("constraints"); 445 for (Constraint<TeachingRequest.Variable, TeachingAssignment> c: constraints()) { 446 if (c instanceof SameInstructorConstraint) { 447 SameInstructorConstraint si = (SameInstructorConstraint) c; 448 Element sameInstEl = constraintsEl.addElement("same-instructor"); 449 if (si.getConstraintId() != null) 450 sameInstEl.addAttribute("id", String.valueOf(si.getConstraintId())); 451 if (si.getName() != null) 452 sameInstEl.addAttribute("name", si.getName()); 453 sameInstEl.addAttribute("preference", Constants.preferenceLevel2preference(si.getPreference())); 454 for (TeachingRequest.Variable request: c.variables()) { 455 Element assignmentEl = sameInstEl.addElement("request"); 456 assignmentEl.addAttribute("id", String.valueOf(request.getRequest().getRequestId())); 457 if (request.getRequest().getNrInstructors() != 1) 458 assignmentEl.addAttribute("index", String.valueOf(request.getInstructorIndex())); 459 } 460 } else if (c instanceof SameLinkConstraint) { 461 SameLinkConstraint si = (SameLinkConstraint) c; 462 Element sameInstEl = constraintsEl.addElement("same-link"); 463 if (si.getConstraintId() != null) 464 sameInstEl.addAttribute("id", String.valueOf(si.getConstraintId())); 465 if (si.getName() != null) 466 sameInstEl.addAttribute("name", si.getName()); 467 sameInstEl.addAttribute("preference", Constants.preferenceLevel2preference(si.getPreference())); 468 for (TeachingRequest.Variable request: c.variables()) { 469 Element assignmentEl = sameInstEl.addElement("request"); 470 assignmentEl.addAttribute("id", String.valueOf(request.getRequest().getRequestId())); 471 if (request.getRequest().getNrInstructors() != 1) 472 assignmentEl.addAttribute("index", String.valueOf(request.getInstructorIndex())); 473 } 474 } 475 } 476 return document; 477 } 478 479 /** 480 * Load the problem (and its solution) from an XML format 481 * @param document XML document 482 * @param assignment current assignment 483 * @return true, if the problem was successfully loaded in 484 */ 485 public boolean load(Document document, Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) { 486 boolean loadInitial = getProperties().getPropertyBoolean("Xml.LoadInitial", true); 487 boolean loadBest = getProperties().getPropertyBoolean("Xml.LoadBest", true); 488 boolean loadSolution = getProperties().getPropertyBoolean("Xml.LoadSolution", true); 489 String defaultBtb = getProperties().getProperty("Defaults.BackToBack", "0"); 490 String defaultSameDays = getProperties().getProperty("Defaults.SameDays", "0"); 491 String defaultSameRoom = getProperties().getProperty("Defaults.SameRoom", "0"); 492 String defaultConjunctive = getProperties().getProperty("Defaults.Conjunctive", "false"); 493 String defaultRequired = getProperties().getProperty("Defaults.Required", "false"); 494 String defaultSameCourse = getProperties().getProperty("Defaults.SameCourse", "R"); 495 String defaultSameCommon = getProperties().getProperty("Defaults.SameCommon", "R"); 496 Element root = document.getRootElement(); 497 if (!"instructor-schedule".equals(root.getName())) 498 return false; 499 Map<String, Attribute.Type> types = new HashMap<String, Attribute.Type>(); 500 Map<Long, Attribute> attributes = new HashMap<Long, Attribute>(); 501 Map<Long, Long> parents = new HashMap<Long, Long>(); 502 if (root.element("attributes") != null) { 503 for (Iterator<?> i = root.element("attributes").elementIterator("type"); i.hasNext();) { 504 Element typeEl = (Element) i.next(); 505 Attribute.Type type = new Attribute.Type( 506 Long.parseLong(typeEl.attributeValue("id")), 507 typeEl.attributeValue("name"), 508 "true".equalsIgnoreCase(typeEl.attributeValue("conjunctive", defaultConjunctive)), 509 "true".equalsIgnoreCase(typeEl.attributeValue("required", defaultRequired))); 510 addAttributeType(type); 511 if (type.getTypeName() != null) 512 types.put(type.getTypeName(), type); 513 for (Iterator<?> j = typeEl.elementIterator("attribute"); j.hasNext();) { 514 Element attributeEl = (Element) j.next(); 515 Attribute attribute = new Attribute( 516 Long.parseLong(attributeEl.attributeValue("id")), 517 attributeEl.attributeValue("name"), 518 type); 519 attributes.put(attribute.getAttributeId(), attribute); 520 if (attributeEl.attributeValue("parent") != null) 521 parents.put(attribute.getAttributeId(), Long.parseLong(attributeEl.attributeValue("parent"))); 522 } 523 } 524 } 525 Map<Long, Course> courses = new HashMap<Long, Course>(); 526 if (root.element("courses") != null) { 527 for (Iterator<?> i = root.element("courses").elementIterator("course"); i.hasNext();) { 528 Element courseEl = (Element) i.next(); 529 Course course = new Course( 530 Long.parseLong(courseEl.attributeValue("id")), 531 courseEl.attributeValue("name")); 532 courses.put(course.getCourseId(), course); 533 } 534 } 535 Map<Long, Instructor> instructors = new HashMap<Long, Instructor>(); 536 for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) { 537 Element instructorEl = (Element) i.next(); 538 Instructor instructor = new Instructor( 539 Long.parseLong(instructorEl.attributeValue("id")), 540 instructorEl.attributeValue("externalId"), 541 instructorEl.attributeValue("name"), 542 string2preference(instructorEl.attributeValue("preference")), 543 Float.parseFloat(instructorEl.attributeValue("maxLoad", "0"))); 544 instructor.setBackToBackPreference(Integer.valueOf(instructorEl.attributeValue("btb", defaultBtb))); 545 instructor.setSameDaysPreference(Integer.valueOf(instructorEl.attributeValue("same-days", defaultSameDays))); 546 instructor.setSameRoomPreference(Integer.valueOf(instructorEl.attributeValue("same-room", defaultSameRoom))); 547 for (Iterator<?> j = instructorEl.elementIterator("attribute"); j.hasNext();) { 548 Element f = (Element) j.next(); 549 Long attributeId = Long.valueOf(f.attributeValue("id")); 550 Attribute attribute = attributes.get(attributeId); 551 if (attribute == null) { 552 Attribute.Type type = types.get(f.attributeValue("type")); 553 if (type == null) { 554 type = new Attribute.Type(types.size(), f.attributeValue("type"), 555 "true".equalsIgnoreCase(f.attributeValue("conjunctive", defaultConjunctive)), 556 "true".equalsIgnoreCase(f.attributeValue("required", defaultRequired))); 557 types.put(type.getTypeName(), type); 558 } 559 attribute = new Attribute(attributeId, f.attributeValue("name"), type); 560 attributes.put(attributeId, attribute); 561 if (f.attributeValue("parent") != null) 562 parents.put(attribute.getAttributeId(), Long.parseLong(f.attributeValue("parent"))); 563 } 564 instructor.addAttribute(attribute); 565 } 566 for (Iterator<?> j = instructorEl.elementIterator("distribution"); j.hasNext();) { 567 Element dEl = (Element) j.next(); 568 instructor.addDistribution(dEl.attributeValue("type"), dEl.attributeValue("preference", "0"), dEl.attributeValue("name")); 569 } 570 for (Iterator<?> j = instructorEl.elementIterator("time"); j.hasNext();) { 571 Element f = (Element) j.next(); 572 Element classEl = f.element("section"); 573 Element courseEl = f.element("course"); 574 TimeLocation time = null; 575 if (classEl != null) { 576 time = new EnrolledClass( 577 courseEl == null || courseEl.attributeValue("id") == null ? null : Long.valueOf(courseEl.attributeValue("id")), 578 classEl.attributeValue("id") == null ? null : Long.valueOf(classEl.attributeValue("id")), 579 courseEl == null ? null : courseEl.attributeValue("name"), 580 classEl.attributeValue("type"), 581 classEl.attributeValue("name"), 582 classEl.attributeValue("externalId"), 583 Integer.parseInt(f.attributeValue("days"), 2), 584 Integer.parseInt(f.attributeValue("start")), 585 Integer.parseInt(f.attributeValue("length")), 586 f.attributeValue("datePattern") == null ? null : Long.valueOf(f.attributeValue("datePattern")), 587 f.attributeValue("datePatternName", ""), 588 createBitSet(f.attributeValue("dates")), 589 Integer.parseInt(f.attributeValue("breakTime", "0")), 590 classEl.attributeValue("room"), 591 "instructor".equalsIgnoreCase(classEl.attributeValue("role", "instructor"))); 592 } else { 593 time = new TimeLocation( 594 Integer.parseInt(f.attributeValue("days"), 2), 595 Integer.parseInt(f.attributeValue("start")), 596 Integer.parseInt(f.attributeValue("length")), 0, 0, 597 f.attributeValue("datePattern") == null ? null : Long.valueOf(f.attributeValue("datePattern")), 598 f.attributeValue("datePatternName", ""), 599 createBitSet(f.attributeValue("dates")), 600 Integer.parseInt(f.attributeValue("breakTime", "0"))); 601 } 602 if (f.attributeValue("pattern") != null) 603 time.setTimePatternId(Long.valueOf(f.attributeValue("pattern"))); 604 instructor.addTimePreference(new Preference<TimeLocation>(time, string2preference(f.attributeValue("preference")))); 605 } 606 for (Iterator<?> j = instructorEl.elementIterator("course"); j.hasNext();) { 607 Element f = (Element) j.next(); 608 instructor.addCoursePreference(new Preference<Course>(new Course(Long.parseLong(f.attributeValue("id")), f.attributeValue("name")), string2preference(f.attributeValue("preference")))); 609 } 610 addInstructor(instructor); 611 instructors.put(instructor.getInstructorId(), instructor); 612 } 613 Map<Long, TeachingRequest> requests = new HashMap<Long, TeachingRequest>(); 614 Map<TeachingRequest, Map<Integer, Instructor>> current = new HashMap<TeachingRequest, Map<Integer, Instructor>>(); 615 Map<TeachingRequest, Map<Integer, Instructor>> best = new HashMap<TeachingRequest, Map<Integer, Instructor>>(); 616 Map<TeachingRequest, Map<Integer, Instructor>> initial = new HashMap<TeachingRequest, Map<Integer, Instructor>>(); 617 for (Iterator<?> i = root.element("teaching-requests").elementIterator("request"); i.hasNext();) { 618 Element requestEl = (Element) i.next(); 619 Element courseEl = requestEl.element("course"); 620 Course course = null; 621 if (courseEl != null) { 622 Long courseId = Long.valueOf(courseEl.attributeValue("id")); 623 course = courses.get(courseId); 624 if (course == null) { 625 course = new Course(courseId, courseEl.attributeValue("name")); 626 } 627 } else { 628 course = courses.get(Long.valueOf(requestEl.attributeValue("course"))); 629 } 630 List<Section> sections = new ArrayList<Section>(); 631 for (Iterator<?> j = requestEl.elementIterator("section"); j.hasNext();) { 632 Element f = (Element) j.next(); 633 TimeLocation time = null; 634 Element timeEl = f.element("time"); 635 if (timeEl != null) { 636 time = new TimeLocation( 637 Integer.parseInt(timeEl.attributeValue("days"), 2), 638 Integer.parseInt(timeEl.attributeValue("start")), 639 Integer.parseInt(timeEl.attributeValue("length")), 0, 0, 640 timeEl.attributeValue("datePattern") == null ? null : Long.valueOf(timeEl.attributeValue("datePattern")), 641 timeEl.attributeValue("datePatternName", ""), 642 createBitSet(timeEl.attributeValue("dates")), 643 Integer.parseInt(timeEl.attributeValue("breakTime", "0"))); 644 if (timeEl.attributeValue("pattern") != null) 645 time.setTimePatternId(Long.valueOf(timeEl.attributeValue("pattern"))); 646 } 647 Section section = new Section( 648 Long.valueOf(f.attributeValue("id")), 649 f.attributeValue("externalId"), 650 f.attributeValue("type"), 651 f.attributeValue("name"), 652 time, 653 f.attributeValue("room"), 654 "true".equalsIgnoreCase(f.attributeValue("canOverlap", "false")), 655 "true".equalsIgnoreCase(f.attributeValue("common", "false"))); 656 sections.add(section); 657 } 658 TeachingRequest request = new TeachingRequest( 659 Long.parseLong(requestEl.attributeValue("id")), 660 Integer.parseInt(requestEl.attributeValue("nrInstructors", "1")), 661 course, 662 Float.valueOf(requestEl.attributeValue("load", "0")), 663 sections, 664 Constants.preference2preferenceLevel(requestEl.attributeValue("sameCourse", defaultSameCourse)), 665 Constants.preference2preferenceLevel(requestEl.attributeValue("sameCommon", defaultSameCommon))); 666 requests.put(request.getRequestId(), request); 667 for (Iterator<?> j = requestEl.elementIterator("attribute"); j.hasNext();) { 668 Element f = (Element) j.next(); 669 Long attributeId = Long.valueOf(f.attributeValue("id")); 670 Attribute attribute = attributes.get(attributeId); 671 if (attribute == null) { 672 Attribute.Type type = types.get(f.attributeValue("type")); 673 if (type == null) { 674 type = new Attribute.Type(types.size(), f.attributeValue("type"), 675 "true".equalsIgnoreCase(f.attributeValue("conjunctive", defaultConjunctive)), 676 "true".equalsIgnoreCase(f.attributeValue("required", defaultRequired))); 677 types.put(type.getTypeName(), type); 678 } 679 attribute = new Attribute(attributeId, f.attributeValue("name"), type); 680 attributes.put(attributeId, attribute); 681 if (f.attributeValue("parent") != null) 682 parents.put(attribute.getAttributeId(), Long.parseLong(f.attributeValue("parent"))); 683 } 684 request.addAttributePreference(new Preference<Attribute>(attribute, string2preference(f.attributeValue("preference")))); 685 } 686 for (Iterator<?> j = requestEl.elementIterator("instructor"); j.hasNext();) { 687 Element f = (Element) j.next(); 688 Long instructorId = Long.valueOf(f.attributeValue("id")); 689 Instructor instructor = instructors.get(instructorId); 690 if (instructor != null) 691 request.addInstructorPreference(new Preference<Instructor>(instructor, string2preference(f.attributeValue("preference")))); 692 } 693 if (loadBest) { 694 for (Iterator<?> j = requestEl.elementIterator("best-instructor"); j.hasNext();) { 695 Element f = (Element) j.next(); 696 Map<Integer, Instructor> idx2inst = best.get(request); 697 if (idx2inst == null) { 698 idx2inst = new HashMap<Integer, Instructor>(); 699 best.put(request, idx2inst); 700 } 701 int index = 1 + Integer.parseInt(f.attributeValue("index", String.valueOf(idx2inst.size()))); 702 Instructor instructor = instructors.get(Long.valueOf(f.attributeValue("id"))); 703 if (instructor != null) 704 idx2inst.put(index, instructor); 705 } 706 } 707 if (loadInitial) { 708 for (Iterator<?> j = requestEl.elementIterator("initial-instructor"); j.hasNext();) { 709 Element f = (Element) j.next(); 710 Map<Integer, Instructor> idx2inst = initial.get(request); 711 if (idx2inst == null) { 712 idx2inst = new HashMap<Integer, Instructor>(); 713 initial.put(request, idx2inst); 714 } 715 int index = 1 + Integer.parseInt(f.attributeValue("index", String.valueOf(idx2inst.size()))); 716 Instructor instructor = instructors.get(Long.valueOf(f.attributeValue("id"))); 717 if (instructor != null) 718 idx2inst.put(index, instructor); 719 } 720 } 721 if (loadSolution) { 722 for (Iterator<?> j = requestEl.elementIterator("assigned-instructor"); j.hasNext();) { 723 Element f = (Element) j.next(); 724 Map<Integer, Instructor> idx2inst = current.get(request); 725 if (idx2inst == null) { 726 idx2inst = new HashMap<Integer, Instructor>(); 727 current.put(request, idx2inst); 728 } 729 int index = Integer.parseInt(f.attributeValue("index", String.valueOf(idx2inst.size()))); 730 Instructor instructor = instructors.get(Long.valueOf(f.attributeValue("id"))); 731 if (instructor != null) 732 idx2inst.put(index, instructor); 733 } 734 } 735 addRequest(request); 736 } 737 if (root.element("constraints") != null) { 738 for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) { 739 Element constraintEl = (Element) i.next(); 740 Constraint<TeachingRequest.Variable, TeachingAssignment> constraint = null; 741 if ("same-link".equals(constraintEl.getName())) { 742 constraint = new SameLinkConstraint( 743 (constraintEl.attributeValue("id") == null ? null : Long.valueOf(constraintEl.attributeValue("id"))), 744 constraintEl.attributeValue("name"), 745 constraintEl.attributeValue("preference")); 746 } else if ("same-instructor".equals(constraintEl.getName())) { 747 constraint = new SameInstructorConstraint( 748 (constraintEl.attributeValue("id") == null ? null : Long.valueOf(constraintEl.attributeValue("id"))), 749 constraintEl.attributeValue("name"), 750 constraintEl.attributeValue("preference")); 751 } 752 if (constraint != null) { 753 for (Iterator<?> j = constraintEl.elementIterator("request"); j.hasNext();) { 754 Element f = (Element) j.next(); 755 TeachingRequest request = requests.get(Long.valueOf(f.attributeValue("id"))); 756 if (request != null) { 757 int index = Integer.valueOf(f.attributeValue("index", "0")); 758 if (index >= 0 && index < request.getNrInstructors()) 759 constraint.addVariable(request.getVariables()[index]); 760 } 761 } 762 addConstraint(constraint); 763 } 764 } 765 } 766 for (Map.Entry<Long, Long> e: parents.entrySet()) 767 attributes.get(e.getKey()).setParentAttribute(attributes.get(e.getValue())); 768 for (Map.Entry<TeachingRequest, Map<Integer, Instructor>> e1: best.entrySet()) 769 for (Map.Entry<Integer, Instructor> e2: e1.getValue().entrySet()) 770 if (e2.getKey() >= 0 && e2.getKey() < e1.getKey().getNrInstructors()) { 771 TeachingRequest.Variable variable = e1.getKey().getVariables()[e2.getKey()]; 772 variable.setBestAssignment(new TeachingAssignment(variable, e2.getValue()), 0l); 773 } 774 775 for (Map.Entry<TeachingRequest, Map<Integer, Instructor>> e1: initial.entrySet()) 776 for (Map.Entry<Integer, Instructor> e2: e1.getValue().entrySet()) 777 if (e2.getKey() >= 0 && e2.getKey() < e1.getKey().getNrInstructors()) { 778 TeachingRequest.Variable variable = e1.getKey().getVariables()[e2.getKey()]; 779 variable.setInitialAssignment(new TeachingAssignment(variable, e2.getValue())); 780 } 781 782 if (!current.isEmpty()) { 783 for (Map.Entry<TeachingRequest, Map<Integer, Instructor>> e1: current.entrySet()) 784 for (Map.Entry<Integer, Instructor> e2: e1.getValue().entrySet()) 785 if (e2.getKey() >= 0 && e2.getKey() < e1.getKey().getNrInstructors()) { 786 TeachingRequest.Variable variable = e1.getKey().getVariables()[e2.getKey()]; 787 TeachingAssignment ta = new TeachingAssignment(variable, e2.getValue()); 788 Set<TeachingAssignment> conf = conflictValues(assignment, ta); 789 if (conf.isEmpty()) { 790 assignment.assign(0, ta); 791 } else { 792 sLog.error("Unable to assign " + ta.getName() + " to " + variable.getName()); 793 sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(assignment, ta), 2)); 794 } 795 } 796 } 797 798 return true; 799 } 800 801 /** Convert bitset to a bit string */ 802 protected static String bitset2string(BitSet b) { 803 StringBuffer sb = new StringBuffer(); 804 for (int i = 0; i < b.length(); i++) 805 sb.append(b.get(i) ? "1" : "0"); 806 return sb.toString(); 807 } 808 809 /** Create BitSet from a bit string */ 810 protected static BitSet createBitSet(String bitString) { 811 if (bitString == null) return null; 812 BitSet ret = new BitSet(bitString.length()); 813 for (int i = 0; i < bitString.length(); i++) 814 if (bitString.charAt(i) == '1') 815 ret.set(i); 816 return ret; 817 } 818 819 /** Convert preference string to a preference value */ 820 protected static int string2preference(String pref) { 821 if (pref == null || pref.isEmpty()) return 0; 822 if (Constants.sPreferenceRequired.equals(pref)) 823 return Constants.sPreferenceLevelRequired; 824 if (Constants.sPreferenceProhibited.equals(pref)) 825 return Constants.sPreferenceLevelProhibited; 826 return Integer.valueOf(pref); 827 } 828 829 private List<BitSet> iWeeks = null; 830 /** 831 * The method creates date patterns (bitsets) which represent the weeks of a 832 * semester. 833 * 834 * @return a list of BitSets which represents the weeks of a semester. 835 */ 836 public List<BitSet> getWeeks() { 837 if (iWeeks == null) { 838 String defaultDatePattern = getProperties().getProperty("DatePattern.CustomDatePattern", null); 839 if (defaultDatePattern == null){ 840 defaultDatePattern = getProperties().getProperty("DatePattern.Default"); 841 } 842 BitSet fullTerm = null; 843 if (defaultDatePattern == null) { 844 // Take the date pattern that is being used most often 845 Map<Long, Integer> counter = new HashMap<Long, Integer>(); 846 int max = 0; String name = null; Long id = null; 847 for (TeachingRequest.Variable tr: variables()) { 848 for (Section section: tr.getSections()) { 849 TimeLocation time = section.getTime(); 850 if (time.getWeekCode() != null && time.getDatePatternId() != null) { 851 int count = 1; 852 if (counter.containsKey(time.getDatePatternId())) 853 count += counter.get(time.getDatePatternId()); 854 counter.put(time.getDatePatternId(), count); 855 if (count > max) { 856 max = count; fullTerm = time.getWeekCode(); name = time.getDatePatternName(); id = time.getDatePatternId(); 857 } 858 } 859 } 860 } 861 sLog.info("Using date pattern " + name + " (id " + id + ") as the default."); 862 } else { 863 // Create default date pattern 864 fullTerm = new BitSet(defaultDatePattern.length()); 865 for (int i = 0; i < defaultDatePattern.length(); i++) { 866 if (defaultDatePattern.charAt(i) == 49) { 867 fullTerm.set(i); 868 } 869 } 870 } 871 872 if (fullTerm == null) return null; 873 874 iWeeks = new ArrayList<BitSet>(); 875 if (getProperties().getPropertyBoolean("DatePattern.ShiftWeeks", false)) { 876 // Cut date pattern into weeks (each week takes 7 consecutive bits, starting on the next positive bit) 877 for (int i = fullTerm.nextSetBit(0); i < fullTerm.length(); ) { 878 if (!fullTerm.get(i)) { 879 i++; continue; 880 } 881 BitSet w = new BitSet(i + 7); 882 for (int j = 0; j < 7; j++) 883 if (fullTerm.get(i + j)) w.set(i + j); 884 iWeeks.add(w); 885 i += 7; 886 } 887 } else { 888 // Cut date pattern into weeks (each week takes 7 consecutive bits starting on the first bit of the default date pattern, no pauses between weeks) 889 for (int i = fullTerm.nextSetBit(0); i < fullTerm.length(); ) { 890 BitSet w = new BitSet(i + 7); 891 for (int j = 0; j < 7; j++) 892 if (fullTerm.get(i + j)) w.set(i + j); 893 iWeeks.add(w); 894 i += 7; 895 } 896 } 897 } 898 return iWeeks; 899 } 900}