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