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