001package org.cpsolver.studentsct.constraint; 002 003import java.util.Set; 004 005import org.cpsolver.coursett.Constants; 006import org.cpsolver.coursett.model.TimeLocation; 007import org.cpsolver.ifs.assignment.Assignment; 008import org.cpsolver.ifs.model.GlobalConstraint; 009import org.cpsolver.studentsct.StudentSectioningModel; 010import org.cpsolver.studentsct.extension.StudentQuality; 011import org.cpsolver.studentsct.model.Config; 012import org.cpsolver.studentsct.model.Enrollment; 013import org.cpsolver.studentsct.model.Request; 014import org.cpsolver.studentsct.model.SctAssignment; 015import org.cpsolver.studentsct.model.Section; 016import org.cpsolver.studentsct.model.Student; 017import org.cpsolver.studentsct.model.Unavailability; 018 019/** 020 * Hard distance conflicts constraint. This global constraint checks for distance conflicts 021 * that should not be allowed. These are distance conflicts where the distance betweem the 022 * two sections is longer than HardDistanceConflict.DistanceHardLimitInMinutes minutes (defaults to 60) 023 * and the distance to travel between the two sections is longer than 024 * HardDistanceConflict.AllowedDistanceInMinutes minutes (defaults to 30). 025 * The constraint checks both pairs of sections that the student is to be enrolled in 026 * and distance conflicts with unavailabilities. 027 * Hard distance conflicts are allowed between sections that allow for time conflicts. 028 * 029 * @author Tomáš Müller 030 * @version StudentSct 1.4 (Student Sectioning)<br> 031 * Copyright (C) 2007 - 2025 Tomáš Müller<br> 032 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 033 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 034 * <br> 035 * This library is free software; you can redistribute it and/or modify 036 * it under the terms of the GNU Lesser General Public License as 037 * published by the Free Software Foundation; either version 3 of the 038 * License, or (at your option) any later version. <br> 039 * <br> 040 * This library is distributed in the hope that it will be useful, but 041 * WITHOUT ANY WARRANTY; without even the implied warranty of 042 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 043 * Lesser General Public License for more details. <br> 044 * <br> 045 * You should have received a copy of the GNU Lesser General Public 046 * License along with this library; if not see 047 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 048 */ 049public class HardDistanceConflicts extends GlobalConstraint<Request, Enrollment> { 050 /** 051 * A given enrollment is conflicting, if there is a section that 052 * is disabled and there is not a matching reservation that would allow for that. 053 * 054 * @param enrollment {@link Enrollment} that is being considered 055 * @param conflicts all computed conflicting requests are added into this set 056 */ 057 @Override 058 public void computeConflicts(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<Enrollment> conflicts) { 059 if (enrollment.variable().getModel() == null || !(enrollment.variable().getModel() instanceof StudentSectioningModel)) return; 060 StudentSectioningModel model = (StudentSectioningModel)enrollment.variable().getModel(); 061 StudentQuality studentQuality = model.getStudentQuality(); 062 if (studentQuality == null) return; 063 StudentQuality.Context cx = studentQuality.getStudentQualityContext(); 064 065 // no distance conflicts when overlaps are allowed by a reservation 066 if (enrollment.getReservation() != null && enrollment.getReservation().isAllowOverlap()) return; 067 068 // enrollment's student 069 Student student = enrollment.getStudent(); 070 // no unavailabilities > no distance conflicts 071 if (student.getUnavailabilities().isEmpty()) return; 072 073 // enrollment's config 074 Config config = enrollment.getConfig(); 075 076 // exclude free time requests 077 if (config == null) return; 078 079 // check for an unavailability distance conflict 080 if (cx.getUnavailabilityDistanceMetric().isHardDistanceConflictsEnabled()) { 081 for (Section s1: enrollment.getSections()) { 082 // no time or no room > no conflict 083 if (!s1.hasTime() || s1.getNrRooms() == 0 || s1.isAllowOverlap()) continue; 084 for (Unavailability s2: student.getUnavailabilities()) { 085 // no time or no room > no conflict 086 if (s2.getTime() == null || s2.getNrRooms() == 0) continue; 087 TimeLocation t1 = s1.getTime(); 088 TimeLocation t2 = s2.getTime(); 089 // no shared day > no conflict 090 if (!t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 091 int a1 = t1.getStartSlot(), a2 = t2.getStartSlot(); 092 if (a1 + t1.getNrSlotsPerMeeting() <= a2) { 093 int dist = cx.getUnavailabilityDistanceInMinutes(s1.getPlacement(), s2); 094 if (dist >= cx.getUnavailabilityDistanceMetric().getDistanceHardLimitInMinutes() 095 && dist > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a2 - a1 - t1.getLength()) + cx.getUnavailabilityDistanceMetric().getAllowedDistanceInMinutes()) { 096 conflicts.add(enrollment); 097 return; 098 } 099 } else if (a2 + t2.getNrSlotsPerMeeting() <= a1) { 100 int dist = cx.getUnavailabilityDistanceInMinutes(s1.getPlacement(), s2); 101 if (dist >= cx.getUnavailabilityDistanceMetric().getDistanceHardLimitInMinutes() 102 && dist > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a1 - a2 - t2.getLength()) + cx.getUnavailabilityDistanceMetric().getAllowedDistanceInMinutes()) { 103 conflicts.add(enrollment); 104 return; 105 } 106 } 107 } 108 } 109 } 110 111 // check for distance conflicts within the enrollment 112 if (cx.getDistanceMetric().isHardDistanceConflictsEnabled()) { 113 for (Section s1: enrollment.getSections()) { 114 // no time or no room > no conflict 115 if (!s1.hasTime() || s1.getNrRooms() == 0 || s1.isAllowOverlap()) continue; 116 for (Section s2: enrollment.getSections()) { 117 if (s1.getId() < s2.getId()) { 118 // no time or no room > no conflict 119 if (!s2.hasTime() || s2.getNrRooms() == 0 || s2.isAllowOverlap() || s1.isToIgnoreStudentConflictsWith(s2.getId())) continue; 120 TimeLocation t1 = s1.getTime(); 121 TimeLocation t2 = s2.getTime(); 122 // no shared day > no conflict 123 if (!t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 124 int a1 = t1.getStartSlot(), a2 = t2.getStartSlot(); 125 if (cx.getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) { 126 if (a1 + t1.getNrSlotsPerMeeting() <= a2) { 127 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 128 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 129 && dist > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a2 - a1 - t1.getLength()) + cx.getDistanceMetric().getAllowedDistanceInMinutes()) { 130 conflicts.add(enrollment); 131 return; 132 } 133 } else if (a2 + t2.getNrSlotsPerMeeting() <= a1) { 134 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 135 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 136 && dist > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a1 - a2 - t2.getLength()) + cx.getDistanceMetric().getAllowedDistanceInMinutes()) { 137 conflicts.add(enrollment); 138 return; 139 } 140 } 141 } else { 142 if (a1 + t1.getNrSlotsPerMeeting() == a2) { 143 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 144 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 145 && dist > t1.getBreakTime() + cx.getDistanceMetric().getAllowedDistanceInMinutes()) { 146 conflicts.add(enrollment); 147 return; 148 } 149 } else if (a2 + t2.getNrSlotsPerMeeting() == a1) { 150 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 151 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 152 && dist > t2.getBreakTime() + cx.getDistanceMetric().getAllowedDistanceInMinutes()) { 153 conflicts.add(enrollment); 154 return; 155 } 156 } 157 } 158 } 159 } 160 } 161 162 // check conflicts with other enrollments of the student 163 other: for (Request other: student.getRequests()) { 164 if (other.equals(enrollment.variable())) continue; 165 Enrollment e2 = other.getAssignment(assignment); 166 if (e2 == null || conflicts.contains(e2)) continue; 167 if (e2.getReservation() != null && e2.getReservation().isAllowOverlap()) continue; 168 for (Section s1: enrollment.getSections()) { 169 // no time or no room > no conflict 170 if (!s1.hasTime() || s1.getNrRooms() == 0 || s1.isAllowOverlap()) continue; 171 for (Section s2: e2.getSections()) { 172 // no time or no room > no conflict 173 if (!s2.hasTime() || s2.getNrRooms() == 0 || s2.isAllowOverlap() || s1.isToIgnoreStudentConflictsWith(s2.getId())) continue; 174 TimeLocation t1 = s1.getTime(); 175 TimeLocation t2 = s2.getTime(); 176 // no shared day > no conflict 177 if (!t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 178 int a1 = t1.getStartSlot(), a2 = t2.getStartSlot(); 179 if (cx.getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) { 180 if (a1 + t1.getNrSlotsPerMeeting() <= a2) { 181 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 182 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 183 && dist > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a2 - a1 - t1.getLength()) + cx.getDistanceMetric().getAllowedDistanceInMinutes()) { 184 conflicts.add(e2); 185 continue other; 186 } 187 } else if (a2 + t2.getNrSlotsPerMeeting() <= a1) { 188 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 189 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 190 && dist > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a1 - a2 - t2.getLength()) + cx.getDistanceMetric().getAllowedDistanceInMinutes()) { 191 conflicts.add(e2); 192 continue other; 193 } 194 } 195 } else { 196 if (a1 + t1.getNrSlotsPerMeeting() == a2) { 197 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 198 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 199 && dist > t1.getBreakTime() + cx.getDistanceMetric().getAllowedDistanceInMinutes()) { 200 conflicts.add(e2); 201 continue other; 202 } 203 } else if (a2 + t2.getNrSlotsPerMeeting() == a1) { 204 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 205 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 206 && dist > t2.getBreakTime() + cx.getDistanceMetric().getAllowedDistanceInMinutes()) { 207 conflicts.add(e2); 208 continue other; 209 } 210 } 211 } 212 } 213 } 214 } 215 } 216 } 217 218 /** 219 * A given enrollment is conflicting, if there is a section that 220 * is disabled and there is not a matching reservation that would allow for that. 221 * 222 * @param enrollment {@link Enrollment} that is being considered 223 * @return true, if the enrollment does not follow a reservation that must be used 224 */ 225 @Override 226 public boolean inConflict(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 227 if (enrollment.variable().getModel() == null || !(enrollment.variable().getModel() instanceof StudentSectioningModel)) return false; 228 StudentSectioningModel model = (StudentSectioningModel)enrollment.variable().getModel(); 229 StudentQuality studentQuality = model.getStudentQuality(); 230 if (studentQuality == null) return false; 231 StudentQuality.Context cx = studentQuality.getStudentQualityContext(); 232 233 // no distance conflicts when overlaps are allowed by a reservation 234 if (enrollment.getReservation() != null && enrollment.getReservation().isAllowOverlap()) return false; 235 236 // enrollment's student 237 Student student = enrollment.getStudent(); 238 // no unavailabilities > no distance conflicts 239 if (student.getUnavailabilities().isEmpty()) return false; 240 241 // enrollment's config 242 Config config = enrollment.getConfig(); 243 244 // exclude free time requests 245 if (config == null) return false; 246 247 // check for an unavailability distance conflict 248 if (cx.getUnavailabilityDistanceMetric().isHardDistanceConflictsEnabled()) { 249 for (Section s1: enrollment.getSections()) { 250 // no time or no room > no conflict 251 if (!s1.hasTime() || s1.getNrRooms() == 0 || s1.isAllowOverlap()) continue; 252 for (Unavailability s2: student.getUnavailabilities()) { 253 // no time or no room > no conflict 254 if (s2.getTime() == null || s2.getNrRooms() == 0 || s2.isAllowOverlap()) continue; 255 TimeLocation t1 = s1.getTime(); 256 TimeLocation t2 = s2.getTime(); 257 // no shared day > no conflict 258 if (!t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 259 int a1 = t1.getStartSlot(), a2 = t2.getStartSlot(); 260 if (a1 + t1.getNrSlotsPerMeeting() <= a2) { 261 int dist = cx.getUnavailabilityDistanceInMinutes(s1.getPlacement(), s2); 262 if (dist >= cx.getUnavailabilityDistanceMetric().getDistanceHardLimitInMinutes() 263 && dist > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a2 - a1 - t1.getLength()) + cx.getUnavailabilityDistanceMetric().getAllowedDistanceInMinutes()) 264 return true; 265 } else if (a2 + t2.getNrSlotsPerMeeting() <= a1) { 266 int dist = cx.getUnavailabilityDistanceInMinutes(s1.getPlacement(), s2); 267 if (dist >= cx.getUnavailabilityDistanceMetric().getDistanceHardLimitInMinutes() 268 && dist > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a1 - a2 - t2.getLength()) + cx.getUnavailabilityDistanceMetric().getAllowedDistanceInMinutes()) 269 return true; 270 } 271 } 272 } 273 } 274 275 // check for distance conflicts within the enrollment 276 if (cx.getDistanceMetric().isHardDistanceConflictsEnabled()) { 277 for (Section s1: enrollment.getSections()) { 278 // no time or no room > no conflict 279 if (!s1.hasTime() || s1.getNrRooms() == 0 || s1.isAllowOverlap()) continue; 280 for (Section s2: enrollment.getSections()) { 281 if (s1.getId() < s2.getId()) { 282 // no time or no room > no conflict 283 if (!s2.hasTime() || s2.getNrRooms() == 0 || s2.isAllowOverlap() || s1.isToIgnoreStudentConflictsWith(s2.getId())) continue; 284 TimeLocation t1 = s1.getTime(); 285 TimeLocation t2 = s2.getTime(); 286 // no shared day > no conflict 287 if (!t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 288 int a1 = t1.getStartSlot(), a2 = t2.getStartSlot(); 289 if (cx.getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) { 290 if (a1 + t1.getNrSlotsPerMeeting() <= a2) { 291 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 292 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 293 && dist > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a2 - a1 - t1.getLength()) + cx.getDistanceMetric().getAllowedDistanceInMinutes()) 294 return true; 295 } else if (a2 + t2.getNrSlotsPerMeeting() <= a1) { 296 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 297 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 298 && dist > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a1 - a2 - t2.getLength()) + cx.getDistanceMetric().getAllowedDistanceInMinutes()) 299 return true; 300 } 301 } else { 302 if (a1 + t1.getNrSlotsPerMeeting() == a2) { 303 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 304 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 305 && dist > t1.getBreakTime() + cx.getDistanceMetric().getAllowedDistanceInMinutes()) 306 return true; 307 } else if (a2 + t2.getNrSlotsPerMeeting() == a1) { 308 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 309 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() 310 && dist > t2.getBreakTime() + cx.getDistanceMetric().getAllowedDistanceInMinutes()) 311 return true; 312 } 313 } 314 } 315 } 316 } 317 318 // check conflicts with other enrollments of the student 319 for (Request other: student.getRequests()) { 320 if (other.equals(enrollment.variable())) continue; 321 Enrollment e2 = other.getAssignment(assignment); 322 if (e2 == null) continue; 323 if (e2.getReservation() != null && e2.getReservation().isAllowOverlap()) continue; 324 for (Section s1: enrollment.getSections()) { 325 // no time or no room > no conflict 326 if (!s1.hasTime() || s1.getNrRooms() == 0 || s1.isAllowOverlap()) continue; 327 for (Section s2: e2.getSections()) { 328 // no time or no room > no conflict 329 if (!s2.hasTime() || s2.getNrRooms() == 0 || s2.isAllowOverlap() || s1.isToIgnoreStudentConflictsWith(s2.getId())) continue; 330 TimeLocation t1 = s1.getTime(); 331 TimeLocation t2 = s2.getTime(); 332 // no shared day > no conflict 333 if (!t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 334 int a1 = t1.getStartSlot(), a2 = t2.getStartSlot(); 335 if (cx.getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) { 336 if (a1 + t1.getNrSlotsPerMeeting() <= a2) { 337 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 338 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() && dist > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a2 - a1 - t1.getLength()) + cx.getDistanceMetric().getAllowedDistanceInMinutes()) 339 return true; 340 } else if (a2 + t2.getNrSlotsPerMeeting() <= a1) { 341 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 342 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() && dist > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a1 - a2 - t2.getLength()) + cx.getDistanceMetric().getAllowedDistanceInMinutes()) 343 return true; 344 } 345 } else { 346 if (a1 + t1.getNrSlotsPerMeeting() == a2) { 347 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 348 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() && dist > t1.getBreakTime() + cx.getDistanceMetric().getAllowedDistanceInMinutes()) 349 return true; 350 } else if (a2 + t2.getNrSlotsPerMeeting() == a1) { 351 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 352 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() && dist > t2.getBreakTime() + cx.getDistanceMetric().getAllowedDistanceInMinutes()) 353 return true; 354 } 355 } 356 357 } 358 359 } 360 } 361 } 362 363 return false; 364 } 365 366 public static boolean inConflict(StudentQuality sq, Section s1, Unavailability s2) { 367 if (sq == null || s1 == null || s2 == null) return false; 368 if (s1.getPlacement() == null || s2.getTime() == null || s2.getNrRooms() == 0 369 || s1.isAllowOverlap() || s2.isAllowOverlap()) return false; 370 StudentQuality.Context cx = sq.getStudentQualityContext(); 371 if (!cx.getUnavailabilityDistanceMetric().isHardDistanceConflictsEnabled()) return false; 372 TimeLocation t1 = s1.getTime(); 373 TimeLocation t2 = s2.getTime(); 374 if (!t1.shareDays(t2) || !t1.shareWeeks(t2)) 375 return false; 376 int a1 = t1.getStartSlot(), a2 = t2.getStartSlot(); 377 if (a1 + t1.getNrSlotsPerMeeting() <= a2) { 378 int dist = cx.getUnavailabilityDistanceInMinutes(s1.getPlacement(), s2); 379 if (dist >= cx.getUnavailabilityDistanceMetric().getDistanceHardLimitInMinutes() && 380 dist > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a2 - a1 - t1.getLength()) + cx.getUnavailabilityDistanceMetric().getAllowedDistanceInMinutes()) 381 return true; 382 } else if (a2 + t2.getNrSlotsPerMeeting() <= a1) { 383 int dist = cx.getUnavailabilityDistanceInMinutes(s1.getPlacement(), s2); 384 if (dist >= cx.getUnavailabilityDistanceMetric().getDistanceHardLimitInMinutes() && 385 dist > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a1 - a2 - t2.getLength()) + cx.getUnavailabilityDistanceMetric().getAllowedDistanceInMinutes()) 386 return true; 387 } 388 return false; 389 } 390 391 public static boolean inConflict(StudentQuality sq, Section s1, Section s2) { 392 if (sq == null || s1 == null || s2 == null) return false; 393 if (s1.getPlacement() == null || s2.getPlacement() == null 394 || s1.isAllowOverlap() || s2.isAllowOverlap() || s1.isToIgnoreStudentConflictsWith(s2.getId())) return false; 395 StudentQuality.Context cx = sq.getStudentQualityContext(); 396 if (!cx.getDistanceMetric().isHardDistanceConflictsEnabled()) return false; 397 TimeLocation t1 = s1.getTime(); 398 TimeLocation t2 = s2.getTime(); 399 if (!t1.shareDays(t2) || !t1.shareWeeks(t2)) 400 return false; 401 int a1 = t1.getStartSlot(), a2 = t2.getStartSlot(); 402 if (a1 + t1.getNrSlotsPerMeeting() <= a2) { 403 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 404 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() && 405 dist > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a2 - a1 - t1.getLength()) + cx.getDistanceMetric().getAllowedDistanceInMinutes()) 406 return true; 407 } else if (a2 + t2.getNrSlotsPerMeeting() <= a1) { 408 int dist = cx.getDistanceInMinutes(s1.getPlacement(), s2.getPlacement()); 409 if (dist >= cx.getDistanceMetric().getDistanceHardLimitInMinutes() && 410 dist > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (a1 - a2 - t2.getLength()) + cx.getDistanceMetric().getAllowedDistanceInMinutes()) 411 return true; 412 } 413 return false; 414 } 415 416 public static boolean inConflict(StudentQuality sq, SctAssignment s1, Enrollment e) { 417 if (sq == null || s1 == null || e == null) return false; 418 if (!sq.getStudentQualityContext().getDistanceMetric().isHardDistanceConflictsEnabled()) return false; 419 if (e.getReservation() != null && e.getReservation().isAllowOverlap()) return false; 420 if (s1 instanceof Section && e.getCourse() != null) 421 for (SctAssignment s2: e.getAssignments()) 422 if (s2 instanceof Section && inConflict(sq, (Section)s1, (Section)s2)) return true; 423 return false; 424 } 425 426 @Override 427 public String toString() { 428 return "DistanceConflict"; 429 } 430}