001package net.sf.cpsolver.coursett.constraint;
002
003import java.util.HashSet;
004import java.util.Set;
005
006import net.sf.cpsolver.coursett.criteria.StudentConflict;
007import net.sf.cpsolver.coursett.model.Lecture;
008import net.sf.cpsolver.coursett.model.Placement;
009import net.sf.cpsolver.coursett.model.Student;
010import net.sf.cpsolver.coursett.model.TimetableModel;
011import net.sf.cpsolver.ifs.criteria.Criterion;
012import net.sf.cpsolver.ifs.model.BinaryConstraint;
013import net.sf.cpsolver.ifs.model.WeakeningConstraint;
014import net.sf.cpsolver.ifs.util.DistanceMetric;
015import net.sf.cpsolver.ifs.util.ToolBox;
016
017/**
018 * Join student enrollment constraint. <br>
019 * This constraint is placed between all pairs of classes where there is at
020 * least one student attending both classes. It represents a number of student
021 * conflicts (number of joined enrollments), if the given two classes overlap in
022 * time. <br>
023 * Also, it dynamically maintains the counter of all student conflicts. It is a
024 * soft constraint.
025 * 
026 * 
027 * @version CourseTT 1.2 (University Course Timetabling)<br>
028 *          Copyright (C) 2006 - 2010 Tomáš Müller<br>
029 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
030 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
031 * <br>
032 *          This library is free software; you can redistribute it and/or modify
033 *          it under the terms of the GNU Lesser General Public License as
034 *          published by the Free Software Foundation; either version 3 of the
035 *          License, or (at your option) any later version. <br>
036 * <br>
037 *          This library is distributed in the hope that it will be useful, but
038 *          WITHOUT ANY WARRANTY; without even the implied warranty of
039 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
040 *          Lesser General Public License for more details. <br>
041 * <br>
042 *          You should have received a copy of the GNU Lesser General Public
043 *          License along with this library; if not see
044 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
045 */
046
047public class JenrlConstraint extends BinaryConstraint<Lecture, Placement> implements WeakeningConstraint<Lecture, Placement> {
048    private double iJenrl = 0.0;
049    private double iPriority = 0.0;
050    private Set<Student> iStudents = new HashSet<Student>();
051    private Set<Student> iInstructors = new HashSet<Student>();
052    private boolean iAdded = false;
053    private Double iJenrlLimit = null;
054    private double iTwiggle = 0.0;
055
056    /**
057     * Constructor
058     */
059    public JenrlConstraint() {
060        super();
061    }
062    
063    @Override
064    public void addVariable(Lecture variable) {
065        super.addVariable(variable);
066        if (second() != null && variable.getModel() != null && variable.getModel() instanceof TimetableModel) {
067            double maxConflicts = ((TimetableModel)variable.getModel()).getProperties().getPropertyDouble("General.JenrlMaxConflicts", 1.0);
068            if (maxConflicts >= 0.0 && maxConflicts < 1.0) {
069                iJenrlLimit = Math.min(first().maxClassLimit(), second().maxClassLimit()) * maxConflicts;
070            }
071        }
072    }
073    
074    @Override
075    public void computeConflicts(Placement value, Set<Placement> conflicts) {
076        if (inConflict(value))
077            conflicts.add(another(value.variable()).getAssignment());
078    }
079
080    @Override
081    public boolean inConflict(Placement value) {
082        if (!isOverLimit()) return false;
083        Lecture other = another(value.variable());
084        return other != null && other.getAssignment() != null && !other.isCommitted() && isInConflict(value, other.getAssignment(), getDistanceMetric());
085    }
086
087    @Override
088    public boolean isConsistent(Placement value1, Placement value2) {
089        return !isOverLimit() || !isInConflict(value1, value2, getDistanceMetric());
090    }
091
092    @Override
093    public void unassigned(long iteration, Placement value) {
094        super.unassigned(iteration, value);
095        if (iAdded) {
096            iAdded = false;
097            (first()).removeActiveJenrl(this);
098            (second()).removeActiveJenrl(this);
099        }
100    }
101
102    /**
103     * Returns true if the given placements are overlapping or they are
104     * back-to-back and too far for students.
105     */
106    public static boolean isInConflict(Placement p1, Placement p2, DistanceMetric m) {
107        return !StudentConflict.ignore(p1, p2) && (StudentConflict.distance(m, p1, p2) || StudentConflict.overlaps(p1, p2));
108    }
109
110    @Override
111    public void assigned(long iteration, Placement value) {
112        super.assigned(iteration, value);
113        if (second() == null || first().getAssignment() == null || second().getAssignment() == null)
114            return;
115        if (isInConflict(first().getAssignment(), second().getAssignment(), getDistanceMetric())) {
116            iAdded = true;
117            (first()).addActiveJenrl(this);
118            (second()).addActiveJenrl(this);
119        }
120    }
121
122    /**
123     * Number of joined enrollments if the given value is assigned to the given
124     * variable
125     */
126    public long jenrl(Lecture variable, Placement value) {
127        Lecture anotherLecture = (first().equals(variable) ? second() : first());
128        if (anotherLecture.getAssignment() == null)
129            return 0;
130        return (isInConflict(anotherLecture.getAssignment(), value, getDistanceMetric()) ? Math.round(iJenrl) : 0);
131    }
132    
133    private DistanceMetric getDistanceMetric() {
134        return (getModel() == null ? null : ((TimetableModel)getModel()).getDistanceMetric());
135    }
136
137    /** True if the given two lectures overlap in time */
138    public boolean isInConflict() {
139        return iAdded;
140    }
141
142    /**
143     * Increment the number of joined enrollments (during student final
144     * sectioning)
145     */
146    public void incJenrl(Student student) {
147        boolean hard = isOverLimit();
148        double jenrlWeight = student.getJenrlWeight(first(), second());
149        iJenrl += jenrlWeight;
150        Double conflictPriority = student.getConflictingPriorty(first(), second());
151        if (conflictPriority != null) iPriority += conflictPriority * jenrlWeight;
152        iStudents.add(student);
153        if (student.getInstructor() != null && (student.getInstructor().variables().contains(first()) ||
154                student.getInstructor().variables().contains(second())))
155            iInstructors.add(student);
156        for (Criterion<Lecture, Placement> criterion: getModel().getCriteria())
157            if (criterion instanceof StudentConflict)
158                ((StudentConflict)criterion).incJenrl(this, jenrlWeight, conflictPriority, student);
159        if (!hard && isOverLimit() && isInConflict()) {
160            iJenrlLimit += jenrlWeight;
161        }
162    }
163
164    public double getJenrlWeight(Student student) {
165        return student.getJenrlWeight(first(), second());
166    }
167
168    /**
169     * Decrement the number of joined enrollments (during student final
170     * sectioning)
171     */
172    public void decJenrl(Student student) {
173        boolean hard = isOverLimit();
174        double jenrlWeight = student.getJenrlWeight(first(), second());
175        iJenrl -= jenrlWeight;
176        Double conflictPriority = student.getConflictingPriorty(first(), second());
177        if (conflictPriority != null) iPriority -= conflictPriority * jenrlWeight;
178        iStudents.remove(student);
179        iInstructors.remove(student);
180        for (Criterion<Lecture, Placement> criterion: getModel().getCriteria())
181            if (criterion instanceof StudentConflict)
182                ((StudentConflict)criterion).incJenrl(this, -jenrlWeight, conflictPriority, student);
183        if (hard && !isOverLimit()) {
184            double maxConflicts = ((TimetableModel)second().getModel()).getProperties().getPropertyDouble("General.JenrlMaxConflicts", 1.0) + iTwiggle;
185            if (maxConflicts >= 0.0 && maxConflicts < 1.0) {
186                iJenrlLimit = Math.max(Math.min(first().maxClassLimit(), second().maxClassLimit()) * maxConflicts, iJenrlLimit - jenrlWeight);
187            } else {
188                iJenrlLimit = null;
189            }
190        }
191    }
192
193    /** Number of joined enrollments (during student final sectioning) */
194    public long getJenrl() {
195        return Math.round(iJenrl);
196    }
197
198    public double jenrl() {
199        return iJenrl;
200    }
201
202    public double priority() {
203        return iPriority;
204    }
205
206    public int getNrStudents() {
207        return iStudents.size();
208    }
209    
210    public Set<Student> getStudents() {
211        return iStudents;
212    }
213    
214    public int getNrInstructors() {
215        return iInstructors.size();
216    }
217    
218    public Set<Student> getInstructors() {
219        return iInstructors;
220    }
221
222    @Override
223    public boolean isHard() {
224        return true;
225    }
226    
227    public boolean isOverLimit() {
228        return iJenrlLimit != null && iJenrl > iJenrlLimit;
229    }
230    
231    @Override
232    public String getName() {
233        return "Join Enrollment";
234    }
235
236    @Override
237    public String toString() {
238        return "Join Enrollment between " + first().getName() + " and " + second().getName();
239    }
240
241    public boolean areStudentConflictsHard() {
242        return StudentConflict.hard(first(), second());
243    }
244
245    public boolean areStudentConflictsDistance() {
246        return StudentConflict.distance(getDistanceMetric(), first().getAssignment(), second().getAssignment());
247    }
248
249    public boolean areStudentConflictsCommitted() {
250        return StudentConflict.committed(first(), second());
251    }
252
253    public boolean areStudentConflictsDistance(Placement value) {
254        return StudentConflict.distance(getDistanceMetric(), value, another(value.variable()).getAssignment());
255    }
256
257    public boolean isOfTheSameProblem() {
258        return ToolBox.equals(first().getSolverGroupId(), second().getSolverGroupId());
259    }
260
261    @Override
262    public void weaken() {
263        if (second() == null) return;
264        iTwiggle += ((TimetableModel)second().getModel()).getProperties().getPropertyDouble("General.JenrlMaxConflictsWeaken", 0.001);
265        double maxConflicts = ((TimetableModel)second().getModel()).getProperties().getPropertyDouble("General.JenrlMaxConflicts", 1.0) + iTwiggle;
266        if (maxConflicts >= 0.0 && maxConflicts < 1.0) {
267            iJenrlLimit = Math.min(first().maxClassLimit(), second().maxClassLimit()) * maxConflicts;
268        } else {
269            iJenrlLimit = null;
270        }
271    }
272
273    @Override
274    public void weaken(Placement value) {
275        if (second() != null && inConflict(value)) {
276            double maxConflicts = ((TimetableModel)second().getModel()).getProperties().getPropertyDouble("General.JenrlMaxConflicts", 1.0) + iTwiggle;
277            iTwiggle = (iJenrl + 0.00001) / Math.min(first().maxClassLimit(), second().maxClassLimit()) - maxConflicts;
278            if (maxConflicts + iTwiggle >= 0.0 && maxConflicts + iTwiggle < 1.0) {
279                iJenrlLimit = Math.min(first().maxClassLimit(), second().maxClassLimit()) * (maxConflicts + iTwiggle);
280            } else {
281                iJenrlLimit = null;
282            }
283        }
284    }
285    
286    /**
287     * Returns true if there is {@link IgnoreStudentConflictsConstraint} between the two lectures.
288     */
289    public boolean isToBeIgnored() {
290        return first().isToIgnoreStudentConflictsWith(second());
291    }
292}