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