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