    001package org.cpsolver.studentsct.extension;
    002
    003import java.util.HashMap;
    004import java.util.HashSet;
    005import java.util.Iterator;
    006import java.util.Map;
    007import java.util.Set;
    008
    009import org.apache.logging.log4j.Logger;
    010import org.cpsolver.coursett.Constants;
    011import org.cpsolver.coursett.model.Placement;
    012import org.cpsolver.coursett.model.RoomLocation;
    013import org.cpsolver.coursett.model.TimeLocation;
    014import org.cpsolver.ifs.assignment.Assignment;
    015import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
    016import org.cpsolver.ifs.assignment.context.CanInheritContext;
    017import org.cpsolver.ifs.assignment.context.ExtensionWithContext;
    018import org.cpsolver.ifs.model.ModelListener;
    019import org.cpsolver.ifs.solver.Solver;
    020import org.cpsolver.ifs.util.DataProperties;
    021import org.cpsolver.ifs.util.DistanceMetric;
    022import org.cpsolver.studentsct.StudentSectioningModel;
    023import org.cpsolver.studentsct.StudentSectioningModel.StudentSectioningModelContext;
    024import org.cpsolver.studentsct.model.CourseRequest;
    025import org.cpsolver.studentsct.model.Enrollment;
    026import org.cpsolver.studentsct.model.Request;
    027import org.cpsolver.studentsct.model.Section;
    028import org.cpsolver.studentsct.model.Student;
    029
    030
    031/**
    032 * This extension computes student distant conflicts. Two sections that are
    033 * attended by the same student are considered in a distance conflict if they
    034 * are back-to-back taught in locations that are two far away. This means that
    035 * the (walking) distance in minutes between the two classes are longer than
    036 * the break time of the earlier class. See {@link DistanceMetric} for more details.
    037 *
    038 * @see TimeLocation
    039 * @see Placement
    040 *
    041 * <br>
    042 * <br>
    043 *
    044 * @author  Tomáš Müller
    045 * @version StudentSct 1.3 (Student Sectioning)<br>
    046 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    047 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    048 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    049 * <br>
    050 *          This library is free software; you can redistribute it and/or modify
    051 *          it under the terms of the GNU Lesser General Public License as
    052 *          published by the Free Software Foundation; either version 3 of the
    053 *          License, or (at your option) any later version. <br>
    054 * <br>
    055 *          This library is distributed in the hope that it will be useful, but
    056 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    057 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    058 *          Lesser General Public License for more details. <br>
    059 * <br>
    060 *          You should have received a copy of the GNU Lesser General Public
    061 *          License along with this library; if not see
    062 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    063 */
    064
    065public class DistanceConflict extends ExtensionWithContext<Request, Enrollment, DistanceConflict.DistanceConflictContext> implements ModelListener<Request, Enrollment>, CanInheritContext<Request, Enrollment, DistanceConflict.DistanceConflictContext> {
    066    private static Logger sLog = org.apache.logging.log4j.LogManager.getLogger(DistanceConflict.class);
    067    /** Debug flag */
    068    public static boolean sDebug = false;
    069    private DistanceMetric iDistanceMetric = null;
    070
    071    /**
    072     * Constructor. Beside of other thigs, this constructor also uses
    073     * {@link StudentSectioningModel#setDistanceConflict(DistanceConflict)} to
    074     * set the this instance to the model.
    075     *
    076     * @param solver
    077     *            constraint solver
    078     * @param properties
    079     *            configuration
    080     */
    081    public DistanceConflict(Solver<Request, Enrollment> solver, DataProperties properties) {
    082        super(solver, properties);
    083        if (solver != null) {
    084            StudentSectioningModel model = (StudentSectioningModel) solver.currentSolution().getModel();
    085            iDistanceMetric = model.getDistanceMetric();
    086            model.setDistanceConflict(this);
    087        }
    088        if (iDistanceMetric == null)
    089            iDistanceMetric = new DistanceMetric(properties);
    090    }
    091
    092    /**
    093     * Alternative constructor (for online student sectioning)
    094     * @param metrics distance metrics
    095     * @param properties configuration
    096     */
    097    public DistanceConflict(DistanceMetric metrics, DataProperties properties) {
    098        super(null, properties);
    099        iDistanceMetric = metrics;
    100    }
    101
    102    @Override
    103    public String toString() {
    104        return "DistanceConstraint";
    105    }
    106
    107    public DistanceMetric getDistanceMetric() {
    108        return iDistanceMetric;
    109    }
    110
    111    private Map<Long, Map<Long, Integer>> iDistanceCache = new HashMap<Long, Map<Long,Integer>>();
    112    protected synchronized int getDistanceInMinutes(RoomLocation r1, RoomLocation r2) {
    113        if (r1.getId().compareTo(r2.getId()) > 0) return getDistanceInMinutes(r2, r1);
    114        if (r1.getId().equals(r2.getId()) || r1.getIgnoreTooFar() || r2.getIgnoreTooFar())
    115            return 0;
    116        if (r1.getPosX() == null || r1.getPosY() == null || r2.getPosX() == null || r2.getPosY() == null)
    117            return iDistanceMetric.getMaxTravelDistanceInMinutes();
    118        Map<Long, Integer> other2distance = iDistanceCache.get(r1.getId());
    119        if (other2distance == null) {
    120            other2distance = new HashMap<Long, Integer>();
    121            iDistanceCache.put(r1.getId(), other2distance);
    122        }
    123        Integer distance = other2distance.get(r2.getId());
    124        if (distance == null) {
    125            distance = iDistanceMetric.getDistanceInMinutes(r1.getId(), r1.getPosX(), r1.getPosY(), r2.getId(), r2.getPosX(), r2.getPosY());
    126            other2distance.put(r2.getId(), distance);
    127        }
    128        return distance;
    129    }
    130
    131    protected int getDistanceInMinutes(Placement p1, Placement p2) {
    132        if (p1.isMultiRoom()) {
    133            if (p2.isMultiRoom()) {
    134                int dist = 0;
    135                for (RoomLocation r1 : p1.getRoomLocations()) {
    136                    for (RoomLocation r2 : p2.getRoomLocations()) {
    137                        dist = Math.max(dist, getDistanceInMinutes(r1, r2));
    138                    }
    139                }
    140                return dist;
    141            } else {
    142                if (p2.getRoomLocation() == null)
    143                    return 0;
    144                int dist = 0;
    145                for (RoomLocation r1 : p1.getRoomLocations()) {
    146                    dist = Math.max(dist, getDistanceInMinutes(r1, p2.getRoomLocation()));
    147                }
    148                return dist;
    149            }
    150        } else if (p2.isMultiRoom()) {
    151            if (p1.getRoomLocation() == null)
    152                return 0;
    153            int dist = 0;
    154            for (RoomLocation r2 : p2.getRoomLocations()) {
    155                dist = Math.max(dist, getDistanceInMinutes(p1.getRoomLocation(), r2));
    156            }
    157            return dist;
    158        } else {
    159            if (p1.getRoomLocation() == null || p2.getRoomLocation() == null)
    160                return 0;
    161            return getDistanceInMinutes(p1.getRoomLocation(), p2.getRoomLocation());
    162        }
    163    }
    164
    165    /**
    166     * Return true if the given two sections are in distance conflict. This
    167     * means that the sections are back-to-back and that they are placed in
    168     * locations that are two far.
    169     *
    170     * @param student a student
    171     * @param s1
    172     *            a section
    173     * @param s2
    174     *            a section
    175     * @return true, if the given sections are in a distance conflict
    176     */
    177    public boolean inConflict(Student student, Section s1, Section s2) {
    178        if (s1.getPlacement() == null || s2.getPlacement() == null)
    179            return false;
    180        TimeLocation t1 = s1.getTime();
    181        TimeLocation t2 = s2.getTime();
    182        if (!t1.shareDays(t2) || !t1.shareWeeks(t2))
    183            return false;
    184        int a1 = t1.getStartSlot(), a2 = t2.getStartSlot();
    185        if (student.isNeedShortDistances()) {
    186            if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) {
    187                if (a1 + t1.getNrSlotsPerMeeting() <= a2) {
    188                    int dist = getDistanceInMinutes(s1.getPlacement(), s2.getPlacement());
    189                    if (dist > Constants.SLOT_LENGTH_MIN * (a2 - a1 - t1.getLength()))
    190                        return true;
    191                } else if (a2 + t2.getNrSlotsPerMeeting() <= a1) {
    192                    int dist = getDistanceInMinutes(s1.getPlacement(), s2.getPlacement());
    193                    if (dist > Constants.SLOT_LENGTH_MIN * (a1 - a2 - t2.getLength()))
    194                        return true;
    195                }
    196            } else {
    197                if (a1 + t1.getNrSlotsPerMeeting() == a2) {
    198                    int dist = getDistanceInMinutes(s1.getPlacement(), s2.getPlacement());
    199                    if (dist > 0) return true;
    200                } else if (a2 + t2.getNrSlotsPerMeeting() == a1) {
    201                    int dist = getDistanceInMinutes(s1.getPlacement(), s2.getPlacement());
    202                    if (dist > 0) return true;
    203                }
    204            }
    205            return false;
    206        }
    207        if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) {
    208            if (a1 + t1.getNrSlotsPerMeeting() <= a2) {
    209                int dist = getDistanceInMinutes(s1.getPlacement(), s2.getPlacement());
    210                if (dist > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a2 - a1 - t1.getLength()))
    211                    return true;
    212            } else if (a2 + t2.getNrSlotsPerMeeting() <= a1) {
    213                int dist = getDistanceInMinutes(s1.getPlacement(), s2.getPlacement());
    214                if (dist > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a1 - a2 - t2.getLength()))
    215                    return true;
    216            }
    217        } else {
    218            if (a1 + t1.getNrSlotsPerMeeting() == a2) {
    219                int dist = getDistanceInMinutes(s1.getPlacement(), s2.getPlacement());
    220                if (dist > t1.getBreakTime())
    221                    return true;
    222            } else if (a2 + t2.getNrSlotsPerMeeting() == a1) {
    223                int dist = getDistanceInMinutes(s1.getPlacement(), s2.getPlacement());
    224                if (dist > t2.getBreakTime())
    225                    return true;
    226            }
    227        }
    228        return false;
    229    }
    230
    231    /**
    232     * Return number of distance conflict of a (course) enrollment. It is the
    233     * number of pairs of assignments of the enrollment that are in a distance
    234     * conflict.
    235     *
    236     * @param e1
    237     *            an enrollment
    238     * @return number of distance conflicts
    239     */
    240    public int nrConflicts(Enrollment e1) {
    241        if (!e1.isCourseRequest())
    242            return 0;
    243        int cnt = 0;
    244        for (Section s1 : e1.getSections()) {
    245            for (Section s2 : e1.getSections()) {
    246                if (s1.getId() < s2.getId() && inConflict(e1.getStudent(), s1, s2))
    247                    cnt ++;
    248            }
    249        }
    250        return cnt;
    251    }
    252
    253    /**
    254     * Return number of distance conflicts that are between two enrollments. It
    255     * is the number of pairs of assignments of these enrollments that are in a
    256     * distance conflict.
    257     *
    258     * @param e1
    259     *            an enrollment
    260     * @param e2
    261     *            an enrollment
    262     * @return number of distance conflict between given enrollments
    263     */
    264    public int nrConflicts(Enrollment e1, Enrollment e2) {
    265        if (!e1.isCourseRequest() || !e2.isCourseRequest() || !e1.getStudent().equals(e2.getStudent()))
    266            return 0;
    267        int cnt = 0;
    268        for (Section s1 : e1.getSections()) {
    269            for (Section s2 : e2.getSections()) {
    270                if (inConflict(e1.getStudent(), s1, s2))
    271                    cnt ++;
    272            }
    273        }
    274        return cnt;
    275    }
    276
    277    /**
    278     * Return a set of distance conflicts ({@link Conflict} objects) of a
    279     * (course) enrollment.
    280     *
    281     * @param e1
    282     *            an enrollment
    283     * @return list of distance conflicts that are between assignment of the
    284     *         given enrollment
    285     */
    286    public Set<Conflict> conflicts(Enrollment e1) {
    287        Set<Conflict> ret = new HashSet<Conflict>();
    288        if (!e1.isCourseRequest())
    289            return ret;
    290        for (Section s1 : e1.getSections()) {
    291            for (Section s2 : e1.getSections()) {
    292                if (s1.getId() < s2.getId() && inConflict(e1.getStudent(), s1, s2))
    293                    ret.add(new Conflict(e1.getStudent(), e1, s1, e1, s2));
    294            }
    295        }
    296        return ret;
    297    }
    298
    299    /**
    300     * Return a set of distance conflicts ({@link Conflict} objects) between
    301     * given (course) enrollments.
    302     *
    303     * @param e1
    304     *            an enrollment
    305     * @param e2
    306     *            an enrollment
    307     * @return list of distance conflicts that are between assignment of the
    308     *         given enrollments
    309     */
    310    public Set<Conflict> conflicts(Enrollment e1, Enrollment e2) {
    311        Set<Conflict> ret = new HashSet<Conflict>();
    312        if (!e1.isCourseRequest() || !e2.isCourseRequest() || !e1.getStudent().equals(e2.getStudent()))
    313            return ret;
    314        for (Section s1 : e1.getSections()) {
    315            for (Section s2 : e2.getSections()) {
    316                if (inConflict(e1.getStudent(), s1, s2))
    317                    ret.add(new Conflict(e1.getStudent(), e1, s1, e2, s2));
    318            }
    319        }
    320        return ret;
    321    }
    322
    323    /**
    324     * The set of all conflicts ({@link Conflict} objects) of the given
    325     * enrollment and other enrollments that are assignmed to the same student.
    326     * @param assignment current assignment
    327     * @param enrollment given enrollment
    328     * @return set of all conflicts
    329     */
    330    public Set<Conflict> allConflicts(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    331        Set<Conflict> ret = conflicts(enrollment);
    332        if (!enrollment.isCourseRequest())
    333            return ret;
    334        for (Request request : enrollment.getStudent().getRequests()) {
    335            if (request.equals(enrollment.getRequest()) || assignment.getValue(request) == null)
    336                continue;
    337            ret.addAll(conflicts(enrollment, assignment.getValue(request)));
    338        }
    339        return ret;
    340    }
    341
    342    /** Checks the counter counting all conflicts
    343     * @param assignment current assignment
    344     */
    345    public void checkAllConflicts(Assignment<Request, Enrollment> assignment) {
    346        getContext(assignment).checkAllConflicts(assignment);
    347    }
    348
    349    /** Actual number of all distance conflicts
    350     * @param assignment current assignment
    351     * @return cached number of all distance conflicts
    352     **/
    353    public int getTotalNrConflicts(Assignment<Request, Enrollment> assignment) {
    354        return getContext(assignment).getTotalNrConflicts();
    355    }
    356
    357    /** Actual number of all distance conflicts of students that need short distances
    358     * @param assignment current assignment
    359     * @return cached number of all distance conflicts
    360     **/
    361    public int getTotalNrShortConflicts(Assignment<Request, Enrollment> assignment) {
    362        return getContext(assignment).getTotalNrShortConflicts();
    363    }
    364
    365    /**
    366     * Compute the actual number of all distance conflicts. Should be equal to
    367     * {@link DistanceConflict#getTotalNrConflicts(Assignment)}.
    368     * @param assignment current assignment
    369     * @return computed number of all distance conflicts
    370     */
    371    public int countTotalNrConflicts(Assignment<Request, Enrollment> assignment) {
    372        int total = 0;
    373        for (Request r1 : getModel().variables()) {
    374            if (assignment.getValue(r1) == null || !(r1 instanceof CourseRequest))
    375                continue;
    376            Enrollment e1 = assignment.getValue(r1);
    377            total += nrConflicts(e1);
    378            for (Request r2 : r1.getStudent().getRequests()) {
    379                Enrollment e2 = assignment.getValue(r2);
    380                if (e2 == null || r1.getId() >= r2.getId() || !(r2 instanceof CourseRequest))
    381                    continue;
    382                total += nrConflicts(e1, e2);
    383            }
    384        }
    385        return total;
    386    }
    387
    388    /**
    389     * Compute a set of all distance conflicts ({@link Conflict} objects).
    390     * @param assignment current assignment
    391     * @return computed set of all distance conflicts
    392     */
    393    public Set<Conflict> computeAllConflicts(Assignment<Request, Enrollment> assignment) {
    394        Set<Conflict> ret = new HashSet<Conflict>();
    395        for (Request r1 : getModel().variables()) {
    396            Enrollment e1 = assignment.getValue(r1);
    397            if (e1 == null || !(r1 instanceof CourseRequest))
    398                continue;
    399            ret.addAll(conflicts(e1));
    400            for (Request r2 : r1.getStudent().getRequests()) {
    401                Enrollment e2 = assignment.getValue(r2);
    402                if (e2 == null || r1.getId() >= r2.getId() || !(r2 instanceof CourseRequest))
    403                    continue;
    404                ret.addAll(conflicts(e1, e2));
    405            }
    406        }
    407        return ret;
    408    }
    409
    410    /**
    411     * Return a set of all distance conflicts ({@link Conflict} objects).
    412     * @param assignment current assignment
    413     * @return cached set of all distance conflicts
    414     */
    415    public Set<Conflict> getAllConflicts(Assignment<Request, Enrollment> assignment) {
    416        return getContext(assignment).getAllConflicts();
    417    }
    418
    419    /**
    420     * Called before a value is assigned to a variable.
    421     */
    422    @Override
    423    public void beforeAssigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment value) {
    424        getContext(assignment).beforeAssigned(assignment, iteration, value);
    425    }
    426
    427    /**
    428     * Called after a value is assigned to a variable.
    429     */
    430    @Override
    431    public void afterAssigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment value) {
    432        getContext(assignment).afterAssigned(assignment, iteration, value);
    433    }
    434
    435    /**
    436     * Called after a value is unassigned from a variable.
    437     */
    438    @Override
    439    public void afterUnassigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment value) {
    440        getContext(assignment).afterUnassigned(assignment, iteration, value);
    441    }
    442
    443    /** A representation of a distance conflict */
    444    public static class Conflict {
    445        private Student iStudent;
    446        private Section iS1, iS2;
    447        private Enrollment iE1, iE2;
    448        private int iHashCode;
    449
    450        /**
    451         * Constructor
    452         *
    453         * @param student
    454         *            related student
    455         * @param e1 first enrollment
    456         * @param s1
    457         *            first conflicting section
    458         * @param e2 second enrollment
    459         * @param s2
    460         *            second conflicting section
    461         */
    462        public Conflict(Student student, Enrollment e1, Section s1, Enrollment e2, Section s2) {
    463            iStudent = student;
    464            if (s1.getId() < s2.getId()) {
    465                iS1 = s1;
    466                iS2 = s2;
    467                iE1 = e1;
    468                iE2 = e2;
    469            } else {
    470                iS1 = s2;
    471                iS2 = s1;
    472                iE1 = e2;
    473                iE2 = e1;
    474            }
    475            iHashCode = (iStudent.getId() + ":" + iS1.getId() + ":" + iS2.getId()).hashCode();
    476        }
    477
    478        /** Related student
    479         * @return student
    480         **/
    481        public Student getStudent() {
    482            return iStudent;
    483        }
    484
    485        /** First section
    486         * @return first section
    487         **/
    488        public Section getS1() {
    489            return iS1;
    490        }
    491
    492        /** Second section
    493         * @return second section
    494         **/
    495        public Section getS2() {
    496            return iS2;
    497        }
    498
    499        /** First request
    500         * @return first request
    501         **/
    502        public Request getR1() {
    503            return iE1.getRequest();
    504        }
    505
    506        /** Second request
    507         * @return second request
    508         **/
    509        public Request getR2() {
    510            return iE2.getRequest();
    511        }
    512
    513        /** First enrollment
    514         * @return first enrollment
    515         **/
    516        public Enrollment getE1() {
    517            return iE1;
    518        }
    519
    520        /** Second enrollment
    521         * @return second enrollment
    522         **/
    523        public Enrollment getE2() {
    524            return iE2;
    525        }
    526
    527        @Override
    528        public int hashCode() {
    529            return iHashCode;
    530        }
    531
    532        /** The distance between conflicting sections
    533         * @param dm distance metrics
    534         * @return distance in meters between conflicting sections
    535         **/
    536        public double getDistance(DistanceMetric dm) {
    537            return Placement.getDistanceInMeters(dm, getS1().getPlacement(), getS2().getPlacement());
    538        }
    539
    540        @Override
    541        public boolean equals(Object o) {
    542            if (o == null || !(o instanceof Conflict)) return false;
    543            Conflict c = (Conflict) o;
    544            return getStudent().equals(c.getStudent()) && getS1().equals(c.getS1()) && getS2().equals(c.getS2());
    545        }
    546
    547        @Override
    548        public String toString() {
    549            return getStudent() + ": " + getS1() + " -- " + getS2();
    550        }
    551    }
    552
    553    public class DistanceConflictContext implements AssignmentConstraintContext<Request, Enrollment> {
    554        private Set<Conflict> iAllConflicts = new HashSet<Conflict>();
    555        private Request iOldVariable = null;
    556        private Enrollment iUnassignedValue = null;
    557
    558        public DistanceConflictContext(Assignment<Request, Enrollment> assignment) {
    559            iAllConflicts = computeAllConflicts(assignment);
    560            StudentSectioningModelContext cx = ((StudentSectioningModel)getModel()).getContext(assignment);
    561            for (Conflict c: iAllConflicts)
    562                cx.add(assignment, c);
    563        }
    564
    565        public DistanceConflictContext(DistanceConflictContext parent) {
    566            iAllConflicts.addAll(parent.iAllConflicts);
    567            iOldVariable = parent.iOldVariable;
    568            iUnassignedValue = parent.iUnassignedValue;
    569        }
    570
    571        /**
    572         * Called before a value is assigned to a variable.
    573         * @param assignment current assignment
    574         * @param iteration current iteration
    575         * @param value value to be assigned
    576         */
    577        public void beforeAssigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment value) {
    578            if (value != null) {
    579                Enrollment old = assignment.getValue(value.variable());
    580                if (old != null) {
    581                    unassigned(assignment, old);
    582                    iUnassignedValue = old;
    583                }
    584                iOldVariable = value.variable();
    585            }
    586        }
    587
    588        /**
    589         * Called after a value is assigned to a variable.
    590         * @param assignment current assignment
    591         * @param iteration current iteration
    592         * @param value value that was assigned
    593         */
    594        public void afterAssigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment value) {
    595            iOldVariable = null;
    596            iUnassignedValue = null;
    597            if (value != null)
    598                assigned(assignment, value);
    599        }
    600
    601        /**
    602         * Called after a value is unassigned from a variable.
    603         * @param assignment current assignment
    604         * @param iteration current iteration
    605         * @param value value to be unassigned
    606         */
    607        public void afterUnassigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment value) {
    608            if (value != null && !value.equals(iUnassignedValue))
    609                unassigned(assignment, value);
    610        }
    611
    612        /**
    613         * Called when a value is assigned to a variable. Internal number of
    614         * distance conflicts is updated, see
    615         * {@link DistanceConflict#getTotalNrConflicts(Assignment)}.
    616         */
    617        @Override
    618        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment value) {
    619            StudentSectioningModelContext cx = ((StudentSectioningModel)getModel()).getContext(assignment);
    620            for (Conflict c: allConflicts(assignment, value)) {
    621                if (iAllConflicts.add(c))
    622                    cx.add(assignment, c);
    623            }
    624            if (sDebug) {
    625                sLog.debug("A:" + value.variable() + " := " + value);
    626                int inc = nrConflicts(value);
    627                if (inc != 0) {
    628                    sLog.debug("-- DC+" + inc + " A: " + value.variable() + " := " + value);
    629                    for (Iterator<Conflict> i = allConflicts(assignment, value).iterator(); i.hasNext();)
    630                        sLog.debug("  -- " + i.next());
    631                }
    632            }
    633        }
    634
    635        /**
    636         * Called when a value is unassigned from a variable. Internal number of
    637         * distance conflicts is updated, see
    638         * {@link DistanceConflict#getTotalNrConflicts(Assignment)}.
    639         */
    640        @Override
    641        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment value) {
    642            if (value.variable().equals(iOldVariable))
    643                return;
    644            StudentSectioningModelContext cx = ((StudentSectioningModel)getModel()).getContext(assignment);
    645            for (Conflict c: allConflicts(assignment, value)) {
    646                if (iAllConflicts.remove(c))
    647                    cx.remove(assignment, c);
    648            }
    649            if (sDebug) {
    650                sLog.debug("U:" + value.variable() + " := " + value);
    651                int dec = nrAllConflicts(assignment, value);
    652                if (dec != 0) {
    653                    sLog.debug("-- DC+" + dec + " U: " + value.variable() + " := " + value);
    654                    Set<Conflict> confs = allConflicts(assignment, value);
    655                    for (Iterator<Conflict> i = confs.iterator(); i.hasNext();)
    656                        sLog.debug("  -- " + i.next());
    657                }
    658            }
    659        }
    660
    661        /** Checks the counter counting all conflicts
    662         * @param assignment current assignment
    663         **/
    664        public void checkAllConflicts(Assignment<Request, Enrollment> assignment) {
    665            Set<Conflict> allConfs = computeAllConflicts(assignment);
    666            if (iAllConflicts.size() != allConfs.size()) {
    667                sLog.error("Different number of conflicts " + iAllConflicts.size() + "!=" + allConfs.size());
    668                for (Iterator<Conflict> i = allConfs.iterator(); i.hasNext();) {
    669                    Conflict c = i.next();
    670                    if (!iAllConflicts.contains(c))
    671                        sLog.debug("  +add+ " + c);
    672                }
    673                for (Iterator<Conflict> i = iAllConflicts.iterator(); i.hasNext();) {
    674                    Conflict c = i.next();
    675                    if (!allConfs.contains(c))
    676                        sLog.debug("  -rem- " + c);
    677                }
    678                iAllConflicts = allConfs;
    679            }
    680        }
    681
    682        /** Actual number of all distance conflicts
    683         * @return number of all distance conflicts
    684         **/
    685        public int getTotalNrConflicts() {
    686            return iAllConflicts.size();
    687        }
    688
    689        /** Actual number of all distance conflicts of students that need short distances
    690         * @return number of all distance conflicts
    691         **/
    692        public int getTotalNrShortConflicts() {
    693            int ret = 0;
    694            for (Conflict c: iAllConflicts)
    695                if (c.getStudent().isNeedShortDistances()) ret ++;
    696            return ret;
    697        }
    698
    699
    700        /**
    701         * Return a set of all distance conflicts ({@link Conflict} objects).
    702         * @return all distance conflicts
    703         */
    704        public Set<Conflict> getAllConflicts() {
    705            return iAllConflicts;
    706        }
    707
    708        /**
    709         * Total sum of all conflict of the given enrollment and other enrollments
    710         * that are assigned to the same student.
    711         * @param assignment current assignment
    712         * @param enrollment given enrollment
    713         * @return number of all conflict of the given enrollment
    714         */
    715        public int nrAllConflicts(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    716            if (!enrollment.isCourseRequest())
    717                return 0;
    718            int cnt = nrConflicts(enrollment);
    719            Request old = iOldVariable;
    720            for (Request request : enrollment.getStudent().getRequests()) {
    721                if (request.equals(enrollment.getRequest()) || assignment.getValue(request) == null || request.equals(old))
    722                    continue;
    723                cnt += nrConflicts(enrollment, assignment.getValue(request));
    724            }
    725            return cnt;
    726        }
    727    }
    728
    729    @Override
    730    public DistanceConflictContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
    731        return new DistanceConflictContext(assignment);
    732    }
    733
    734    @Override
    735    public DistanceConflictContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, DistanceConflictContext parentContext) {
    736        return new DistanceConflictContext(parentContext);
    737    }
    738}