001package org.cpsolver.coursett.model; 002 003import java.util.ArrayList; 004import java.util.BitSet; 005import java.util.Collection; 006import java.util.HashSet; 007import java.util.Iterator; 008import java.util.HashMap; 009import java.util.List; 010import java.util.Locale; 011import java.util.Map; 012import java.util.Set; 013 014import org.cpsolver.coursett.Constants; 015import org.cpsolver.coursett.constraint.ClassLimitConstraint; 016import org.cpsolver.coursett.constraint.DepartmentSpreadConstraint; 017import org.cpsolver.coursett.constraint.FlexibleConstraint; 018import org.cpsolver.coursett.constraint.GroupConstraint; 019import org.cpsolver.coursett.constraint.InstructorConstraint; 020import org.cpsolver.coursett.constraint.JenrlConstraint; 021import org.cpsolver.coursett.constraint.RoomConstraint; 022import org.cpsolver.coursett.constraint.SpreadConstraint; 023import org.cpsolver.coursett.criteria.BackToBackInstructorPreferences; 024import org.cpsolver.coursett.criteria.BrokenTimePatterns; 025import org.cpsolver.coursett.criteria.DepartmentBalancingPenalty; 026import org.cpsolver.coursett.criteria.DistributionPreferences; 027import org.cpsolver.coursett.criteria.FlexibleConstraintCriterion; 028import org.cpsolver.coursett.criteria.Perturbations; 029import org.cpsolver.coursett.criteria.RoomPreferences; 030import org.cpsolver.coursett.criteria.RoomViolations; 031import org.cpsolver.coursett.criteria.SameSubpartBalancingPenalty; 032import org.cpsolver.coursett.criteria.StudentCommittedConflict; 033import org.cpsolver.coursett.criteria.StudentConflict; 034import org.cpsolver.coursett.criteria.StudentDistanceConflict; 035import org.cpsolver.coursett.criteria.StudentHardConflict; 036import org.cpsolver.coursett.criteria.StudentOverlapConflict; 037import org.cpsolver.coursett.criteria.StudentWorkdayConflict; 038import org.cpsolver.coursett.criteria.TimePreferences; 039import org.cpsolver.coursett.criteria.TimeViolations; 040import org.cpsolver.coursett.criteria.TooBigRooms; 041import org.cpsolver.coursett.criteria.UselessHalfHours; 042import org.cpsolver.coursett.criteria.additional.InstructorConflict; 043import org.cpsolver.coursett.criteria.placement.DeltaTimePreference; 044import org.cpsolver.coursett.criteria.placement.HardConflicts; 045import org.cpsolver.coursett.criteria.placement.PotentialHardConflicts; 046import org.cpsolver.coursett.criteria.placement.WeightedHardConflicts; 047import org.cpsolver.ifs.assignment.Assignment; 048import org.cpsolver.ifs.constant.ConstantModel; 049import org.cpsolver.ifs.criteria.Criterion; 050import org.cpsolver.ifs.model.Constraint; 051import org.cpsolver.ifs.model.GlobalConstraint; 052import org.cpsolver.ifs.model.InfoProvider; 053import org.cpsolver.ifs.model.WeakeningConstraint; 054import org.cpsolver.ifs.solution.Solution; 055import org.cpsolver.ifs.termination.TerminationCondition; 056import org.cpsolver.ifs.util.DataProperties; 057import org.cpsolver.ifs.util.DistanceMetric; 058 059 060/** 061 * Timetable model. 062 * 063 * @author Tomáš Müller 064 * @version CourseTT 1.3 (University Course Timetabling)<br> 065 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 066 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 067 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 068 * <br> 069 * This library is free software; you can redistribute it and/or modify 070 * it under the terms of the GNU Lesser General Public License as 071 * published by the Free Software Foundation; either version 3 of the 072 * License, or (at your option) any later version. <br> 073 * <br> 074 * This library is distributed in the hope that it will be useful, but 075 * WITHOUT ANY WARRANTY; without even the implied warranty of 076 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 077 * Lesser General Public License for more details. <br> 078 * <br> 079 * You should have received a copy of the GNU Lesser General Public 080 * License along with this library; if not see 081 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 082 */ 083 084public class TimetableModel extends ConstantModel<Lecture, Placement> { 085 private static org.apache.logging.log4j.Logger sLogger = org.apache.logging.log4j.LogManager.getLogger(TimetableModel.class); 086 private static java.text.DecimalFormat sDoubleFormat = new java.text.DecimalFormat("0.00", 087 new java.text.DecimalFormatSymbols(Locale.US)); 088 089 private List<InstructorConstraint> iInstructorConstraints = new ArrayList<InstructorConstraint>(); 090 private List<JenrlConstraint> iJenrlConstraints = new ArrayList<JenrlConstraint>(); 091 private List<RoomConstraint> iRoomConstraints = new ArrayList<RoomConstraint>(); 092 private List<DepartmentSpreadConstraint> iDepartmentSpreadConstraints = new ArrayList<DepartmentSpreadConstraint>(); 093 private List<SpreadConstraint> iSpreadConstraints = new ArrayList<SpreadConstraint>(); 094 private List<GroupConstraint> iGroupConstraints = new ArrayList<GroupConstraint>(); 095 private List<ClassLimitConstraint> iClassLimitConstraints = new ArrayList<ClassLimitConstraint>(); 096 private List<FlexibleConstraint> iFlexibleConstraints = new ArrayList<FlexibleConstraint>(); 097 private DataProperties iProperties = null; 098 private int iYear = -1; 099 private BitSet iFullTerm = null, iHoliday = null; 100 private List<BitSet> iWeeks = null; 101 private boolean iOnFlySectioning = false; 102 private int iStudentWorkDayLimit = -1; 103 private boolean iAllowBreakHard = false; 104 105 private HashSet<Student> iAllStudents = new HashSet<Student>(); 106 107 private DistanceMetric iDistanceMetric = null; 108 109 private StudentSectioning iStudentSectioning = null; 110 private List<StudentGroup> iStudentGroups = new ArrayList<StudentGroup>(); 111 112 private boolean iUseCriteria = true; 113 private List<StudentConflict> iStudentConflictCriteria = null; 114 115 @SuppressWarnings("unchecked") 116 public TimetableModel(DataProperties properties) { 117 super(); 118 iProperties = properties; 119 iDistanceMetric = new DistanceMetric(properties); 120 if (properties.getPropertyBoolean("OnFlySectioning.Enabled", false)) { 121 addModelListener(new OnFlySectioning(this)); iOnFlySectioning = true; 122 } 123 iStudentWorkDayLimit = properties.getPropertyInt("StudentConflict.WorkDayLimit", -1); 124 iAllowBreakHard = properties.getPropertyBoolean("General.AllowBreakHard", false); 125 String criteria = properties.getProperty("General.Criteria", 126 // Objectives 127 StudentConflict.class.getName() + ";" + 128 StudentDistanceConflict.class.getName() + ";" + 129 StudentHardConflict.class.getName() + ";" + 130 StudentCommittedConflict.class.getName() + ";" + 131 StudentOverlapConflict.class.getName() + ";" + 132 UselessHalfHours.class.getName() + ";" + 133 BrokenTimePatterns.class.getName() + ";" + 134 TooBigRooms.class.getName() + ";" + 135 TimePreferences.class.getName() + ";" + 136 RoomPreferences.class.getName() + ";" + 137 DistributionPreferences.class.getName() + ";" + 138 SameSubpartBalancingPenalty.class.getName() + ";" + 139 DepartmentBalancingPenalty.class.getName() + ";" + 140 BackToBackInstructorPreferences.class.getName() + ";" + 141 Perturbations.class.getName() + ";" + 142 // Additional placement selection criteria 143 // AssignmentCount.class.getName() + ";" + 144 DeltaTimePreference.class.getName() + ";" + 145 HardConflicts.class.getName() + ";" + 146 PotentialHardConflicts.class.getName() + ";" + 147 FlexibleConstraintCriterion.class.getName() + ";" + 148 WeightedHardConflicts.class.getName()); 149 if (iStudentWorkDayLimit > 0) 150 criteria += ";" + StudentWorkdayConflict.class.getName(); 151 // Interactive mode -- count time / room violations 152 if (properties.getPropertyBoolean("General.InteractiveMode", false)) 153 criteria += ";" + TimeViolations.class.getName() + ";" + RoomViolations.class.getName(); 154 else if (properties.getPropertyBoolean("General.AllowProhibitedRooms", false)) { 155 criteria += ";" + RoomViolations.class.getName(); 156 iAllowBreakHard = true; 157 } 158 // Additional (custom) criteria 159 criteria += ";" + properties.getProperty("General.AdditionalCriteria", ""); 160 for (String criterion: criteria.split("\\;")) { 161 if (criterion == null || criterion.isEmpty()) continue; 162 try { 163 Class<Criterion<Lecture, Placement>> clazz = (Class<Criterion<Lecture, Placement>>)Class.forName(criterion); 164 Criterion<Lecture, Placement> c = clazz.newInstance(); 165 c.configure(properties); 166 addCriterion(c); 167 } catch (Exception e) { 168 sLogger.error("Unable to use " + criterion + ": " + e.getMessage()); 169 } 170 } 171 if (properties.getPropertyBoolean("General.SoftInstructorConstraints", false)) { 172 InstructorConflict ic = new InstructorConflict(); ic.configure(properties); 173 addCriterion(ic); 174 } 175 try { 176 String studentSectioningClassName = properties.getProperty("StudentSectioning.Class", DefaultStudentSectioning.class.getName()); 177 Class<?> studentSectioningClass = Class.forName(studentSectioningClassName); 178 iStudentSectioning = (StudentSectioning)studentSectioningClass.getConstructor(TimetableModel.class).newInstance(this); 179 } catch (Exception e) { 180 sLogger.error("Failed to load custom student sectioning class: " + e.getMessage()); 181 iStudentSectioning = new DefaultStudentSectioning(this); 182 } 183 if (iStudentSectioning instanceof InfoProvider<?, ?>) { 184 getInfoProviders().add((InfoProvider<Lecture, Placement>)iStudentSectioning); 185 } 186 String constraints = properties.getProperty("General.GlobalConstraints", ""); 187 for (String constraint: constraints.split("\\;")) { 188 if (constraint == null || constraint.isEmpty()) continue; 189 try { 190 Class<GlobalConstraint<Lecture, Placement>> clazz = (Class<GlobalConstraint<Lecture, Placement>>)Class.forName(constraint); 191 GlobalConstraint<Lecture, Placement> c = clazz.newInstance(); 192 addGlobalConstraint(c); 193 } catch (Exception e) { 194 sLogger.error("Unable to use " + constraint + ": " + e.getMessage()); 195 } 196 } 197 iUseCriteria = properties.getPropertyBoolean("SctSectioning.UseCriteria", true); 198 } 199 200 public DistanceMetric getDistanceMetric() { 201 return iDistanceMetric; 202 } 203 204 public int getStudentWorkDayLimit() { 205 return iStudentWorkDayLimit; 206 } 207 208 /** 209 * Returns interface to the student sectioning functions needed during course timetabling. 210 * Defaults to an instance of {@link DefaultStudentSectioning}, can be changed using the StudentSectioning.Class parameter. 211 * @return student sectioning 212 */ 213 public StudentSectioning getStudentSectioning() { 214 return iStudentSectioning; 215 } 216 217 public DataProperties getProperties() { 218 return iProperties; 219 } 220 221 /** 222 * Student final sectioning (switching students between sections of the same 223 * class in order to minimize overall number of student conflicts) 224 * @param assignment current assignment 225 * @param termination optional termination condition 226 */ 227 public void switchStudents(Assignment<Lecture, Placement> assignment, TerminationCondition<Lecture, Placement> termination) { 228 getStudentSectioning().switchStudents(new Solution<Lecture, Placement>(this, assignment), termination); 229 } 230 231 /** 232 * Student final sectioning (switching students between sections of the same 233 * class in order to minimize overall number of student conflicts) 234 * @param assignment current assignment 235 */ 236 public void switchStudents(Assignment<Lecture, Placement> assignment) { 237 getStudentSectioning().switchStudents(new Solution<Lecture, Placement>(this, assignment), null); 238 } 239 240 public Map<String, String> getBounds(Assignment<Lecture, Placement> assignment) { 241 Map<String, String> ret = new HashMap<String, String>(); 242 ret.put("Room preferences min", "" + getCriterion(RoomPreferences.class).getBounds(assignment)[0]); 243 ret.put("Room preferences max", "" + getCriterion(RoomPreferences.class).getBounds(assignment)[1]); 244 ret.put("Time preferences min", "" + getCriterion(TimePreferences.class).getBounds(assignment)[0]); 245 ret.put("Time preferences max", "" + getCriterion(TimePreferences.class).getBounds(assignment)[1]); 246 ret.put("Distribution preferences min", "" + getCriterion(DistributionPreferences.class).getBounds(assignment)[0]); 247 ret.put("Distribution preferences max", "" + getCriterion(DistributionPreferences.class).getBounds(assignment)[1]); 248 if (getProperties().getPropertyBoolean("General.UseDistanceConstraints", false)) { 249 ret.put("Back-to-back instructor preferences max", "" + getCriterion(BackToBackInstructorPreferences.class).getBounds(assignment)[1]); 250 } 251 ret.put("Too big rooms max", "" + getCriterion(TooBigRooms.class).getBounds(assignment)[0]); 252 ret.put("Useless half-hours", "" + getCriterion(UselessHalfHours.class).getBounds(assignment)[0]); 253 return ret; 254 } 255 256 /** Global info */ 257 @Override 258 public Map<String, String> getInfo(Assignment<Lecture, Placement> assignment) { 259 Map<String, String> ret = super.getInfo(assignment); 260 ret.put("Memory usage", getMem()); 261 262 Criterion<Lecture, Placement> rp = getCriterion(RoomPreferences.class); 263 Criterion<Lecture, Placement> rv = getCriterion(RoomViolations.class); 264 ret.put("Room preferences", getPerc(rp.getValue(assignment), rp.getBounds(assignment)[0], rp.getBounds(assignment)[1]) + "% (" + Math.round(rp.getValue(assignment)) + ")" 265 + (rv != null && rv.getValue(assignment) >= 0.5 ? " [hard:" + Math.round(rv.getValue(assignment)) + "]" : "")); 266 267 Criterion<Lecture, Placement> tp = getCriterion(TimePreferences.class); 268 Criterion<Lecture, Placement> tv = getCriterion(TimeViolations.class); 269 ret.put("Time preferences", getPerc(tp.getValue(assignment), tp.getBounds(assignment)[0], tp.getBounds(assignment)[1]) + "% (" + sDoubleFormat.format(tp.getValue(assignment)) + ")" 270 + (tv != null && tv.getValue(assignment) >= 0.5 ? " [hard:" + Math.round(tv.getValue(assignment)) + "]" : "")); 271 272 Criterion<Lecture, Placement> dp = getCriterion(DistributionPreferences.class); 273 ret.put("Distribution preferences", getPerc(dp.getValue(assignment), dp.getBounds(assignment)[0], dp.getBounds(assignment)[1]) + "% (" + sDoubleFormat.format(dp.getValue(assignment)) + ")"); 274 275 Criterion<Lecture, Placement> sc = getCriterion(StudentConflict.class); 276 Criterion<Lecture, Placement> shc = getCriterion(StudentHardConflict.class); 277 Criterion<Lecture, Placement> sdc = getCriterion(StudentDistanceConflict.class); 278 Criterion<Lecture, Placement> scc = getCriterion(StudentCommittedConflict.class); 279 ret.put("Student conflicts", Math.round(scc.getValue(assignment) + sc.getValue(assignment)) + 280 " [committed:" + Math.round(scc.getValue(assignment)) + 281 ", distance:" + Math.round(sdc.getValue(assignment)) + 282 ", hard:" + Math.round(shc.getValue(assignment)) + "]"); 283 284 if (!getSpreadConstraints().isEmpty()) { 285 Criterion<Lecture, Placement> ip = getCriterion(BackToBackInstructorPreferences.class); 286 ret.put("Back-to-back instructor preferences", getPerc(ip.getValue(assignment), ip.getBounds(assignment)[0], ip.getBounds(assignment)[1]) + "% (" + Math.round(ip.getValue(assignment)) + ")"); 287 } 288 289 if (!getDepartmentSpreadConstraints().isEmpty()) { 290 Criterion<Lecture, Placement> dbp = getCriterion(DepartmentBalancingPenalty.class); 291 ret.put("Department balancing penalty", sDoubleFormat.format(dbp.getValue(assignment))); 292 } 293 294 Criterion<Lecture, Placement> sbp = getCriterion(SameSubpartBalancingPenalty.class); 295 ret.put("Same subpart balancing penalty", sDoubleFormat.format(sbp.getValue(assignment))); 296 297 Criterion<Lecture, Placement> tbr = getCriterion(TooBigRooms.class); 298 ret.put("Too big rooms", getPercRev(tbr.getValue(assignment), tbr.getBounds(assignment)[1], tbr.getBounds(assignment)[0]) + "% (" + Math.round(tbr.getValue(assignment)) + ")"); 299 300 Criterion<Lecture, Placement> uh = getCriterion(UselessHalfHours.class); 301 Criterion<Lecture, Placement> bt = getCriterion(BrokenTimePatterns.class); 302 303 ret.put("Useless half-hours", getPercRev(uh.getValue(assignment) + bt.getValue(assignment), 0, Constants.sPreferenceLevelStronglyDiscouraged * bt.getBounds(assignment)[0]) + 304 "% (" + Math.round(uh.getValue(assignment)) + " + " + Math.round(bt.getValue(assignment)) + ")"); 305 return ret; 306 } 307 308 @Override 309 public Map<String, String> getInfo(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) { 310 Map<String, String> ret = super.getInfo(assignment, variables); 311 312 ret.put("Memory usage", getMem()); 313 314 Criterion<Lecture, Placement> rp = getCriterion(RoomPreferences.class); 315 ret.put("Room preferences", getPerc(rp.getValue(assignment, variables), rp.getBounds(assignment, variables)[0], rp.getBounds(assignment, variables)[1]) + "% (" + Math.round(rp.getValue(assignment, variables)) + ")"); 316 317 Criterion<Lecture, Placement> tp = getCriterion(TimePreferences.class); 318 ret.put("Time preferences", getPerc(tp.getValue(assignment, variables), tp.getBounds(assignment, variables)[0], tp.getBounds(assignment, variables)[1]) + "% (" + sDoubleFormat.format(tp.getValue(assignment, variables)) + ")"); 319 320 Criterion<Lecture, Placement> dp = getCriterion(DistributionPreferences.class); 321 ret.put("Distribution preferences", getPerc(dp.getValue(assignment, variables), dp.getBounds(assignment, variables)[0], dp.getBounds(assignment, variables)[1]) + "% (" + sDoubleFormat.format(dp.getValue(assignment, variables)) + ")"); 322 323 Criterion<Lecture, Placement> sc = getCriterion(StudentConflict.class); 324 Criterion<Lecture, Placement> shc = getCriterion(StudentHardConflict.class); 325 Criterion<Lecture, Placement> sdc = getCriterion(StudentDistanceConflict.class); 326 Criterion<Lecture, Placement> scc = getCriterion(StudentCommittedConflict.class); 327 ret.put("Student conflicts", Math.round(scc.getValue(assignment, variables) + sc.getValue(assignment, variables)) + 328 " [committed:" + Math.round(scc.getValue(assignment, variables)) + 329 ", distance:" + Math.round(sdc.getValue(assignment, variables)) + 330 ", hard:" + Math.round(shc.getValue(assignment, variables)) + "]"); 331 332 if (!getSpreadConstraints().isEmpty()) { 333 Criterion<Lecture, Placement> ip = getCriterion(BackToBackInstructorPreferences.class); 334 ret.put("Back-to-back instructor preferences", getPerc(ip.getValue(assignment, variables), ip.getBounds(assignment, variables)[0], ip.getBounds(assignment, variables)[1]) + "% (" + Math.round(ip.getValue(assignment, variables)) + ")"); 335 } 336 337 if (!getDepartmentSpreadConstraints().isEmpty()) { 338 Criterion<Lecture, Placement> dbp = getCriterion(DepartmentBalancingPenalty.class); 339 ret.put("Department balancing penalty", sDoubleFormat.format(dbp.getValue(assignment, variables))); 340 } 341 342 Criterion<Lecture, Placement> sbp = getCriterion(SameSubpartBalancingPenalty.class); 343 ret.put("Same subpart balancing penalty", sDoubleFormat.format(sbp.getValue(assignment, variables))); 344 345 Criterion<Lecture, Placement> tbr = getCriterion(TooBigRooms.class); 346 ret.put("Too big rooms", getPercRev(tbr.getValue(assignment, variables), tbr.getBounds(assignment, variables)[1], tbr.getBounds(assignment, variables)[0]) + "% (" + Math.round(tbr.getValue(assignment, variables)) + ")"); 347 348 Criterion<Lecture, Placement> uh = getCriterion(UselessHalfHours.class); 349 Criterion<Lecture, Placement> bt = getCriterion(BrokenTimePatterns.class); 350 351 ret.put("Useless half-hours", getPercRev(uh.getValue(assignment, variables) + bt.getValue(assignment, variables), 0, Constants.sPreferenceLevelStronglyDiscouraged * bt.getBounds(assignment, variables)[0]) + 352 "% (" + Math.round(uh.getValue(assignment, variables)) + " + " + Math.round(bt.getValue(assignment, variables)) + ")"); 353 return ret; 354 } 355 356 @Override 357 public void addConstraint(Constraint<Lecture, Placement> constraint) { 358 super.addConstraint(constraint); 359 if (constraint instanceof InstructorConstraint) { 360 iInstructorConstraints.add((InstructorConstraint) constraint); 361 } else if (constraint instanceof JenrlConstraint) { 362 iJenrlConstraints.add((JenrlConstraint) constraint); 363 } else if (constraint instanceof RoomConstraint) { 364 iRoomConstraints.add((RoomConstraint) constraint); 365 } else if (constraint instanceof DepartmentSpreadConstraint) { 366 iDepartmentSpreadConstraints.add((DepartmentSpreadConstraint) constraint); 367 } else if (constraint instanceof SpreadConstraint) { 368 iSpreadConstraints.add((SpreadConstraint) constraint); 369 } else if (constraint instanceof ClassLimitConstraint) { 370 iClassLimitConstraints.add((ClassLimitConstraint) constraint); 371 } else if (constraint instanceof GroupConstraint) { 372 iGroupConstraints.add((GroupConstraint) constraint); 373 } else if (constraint instanceof FlexibleConstraint) { 374 iFlexibleConstraints.add((FlexibleConstraint) constraint); 375 } 376 } 377 378 @Override 379 public void removeConstraint(Constraint<Lecture, Placement> constraint) { 380 super.removeConstraint(constraint); 381 if (constraint instanceof InstructorConstraint) { 382 iInstructorConstraints.remove(constraint); 383 } else if (constraint instanceof JenrlConstraint) { 384 iJenrlConstraints.remove(constraint); 385 } else if (constraint instanceof RoomConstraint) { 386 iRoomConstraints.remove(constraint); 387 } else if (constraint instanceof DepartmentSpreadConstraint) { 388 iDepartmentSpreadConstraints.remove(constraint); 389 } else if (constraint instanceof SpreadConstraint) { 390 iSpreadConstraints.remove(constraint); 391 } else if (constraint instanceof ClassLimitConstraint) { 392 iClassLimitConstraints.remove(constraint); 393 } else if (constraint instanceof GroupConstraint) { 394 iGroupConstraints.remove(constraint); 395 } else if (constraint instanceof FlexibleConstraint) { 396 iFlexibleConstraints.remove(constraint); 397 } 398 } 399 400 /** The list of all instructor constraints 401 * @return list of instructor constraints 402 **/ 403 public List<InstructorConstraint> getInstructorConstraints() { 404 return iInstructorConstraints; 405 } 406 407 /** The list of all group constraints 408 * @return list of group (distribution) constraints 409 **/ 410 public List<GroupConstraint> getGroupConstraints() { 411 return iGroupConstraints; 412 } 413 414 /** The list of all jenrl constraints 415 * @return list of join enrollment constraints 416 **/ 417 public List<JenrlConstraint> getJenrlConstraints() { 418 return iJenrlConstraints; 419 } 420 421 /** The list of all room constraints 422 * @return list of room constraints 423 **/ 424 public List<RoomConstraint> getRoomConstraints() { 425 return iRoomConstraints; 426 } 427 428 /** The list of all departmental spread constraints 429 * @return list of department spread constraints 430 **/ 431 public List<DepartmentSpreadConstraint> getDepartmentSpreadConstraints() { 432 return iDepartmentSpreadConstraints; 433 } 434 435 public List<SpreadConstraint> getSpreadConstraints() { 436 return iSpreadConstraints; 437 } 438 439 public List<ClassLimitConstraint> getClassLimitConstraints() { 440 return iClassLimitConstraints; 441 } 442 443 public List<FlexibleConstraint> getFlexibleConstraints() { 444 return iFlexibleConstraints; 445 } 446 447 @Override 448 public double getTotalValue(Assignment<Lecture, Placement> assignment) { 449 double ret = 0; 450 for (Criterion<Lecture, Placement> criterion: getCriteria()) 451 ret += criterion.getWeightedValue(assignment); 452 return ret; 453 } 454 455 @Override 456 public double getTotalValue(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) { 457 double ret = 0; 458 for (Criterion<Lecture, Placement> criterion: getCriteria()) 459 ret += criterion.getWeightedValue(assignment, variables); 460 return ret; 461 } 462 463 public int getYear() { 464 return iYear; 465 } 466 467 public void setYear(int year) { 468 iYear = year; 469 } 470 471 public Set<Student> getAllStudents() { 472 return iAllStudents; 473 } 474 475 public void addStudent(Student student) { 476 iAllStudents.add(student); 477 } 478 479 public void removeStudent(Student student) { 480 iAllStudents.remove(student); 481 } 482 483 /** 484 * Returns amount of allocated memory. 485 * 486 * @return amount of allocated memory to be written in the log 487 */ 488 public static synchronized String getMem() { 489 Runtime rt = Runtime.getRuntime(); 490 return sDoubleFormat.format(((double) (rt.totalMemory() - rt.freeMemory())) / 1048576) + "M"; 491 } 492 493 494 /** 495 * Returns the set of conflicting variables with this value, if it is 496 * assigned to its variable. Conflicts with constraints that implement 497 * {@link WeakeningConstraint} are ignored. 498 * @param assignment current assignment 499 * @param value placement that is being considered 500 * @return computed conflicting assignments 501 */ 502 public Set<Placement> conflictValuesSkipWeakeningConstraints(Assignment<Lecture, Placement> assignment, Placement value) { 503 Set<Placement> conflictValues = new HashSet<Placement>(); 504 for (Constraint<Lecture, Placement> constraint : value.variable().hardConstraints()) { 505 if (constraint instanceof WeakeningConstraint) continue; 506 if (constraint instanceof GroupConstraint) 507 ((GroupConstraint)constraint).computeConflictsNoForwardCheck(assignment, value, conflictValues); 508 else 509 constraint.computeConflicts(assignment, value, conflictValues); 510 } 511 for (GlobalConstraint<Lecture, Placement> constraint : globalConstraints()) { 512 if (constraint instanceof WeakeningConstraint) continue; 513 constraint.computeConflicts(assignment, value, conflictValues); 514 } 515 return conflictValues; 516 } 517 518 519 /** 520 * The method returns the default date pattern. 521 * It is typically set using the DatePattern.Default parameter. 522 * If not set, return the most commonly used date pattern. 523 * 524 * @return default date pattern 525 */ 526 public BitSet getDefaultDatePattern() { 527 if (iFullTerm == null) { 528 String defaultDatePattern = getProperties().getProperty("DatePattern.CustomDatePattern", null); 529 if (defaultDatePattern == null){ 530 defaultDatePattern = getProperties().getProperty("DatePattern.Default"); 531 } 532 if (defaultDatePattern == null) { 533 // Take the date pattern that is being used most often 534 Map<Long, Integer> counter = new HashMap<Long, Integer>(); 535 int max = 0; String name = null; Long id = null; 536 for (Lecture lecture: variables()) { 537 if (lecture.isCommitted()) continue; 538 for (TimeLocation time: lecture.timeLocations()) { 539 if (time.getWeekCode() != null && time.getDatePatternId() != null) { 540 int count = 1; 541 if (counter.containsKey(time.getDatePatternId())) 542 count += counter.get(time.getDatePatternId()); 543 counter.put(time.getDatePatternId(), count); 544 if (count > max) { 545 max = count; iFullTerm = time.getWeekCode(); name = time.getDatePatternName(); id = time.getDatePatternId(); 546 } 547 } 548 } 549 } 550 sLogger.info("Using date pattern " + name + " (id " + id + ") as the default."); 551 } else { 552 // Create default date pattern 553 iFullTerm = new BitSet(defaultDatePattern.length()); 554 for (int i = 0; i < defaultDatePattern.length(); i++) { 555 if (defaultDatePattern.charAt(i) == 49) { 556 iFullTerm.set(i); 557 } 558 } 559 } 560 } 561 return iFullTerm; 562 } 563 564 /** 565 * The method returns the holiday date pattern. 566 * It is typically set using the DatePattern.Holiday parameter. 567 * 568 * @return holiday date pattern (1 holiday, 0 ordinary day) 569 */ 570 public BitSet getHolidayDatePattern() { 571 if (iHoliday == null) { 572 String holidayDatePattern = getProperties().getProperty("DatePattern.Holidays", null); 573 if (holidayDatePattern != null) { 574 // Create default date pattern 575 iHoliday = new BitSet(holidayDatePattern.length()); 576 for (int i = 0; i < holidayDatePattern.length(); i++) { 577 if (holidayDatePattern.charAt(i) == '1' || holidayDatePattern.charAt(i) == '2') { 578 iHoliday.set(i); 579 } 580 } 581 } 582 } 583 return iHoliday; 584 } 585 586 /** 587 * The method creates date patterns (bitsets) which represent the weeks of a 588 * semester. 589 * 590 * @return a list of BitSets which represents the weeks of a semester. 591 */ 592 public List<BitSet> getWeeks() { 593 if (iWeeks == null) { 594 BitSet fullTerm = getDefaultDatePattern(); 595 if (fullTerm == null) return null; 596 597 iWeeks = new ArrayList<BitSet>(); 598 if (getProperties().getPropertyBoolean("DatePattern.ShiftWeeks", false)) { 599 // Cut date pattern into weeks (each week takes 7 consecutive bits, starting on the next positive bit) 600 for (int i = fullTerm.nextSetBit(0); i < fullTerm.length(); ) { 601 if (!fullTerm.get(i)) { 602 i++; continue; 603 } 604 BitSet w = new BitSet(i + 7); 605 for (int j = 0; j < 7; j++) 606 if (fullTerm.get(i + j)) w.set(i + j); 607 iWeeks.add(w); 608 i += 7; 609 } 610 } else { 611 // 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) 612 for (int i = fullTerm.nextSetBit(0); i < fullTerm.length(); ) { 613 BitSet w = new BitSet(i + 7); 614 for (int j = 0; j < 7; j++) 615 if (fullTerm.get(i + j)) w.set(i + j); 616 iWeeks.add(w); 617 i += 7; 618 } 619 } 620 } 621 return iWeeks; 622 } 623 624 public List<StudentGroup> getStudentGroups() { return iStudentGroups; } 625 public void addStudentGroup(StudentGroup group) { iStudentGroups.add(group); } 626 627 Map<Student, Set<Lecture>> iBestEnrollment = null; 628 @Override 629 public void saveBest(Assignment<Lecture, Placement> assignment) { 630 super.saveBest(assignment); 631 if (iOnFlySectioning) { 632 if (iBestEnrollment == null) 633 iBestEnrollment = new HashMap<Student, Set<Lecture>>(); 634 else 635 iBestEnrollment.clear(); 636 for (Student student: getAllStudents()) 637 iBestEnrollment.put(student, new HashSet<Lecture>(student.getLectures())); 638 } 639 } 640 641 /** 642 * Increment {@link JenrlConstraint} between the given two classes by the given student 643 */ 644 protected void incJenrl(Assignment<Lecture, Placement> assignment, Student student, Lecture l1, Lecture l2) { 645 if (l1.equals(l2)) return; 646 JenrlConstraint jenrl = l1.jenrlConstraint(l2); 647 if (jenrl == null) { 648 jenrl = new JenrlConstraint(); 649 jenrl.addVariable(l1); 650 jenrl.addVariable(l2); 651 addConstraint(jenrl); 652 } 653 jenrl.incJenrl(assignment, student); 654 } 655 656 /** 657 * Decrement {@link JenrlConstraint} between the given two classes by the given student 658 */ 659 protected void decJenrl(Assignment<Lecture, Placement> assignment, Student student, Lecture l1, Lecture l2) { 660 if (l1.equals(l2)) return; 661 JenrlConstraint jenrl = l1.jenrlConstraint(l2); 662 if (jenrl != null) { 663 jenrl.decJenrl(assignment, student); 664 } 665 } 666 667 @Override 668 public void restoreBest(Assignment<Lecture, Placement> assignment) { 669 if (iOnFlySectioning && iBestEnrollment != null) { 670 671 // unassign changed classes 672 for (Lecture lecture: variables()) { 673 Placement placement = assignment.getValue(lecture); 674 if (placement != null && !placement.equals(lecture.getBestAssignment())) 675 assignment.unassign(0, lecture); 676 } 677 678 for (Map.Entry<Student, Set<Lecture>> entry: iBestEnrollment.entrySet()) { 679 Student student = entry.getKey(); 680 Set<Lecture> lectures = entry.getValue(); 681 Set<Configuration> configs = new HashSet<Configuration>(); 682 for (Lecture lecture: lectures) 683 if (lecture.getConfiguration() != null) configs.add(lecture.getConfiguration()); 684 685 // drop student from classes that are not in the best enrollment 686 for (Lecture lecture: new ArrayList<Lecture>(student.getLectures())) { 687 if (lectures.contains(lecture)) continue; // included in best 688 for (Lecture other: student.getLectures()) 689 decJenrl(assignment, student, lecture, other); 690 lecture.removeStudent(assignment, student); 691 student.removeLecture(lecture); 692 if (lecture.getConfiguration() != null && !configs.contains(lecture.getConfiguration())) 693 student.removeConfiguration(lecture.getConfiguration()); 694 } 695 696 // add student to classes that are in the best enrollment 697 for (Lecture lecture: lectures) { 698 if (student.getLectures().contains(lecture)) continue; // already in 699 for (Lecture other: student.getLectures()) 700 incJenrl(assignment, student, lecture, other); 701 lecture.addStudent(assignment, student); 702 student.addLecture(lecture); 703 student.addConfiguration(lecture.getConfiguration()); 704 } 705 } 706 // remove empty joint enrollments 707 for (Iterator<JenrlConstraint> i = iJenrlConstraints.iterator(); i.hasNext(); ) { 708 JenrlConstraint jenrl = i.next(); 709 if (jenrl.getNrStudents() == 0) { 710 jenrl.getContext(assignment).unassigned(assignment, null); 711 Object[] vars = jenrl.variables().toArray(); 712 for (int k = 0; k < vars.length; k++) 713 jenrl.removeVariable((Lecture) vars[k]); 714 i.remove(); 715 } 716 } 717 for (Iterator<Constraint<Lecture, Placement>> i = constraints().iterator(); i.hasNext(); ) { 718 Constraint<Lecture, Placement> c = i.next(); 719 if (c instanceof JenrlConstraint && ((JenrlConstraint)c).getNrStudents() == 0) { 720 removeReference((JenrlConstraint)c); 721 i.remove(); 722 } 723 } 724 /* 725 for (JenrlConstraint jenrl: new ArrayList<JenrlConstraint>(getJenrlConstraints())) { 726 if (jenrl.getNrStudents() == 0) { 727 jenrl.getContext(assignment).unassigned(assignment, null); 728 Object[] vars = jenrl.variables().toArray(); 729 for (int k = 0; k < vars.length; k++) 730 jenrl.removeVariable((Lecture) vars[k]); 731 removeConstraint(jenrl); 732 } 733 } 734 */ 735 } 736 super.restoreBest(assignment); 737 } 738 739 public boolean isAllowBreakHard() { return iAllowBreakHard; } 740 741 public boolean isOnFlySectioningEnabled() { return iOnFlySectioning; } 742 public void setOnFlySectioningEnabled(boolean onFlySectioning) { iOnFlySectioning = onFlySectioning; } 743 744 @Override 745 public void addCriterion(Criterion<Lecture, Placement> criterion) { 746 super.addCriterion(criterion); 747 iStudentConflictCriteria = null; 748 } 749 750 @Override 751 public void removeCriterion(Criterion<Lecture, Placement> criterion) { 752 super.removeCriterion(criterion); 753 iStudentConflictCriteria = null; 754 } 755 756 /** 757 * List of student conflict criteria 758 */ 759 public List<StudentConflict> getStudentConflictCriteria() { 760 if (!iUseCriteria) return null; 761 if (iStudentConflictCriteria == null) { 762 iStudentConflictCriteria = new ArrayList<StudentConflict>(); 763 for (Criterion<Lecture, Placement> criterion: getCriteria()) 764 if (criterion instanceof StudentConflict) 765 iStudentConflictCriteria.add((StudentConflict)criterion); 766 } 767 return iStudentConflictCriteria; 768 } 769}