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}