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