001package org.cpsolver.coursett.model; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.Collections; 006import java.util.Comparator; 007import java.util.Enumeration; 008import java.util.HashSet; 009import java.util.HashMap; 010import java.util.Iterator; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014import java.util.concurrent.atomic.AtomicReference; 015import java.util.concurrent.locks.ReentrantReadWriteLock; 016 017import org.cpsolver.coursett.Constants; 018import org.cpsolver.coursett.constraint.ClassLimitConstraint; 019import org.cpsolver.coursett.constraint.DepartmentSpreadConstraint; 020import org.cpsolver.coursett.constraint.FlexibleConstraint; 021import org.cpsolver.coursett.constraint.GroupConstraint; 022import org.cpsolver.coursett.constraint.IgnoreStudentConflictsConstraint; 023import org.cpsolver.coursett.constraint.InstructorConstraint; 024import org.cpsolver.coursett.constraint.JenrlConstraint; 025import org.cpsolver.coursett.constraint.RoomConstraint; 026import org.cpsolver.coursett.constraint.SpreadConstraint; 027import org.cpsolver.coursett.criteria.StudentCommittedConflict; 028import org.cpsolver.coursett.criteria.StudentConflict; 029import org.cpsolver.ifs.assignment.Assignment; 030import org.cpsolver.ifs.assignment.context.AssignmentContext; 031import org.cpsolver.ifs.assignment.context.VariableWithContext; 032import org.cpsolver.ifs.constant.ConstantVariable; 033import org.cpsolver.ifs.model.Constraint; 034import org.cpsolver.ifs.model.GlobalConstraint; 035import org.cpsolver.ifs.model.WeakeningConstraint; 036import org.cpsolver.ifs.util.DistanceMetric; 037import org.cpsolver.ifs.util.ToolBox; 038 039 040/** 041 * Lecture (variable). 042 * 043 * @version CourseTT 1.3 (University Course Timetabling)<br> 044 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 045 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 046 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 047 * <br> 048 * This library is free software; you can redistribute it and/or modify 049 * it under the terms of the GNU Lesser General Public License as 050 * published by the Free Software Foundation; either version 3 of the 051 * License, or (at your option) any later version. <br> 052 * <br> 053 * This library is distributed in the hope that it will be useful, but 054 * WITHOUT ANY WARRANTY; without even the implied warranty of 055 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 056 * Lesser General Public License for more details. <br> 057 * <br> 058 * You should have received a copy of the GNU Lesser General Public 059 * License along with this library; if not see 060 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 061 */ 062 063public class Lecture extends VariableWithContext<Lecture, Placement, Lecture.LectureContext> implements ConstantVariable<Placement> { 064 private Long iClassId; 065 private Long iSolverGroupId; 066 private Long iSchedulingSubpartId; 067 private String iName; 068 private Long iDept; 069 private Long iScheduler; 070 private List<TimeLocation> iTimeLocations; 071 private List<RoomLocation> iRoomLocations; 072 private String iNote = null; 073 074 private int iMinClassLimit; 075 private int iMaxClassLimit; 076 private float iRoomToLimitRatio; 077 private int iNrRooms; 078 private int iOrd; 079 private double iWeight = 1.0; 080 private boolean iSplitAttendance = false; 081 082 private Set<Student> iStudents = new HashSet<Student>(); 083 private DepartmentSpreadConstraint iDeptSpreadConstraint = null; 084 private Set<SpreadConstraint> iSpreadConstraints = new HashSet<SpreadConstraint>(); 085 private Set<Constraint<Lecture, Placement>> iWeakeningConstraints = new HashSet<Constraint<Lecture, Placement>>(); 086 private List<InstructorConstraint> iInstructorConstraints = new ArrayList<InstructorConstraint>(); 087 private AtomicReference<Set<Long>> iIgnoreStudentConflictsWith = new AtomicReference<Set<Long>>(); 088 private ClassLimitConstraint iClassLimitConstraint = null; 089 090 private Lecture iParent = null; 091 private HashMap<Long, List<Lecture>> iChildren = null; 092 private java.util.List<Lecture> iSameSubpartLectures = null; 093 private Configuration iParentConfiguration = null; 094 095 private List<JenrlConstraint> iJenrlConstraints = new ArrayList<JenrlConstraint>(); 096 private HashMap<Lecture, JenrlConstraint> iJenrlConstraintsHash = new HashMap<Lecture, JenrlConstraint>(); 097 private HashMap<Placement, Integer> iCommitedConflicts = new HashMap<Placement, Integer>(); 098 private Set<GroupConstraint> iGroupConstraints = new HashSet<GroupConstraint>(); 099 private Set<GroupConstraint> iHardGroupSoftConstraints = new HashSet<GroupConstraint>(); 100 private Set<GroupConstraint> iCanShareRoomGroupConstraints = new HashSet<GroupConstraint>(); 101 private Set<FlexibleConstraint> iFlexibleGroupConstraints = new HashSet<FlexibleConstraint>(); 102 103 public boolean iCommitted = false; 104 105 public static boolean sSaveMemory = false; 106 private int iMaxRoomCombinations = -1; 107 108 private Integer iCacheMinRoomSize = null; 109 private Integer iCacheMaxRoomSize = null; 110 private Integer iCacheMaxAchievableClassLimit = null; 111 112 private final ReentrantReadWriteLock iLock = new ReentrantReadWriteLock(); 113 114 /** 115 * Constructor 116 * 117 * @param id class unique id 118 * @param solverGroupId solver group unique id 119 * @param schedulingSubpartId scheduling subpart unique id 120 * @param name class name 121 * @param timeLocations set of time locations 122 * @param roomLocations set of room location 123 * @param nrRooms number of rooms into which the class is to be assigned 124 * @param initialPlacement initial placement 125 * @param minClassLimit minimum class limit 126 * @param maxClassLimit maximum class limit 127 * @param room2limitRatio room ratio 128 */ 129 public Lecture(Long id, Long solverGroupId, Long schedulingSubpartId, String name, 130 Collection<TimeLocation> timeLocations, Collection<RoomLocation> roomLocations, int nrRooms, 131 Placement initialPlacement, int minClassLimit, int maxClassLimit, double room2limitRatio) { 132 super(initialPlacement); 133 iClassId = id; 134 iSchedulingSubpartId = schedulingSubpartId; 135 iTimeLocations = new ArrayList<TimeLocation>(timeLocations); 136 iRoomLocations = new ArrayList<RoomLocation>(roomLocations); 137 iName = name; 138 iMinClassLimit = minClassLimit; 139 iMaxClassLimit = maxClassLimit; 140 iRoomToLimitRatio = (float)room2limitRatio; 141 iNrRooms = nrRooms; 142 iSolverGroupId = solverGroupId; 143 } 144 145 public Lecture(Long id, Long solverGroupId, String name) { 146 super(null); 147 iClassId = id; 148 iSolverGroupId = solverGroupId; 149 iName = name; 150 } 151 152 public Long getSolverGroupId() { 153 return iSolverGroupId; 154 } 155 156 /** 157 * Add active jenrl constraint (active mean that there is at least one 158 * student between its classes) 159 * @param assignment current assignment 160 * @param constr an active jenrl constraint 161 */ 162 public void addActiveJenrl(Assignment<Lecture, Placement> assignment, JenrlConstraint constr) { 163 getContext(assignment).addActiveJenrl(constr); 164 } 165 166 /** 167 * Active jenrl constraints (active mean that there is at least one student 168 * between its classes) 169 * @param assignment current assignment 170 * @return set of active jenrl constraints 171 */ 172 public Set<JenrlConstraint> activeJenrls(Assignment<Lecture, Placement> assignment) { 173 return getContext(assignment).activeJenrls(); 174 } 175 176 /** 177 * Remove active jenrl constraint (active mean that there is at least one 178 * student between its classes) 179 * @param assignment current assignment 180 * @param constr an active jenrl constraint 181 */ 182 public void removeActiveJenrl(Assignment<Lecture, Placement> assignment, JenrlConstraint constr) { 183 getContext(assignment).removeActiveJenrl(constr); 184 } 185 186 /** Class id 187 * @return class unique id 188 **/ 189 public Long getClassId() { 190 return iClassId; 191 } 192 193 /** 194 * Scheduling subpart id 195 * @return scheduling subpart unique id 196 */ 197 public Long getSchedulingSubpartId() { 198 return iSchedulingSubpartId; 199 } 200 201 /** Class name */ 202 @Override 203 public String getName() { 204 return iName; 205 } 206 207 /** Class id */ 208 @Override 209 public long getId() { 210 return iClassId.longValue(); 211 } 212 213 /** Instructor name 214 * @return list of instructor names 215 **/ 216 public List<String> getInstructorNames() { 217 List<String> ret = new ArrayList<String>(); 218 for (InstructorConstraint ic : iInstructorConstraints) { 219 ret.add(ic.getName()); 220 } 221 return ret; 222 } 223 224 public String getInstructorName() { 225 StringBuffer sb = new StringBuffer(); 226 for (InstructorConstraint ic : iInstructorConstraints) { 227 if (sb.length() > 0) 228 sb.append(", "); 229 sb.append(ic.getName()); 230 } 231 return sb.toString(); 232 } 233 234 /** List of enrolled students 235 * @return list of enrolled students 236 **/ 237 public Set<Student> students() { 238 return iStudents; 239 } 240 241 /** 242 * Total weight of all enrolled students 243 * @return sum of {@link Student#getOfferingWeight(Configuration)} of each enrolled student 244 */ 245 public double nrWeightedStudents() { 246 double w = 0.0; 247 for (Student s : iStudents) { 248 w += s.getOfferingWeight(getConfiguration()); 249 } 250 return w; 251 } 252 253 /** Add an enrolled student 254 * @param assignment current assignment 255 * @param student a student to add 256 **/ 257 public void addStudent(Assignment<Lecture, Placement> assignment, Student student) { 258 if (!iStudents.add(student)) 259 return; 260 Placement value = (assignment == null ? null : assignment.getValue(this)); 261 if (value != null && getModel() != null) 262 getModel().getCriterion(StudentCommittedConflict.class).inc(assignment, student.countConflictPlacements(value)); 263 iLock.writeLock().lock(); 264 iCommitedConflicts.clear(); 265 iLock.writeLock().unlock(); 266 } 267 268 /** 269 * Remove an enrolled student 270 * @param assignment current assignment 271 * @param student a student to remove 272 */ 273 public void removeStudent(Assignment<Lecture, Placement> assignment, Student student) { 274 if (!iStudents.remove(student)) 275 return; 276 Placement value = (assignment == null ? null : assignment.getValue(this)); 277 if (value != null && getModel() != null) 278 getModel().getCriterion(StudentCommittedConflict.class).inc(assignment, -student.countConflictPlacements(value)); 279 iLock.writeLock().lock(); 280 iCommitedConflicts.clear(); 281 iLock.writeLock().unlock(); 282 } 283 284 /** Returns true if the given student is enrolled 285 * @param student a student 286 * @return true if the given student is enrolled in this class 287 **/ 288 public boolean hasStudent(Student student) { 289 return iStudents.contains(student); 290 } 291 292 /** Set of lectures of the same class (only section is different) 293 * @param sameSubpartLectures list of lectures of the same scheduling subpart 294 **/ 295 public void setSameSubpartLectures(List<Lecture> sameSubpartLectures) { 296 iSameSubpartLectures = sameSubpartLectures; 297 } 298 299 /** Set of lectures of the same class (only section is different) 300 * @return list of lectures of the same scheduling subpart 301 **/ 302 public List<Lecture> sameSubpartLectures() { 303 return iSameSubpartLectures; 304 } 305 306 /** List of students enrolled in this class as well as in the given class 307 * @param lecture a lecture 308 * @return a set of students that are enrolled in both lectures 309 **/ 310 public Set<Student> sameStudents(Lecture lecture) { 311 JenrlConstraint jenrl = jenrlConstraint(lecture); 312 return (jenrl == null ? new HashSet<Student>() : jenrl.getStudents()); 313 } 314 315 /** List of students of this class in conflict with the given assignment 316 * @param assignment current assignment 317 * @param value given placement 318 * @return list of student conflicts 319 **/ 320 public Set<Student> conflictStudents(Assignment<Lecture, Placement> assignment, Placement value) { 321 if (value == null) 322 return new HashSet<Student>(); 323 if (value.equals(assignment.getValue(this))) 324 return conflictStudents(assignment); 325 Set<Student> ret = new HashSet<Student>(); 326 for (JenrlConstraint jenrl : jenrlConstraints()) { 327 if (jenrl.jenrl(assignment, this, value) > 0) 328 ret.addAll(sameStudents(jenrl.another(this))); 329 } 330 return ret; 331 } 332 333 /** 334 * List of students of this class which are in conflict with any other 335 * assignment 336 * @param assignment current assignment 337 * @return list of student conflicts 338 */ 339 public Set<Student> conflictStudents(Assignment<Lecture, Placement> assignment) { 340 Set<Student> ret = new HashSet<Student>(); 341 Placement placement = assignment.getValue(this); 342 if (placement == null) 343 return ret; 344 for (JenrlConstraint jenrl : activeJenrls(assignment)) { 345 ret.addAll(sameStudents(jenrl.another(this))); 346 } 347 for (Student student : students()) { 348 if (student.countConflictPlacements(placement) > 0) 349 ret.add(student); 350 } 351 return ret; 352 } 353 354 /** 355 * Lectures different from this one, where it is student conflict of the 356 * given student between this and the lecture 357 * @param assignment current assignment 358 * @param student a student 359 * @return list of lectures with a student conflict 360 */ 361 public List<Lecture> conflictLectures(Assignment<Lecture, Placement> assignment, Student student) { 362 List<Lecture> ret = new ArrayList<Lecture>(); 363 if (assignment.getValue(this) == null) 364 return ret; 365 for (JenrlConstraint jenrl : activeJenrls(assignment)) { 366 Lecture lect = jenrl.another(this); 367 if (lect.students().contains(student)) 368 ret.add(lect); 369 } 370 return ret; 371 } 372 373 /** True if this lecture is in a student conflict with the given student 374 * @param assignment current assignment 375 * @param student a student 376 * @return number of other lectures with a student conflict 377 **/ 378 public int isInConflict(Assignment<Lecture, Placement> assignment, Student student) { 379 if (assignment.getValue(this) == null) 380 return 0; 381 int ret = 0; 382 for (JenrlConstraint jenrl : activeJenrls(assignment)) { 383 Lecture lect = jenrl.another(this); 384 if (lect.students().contains(student)) 385 ret++; 386 } 387 return ret; 388 } 389 390 private boolean isCacheDomain() { 391 return isCommitted() || (!sSaveMemory && (iNrRooms <= 1 || getMaxRoomCombinations() <= 0 || ToolBox.binomial(iRoomLocations.size(), iNrRooms) <= getMaxRoomCombinations())); 392 } 393 394 /** Domain -- all combinations of room and time locations 395 * @param assignment current assignment 396 * @param allowBreakHard breaking of hard constraints is allowed 397 * @return list of possible placements 398 **/ 399 public List<Placement> computeValues(Assignment<Lecture, Placement> assignment, boolean allowBreakHard) { 400 List<Placement> values = new ArrayList<Placement>(iRoomLocations.size() * iTimeLocations.size()); 401 for (TimeLocation timeLocation : iTimeLocations) { 402 if (!allowBreakHard && Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(timeLocation.getPreference()))) 403 continue; 404 if (timeLocation.getPreference() > 500) 405 continue; 406 boolean notAvailable = false; 407 for (InstructorConstraint ic : getInstructorConstraints()) { 408 if (!ic.isAvailable(this, timeLocation) && ic.isHard()) { 409 notAvailable = true; 410 break; 411 } 412 } 413 if (notAvailable) 414 continue; 415 if (iNrRooms == 0) { 416 Placement p = new Placement(this, timeLocation, (RoomLocation) null); 417 for (InstructorConstraint ic : getInstructorConstraints()) { 418 if (!ic.isAvailable(this, p) && ic.isHard()) { 419 notAvailable = true; 420 break; 421 } 422 } 423 if (notAvailable) 424 continue; 425 p.setVariable(this); 426 if (sSaveMemory && !isValid(p)) 427 continue; 428 if (getInitialAssignment() != null && p.equals(getInitialAssignment())) 429 setInitialAssignment(p); 430 // if (getAssignment() != null && getAssignment().equals(p)) iValue = getAssignment(); 431 if (getBestAssignment() != null && getBestAssignment().equals(p)) 432 setBestAssignment(p, getBestAssignmentIteration()); 433 values.add(p); 434 } else if (iNrRooms == 1) { 435 rooms: for (RoomLocation roomLocation : iRoomLocations) { 436 if (!allowBreakHard && Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(roomLocation.getPreference()))) continue; 437 if (roomLocation.getPreference() > 500) continue; 438 if (roomLocation.getRoomConstraint() != null && !roomLocation.getRoomConstraint().isAvailable(this, timeLocation, getScheduler())) continue; 439 Placement p = new Placement(this, timeLocation, roomLocation); 440 p.setVariable(this); 441 for (InstructorConstraint ic : getInstructorConstraints()) 442 if (!ic.isAvailable(this, p) && ic.isHard()) continue rooms; 443 if (sSaveMemory && !isValid(p)) continue; 444 if (getInitialAssignment() != null && p.equals(getInitialAssignment())) setInitialAssignment(p); 445 if (getBestAssignment() != null && getBestAssignment().equals(p)) setBestAssignment(p, getBestAssignmentIteration()); 446 values.add(p); 447 } 448 } else { 449 if (getMaxRoomCombinations() > 0 && ToolBox.binomial(iRoomLocations.size(), iNrRooms) > getMaxRoomCombinations()) { 450 if (getInitialAssignment() != null && getInitialAssignment().getNrRooms() == getNrRooms()) { 451 boolean av = true; 452 for (RoomLocation room: getInitialAssignment().getRoomLocations()) { 453 if (room.getRoomConstraint() != null && !room.getRoomConstraint().isAvailable(this, timeLocation, getScheduler())) { av = false; break; } 454 if (room.getMinPreference() > 500) { av = false; break; } 455 if (!allowBreakHard && Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(room.getMinPreference()))) { av = false; break; } 456 } 457 Placement p = new Placement(this, timeLocation, new ArrayList<RoomLocation>(getInitialAssignment().getRoomLocations())); 458 if (!allowBreakHard && p.isRoomProhibited()) av = false; 459 for (InstructorConstraint ic : getInstructorConstraints()) 460 if (!ic.isAvailable(this, p) && ic.isHard()) { av = false; break; } 461 if (av && !isTooSmall(p) && !checkParents(p) && (!sSaveMemory || isValid(p))) { 462 p.setVariable(this); 463 if (p.equals(getInitialAssignment())) setInitialAssignment(p); 464 if (getBestAssignment() != null && getBestAssignment().equals(p)) setBestAssignment(p, getBestAssignmentIteration()); 465 values.add(p); 466 } 467 } 468 List<RoomLocation> available = new ArrayList<RoomLocation>(iRoomLocations.size()); 469 List<RoomLocation> other = new ArrayList<RoomLocation>(iRoomLocations.size()); 470 rooms: for (RoomLocation room: iRoomLocations) { 471 if (room.getRoomConstraint() != null && !room.getRoomConstraint().isAvailable(this, timeLocation, getScheduler())) continue; 472 if (room.getMinPreference() > 500) continue; 473 if (!allowBreakHard && Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(room.getMinPreference()))) continue; 474 for (InstructorConstraint ic : getInstructorConstraints()) 475 if (!ic.isAvailable(this, new Placement(this, timeLocation, room)) && ic.isHard()) continue rooms; 476 if (assignment != null && room.getRoomConstraint() != null && !room.getRoomConstraint().getContext(assignment).inConflict(this, timeLocation) && 477 !Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(room.getMinPreference()))) { 478 available.add(room); 479 } else { 480 other.add(room); 481 } 482 } 483 if (available.size() + other.size() < iNrRooms) continue; 484 for (Enumeration<Collection<RoomLocation>> e = ToolBox.sample(available, other, iNrRooms, isSplitAttendance() ? -1 : getMaxRoomCombinations()); e.hasMoreElements(); ) { 485 Collection<RoomLocation> rm = e.nextElement(); 486 if (isTooSmall(rm)) continue; 487 Placement p = new Placement(this, timeLocation, new ArrayList<RoomLocation>(rm)); 488 if (!checkParents(p)) continue; 489 if (!allowBreakHard && p.isRoomProhibited()) continue; 490 if (getInitialAssignment() != null && p.sameRooms(getInitialAssignment())) continue; 491 p.setVariable(this); 492 if (sSaveMemory && !isValid(p)) continue; 493 if (getBestAssignment() != null && getBestAssignment().equals(p)) setBestAssignment(p, getBestAssignmentIteration()); 494 values.add(p); 495 if (values.size() >= getMaxRoomCombinations()) break; 496 } 497 } else { 498 List<RoomLocation> rooms = new ArrayList<RoomLocation>(iRoomLocations.size()); 499 rooms: for (RoomLocation room: iRoomLocations) { 500 if (!allowBreakHard && Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(room.getMinPreference()))) continue; 501 if (room.getMinPreference() > 500) continue; 502 if (room.getRoomConstraint() != null && !room.getRoomConstraint().isAvailable(this, timeLocation, getScheduler())) continue; 503 for (InstructorConstraint ic : getInstructorConstraints()) 504 if (!ic.isAvailable(this, new Placement(this, timeLocation, room)) && ic.isHard()) continue rooms; 505 rooms.add(room); 506 } 507 if (rooms.size() < iNrRooms) continue; 508 for (Enumeration<Collection<RoomLocation>> e = ToolBox.permutations(rooms, iNrRooms); e.hasMoreElements(); ) { 509 Collection<RoomLocation> rm = e.nextElement(); 510 if (isTooSmall(rm)) continue; 511 Placement p = new Placement(this, timeLocation, new ArrayList<RoomLocation>(rm)); 512 if (!checkParents(p)) continue; 513 if (!allowBreakHard && p.isRoomProhibited()) continue; 514 p.setVariable(this); 515 if (sSaveMemory && !isValid(p)) continue; 516 if (getInitialAssignment() != null && p.equals(getInitialAssignment())) setInitialAssignment(p); 517 if (getBestAssignment() != null && getBestAssignment().equals(p)) setBestAssignment(p, getBestAssignmentIteration()); 518 values.add(p); 519 } 520 } 521 } 522 } 523 return values; 524 } 525 526 public void clearValueCache() { 527 super.setValues(null); 528 } 529 530 /** All values */ 531 @Override 532 public List<Placement> values(Assignment<Lecture, Placement> assignment) { 533 if (super.values(assignment) == null) { 534 if (getInitialAssignment() != null && iTimeLocations.size() == 1 && iRoomLocations.size() == getNrRooms()) { 535 List<Placement> values = new ArrayList<Placement>(1); 536 values.add(getInitialAssignment()); 537 setValues(values); 538 return values; 539 } else if (isCacheDomain()) { 540 List<Placement> values = computeValues(null, allowBreakHard()); 541 setValues(values); 542 return values; 543 } else { 544 return computeValues(assignment, allowBreakHard()); 545 } 546 } else { 547 return super.values(assignment); 548 } 549 } 550 551 @Override 552 public boolean equals(Object o) { 553 if (o == null || !(o instanceof Lecture)) 554 return false; 555 return getClassId().equals(((Lecture) o).getClassId()); 556 } 557 558 /** Best time preference of this lecture */ 559 private Double iBestTimePreferenceCache = null; 560 561 public double getBestTimePreference() { 562 if (iBestTimePreferenceCache == null) { 563 double ret = Double.MAX_VALUE; 564 for (TimeLocation time : iTimeLocations) { 565 ret = Math.min(ret, time.getNormalizedPreference()); 566 } 567 iBestTimePreferenceCache = Double.valueOf(ret); 568 } 569 return iBestTimePreferenceCache.doubleValue(); 570 } 571 572 /** Best room preference of this lecture 573 * @return best room preference 574 **/ 575 public int getBestRoomPreference() { 576 int ret = Integer.MAX_VALUE; 577 for (RoomLocation room : iRoomLocations) { 578 ret = Math.min(ret, room.getMinPreference()); 579 } 580 return ret; 581 } 582 583 /** 584 * Number of student conflicts caused by the given assignment of this 585 * lecture 586 * @param assignment current assignment 587 * @param value a placement 588 * @return number of student conflicts if assigned 589 */ 590 public int countStudentConflicts(Assignment<Lecture, Placement> assignment, Placement value) { 591 int studentConflictsSum = 0; 592 for (JenrlConstraint jenrl : jenrlConstraints()) { 593 studentConflictsSum += jenrl.jenrl(assignment, this, value); 594 } 595 return studentConflictsSum; 596 } 597 598 public int countStudentConflictsOfTheSameProblem(Assignment<Lecture, Placement> assignment, Placement value) { 599 int studentConflictsSum = 0; 600 for (JenrlConstraint jenrl : jenrlConstraints()) { 601 if (!jenrl.isOfTheSameProblem()) 602 continue; 603 studentConflictsSum += jenrl.jenrl(assignment, this, value); 604 } 605 return studentConflictsSum; 606 } 607 608 public int countHardStudentConflicts(Assignment<Lecture, Placement> assignment, Placement value) { 609 int studentConflictsSum = 0; 610 if (!isSingleSection()) 611 return 0; 612 for (JenrlConstraint jenrl : jenrlConstraints()) { 613 if (!jenrl.areStudentConflictsHard()) 614 continue; 615 studentConflictsSum += jenrl.jenrl(assignment, this, value); 616 } 617 return studentConflictsSum; 618 } 619 620 public int countCommittedStudentConflictsOfTheSameProblem(Assignment<Lecture, Placement> assignment, Placement value) { 621 int studentConflictsSum = 0; 622 for (JenrlConstraint jenrl : jenrlConstraints()) { 623 if (!jenrl.isOfTheSameProblem()) 624 continue; 625 if (!jenrl.areStudentConflictsCommitted()) 626 continue; 627 studentConflictsSum += jenrl.jenrl(assignment, this, value); 628 } 629 return studentConflictsSum; 630 } 631 632 public int countCommittedStudentConflicts(Assignment<Lecture, Placement> assignment, Placement value) { 633 int studentConflictsSum = 0; 634 for (JenrlConstraint jenrl : jenrlConstraints()) { 635 if (!jenrl.areStudentConflictsCommitted()) 636 continue; 637 studentConflictsSum += jenrl.jenrl(assignment, this, value); 638 } 639 return studentConflictsSum; 640 } 641 642 public int countHardStudentConflictsOfTheSameProblem(Assignment<Lecture, Placement> assignment, Placement value) { 643 int studentConflictsSum = 0; 644 for (JenrlConstraint jenrl : jenrlConstraints()) { 645 if (!jenrl.isOfTheSameProblem()) 646 continue; 647 if (!jenrl.areStudentConflictsHard()) 648 continue; 649 studentConflictsSum += jenrl.jenrl(assignment, this, value); 650 } 651 return studentConflictsSum; 652 } 653 654 public int countDistanceStudentConflicts(Assignment<Lecture, Placement> assignment, Placement value) { 655 int studentConflictsSum = 0; 656 for (JenrlConstraint jenrl : jenrlConstraints()) { 657 if (!jenrl.areStudentConflictsDistance(assignment, value)) 658 continue; 659 studentConflictsSum += jenrl.jenrl(assignment, this, value); 660 } 661 return studentConflictsSum; 662 } 663 664 public int countDistanceStudentConflictsOfTheSameProblem(Assignment<Lecture, Placement> assignment, Placement value) { 665 int studentConflictsSum = 0; 666 for (JenrlConstraint jenrl : jenrlConstraints()) { 667 if (!jenrl.isOfTheSameProblem()) 668 continue; 669 if (!jenrl.areStudentConflictsDistance(assignment, value)) 670 continue; 671 studentConflictsSum += jenrl.jenrl(assignment, this, value); 672 } 673 return studentConflictsSum; 674 } 675 676 private DistanceMetric getDistanceMetric() { 677 return ((TimetableModel)getModel()).getDistanceMetric(); 678 } 679 680 private int getStudentWorkDayLimit() { 681 return ((TimetableModel)getModel()).getStudentWorkDayLimit(); 682 } 683 684 /** 685 * Number of student conflicts caused by the initial assignment of this 686 * lecture 687 * @return number of student conflicts with the initial assignment of this class 688 */ 689 public int countInitialStudentConflicts() { 690 Placement value = getInitialAssignment(); 691 if (value == null) 692 return 0; 693 int studentConflictsSum = 0; 694 for (JenrlConstraint jenrl : jenrlConstraints()) { 695 Lecture another = jenrl.another(this); 696 if (another.getInitialAssignment() != null) 697 if (JenrlConstraint.isInConflict(value, another.getInitialAssignment(), getDistanceMetric(), getStudentWorkDayLimit())) 698 studentConflictsSum += jenrl.getJenrl(); 699 } 700 return studentConflictsSum; 701 } 702 703 /** 704 * Table of student conflicts caused by the initial assignment of this 705 * lecture in format (another lecture, number) 706 * @return table of student conflicts with the initial assignment of this class 707 */ 708 public Map<Lecture, Long> getInitialStudentConflicts() { 709 Placement value = getInitialAssignment(); 710 if (value == null) 711 return null; 712 Map<Lecture, Long> ret = new HashMap<Lecture, Long>(); 713 for (JenrlConstraint jenrl : jenrlConstraints()) { 714 Lecture another = jenrl.another(this); 715 if (another.getInitialAssignment() != null) 716 if (JenrlConstraint.isInConflict(value, another.getInitialAssignment(), getDistanceMetric(), getStudentWorkDayLimit())) 717 ret.put(another, jenrl.getJenrl()); 718 } 719 return ret; 720 } 721 722 /** 723 * List of student conflicts caused by the initial assignment of this 724 * lecture 725 * @return a set of students in a conflict with the initial assignment of this class 726 */ 727 public Set<Student> initialStudentConflicts() { 728 Placement value = getInitialAssignment(); 729 if (value == null) 730 return null; 731 HashSet<Student> ret = new HashSet<Student>(); 732 for (JenrlConstraint jenrl : jenrlConstraints()) { 733 Lecture another = jenrl.another(this); 734 if (another.getInitialAssignment() != null) 735 if (JenrlConstraint.isInConflict(value, another.getInitialAssignment(), getDistanceMetric(), getStudentWorkDayLimit())) 736 ret.addAll(sameStudents(another)); 737 } 738 return ret; 739 } 740 741 @Override 742 public void addContstraint(Constraint<Lecture, Placement> constraint) { 743 super.addContstraint(constraint); 744 745 if (constraint instanceof WeakeningConstraint) 746 iWeakeningConstraints.add(constraint); 747 748 if (constraint instanceof FlexibleConstraint) 749 iFlexibleGroupConstraints.add((FlexibleConstraint) constraint); 750 751 if (constraint instanceof JenrlConstraint) { 752 JenrlConstraint jenrl = (JenrlConstraint) constraint; 753 Lecture another = jenrl.another(this); 754 if (another != null) { 755 iJenrlConstraints.add(jenrl); 756 another.iJenrlConstraints.add(jenrl); 757 iJenrlConstraintsHash.put(another, (JenrlConstraint) constraint); 758 another.iJenrlConstraintsHash.put(this, (JenrlConstraint) constraint); 759 } 760 } else if (constraint instanceof DepartmentSpreadConstraint) 761 iDeptSpreadConstraint = (DepartmentSpreadConstraint) constraint; 762 else if (constraint instanceof SpreadConstraint) 763 iSpreadConstraints.add((SpreadConstraint) constraint); 764 else if (constraint instanceof InstructorConstraint) { 765 InstructorConstraint ic = (InstructorConstraint) constraint; 766 if (ic.getResourceId() != null && ic.getResourceId().longValue() > 0) 767 iInstructorConstraints.add(ic); 768 } else if (constraint instanceof ClassLimitConstraint) 769 iClassLimitConstraint = (ClassLimitConstraint) constraint; 770 else if (constraint instanceof GroupConstraint) { 771 GroupConstraint gc = (GroupConstraint) constraint; 772 if (gc.canShareRoom()) { 773 iCanShareRoomGroupConstraints.add((GroupConstraint) constraint); 774 } else { 775 iGroupConstraints.add((GroupConstraint) constraint); 776 if (Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(gc.getPreference())) 777 || Constants.sPreferenceRequired.equals(Constants 778 .preferenceLevel2preference(gc.getPreference()))) 779 iHardGroupSoftConstraints.add((GroupConstraint) constraint); 780 } 781 } 782 } 783 784 @Override 785 public void removeContstraint(Constraint<Lecture, Placement> constraint) { 786 super.removeContstraint(constraint); 787 788 if (constraint instanceof WeakeningConstraint) 789 iWeakeningConstraints.remove(constraint); 790 791 if (constraint instanceof FlexibleConstraint) 792 iFlexibleGroupConstraints.remove(constraint); 793 794 if (constraint instanceof JenrlConstraint) { 795 JenrlConstraint jenrl = (JenrlConstraint) constraint; 796 Lecture another = jenrl.another(this); 797 if (another != null) { 798 iJenrlConstraints.remove(jenrl); 799 another.iJenrlConstraints.remove(jenrl); 800 iJenrlConstraintsHash.remove(another); 801 another.iJenrlConstraintsHash.remove(this); 802 } 803 } else if (constraint instanceof GroupConstraint) { 804 iCanShareRoomGroupConstraints.remove(constraint); 805 iHardGroupSoftConstraints.remove(constraint); 806 iGroupConstraints.remove(constraint); 807 } else if (constraint instanceof DepartmentSpreadConstraint) 808 iDeptSpreadConstraint = null; 809 else if (constraint instanceof SpreadConstraint) 810 iSpreadConstraints.remove(constraint); 811 else if (constraint instanceof InstructorConstraint) 812 iInstructorConstraints.remove(constraint); 813 else if (constraint instanceof ClassLimitConstraint) 814 iClassLimitConstraint = null; 815 } 816 817 /** All JENRL constraints of this lecture 818 * @param another another class 819 * @return a join enrollment constraint between this and the given class, if there is one 820 **/ 821 public JenrlConstraint jenrlConstraint(Lecture another) { 822 /* 823 * for (Enumeration e=iJenrlConstraints.elements();e.hasMoreElements();) 824 * { JenrlConstraint jenrl = (JenrlConstraint)e.nextElement(); if 825 * (jenrl.another(this).equals(another)) return jenrl; } return null; 826 */ 827 return iJenrlConstraintsHash.get(another); 828 } 829 830 /** All JENRL constraints of this lecture 831 * @return list of all join enrollment constraints in which this lecture is involved 832 **/ 833 public List<JenrlConstraint> jenrlConstraints() { 834 return iJenrlConstraints; 835 } 836 837 public int minClassLimit() { 838 return iMinClassLimit; 839 } 840 841 public int maxClassLimit() { 842 return iMaxClassLimit; 843 } 844 845 public int maxAchievableClassLimit() { 846 iLock.readLock().lock(); 847 try { 848 if (iCacheMaxAchievableClassLimit != null) return iCacheMaxAchievableClassLimit.intValue(); 849 } finally { 850 iLock.readLock().unlock(); 851 } 852 iLock.writeLock().lock(); 853 try { 854 if (iCacheMaxAchievableClassLimit != null) return iCacheMaxAchievableClassLimit.intValue(); 855 856 int maxAchievableClassLimit = Math.min(maxClassLimit(), (int) Math.floor(maxRoomSize() / roomToLimitRatio())); 857 858 if (hasAnyChildren()) { 859 860 for (Long subpartId: getChildrenSubpartIds()) { 861 int maxAchievableChildrenLimit = 0; 862 863 for (Lecture child : getChildren(subpartId)) { 864 maxAchievableChildrenLimit += child.maxAchievableClassLimit(); 865 } 866 867 maxAchievableClassLimit = Math.min(maxAchievableClassLimit, maxAchievableChildrenLimit); 868 } 869 } 870 871 maxAchievableClassLimit = Math.max(minClassLimit(), maxAchievableClassLimit); 872 iCacheMaxAchievableClassLimit = Integer.valueOf(maxAchievableClassLimit); 873 return maxAchievableClassLimit; 874 } finally { 875 iLock.writeLock().unlock(); 876 } 877 } 878 879 public int classLimit(Assignment<Lecture, Placement> assignment) { 880 if (minClassLimit() == maxClassLimit()) 881 return minClassLimit(); 882 return classLimit(assignment, null, null); 883 } 884 885 public int classLimit(Assignment<Lecture, Placement> assignment, Placement value, Set<Placement> conflicts) { 886 Placement a = (assignment == null ? null : assignment.getValue(this)); 887 if (value != null && value.variable().equals(this)) 888 a = value; 889 if (conflicts != null && a != null && conflicts.contains(a)) 890 a = null; 891 int classLimit = (a == null ? maxAchievableClassLimit() : Math.min(maxClassLimit(), (int) Math.floor(a.getRoomSize() / roomToLimitRatio()))); 892 893 if (!hasAnyChildren()) 894 return classLimit; 895 896 for (Long subpartId: getChildrenSubpartIds()) { 897 int childrenClassLimit = 0; 898 899 for (Lecture child : getChildren(subpartId)) { 900 childrenClassLimit += child.classLimit(assignment, value, conflicts); 901 } 902 903 classLimit = Math.min(classLimit, childrenClassLimit); 904 } 905 906 return Math.max(minClassLimit(), classLimit); 907 } 908 909 public double roomToLimitRatio() { 910 return iRoomToLimitRatio; 911 } 912 913 public int minRoomUse() { 914 return iNrRooms == 0 ? 0 : Math.round(iMinClassLimit * iRoomToLimitRatio); 915 } 916 917 public int maxRoomUse() { 918 return iNrRooms == 0 ? 0 : Math.round(iMaxClassLimit * iRoomToLimitRatio); 919 } 920 921 @Override 922 public String toString() { 923 return getName(); 924 } 925 926 /** Controlling Course Offering Department 927 * @return department unique id 928 **/ 929 public Long getDepartment() { 930 return iDept; 931 } 932 933 /** Controlling Course Offering Department 934 * @param dept department unique id 935 **/ 936 public void setDepartment(Long dept) { 937 iDept = dept; 938 } 939 940 /** Scheduler (Managing Department) 941 * @return solver group unique id 942 **/ 943 public Long getScheduler() { 944 return iScheduler; 945 } 946 947 /** Scheduler (Managing Department) 948 * @param scheduler solver group unique id 949 **/ 950 public void setScheduler(Long scheduler) { 951 iScheduler = scheduler; 952 } 953 954 /** Departmental spreading constraint 955 * @return department spread constraint of this class, if any 956 **/ 957 public DepartmentSpreadConstraint getDeptSpreadConstraint() { 958 return iDeptSpreadConstraint; 959 } 960 961 /** Instructor constraint 962 * @return instructors of this class 963 **/ 964 public List<InstructorConstraint> getInstructorConstraints() { 965 return iInstructorConstraints; 966 } 967 968 public ClassLimitConstraint getClassLimitConstraint() { 969 return iClassLimitConstraint; 970 } 971 972 public Set<SpreadConstraint> getSpreadConstraints() { 973 return iSpreadConstraints; 974 } 975 976 public Set<FlexibleConstraint> getFlexibleGroupConstraints() { 977 return iFlexibleGroupConstraints; 978 } 979 980 public Set<Constraint<Lecture, Placement>> getWeakeningConstraints() { 981 return iWeakeningConstraints; 982 } 983 984 /** All room locations 985 * @return possible rooms of this class 986 **/ 987 public List<RoomLocation> roomLocations() { 988 return iRoomLocations; 989 } 990 991 /** All time locations 992 * @return possible times of this class 993 **/ 994 public List<TimeLocation> timeLocations() { 995 return iTimeLocations; 996 } 997 998 public int nrTimeLocations() { 999 int ret = 0; 1000 for (TimeLocation time : iTimeLocations) { 1001 if (!Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(time.getPreference()))) 1002 ret++; 1003 } 1004 return ret; 1005 } 1006 1007 public int nrRoomLocations() { 1008 int ret = 0; 1009 for (RoomLocation room : iRoomLocations) { 1010 if (!Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(room.getMinPreference()))) 1011 ret++; 1012 } 1013 return ret; 1014 } 1015 1016 public long nrValues() { 1017 int nrTimes = 0; 1018 for (TimeLocation time: timeLocations()) 1019 if (!Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(time.getPreference()))) 1020 nrTimes ++; 1021 int nrRooms = 0; 1022 for (RoomLocation room : iRoomLocations) 1023 if (!Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(room.getMinPreference()))) 1024 nrRooms ++; 1025 long estNrValues = nrTimes; 1026 if (getNrRooms() > 1 && getMaxRoomCombinations() > 0) 1027 estNrValues *= Math.min(getMaxRoomCombinations(), ToolBox.binomial(nrRooms, getNrRooms())); 1028 else 1029 estNrValues *= ToolBox.binomial(nrRooms, getNrRooms()); 1030 return estNrValues; 1031 } 1032 1033 public int nrValues(TimeLocation time) { 1034 int ret = 0; 1035 for (RoomLocation room : iRoomLocations) { 1036 if (!Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(room.getMinPreference())) 1037 && (room.getRoomConstraint() == null || room.getRoomConstraint().isAvailable(this, time, getScheduler()))) 1038 ret++; 1039 } 1040 return ret; 1041 } 1042 1043 public int nrValues(RoomLocation room) { 1044 int ret = 0; 1045 for (TimeLocation time : iTimeLocations) { 1046 if (!Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(time.getPreference())) 1047 && (room.getRoomConstraint() == null || room.getRoomConstraint().isAvailable(this, time,getScheduler()))) 1048 ret++; 1049 } 1050 return ret; 1051 } 1052 1053 public int nrValues(List<RoomLocation> rooms) { 1054 int ret = 0; 1055 for (TimeLocation time : iTimeLocations) { 1056 boolean available = true; 1057 for (RoomLocation room : rooms) { 1058 if (Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(time.getPreference())) 1059 || (room.getRoomConstraint() != null && !room.getRoomConstraint().isAvailable(this, time, 1060 getScheduler()))) 1061 available = false; 1062 } 1063 if (available) 1064 ret++; 1065 } 1066 return ret; 1067 } 1068 1069 public boolean allowBreakHard() { 1070 return (getModel() == null ? false : ((TimetableModel)getModel()).isAllowBreakHard()); 1071 } 1072 1073 public int getNrRooms() { 1074 return iNrRooms; 1075 } 1076 1077 public Lecture getParent() { 1078 return iParent; 1079 } 1080 1081 public void setParent(Lecture parent) { 1082 iParent = parent; 1083 iParent.addChild(this); 1084 } 1085 1086 public boolean hasParent() { 1087 return (iParent != null); 1088 } 1089 1090 public boolean hasChildren(Long subpartId) { 1091 return (iChildren != null && iChildren.get(subpartId) != null && !iChildren.get(subpartId).isEmpty()); 1092 } 1093 1094 public boolean hasAnyChildren() { 1095 return (iChildren != null && !iChildren.isEmpty()); 1096 } 1097 1098 public List<Lecture> getChildren(Long subpartId) { 1099 return iChildren.get(subpartId); 1100 } 1101 1102 public Set<Long> getChildrenSubpartIds() { 1103 return (iChildren == null ? null : iChildren.keySet()); 1104 } 1105 1106 public Map<Long, List<Lecture>> getChildren() { 1107 return iChildren; 1108 } 1109 1110 private void addChild(Lecture child) { 1111 if (iChildren == null) 1112 iChildren = new HashMap<Long, List<Lecture>>(); 1113 List<Lecture> childrenThisSubpart = iChildren.get(child.getSchedulingSubpartId()); 1114 if (childrenThisSubpart == null) { 1115 childrenThisSubpart = new ArrayList<Lecture>(); 1116 iChildren.put(child.getSchedulingSubpartId(), childrenThisSubpart); 1117 } 1118 childrenThisSubpart.add(child); 1119 } 1120 1121 public boolean isSingleSection() { 1122 return (iSameSubpartLectures == null || iSameSubpartLectures.size() <= 1); 1123 /* 1124 if (iParent == null) 1125 return (iSameSubpartLectures == null || iSameSubpartLectures.size() <= 1); 1126 return (iParent.getChildren(getSchedulingSubpartId()).size() <= 1); 1127 */ 1128 } 1129 1130 public java.util.List<Lecture> sameStudentsLectures() { 1131 return (hasParent() ? getParent().getChildren(getSchedulingSubpartId()) : sameSubpartLectures()); 1132 } 1133 1134 public Lecture getChild(Student student, Long subpartId) { 1135 if (!hasAnyChildren()) 1136 return null; 1137 List<Lecture> children = getChildren(subpartId); 1138 if (children == null) 1139 return null; 1140 for (Lecture child : children) { 1141 if (child.students().contains(student)) 1142 return child; 1143 } 1144 return null; 1145 } 1146 1147 public int getCommitedConflicts(Placement placement) { 1148 iLock.readLock().lock(); 1149 try { 1150 Integer ret = iCommitedConflicts.get(placement); 1151 if (ret != null) return ret; 1152 } finally { 1153 iLock.readLock().unlock(); 1154 } 1155 iLock.writeLock().lock(); 1156 try { 1157 int ret = placement.getCommitedConflicts(); 1158 iCommitedConflicts.put(placement, ret); 1159 return ret; 1160 } finally { 1161 iLock.writeLock().unlock(); 1162 } 1163 } 1164 1165 public Set<GroupConstraint> hardGroupSoftConstraints() { 1166 return iHardGroupSoftConstraints; 1167 } 1168 1169 public Set<GroupConstraint> groupConstraints() { 1170 return iGroupConstraints; 1171 } 1172 1173 public int minRoomSize() { 1174 iLock.readLock().lock(); 1175 try { 1176 if (iCacheMinRoomSize != null) return iCacheMinRoomSize.intValue(); 1177 } finally { 1178 iLock.readLock().unlock(); 1179 } 1180 iLock.writeLock().lock(); 1181 try { 1182 if (iCacheMinRoomSize != null) return iCacheMinRoomSize.intValue(); 1183 if (getNrRooms() <= 1) { 1184 int min = Integer.MAX_VALUE; 1185 for (RoomLocation r : roomLocations()) { 1186 if (r.getMinPreference() <= Constants.sPreferenceLevelProhibited / 2) 1187 min = Math.min(min, r.getRoomSize()); 1188 } 1189 iCacheMinRoomSize = Integer.valueOf(min); 1190 return min; 1191 } else { 1192 List<RoomLocation> rooms = new ArrayList<RoomLocation>(); 1193 for (RoomLocation r: roomLocations()) 1194 if (r.getMinPreference() <= Constants.sPreferenceLevelProhibited / 2) 1195 rooms.add(r); 1196 Collections.sort(rooms, new Comparator<RoomLocation>() { 1197 @Override 1198 public int compare(RoomLocation r1, RoomLocation r2) { 1199 if (r1.getRoomSize() < r2.getRoomSize()) return -1; 1200 if (r1.getRoomSize() > r2.getRoomSize()) return 1; 1201 return r1.compareTo(r2); 1202 } 1203 }); 1204 int min = rooms.isEmpty() ? 0 : rooms.get(Math.min(getNrRooms(), rooms.size()) - 1).getRoomSize(); 1205 iCacheMinRoomSize = Integer.valueOf(min); 1206 return min; 1207 } 1208 } finally { 1209 iLock.writeLock().unlock(); 1210 } 1211 } 1212 1213 public int maxRoomSize() { 1214 iLock.readLock().lock(); 1215 try { 1216 if (iCacheMaxRoomSize != null) return iCacheMaxRoomSize.intValue(); 1217 } finally { 1218 iLock.readLock().unlock(); 1219 } 1220 iLock.writeLock().lock(); 1221 try { 1222 if (iCacheMaxRoomSize != null) return iCacheMaxRoomSize.intValue(); 1223 if (getNrRooms() <= 1) { 1224 int max = Integer.MIN_VALUE; 1225 for (RoomLocation r : roomLocations()) { 1226 if (r.getMinPreference() <= Constants.sPreferenceLevelProhibited / 2) 1227 max = Math.max(max, r.getRoomSize()); 1228 } 1229 iCacheMaxRoomSize = Integer.valueOf(max); 1230 return max; 1231 } else { 1232 List<RoomLocation> rooms = new ArrayList<RoomLocation>(); 1233 for (RoomLocation r: roomLocations()) 1234 if (r.getMinPreference() <= Constants.sPreferenceLevelProhibited / 2) rooms.add(r); 1235 Collections.sort(rooms, new Comparator<RoomLocation>() { 1236 @Override 1237 public int compare(RoomLocation r1, RoomLocation r2) { 1238 if (r1.getRoomSize() > r2.getRoomSize()) return -1; 1239 if (r1.getRoomSize() < r2.getRoomSize()) return 1; 1240 return r1.compareTo(r2); 1241 } 1242 }); 1243 int max = rooms.isEmpty() ? 0 : rooms.get(Math.min(getNrRooms(), rooms.size()) - 1).getRoomSize(); 1244 iCacheMaxRoomSize = Integer.valueOf(max); 1245 return max; 1246 } 1247 } finally { 1248 iLock.writeLock().unlock(); 1249 } 1250 } 1251 1252 public boolean canShareRoom() { 1253 return (!iCanShareRoomGroupConstraints.isEmpty()); 1254 } 1255 1256 public boolean canShareRoom(Lecture other) { 1257 if (other.equals(this)) 1258 return true; 1259 for (GroupConstraint gc : iCanShareRoomGroupConstraints) { 1260 if (gc.variables().contains(other)) 1261 return true; 1262 } 1263 return false; 1264 } 1265 1266 public Set<GroupConstraint> canShareRoomConstraints() { 1267 return iCanShareRoomGroupConstraints; 1268 } 1269 1270 public boolean isSingleton() { 1271 return getNrRooms() == roomLocations().size() && timeLocations().size() == 1; 1272 } 1273 1274 public boolean isValid(Placement placement) { 1275 TimetableModel model = (TimetableModel) getModel(); 1276 if (model == null) 1277 return true; 1278 if (!model.isAllowBreakHard() && !model.getProperties().getPropertyBoolean("General.InteractiveMode", false)) { 1279 if (getNrRooms() > 1 && isSplitAttendance() && placement.getRoomSize() < minRoomUse()) return false; 1280 if (placement.isRoomProhibited()) return false; 1281 } 1282 if (!checkParents(placement)) return false; 1283 if (model.hasConstantVariables()) { 1284 for (Placement confPlacement : model.conflictValuesSkipWeakeningConstraints(model.getEmptyAssignment(), placement)) { 1285 Lecture lecture = confPlacement.variable(); 1286 if (lecture.isCommitted()) 1287 return false; 1288 if (confPlacement.equals(placement)) 1289 return false; 1290 } 1291 } else { 1292 Set<Placement> conflicts = new HashSet<Placement>(); 1293 for (Constraint<Lecture, Placement> constraint : hardConstraints()) { 1294 if (constraint instanceof WeakeningConstraint) continue; 1295 constraint.computeConflicts(model.getEmptyAssignment(), placement, conflicts); 1296 } 1297 for (GlobalConstraint<Lecture, Placement> constraint : model.globalConstraints()) { 1298 if (constraint instanceof WeakeningConstraint) continue; 1299 constraint.computeConflicts(model.getEmptyAssignment(), placement, conflicts); 1300 } 1301 if (conflicts.contains(placement)) 1302 return false; 1303 } 1304 return true; 1305 } 1306 1307 public String getNotValidReason(Assignment<Lecture, Placement> assignment, Placement placement, boolean useAmPm) { 1308 TimetableModel model = (TimetableModel) getModel(); 1309 if (model == null) 1310 return "no model for class " + getName(); 1311 Map<Constraint<Lecture, Placement>, Set<Placement>> conflictConstraints = model.conflictConstraints(assignment, placement); 1312 for (Map.Entry<Constraint<Lecture, Placement>, Set<Placement>> entry : conflictConstraints.entrySet()) { 1313 Constraint<Lecture, Placement> constraint = entry.getKey(); 1314 Set<Placement> conflicts = entry.getValue(); 1315 String cname = constraint.getName(); 1316 if (constraint instanceof RoomConstraint) { 1317 cname = "Room " + constraint.getName(); 1318 } else if (constraint instanceof InstructorConstraint) { 1319 cname = "Instructor " + constraint.getName(); 1320 } else if (constraint instanceof GroupConstraint) { 1321 cname = "Distribution " + constraint.getName(); 1322 } else if (constraint instanceof DepartmentSpreadConstraint) { 1323 cname = "Balancing of department " + constraint.getName(); 1324 } else if (constraint instanceof SpreadConstraint) { 1325 cname = "Same subpart spread " + constraint.getName(); 1326 } else if (constraint instanceof ClassLimitConstraint) { 1327 cname = "Class limit " + constraint.getName(); 1328 } 1329 for (Placement confPlacement : conflicts) { 1330 Lecture lecture = confPlacement.variable(); 1331 if (lecture.isCommitted()) { 1332 return placement.getLongName(useAmPm) + " conflicts with " + lecture.getName() + " " 1333 + confPlacement.getLongName(useAmPm) + " due to constraint " + cname; 1334 } 1335 if (confPlacement.equals(placement)) { 1336 return placement.getLongName(useAmPm) + " is not valid due to constraint " + cname; 1337 } 1338 } 1339 } 1340 if (getNrRooms() > 1 && isSplitAttendance() && placement.getRoomSize() < minRoomUse()) 1341 return "selected rooms are too small (" + placement.getRoomSize() + " < " + minRoomUse() + ")"; 1342 if (placement.isMultiRoom() && placement.isRoomProhibited()) { 1343 int roomIndex = 0; 1344 for (RoomLocation r : placement.getRoomLocations()) { 1345 if (Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(r.getPreference(roomIndex)))) 1346 return "invalid room combination (" + r.getName() + " is prohibited as " + (roomIndex + 1) + ". room)"; 1347 roomIndex++; 1348 } 1349 } 1350 if (placement.isMultiRoom()) { 1351 for (RoomLocation r1: placement.getRoomLocations()) 1352 for (RoomLocation r2: placement.getRoomLocations()) 1353 if (r2.getRoomConstraint() != null && r2.getRoomConstraint().getParentRoom() != null && r2.getRoomConstraint().getParentRoom().equals(r1.getRoomConstraint())) 1354 return "invalid room combination (" + r2.getName() + " is a partition of " + r1.getName() + ")"; 1355 1356 } 1357 return null; 1358 } 1359 1360 @Deprecated 1361 public String getNotValidReason(Assignment<Lecture, Placement> assignment, Placement placement) { 1362 return getNotValidReason(assignment, placement, true); 1363 } 1364 1365 public void purgeInvalidValues(boolean interactiveMode) { 1366 if (isCommitted() || sSaveMemory) return; 1367 TimetableModel model = (TimetableModel) getModel(); 1368 if (model == null) 1369 return; 1370 List<Placement> newValues = new ArrayList<Placement>(values(null).size()); 1371 for (Placement placement : values(null)) { 1372 if (placement.isValid()) 1373 newValues.add(placement); 1374 } 1375 if (!interactiveMode && newValues.size() != values(null).size()) { 1376 for (Iterator<TimeLocation> i = timeLocations().iterator(); i.hasNext();) { 1377 TimeLocation timeLocation = i.next(); 1378 boolean hasPlacement = false; 1379 for (Placement placement : newValues) { 1380 if (timeLocation.equals(placement.getTimeLocation())) { 1381 hasPlacement = true; 1382 break; 1383 } 1384 } 1385 if (!hasPlacement) 1386 i.remove(); 1387 } 1388 for (Iterator<RoomLocation> i = roomLocations().iterator(); i.hasNext();) { 1389 RoomLocation roomLocation = i.next(); 1390 boolean hasPlacement = false; 1391 for (Placement placement : newValues) { 1392 if (placement.isMultiRoom()) { 1393 if (placement.getRoomLocations().contains(roomLocation)) { 1394 hasPlacement = true; 1395 break; 1396 } 1397 } else { 1398 if (roomLocation.equals(placement.getRoomLocation())) { 1399 hasPlacement = true; 1400 break; 1401 } 1402 } 1403 } 1404 if (!hasPlacement) 1405 i.remove(); 1406 } 1407 } 1408 setValues(newValues); 1409 } 1410 1411 public void setCommitted(boolean committed) { 1412 iCommitted = committed; 1413 } 1414 1415 public boolean isCommitted() { 1416 return iCommitted; 1417 } 1418 1419 @Override 1420 public boolean isConstant() { 1421 return iCommitted; 1422 } 1423 1424 @Override 1425 public Placement getConstantValue() { 1426 return (isCommitted() ? getInitialAssignment() : null); 1427 } 1428 1429 public void setConstantValue(Placement value) { 1430 setInitialAssignment(value); 1431 } 1432 1433 public int getSpreadPenalty(Assignment<Lecture, Placement> assignment) { 1434 int spread = 0; 1435 for (SpreadConstraint sc : getSpreadConstraints()) { 1436 spread += sc.getPenalty(assignment); 1437 } 1438 return spread; 1439 } 1440 1441 @Override 1442 public int hashCode() { 1443 return getClassId().hashCode(); 1444 } 1445 1446 public Configuration getConfiguration() { 1447 Lecture lecture = this; 1448 while (lecture.getParent() != null) 1449 lecture = lecture.getParent(); 1450 return lecture.iParentConfiguration; 1451 } 1452 1453 public void setConfiguration(Configuration configuration) { 1454 Lecture lecture = this; 1455 while (lecture.getParent() != null) 1456 lecture = lecture.getParent(); 1457 lecture.iParentConfiguration = configuration; 1458 configuration.addTopLecture(lecture); 1459 } 1460 1461 private int[] iMinMaxRoomPreference = null; 1462 1463 public int[] getMinMaxRoomPreference() { 1464 iLock.readLock().lock(); 1465 try { 1466 if (iMinMaxRoomPreference != null) return iMinMaxRoomPreference; 1467 } finally { 1468 iLock.readLock().unlock(); 1469 } 1470 iLock.writeLock().lock(); 1471 try { 1472 if (iMinMaxRoomPreference != null) return iMinMaxRoomPreference; 1473 1474 if (getNrRooms() <= 0 || roomLocations().isEmpty()) { 1475 iMinMaxRoomPreference = new int[] { 0, 0 }; 1476 } else { 1477 Integer minRoomPref = null, maxRoomPref = null; 1478 for (RoomLocation r : roomLocations()) { 1479 int pref = r.getMinPreference(); 1480 if (pref >= Constants.sPreferenceLevelRequired / 2 && pref <= Constants.sPreferenceLevelProhibited / 2) { 1481 minRoomPref = (minRoomPref == null ? pref : Math.min(minRoomPref, pref)); 1482 maxRoomPref = (maxRoomPref == null ? pref : Math.max(maxRoomPref, pref)); 1483 } 1484 pref = r.getMaxPreference(); 1485 if (pref >= Constants.sPreferenceLevelRequired / 2 && pref <= Constants.sPreferenceLevelProhibited / 2) { 1486 minRoomPref = (minRoomPref == null ? pref : Math.min(minRoomPref, pref)); 1487 maxRoomPref = (maxRoomPref == null ? pref : Math.max(maxRoomPref, pref)); 1488 } 1489 } 1490 iMinMaxRoomPreference = new int[] { minRoomPref == null ? 0 : minRoomPref, maxRoomPref == null ? 0 : maxRoomPref }; 1491 } 1492 1493 return iMinMaxRoomPreference; 1494 } finally { 1495 iLock.writeLock().unlock(); 1496 } 1497 } 1498 1499 private double[] iMinMaxTimePreference = null; 1500 1501 public double[] getMinMaxTimePreference() { 1502 iLock.readLock().lock(); 1503 try { 1504 if (iMinMaxTimePreference != null) return iMinMaxTimePreference; 1505 } finally { 1506 iLock.readLock().unlock(); 1507 } 1508 iLock.writeLock().lock(); 1509 try { 1510 if (iMinMaxTimePreference != null) return iMinMaxTimePreference; 1511 1512 Double minTimePref = null, maxTimePref = null; 1513 for (TimeLocation t : timeLocations()) { 1514 double npref = t.getNormalizedPreference(); 1515 int pref = t.getPreference(); 1516 if (pref >= Constants.sPreferenceLevelRequired / 2 && pref <= Constants.sPreferenceLevelProhibited / 2) { 1517 minTimePref = (minTimePref == null ? npref : Math.min(minTimePref, npref)); 1518 maxTimePref = (maxTimePref == null ? npref : Math.max(maxTimePref, npref)); 1519 } 1520 } 1521 iMinMaxTimePreference = new double[] { minTimePref == null ? 0.0 : minTimePref, maxTimePref == null ? 0.0 : maxTimePref }; 1522 1523 return iMinMaxTimePreference; 1524 } finally { 1525 iLock.writeLock().unlock(); 1526 } 1527 } 1528 1529 public void setOrd(int ord) { 1530 iOrd = ord; 1531 } 1532 1533 public int getOrd() { 1534 return iOrd; 1535 } 1536 1537 @Override 1538 public int compareTo(Lecture o) { 1539 int cmp = Double.compare(getOrd(), o.getOrd()); 1540 if (cmp != 0) 1541 return cmp; 1542 return super.compareTo(o); 1543 } 1544 1545 public String getNote() { 1546 return iNote; 1547 } 1548 1549 public void setNote(String note) { 1550 iNote = note; 1551 } 1552 1553 public boolean areStudentConflictsHard(Lecture other) { 1554 return StudentConflict.hard(this, other); 1555 } 1556 1557 public void clearIgnoreStudentConflictsWithCache() { 1558 iIgnoreStudentConflictsWith.set(null); 1559 } 1560 1561 /** 1562 * Returns true if there is {@link IgnoreStudentConflictsConstraint} between the two lectures. 1563 * @param other another class 1564 * @return true if student conflicts between this and the given calls are to be ignored 1565 */ 1566 public boolean isToIgnoreStudentConflictsWith(Lecture other) { 1567 Set<Long> cache = iIgnoreStudentConflictsWith.get(); 1568 if (cache != null) 1569 return cache.contains(other.getClassId()); 1570 cache = new HashSet<Long>(); 1571 for (Constraint<Lecture, Placement> constraint: constraints()) { 1572 if (constraint instanceof IgnoreStudentConflictsConstraint) 1573 for (Lecture x: constraint.variables()) { 1574 if (!x.equals(this)) cache.add(x.getClassId()); 1575 } 1576 } 1577 iIgnoreStudentConflictsWith.set(cache); 1578 return cache.contains(other.getClassId()); 1579 } 1580 1581 /** 1582 * Get class weight. This weight is used with the criteria. E.g., class that is not meeting all the 1583 * semester can have a lower weight. Defaults to 1.0 1584 * @return class weight 1585 */ 1586 public double getWeight() { return iWeight; } 1587 /** 1588 * Set class weight. This weight is used with the criteria. E.g., class that is not meeting all the 1589 * semester can have a lower weight. 1590 * @param weight class weight 1591 */ 1592 public void setWeight(double weight) { iWeight = weight; } 1593 1594 @Override 1595 public LectureContext createAssignmentContext(Assignment<Lecture, Placement> assignment) { 1596 return new LectureContext(); 1597 } 1598 1599 public class LectureContext implements AssignmentContext { 1600 private Set<JenrlConstraint> iActiveJenrls = new HashSet<JenrlConstraint>(); 1601 1602 /** 1603 * Add active jenrl constraint (active mean that there is at least one 1604 * student between its classes) 1605 * @param constr active join enrollment constraint 1606 */ 1607 public void addActiveJenrl(JenrlConstraint constr) { 1608 iActiveJenrls.add(constr); 1609 } 1610 1611 /** 1612 * Active jenrl constraints (active mean that there is at least one student 1613 * between its classes) 1614 * @return set of active join enrollment constraints 1615 */ 1616 public Set<JenrlConstraint> activeJenrls() { 1617 return iActiveJenrls; 1618 } 1619 1620 /** 1621 * Remove active jenrl constraint (active mean that there is at least one 1622 * student between its classes) 1623 * @param constr active join enrollment constraint 1624 */ 1625 public void removeActiveJenrl(JenrlConstraint constr) { 1626 iActiveJenrls.remove(constr); 1627 } 1628 } 1629 1630 public int getMaxRoomCombinations() { 1631 return iMaxRoomCombinations; 1632 } 1633 1634 public void setMaxRoomCombinations(int maxRoomCombinations) { 1635 iMaxRoomCombinations = maxRoomCombinations; 1636 } 1637 1638 private Integer iMinWeeks = null; 1639 public int getMinWeeks() { 1640 if (iMinWeeks == null) { 1641 iMinWeeks = 0; 1642 for (TimeLocation t: timeLocations()) { 1643 int s = t.getWeekCode().nextSetBit(0); 1644 int e = t.getWeekCode().previousSetBit(t.getWeekCode().size()); 1645 int weeks = 1 + (e - s) / 7; 1646 if (iMinWeeks == 0 || weeks < iMinWeeks) iMinWeeks = weeks; 1647 } 1648 } 1649 return iMinWeeks; 1650 } 1651 1652 /** 1653 * When using multiple rooms, split attendance means that the class is split among the given rooms. 1654 * @return true means class is split among rooms, false means each room must be big enough for the class 1655 */ 1656 public boolean isSplitAttendance() { return iSplitAttendance; } 1657 1658 /** 1659 * When using multiple rooms, split attendance means that the class is split among the given rooms. 1660 * @param splitAttendance true means class is split among rooms, false means each room must be big enough for the class 1661 */ 1662 public void setSplitAttendance(boolean splitAttendance) { iSplitAttendance = splitAttendance; } 1663 1664 /** 1665 * Check if the assigned rooms are too small for a class with multiple rooms and split attendace. 1666 */ 1667 private boolean isTooSmall(Collection<RoomLocation> rooms) { 1668 if (getNrRooms() <= 1 && !isSplitAttendance()) return false; 1669 int size = 0; 1670 for (RoomLocation room: rooms) 1671 size += room.getRoomSize(); 1672 return size < minRoomSize(); 1673 } 1674 1675 /** 1676 * Check if the assigned rooms are too small for a class with multiple rooms and split attendace. 1677 */ 1678 private boolean isTooSmall(Placement p) { 1679 return getNrRooms() > 1 && isSplitAttendance() && p.getRoomSize() < minRoomUse(); 1680 } 1681 1682 /** 1683 * Check that the room and its parent are not used at the same time 1684 */ 1685 private boolean checkParents(Placement p) { 1686 if (p.isMultiRoom()) { 1687 for (RoomLocation r1: p.getRoomLocations()) 1688 for (RoomLocation r2: p.getRoomLocations()) 1689 if (r2.getRoomConstraint() != null && r2.getRoomConstraint().getParentRoom() != null && r2.getRoomConstraint().getParentRoom().equals(r1.getRoomConstraint())) 1690 return false; 1691 } 1692 return true; 1693 } 1694}