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