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