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