001package org.cpsolver.coursett.criteria;
002
003import java.util.Collection;
004import java.util.HashSet;
005import java.util.Set;
006
007import org.cpsolver.coursett.Constants;
008import org.cpsolver.coursett.constraint.JenrlConstraint;
009import org.cpsolver.coursett.model.Lecture;
010import org.cpsolver.coursett.model.Placement;
011import org.cpsolver.coursett.model.Student;
012import org.cpsolver.coursett.model.TimeLocation;
013import org.cpsolver.coursett.model.TimetableModel;
014import org.cpsolver.ifs.assignment.Assignment;
015import org.cpsolver.ifs.util.DataProperties;
016import org.cpsolver.ifs.util.DistanceMetric;
017
018
019/**
020 * Student conflicts. This criterion counts student conflicts between classes. A conflict
021 * occurs when two classes that are attended by the same student (or students) are overlapping
022 * in time or place back-to-back in rooms that are too far a part. The combinations of classes
023 * that share students are maintained by {@link JenrlConstraint}.  
024 * <br>
025 * 
026 * @version CourseTT 1.3 (University Course Timetabling)<br>
027 *          Copyright (C) 2006 - 2014 Tomas Muller<br>
028 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
029 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
030 * <br>
031 *          This library is free software; you can redistribute it and/or modify
032 *          it under the terms of the GNU Lesser General Public License as
033 *          published by the Free Software Foundation; either version 3 of the
034 *          License, or (at your option) any later version. <br>
035 * <br>
036 *          This library is distributed in the hope that it will be useful, but
037 *          WITHOUT ANY WARRANTY; without even the implied warranty of
038 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
039 *          Lesser General Public License for more details. <br>
040 * <br>
041 *          You should have received a copy of the GNU Lesser General Public
042 *          License along with this library; if not see
043 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
044 */
045public class StudentConflict extends TimetablingCriterion {
046    protected boolean iIncludeConflicts = false;
047    
048    public StudentConflict() {
049        setValueUpdateType(ValueUpdateType.BeforeUnassignedBeforeAssigned);
050    }
051    
052    @Override
053    public void configure(DataProperties properties) {   
054        super.configure(properties);
055        iIncludeConflicts = properties.getPropertyBoolean("StudentConflict.IncludeConflicts", false);
056    }
057    
058    @Override
059    public String getPlacementSelectionWeightName() {
060        return null;
061    }
062
063    public DistanceMetric getMetrics() {
064        return (getModel() == null ? null : ((TimetableModel)getModel()).getDistanceMetric());
065    }
066
067    public static boolean overlaps(Placement p1, Placement p2) {
068        return p1 != null && p2 != null && p1.getTimeLocation().hasIntersection(p2.getTimeLocation()) && (!p1.variable().isCommitted() || !p2.variable().isCommitted());
069    }
070    
071    protected double jointEnrollment(JenrlConstraint jenrl, Placement p1, Placement p2) {
072        return jointEnrollment(jenrl);
073    }
074    
075    protected double jointEnrollment(JenrlConstraint jenrl) {
076        return jenrl.jenrl();
077    }
078    
079    public static boolean distance(DistanceMetric m, Placement p1, Placement p2) {
080        if (m == null && p1 != null) m = ((TimetableModel)p1.variable().getModel()).getDistanceMetric();
081        if (m == null && p2 != null) m = ((TimetableModel)p2.variable().getModel()).getDistanceMetric();
082        if (p1 == null || p2 == null || m == null) return false;
083        if (p1.variable().isCommitted() && p2.variable().isCommitted()) return false;
084        TimeLocation t1 = p1.getTimeLocation(), t2 = p2.getTimeLocation();
085        if (!t1.shareDays(t2) || !t1.shareWeeks(t2)) return false;
086        if (m.doComputeDistanceConflictsBetweenNonBTBClasses()) {
087            if (t1.getStartSlot() + t1.getLength() <= t2.getStartSlot()) {
088                return Placement.getDistanceInMinutes(m, p1, p2) > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength());
089            } else if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) {
090                return Placement.getDistanceInMinutes(m, p1, p2) > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength());
091            }
092        } else {
093            if (t1.getStartSlot() + t1.getLength() == t2.getStartSlot()) {
094                return Placement.getDistanceInMinutes(m, p1, p2) > t1.getBreakTime();
095            } else if (t2.getStartSlot() + t2.getLength() == t1.getStartSlot()) {
096                return Placement.getDistanceInMinutes(m, p1, p2) > t2.getBreakTime();
097            }
098        }
099        return false;
100    }
101    
102    public static boolean ignore(Lecture l1, Lecture l2) {
103        return l1 != null && l2 != null && l1.isToIgnoreStudentConflictsWith(l2);
104    }
105    
106    public static boolean committed(Lecture l1, Lecture l2) {
107        return l1 != null && l2 != null && (l1.isCommitted() || l2.isCommitted()) && (!l1.isCommitted() || !l2.isCommitted());
108    }
109    
110    public static boolean uncommitted(Lecture l1, Lecture l2) {
111        return l1 != null && l2 != null && !l1.isCommitted() && !l2.isCommitted();
112    }
113    
114    public static boolean applicable(Lecture l1, Lecture l2) {
115        return l1 != null && l2 != null && (!l1.isCommitted() || !l2.isCommitted());
116    }
117
118    public static boolean hard(Lecture l1, Lecture l2) {
119        return l1 != null && l2 != null && l1.isSingleSection() && l2.isSingleSection() && (!l1.isCommitted() || !l2.isCommitted());
120    }
121    
122    public boolean isApplicable(Lecture l1, Lecture l2) {
123        return l1 != null && l2 != null && !ignore(l1, l2) && uncommitted(l1, l2); // exclude committed and outside student conflicts
124    }
125    
126    public boolean isApplicable(Student student, Lecture l1, Lecture l2) {
127        return isApplicable(l1, l2);
128    }
129    
130    public boolean inConflict(Placement p1, Placement p2) {
131        return overlaps(p1, p2) || distance(getMetrics(), p1, p2);
132    }
133    
134    @Override
135    public double getValue(Assignment<Lecture, Placement> assignment, Placement value, Set<Placement> conflicts) {
136        double ret = 0.0;
137        for (JenrlConstraint jenrl: value.variable().jenrlConstraints()) {
138            Lecture other = jenrl.another(value.variable());
139            if (!isApplicable(value.variable(), other)) continue;
140            Placement another = assignment.getValue(other);
141            if (another == null) continue;
142            if (conflicts != null && conflicts.contains(another)) continue;
143            if (inConflict(value, another))
144                ret += jointEnrollment(jenrl, value, another);
145        }
146        if (iIncludeConflicts && conflicts != null)
147            for (Placement conflict: conflicts) {
148                for (JenrlConstraint jenrl: conflict.variable().jenrlConstraints()) {
149                    Lecture other = jenrl.another(conflict.variable());
150                    if (!isApplicable(conflict.variable(), other)) continue;
151                    Placement another = assignment.getValue(other);
152                    if (another == null || another.variable().equals(value.variable())) continue;
153                    if (conflicts != null && conflicts.contains(another)) continue;
154                    if (inConflict(conflict, another))
155                        ret -= jointEnrollment(jenrl, conflict, another);
156                }
157            }
158        return ret;
159    }
160    
161    @Override
162    public double getValue(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) {
163        double ret = 0.0;
164        Set<JenrlConstraint> constraints = new HashSet<JenrlConstraint>();
165        for (Lecture lect: variables) {
166            Placement plac = assignment.getValue(lect);
167            if (plac == null) continue;
168            for (JenrlConstraint jenrl: lect.jenrlConstraints()) {
169                if (!constraints.add(jenrl)) continue;
170                Lecture other = jenrl.another(lect);
171                if (!other.isCommitted() && !variables.contains(other)) continue;
172                if (!isApplicable(lect, other)) continue;
173                if (inConflict(plac, assignment.getValue(other)))
174                    ret += jointEnrollment(jenrl, plac, assignment.getValue(other));
175            }
176        }
177        return ret;
178    }
179
180    @Override
181    public double[] getBounds(Assignment<Lecture, Placement> assignment) {
182        double[] bounds = { 0.0, 0.0 };
183        for (JenrlConstraint jenrl: ((TimetableModel)getModel()).getJenrlConstraints())
184            if (isApplicable(jenrl.first(), jenrl.second()))
185                bounds[0] += jointEnrollment(jenrl);
186        return bounds;
187    }
188    
189    @Override
190    public double[] getBounds(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) {
191        double[] bounds = { 0.0, 0.0 };
192        Set<JenrlConstraint> constraints = new HashSet<JenrlConstraint>();
193        for (Lecture lect: variables) {
194            if (assignment.getValue(lect) == null) continue;
195            for (JenrlConstraint jenrl: lect.jenrlConstraints()) {
196                if (isApplicable(jenrl.first(), jenrl.second()) && constraints.add(jenrl) && (jenrl.another(lect).isCommitted() || variables.contains(jenrl.another(lect))))
197                    bounds[0] += jointEnrollment(jenrl);
198            }
199        }
200        return bounds;
201    }
202    
203    public void incJenrl(Assignment<Lecture, Placement> assignment, JenrlConstraint jenrl, double studentWeight, Double conflictPriority, Student student) {
204        if (isApplicable(jenrl.first(), jenrl.second()) && inConflict(assignment.getValue(jenrl.first()), assignment.getValue(jenrl.second())))
205            super.inc(assignment, studentWeight);
206    }
207    
208    @Override
209    public void bestRestored(Assignment<Lecture, Placement> assignment) {
210        super.bestRestored(assignment);
211        getContext(assignment).setTotal(getValue(assignment, getModel().variables()));
212    }
213}