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