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