001package org.cpsolver.coursett.constraint; 002 003import java.util.ArrayList; 004import java.util.BitSet; 005import java.util.Enumeration; 006import java.util.HashSet; 007import java.util.List; 008import java.util.Set; 009 010import org.cpsolver.coursett.Constants; 011import org.cpsolver.coursett.criteria.BackToBackInstructorPreferences; 012import org.cpsolver.coursett.model.Lecture; 013import org.cpsolver.coursett.model.Placement; 014import org.cpsolver.coursett.model.TimeLocation; 015import org.cpsolver.coursett.model.TimetableModel; 016import org.cpsolver.ifs.assignment.Assignment; 017import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext; 018import org.cpsolver.ifs.assignment.context.ConstraintWithContext; 019import org.cpsolver.ifs.util.DistanceMetric; 020 021 022/** 023 * Instructor constraint. <br> 024 * Classes with this instructor can not overlap in time. Also, for back-to-back 025 * classes, there is the following reasoning: 026 * <ul> 027 * <li>if the distance is equal or below 028 * {@link DistanceMetric#getInstructorNoPreferenceLimit()} .. no preference 029 * <li>if the distance is above 030 * {@link DistanceMetric#getInstructorNoPreferenceLimit()} and below 031 * {@link DistanceMetric#getInstructorDiscouragedLimit()} .. constraint is 032 * discouraged (soft, preference = 1) 033 * <li>if the distance is above 034 * {@link DistanceMetric#getInstructorDiscouragedLimit()} and below 035 * {@link DistanceMetric#getInstructorProhibitedLimit()} .. constraint is 036 * strongly discouraged (soft, preference = 2) 037 * <li>if the distance is above 038 * {@link DistanceMetric#getInstructorProhibitedLimit()} .. constraint is 039 * prohibited (hard) 040 * </ul> 041 * <br> 042 * When {@link InstructorConstraint#isIgnoreDistances()} is set to true, the 043 * constraint never prohibits two back-to-back classes (but it still tries to 044 * minimize the above back-to-back preferences). 045 * 046 * @author Tomáš Müller 047 * @version CourseTT 1.3 (University Course Timetabling)<br> 048 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 049 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 050 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 051 * <br> 052 * This library is free software; you can redistribute it and/or modify 053 * it under the terms of the GNU Lesser General Public License as 054 * published by the Free Software Foundation; either version 3 of the 055 * License, or (at your option) any later version. <br> 056 * <br> 057 * This library is distributed in the hope that it will be useful, but 058 * WITHOUT ANY WARRANTY; without even the implied warranty of 059 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 060 * Lesser General Public License for more details. <br> 061 * <br> 062 * You should have received a copy of the GNU Lesser General Public 063 * License along with this library; if not see 064 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 065 */ 066 067public class InstructorConstraint extends ConstraintWithContext<Lecture, Placement, InstructorConstraint.InstructorConstraintContext> { 068 private Long iResourceId; 069 private String iName; 070 private String iPuid; 071 private List<Placement> iUnavailabilities = null; 072 private boolean iIgnoreDistances = false; 073 private Long iType = null; 074 075 /** 076 * Constructor 077 * 078 * @param id instructor id 079 * @param puid instructor external id 080 * @param name instructor name 081 * @param ignDist true if distance conflicts are to be ignored 082 */ 083 public InstructorConstraint(Long id, String puid, String name, boolean ignDist) { 084 iResourceId = id; 085 iName = name; 086 iPuid = puid; 087 iIgnoreDistances = ignDist; 088 } 089 090 public void setNotAvailable(Placement placement) { 091 if (iUnavailabilities == null) 092 iUnavailabilities = new ArrayList<Placement>(); 093 iUnavailabilities.add(placement); 094 for (Lecture lecture: variables()) 095 lecture.clearValueCache(); 096 } 097 098 public boolean isAvailable(Lecture lecture, TimeLocation time) { 099 if (iUnavailabilities == null) return true; 100 for (Placement c: iUnavailabilities) { 101 if (c.variable().getId() < 0 && lecture.getDepartment() != null && c.variable().getDepartment() != null 102 && !c.variable().getDepartment().equals(lecture.getDepartment())) continue; 103 if (c.getTimeLocation().hasIntersection(time) && !lecture.canShareRoom(c.variable())) return false; 104 } 105 return true; 106 } 107 108 protected DistanceMetric getDistanceMetric() { 109 return ((TimetableModel)getModel()).getDistanceMetric(); 110 } 111 112 public boolean isAvailable(Lecture lecture, Placement placement) { 113 if (iUnavailabilities == null) return true; 114 TimeLocation t1 = placement.getTimeLocation(); 115 for (Placement c: iUnavailabilities) { 116 if (c.variable().getId() < 0 && lecture.getDepartment() != null && c.variable().getDepartment() != null 117 && !c.variable().getDepartment().equals(lecture.getDepartment())) continue; 118 if (c.getTimeLocation().hasIntersection(placement.getTimeLocation()) && (!lecture.canShareRoom(c.variable()) || !placement.sameRooms(c))) 119 return false; 120 if (!iIgnoreDistances) { 121 TimeLocation t2 = c.getTimeLocation(); 122 if (t1.shareDays(t2) && t1.shareWeeks(t2)) { 123 if (t1.getStartSlot() + t1.getLength() == t2.getStartSlot() || t2.getStartSlot() + t2.getLength() == t1.getStartSlot()) { 124 if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit()) 125 return false; 126 } else if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) { 127 if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) { 128 if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, c) > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) 129 return false; 130 } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) { 131 if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, c) > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) 132 return false; 133 } 134 } 135 } 136 } 137 } 138 return true; 139 } 140 141 public List<Placement> getUnavailabilities() { 142 return iUnavailabilities; 143 } 144 145 @Deprecated 146 @SuppressWarnings("unchecked") 147 public List<Placement>[] getAvailableArray() { 148 if (iUnavailabilities == null) return null; 149 List<Placement>[] available = new List[Constants.SLOTS_PER_DAY * Constants.DAY_CODES.length]; 150 for (int i = 0; i < available.length; i++) 151 available[i] = null; 152 for (Placement p: iUnavailabilities) { 153 for (Enumeration<Integer> e = p.getTimeLocation().getSlots(); e.hasMoreElements();) { 154 int slot = e.nextElement(); 155 if (available[slot] == null) 156 available[slot] = new ArrayList<Placement>(1); 157 available[slot].add(p); 158 } 159 } 160 return available; 161 } 162 163 /** Back-to-back preference of two placements (3 means prohibited) 164 * @param p1 first placement 165 * @param p2 second placement 166 * @return distance preference between the two placements 167 **/ 168 public int getDistancePreference(Placement p1, Placement p2) { 169 TimeLocation t1 = p1.getTimeLocation(); 170 TimeLocation t2 = p2.getTimeLocation(); 171 if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) 172 return Constants.sPreferenceLevelNeutral; 173 if (t1.getStartSlot() + t1.getLength() == t2.getStartSlot() || t2.getStartSlot() + t2.getLength() == t1.getStartSlot()) { 174 double distance = Placement.getDistanceInMeters(getDistanceMetric(), p1, p2); 175 if (distance <= getDistanceMetric().getInstructorNoPreferenceLimit()) 176 return Constants.sPreferenceLevelNeutral; 177 if (distance <= getDistanceMetric().getInstructorDiscouragedLimit()) 178 return Constants.sPreferenceLevelDiscouraged; 179 if (iIgnoreDistances || distance <= getDistanceMetric().getInstructorProhibitedLimit()) 180 return Constants.sPreferenceLevelStronglyDiscouraged; 181 return Constants.sPreferenceLevelProhibited; 182 } else if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) { 183 if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) { 184 int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), p1, p2); 185 if (distanceInMinutes > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) // too far apart 186 return (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited); 187 if (distanceInMinutes >= getDistanceMetric().getInstructorLongTravelInMinutes()) // long travel 188 return Constants.sPreferenceLevelStronglyDiscouraged; 189 if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) // too far if no break time 190 return Constants.sPreferenceLevelDiscouraged; 191 } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) { 192 int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), p1, p2); 193 if (distanceInMinutes > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) // too far apart 194 return (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited); 195 if (distanceInMinutes >= getDistanceMetric().getInstructorLongTravelInMinutes()) // long travel 196 return Constants.sPreferenceLevelStronglyDiscouraged; 197 if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) // too far if no break time 198 return Constants.sPreferenceLevelDiscouraged; 199 } 200 } 201 return Constants.sPreferenceLevelNeutral; 202 } 203 204 /** Resource id 205 * @return instructor unique id 206 **/ 207 public Long getResourceId() { 208 return iResourceId; 209 } 210 211 /** Resource name */ 212 @Override 213 public String getName() { 214 return iName; 215 } 216 217 @Override 218 public void computeConflicts(Assignment<Lecture, Placement> assignment, Placement placement, Set<Placement> conflicts) { 219 Lecture lecture = placement.variable(); 220 Placement current = assignment.getValue(lecture); 221 BitSet weekCode = placement.getTimeLocation().getWeekCode(); 222 InstructorConstraintContext context = getContext(assignment); 223 224 for (Enumeration<Integer> e = placement.getTimeLocation().getSlots(); e.hasMoreElements();) { 225 int slot = e.nextElement(); 226 for (Placement p : context.getPlacements(slot)) { 227 if (!p.equals(current) && p.getTimeLocation().shareWeeks(weekCode)) { 228 if (p.canShareRooms(placement) && p.sameRooms(placement)) 229 continue; 230 conflicts.add(p); 231 } 232 } 233 } 234 if (!iIgnoreDistances) { 235 for (Enumeration<Integer> e = placement.getTimeLocation().getStartSlots(); e.hasMoreElements();) { 236 int startSlot = e.nextElement(); 237 238 int prevSlot = startSlot - 1; 239 if (prevSlot >= 0 && (prevSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) { 240 for (Placement c : context.getPlacements(prevSlot, placement)) { 241 if (lecture.equals(c.variable())) continue; 242 if (c.canShareRooms(placement) && c.sameRooms(placement)) continue; 243 if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit()) 244 conflicts.add(c); 245 } 246 } 247 int nextSlot = startSlot + placement.getTimeLocation().getLength(); 248 if ((nextSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) { 249 for (Placement c : context.getPlacements(nextSlot, placement)) { 250 if (lecture.equals(c.variable())) continue; 251 if (c.canShareRooms(placement) && c.sameRooms(placement)) continue; 252 if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit()) 253 conflicts.add(c); 254 } 255 } 256 257 if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) { 258 TimeLocation t1 = placement.getTimeLocation(); 259 for (Lecture other: variables()) { 260 Placement otherPlacement = assignment.getValue(other); 261 if (otherPlacement == null || other.equals(placement.variable())) continue; 262 TimeLocation t2 = otherPlacement.getTimeLocation(); 263 if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 264 if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) { 265 if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, otherPlacement) > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) 266 conflicts.add(otherPlacement); 267 } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) { 268 if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, otherPlacement) > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) 269 conflicts.add(otherPlacement); 270 } 271 } 272 } 273 } 274 } 275 } 276 277 @Override 278 public boolean inConflict(Assignment<Lecture, Placement> assignment, Placement placement) { 279 Lecture lecture = placement.variable(); 280 Placement current = assignment.getValue(lecture); 281 BitSet weekCode = placement.getTimeLocation().getWeekCode(); 282 InstructorConstraintContext context = getContext(assignment); 283 284 for (Enumeration<Integer> e = placement.getTimeLocation().getSlots(); e.hasMoreElements();) { 285 int slot = e.nextElement(); 286 for (Placement p : context.getPlacements(slot)) { 287 if (!p.equals(current) && p.getTimeLocation().shareWeeks(weekCode)) { 288 if (p.canShareRooms(placement) && p.sameRooms(placement)) 289 continue; 290 return true; 291 } 292 } 293 } 294 if (!iIgnoreDistances) { 295 for (Enumeration<Integer> e = placement.getTimeLocation().getStartSlots(); e.hasMoreElements();) { 296 int startSlot = e.nextElement(); 297 298 int prevSlot = startSlot - 1; 299 if (prevSlot >= 0 && (prevSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) { 300 for (Placement c : context.getPlacements(prevSlot, placement)) { 301 if (lecture.equals(c.variable())) continue; 302 if (c.canShareRooms(placement) && c.sameRooms(placement)) continue; 303 if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit()) 304 return true; 305 } 306 } 307 int nextSlot = startSlot + placement.getTimeLocation().getLength(); 308 if ((nextSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) { 309 for (Placement c : context.getPlacements(nextSlot, placement)) { 310 if (lecture.equals(c.variable())) continue; 311 if (c.canShareRooms(placement) && c.sameRooms(placement)) continue; 312 if (Placement.getDistanceInMeters(getDistanceMetric(), placement, c) > getDistanceMetric().getInstructorProhibitedLimit()) 313 return true; 314 } 315 } 316 317 if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) { 318 TimeLocation t1 = placement.getTimeLocation(); 319 for (Lecture other: variables()) { 320 Placement otherPlacement = assignment.getValue(other); 321 if (otherPlacement == null || other.equals(placement.variable())) continue; 322 TimeLocation t2 = otherPlacement.getTimeLocation(); 323 if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 324 if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) { 325 if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, otherPlacement) > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) 326 return true; 327 } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) { 328 if (Placement.getDistanceInMinutes(getDistanceMetric(), placement, otherPlacement) > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) 329 return true; 330 } 331 } 332 } 333 } 334 } 335 return false; 336 } 337 338 @Override 339 public boolean isConsistent(Placement p1, Placement p2) { 340 if (p1.canShareRooms(p2) && p1.sameRooms(p2)) 341 return true; 342 if (p1.getTimeLocation().hasIntersection(p2.getTimeLocation())) 343 return false; 344 return getDistancePreference(p1, p2) != Constants.sPreferenceLevelProhibited; 345 } 346 347 @Override 348 public String toString() { 349 return "Instructor " + getName(); 350 } 351 352 /** Back-to-back preference of the given placement 353 * @param assignment current assignment 354 * @param value placement under consideration 355 * @return distance preference for the given placement 356 **/ 357 public int getPreference(Assignment<Lecture, Placement> assignment, Placement value) { 358 Lecture lecture = value.variable(); 359 Placement placement = value; 360 int pref = 0; 361 HashSet<Placement> checked = new HashSet<Placement>(); 362 InstructorConstraintContext context = getContext(assignment); 363 364 for (Enumeration<Integer> e = placement.getTimeLocation().getStartSlots(); e.hasMoreElements();) { 365 int startSlot = e.nextElement(); 366 367 int prevSlot = startSlot - 1; 368 if (prevSlot >= 0 && (prevSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) { 369 for (Placement c : context.getPlacements(prevSlot, placement)) { 370 if (lecture.equals(c.variable()) || !checked.add(c)) continue; 371 double dist = Placement.getDistanceInMeters(getDistanceMetric(), placement, c); 372 if (dist > getDistanceMetric().getInstructorNoPreferenceLimit() && dist <= getDistanceMetric().getInstructorDiscouragedLimit()) 373 pref += Constants.sPreferenceLevelDiscouraged; 374 if (dist > getDistanceMetric().getInstructorDiscouragedLimit() 375 && (dist <= getDistanceMetric().getInstructorProhibitedLimit() || iIgnoreDistances)) 376 pref += Constants.sPreferenceLevelStronglyDiscouraged; 377 if (!iIgnoreDistances && dist > getDistanceMetric().getInstructorProhibitedLimit()) 378 pref += Constants.sPreferenceLevelProhibited; 379 } 380 } 381 int nextSlot = startSlot + placement.getTimeLocation().getLength(); 382 if ((nextSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) { 383 for (Placement c : context.getPlacements(nextSlot, placement)) { 384 if (lecture.equals(c.variable()) || !checked.add(c)) continue; 385 double dist = Placement.getDistanceInMeters(getDistanceMetric(), placement, c); 386 if (dist > getDistanceMetric().getInstructorNoPreferenceLimit() && dist <= getDistanceMetric().getInstructorDiscouragedLimit()) 387 pref += Constants.sPreferenceLevelDiscouraged; 388 if (dist > getDistanceMetric().getInstructorDiscouragedLimit() 389 && (dist <= getDistanceMetric().getInstructorProhibitedLimit() || iIgnoreDistances)) 390 pref += Constants.sPreferenceLevelStronglyDiscouraged; 391 if (!iIgnoreDistances && dist > getDistanceMetric().getInstructorProhibitedLimit()) 392 pref = Constants.sPreferenceLevelProhibited; 393 } 394 } 395 } 396 397 if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) { 398 TimeLocation t1 = placement.getTimeLocation(); 399 Placement before = null, after = null; 400 for (Lecture other: variables()) { 401 Placement otherPlacement = assignment.getValue(other); 402 if (otherPlacement == null || other.equals(placement.variable())) continue; 403 TimeLocation t2 = otherPlacement.getTimeLocation(); 404 if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 405 if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) { 406 int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), placement, otherPlacement); 407 if (distanceInMinutes > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) 408 pref += (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited); 409 else if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) 410 pref += Constants.sPreferenceLevelDiscouraged; 411 } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) { 412 int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), placement, otherPlacement); 413 if (distanceInMinutes > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) 414 pref += (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited); 415 else if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) 416 pref += Constants.sPreferenceLevelDiscouraged; 417 } 418 if (t1.getStartSlot() + t1.getLength() <= t2.getStartSlot()) { 419 if (after == null || t2.getStartSlot() < after.getTimeLocation().getStartSlot()) 420 after = otherPlacement; 421 } else if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) { 422 if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot()) 423 before = otherPlacement; 424 } 425 } 426 if (iUnavailabilities != null) { 427 for (Placement c: iUnavailabilities) { 428 TimeLocation t2 = c.getTimeLocation(); 429 if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 430 if (t1.getStartSlot() + t1.getLength() <= t2.getStartSlot()) { 431 if (after == null || t2.getStartSlot() < after.getTimeLocation().getStartSlot()) 432 after = c; 433 } else if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) { 434 if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot()) 435 before = c; 436 } 437 } 438 } 439 if (before != null && Placement.getDistanceInMinutes(getDistanceMetric(), before, placement) > getDistanceMetric().getInstructorLongTravelInMinutes()) 440 pref += Constants.sPreferenceLevelStronglyDiscouraged; 441 if (after != null && Placement.getDistanceInMinutes(getDistanceMetric(), after, placement) > getDistanceMetric().getInstructorLongTravelInMinutes()) 442 pref += Constants.sPreferenceLevelStronglyDiscouraged; 443 if (before != null && after != null && Placement.getDistanceInMinutes(getDistanceMetric(), before, after) > getDistanceMetric().getInstructorLongTravelInMinutes()) 444 pref -= Constants.sPreferenceLevelStronglyDiscouraged; 445 } 446 return pref; 447 } 448 449 public int getPreference(Assignment<Lecture, Placement> assignment) { 450 return getContext(assignment).getPreference(); 451 } 452 453 public int getPreferenceCombination(Assignment<Lecture, Placement> assignment, Placement value) { 454 Lecture lecture = value.variable(); 455 Placement placement = value; 456 int pref = 0; 457 HashSet<Placement> checked = new HashSet<Placement>(); 458 InstructorConstraintContext context = getContext(assignment); 459 460 for (Enumeration<Integer> e = placement.getTimeLocation().getStartSlots(); e.hasMoreElements();) { 461 int startSlot = e.nextElement(); 462 463 int prevSlot = startSlot - 1; 464 if (prevSlot >= 0 && (prevSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) { 465 for (Placement c : context.getPlacements(prevSlot, placement)) { 466 if (lecture.equals(c.variable()) || !checked.add(c)) continue; 467 double dist = Placement.getDistanceInMeters(getDistanceMetric(), placement, c); 468 if (dist > getDistanceMetric().getInstructorNoPreferenceLimit() && dist <= getDistanceMetric().getInstructorDiscouragedLimit()) 469 pref = Math.max(pref, Constants.sPreferenceLevelDiscouraged); 470 if (dist > getDistanceMetric().getInstructorDiscouragedLimit() 471 && (dist <= getDistanceMetric().getInstructorProhibitedLimit() || iIgnoreDistances)) 472 pref = Math.max(pref, Constants.sPreferenceLevelStronglyDiscouraged); 473 if (!iIgnoreDistances && dist > getDistanceMetric().getInstructorProhibitedLimit()) 474 pref = Math.max(pref, Constants.sPreferenceLevelProhibited); 475 } 476 } 477 int nextSlot = startSlot + placement.getTimeLocation().getLength(); 478 if ((nextSlot / Constants.SLOTS_PER_DAY) == (startSlot / Constants.SLOTS_PER_DAY)) { 479 for (Placement c : context.getPlacements(nextSlot, placement)) { 480 if (lecture.equals(c.variable()) || !checked.add(c)) continue; 481 double dist = Placement.getDistanceInMeters(getDistanceMetric(), placement, c); 482 if (dist > getDistanceMetric().getInstructorNoPreferenceLimit() && dist <= getDistanceMetric().getInstructorDiscouragedLimit()) 483 pref = Math.max(pref, Constants.sPreferenceLevelDiscouraged); 484 if (dist > getDistanceMetric().getInstructorDiscouragedLimit() 485 && (dist <= getDistanceMetric().getInstructorProhibitedLimit() || iIgnoreDistances)) 486 pref = Math.max(pref, Constants.sPreferenceLevelStronglyDiscouraged); 487 if (!iIgnoreDistances && dist > getDistanceMetric().getInstructorProhibitedLimit()) 488 pref = Constants.sPreferenceLevelProhibited; 489 } 490 } 491 492 if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) { 493 TimeLocation t1 = placement.getTimeLocation(); 494 Placement before = null, after = null; 495 for (Lecture other: variables()) { 496 Placement otherPlacement = assignment.getValue(other); 497 if (otherPlacement == null || other.equals(placement.variable())) continue; 498 TimeLocation t2 = otherPlacement.getTimeLocation(); 499 if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 500 if (t1.getStartSlot() + t1.getLength() < t2.getStartSlot()) { 501 int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), placement, otherPlacement); 502 if (distanceInMinutes > t1.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) 503 pref = Math.max(pref, (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited)); 504 else if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t2.getStartSlot() - t1.getStartSlot() - t1.getLength())) 505 pref = Math.max(pref, Constants.sPreferenceLevelDiscouraged); 506 } else if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) { 507 int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), placement, otherPlacement); 508 if (distanceInMinutes > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) 509 pref = Math.max(pref, (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited)); 510 else if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) 511 pref = Math.max(pref, Constants.sPreferenceLevelDiscouraged); 512 } 513 if (t1.getStartSlot() + t1.getLength() <= t2.getStartSlot()) { 514 if (after == null || t2.getStartSlot() < after.getTimeLocation().getStartSlot()) 515 after = otherPlacement; 516 } else if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) { 517 if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot()) 518 before = otherPlacement; 519 } 520 } 521 if (iUnavailabilities != null) { 522 for (Placement c: iUnavailabilities) { 523 TimeLocation t2 = c.getTimeLocation(); 524 if (t1 == null || t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 525 if (t1.getStartSlot() + t1.getLength() <= t2.getStartSlot()) { 526 if (after == null || t2.getStartSlot() < after.getTimeLocation().getStartSlot()) 527 after = c; 528 } else if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) { 529 if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot()) 530 before = c; 531 } 532 } 533 } 534 int tooLongTravel = 0; 535 if (before != null && Placement.getDistanceInMinutes(getDistanceMetric(), before, placement) > getDistanceMetric().getInstructorLongTravelInMinutes()) 536 tooLongTravel++; 537 if (after != null && Placement.getDistanceInMinutes(getDistanceMetric(), after, placement) > getDistanceMetric().getInstructorLongTravelInMinutes()) 538 tooLongTravel++; 539 if (tooLongTravel > 0) 540 pref += Math.max(pref, Constants.sPreferenceLevelStronglyDiscouraged); 541 } 542 } 543 return pref; 544 } 545 546 /** Worst back-to-back preference of this instructor 547 * @return worst possible distance preference 548 **/ 549 public int getWorstPreference() { 550 return Constants.sPreferenceLevelStronglyDiscouraged * (variables().size() - 1); 551 } 552 553 public String getPuid() { 554 return iPuid; 555 } 556 557 public boolean isIgnoreDistances() { 558 return iIgnoreDistances; 559 } 560 561 public void setIgnoreDistances(boolean ignDist) { 562 iIgnoreDistances = ignDist; 563 } 564 565 public Long getType() { 566 return iType; 567 } 568 569 public void setType(Long type) { 570 iType = type; 571 } 572 573 @Override 574 public InstructorConstraintContext createAssignmentContext(Assignment<Lecture, Placement> assignment) { 575 return new InstructorConstraintContext(assignment); 576 } 577 578 public class InstructorConstraintContext implements AssignmentConstraintContext<Lecture, Placement> { 579 public int iPreference = 0; 580 protected List<Placement>[] iResource; 581 582 @SuppressWarnings("unchecked") 583 public InstructorConstraintContext(Assignment<Lecture, Placement> assignment) { 584 iResource = new List[Constants.SLOTS_PER_DAY * Constants.DAY_CODES.length]; 585 for (int i = 0; i < iResource.length; i++) 586 iResource[i] = new ArrayList<Placement>(3); 587 for (Lecture lecture: variables()) { 588 Placement placement = assignment.getValue(lecture); 589 if (placement != null) { 590 for (Enumeration<Integer> e = placement.getTimeLocation().getSlots(); e.hasMoreElements();) { 591 int slot = e.nextElement(); 592 iResource[slot].add(placement); 593 } 594 } 595 } 596 iPreference = countPreference(assignment); 597 getModel().getCriterion(BackToBackInstructorPreferences.class).inc(assignment, iPreference); 598 } 599 600 @Override 601 public void assigned(Assignment<Lecture, Placement> assignment, Placement placement) { 602 for (Enumeration<Integer> e = placement.getTimeLocation().getSlots(); e.hasMoreElements();) { 603 int slot = e.nextElement(); 604 iResource[slot].add(placement); 605 } 606 getModel().getCriterion(BackToBackInstructorPreferences.class).inc(assignment, -iPreference); 607 iPreference = countPreference(assignment); 608 getModel().getCriterion(BackToBackInstructorPreferences.class).inc(assignment, iPreference); 609 } 610 611 @Override 612 public void unassigned(Assignment<Lecture, Placement> assignment, Placement placement) { 613 for (Enumeration<Integer> e = placement.getTimeLocation().getSlots(); e.hasMoreElements();) { 614 int slot = e.nextElement(); 615 iResource[slot].remove(placement); 616 } 617 getModel().getCriterion(BackToBackInstructorPreferences.class).inc(assignment, -iPreference); 618 iPreference = countPreference(assignment); 619 getModel().getCriterion(BackToBackInstructorPreferences.class).inc(assignment, iPreference); 620 } 621 622 public List<Placement> getPlacements(int slot) { return iResource[slot]; } 623 624 public Placement getPlacement(int slot, int day) { 625 for (Placement p : iResource[slot]) { 626 if (p.getTimeLocation().hasDay(day)) 627 return p; 628 } 629 return null; 630 } 631 632 public List<Placement> getPlacements(int slot, BitSet weekCode) { 633 List<Placement> placements = new ArrayList<Placement>(iResource[slot].size()); 634 for (Placement p : iResource[slot]) { 635 if (p.getTimeLocation().shareWeeks(weekCode)) 636 placements.add(p); 637 } 638 return placements; 639 } 640 641 public List<Placement> getPlacements(int slot, Placement placement) { 642 return getPlacements(slot, placement.getTimeLocation().getWeekCode()); 643 } 644 645 public int getNrSlots() { return iResource.length; } 646 647 public Placement[] getResourceOfWeek(int startDay) { 648 Placement[] ret = new Placement[iResource.length]; 649 for (int i = 0; i < iResource.length; i++) { 650 ret[i] = getPlacement(i, startDay + (i / Constants.SLOTS_PER_DAY)); 651 } 652 return ret; 653 } 654 655 /** Overall back-to-back preference of this instructor 656 * @return current distance preference 657 **/ 658 public int getPreference() { 659 return iPreference; 660 } 661 662 public int countPreference(Assignment<Lecture, Placement> assignment) { 663 int pref = 0; 664 HashSet<Placement> checked = new HashSet<Placement>(); 665 666 for (int slot = 1; slot < getNrSlots(); slot++) { 667 if ((slot % Constants.SLOTS_PER_DAY) == 0) continue; 668 for (Placement placement : getPlacements(slot)) { 669 for (Placement c : getPlacements(slot - 1, placement)) { 670 if (placement.variable().equals(c.variable()) || !checked.add(c)) continue; 671 double dist = Placement.getDistanceInMeters(getDistanceMetric(), c, placement); 672 if (dist > getDistanceMetric().getInstructorNoPreferenceLimit() && dist <= getDistanceMetric().getInstructorDiscouragedLimit()) 673 pref += Constants.sPreferenceLevelDiscouraged; 674 if (dist > getDistanceMetric().getInstructorDiscouragedLimit()) 675 pref += Constants.sPreferenceLevelStronglyDiscouraged; 676 } 677 } 678 } 679 680 if (getDistanceMetric().doComputeDistanceConflictsBetweenNonBTBClasses()) { 681 for (Lecture v1: variables()) { 682 Placement p1 = assignment.getValue(v1); 683 TimeLocation t1 = (p1 == null ? null : p1.getTimeLocation()); 684 if (t1 == null) continue; 685 Placement before = null; 686 for (Lecture l2: variables()) { 687 Placement p2 = assignment.getValue(l2); 688 if (p2 == null || l2.equals(v1)) continue; 689 TimeLocation t2 = p2.getTimeLocation(); 690 if (t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 691 if (t2.getStartSlot() + t2.getLength() < t1.getStartSlot()) { 692 int distanceInMinutes = Placement.getDistanceInMinutes(getDistanceMetric(), p1, p2); 693 if (distanceInMinutes > t2.getBreakTime() + Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) 694 pref += (iIgnoreDistances ? Constants.sPreferenceLevelStronglyDiscouraged : Constants.sPreferenceLevelProhibited); 695 else if (distanceInMinutes > Constants.SLOT_LENGTH_MIN * (t1.getStartSlot() - t2.getStartSlot() - t2.getLength())) 696 pref += Constants.sPreferenceLevelDiscouraged; 697 } 698 if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) { 699 if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot()) 700 before = p2; 701 } 702 } 703 if (iUnavailabilities != null) { 704 for (Placement c: iUnavailabilities) { 705 TimeLocation t2 = c.getTimeLocation(); 706 if (t2 == null || !t1.shareDays(t2) || !t1.shareWeeks(t2)) continue; 707 if (t2.getStartSlot() + t2.getLength() <= t1.getStartSlot()) { 708 if (before == null || before.getTimeLocation().getStartSlot() < t2.getStartSlot()) 709 before = c; 710 } 711 } 712 } 713 if (before != null && Placement.getDistanceInMinutes(getDistanceMetric(), before, p1) > getDistanceMetric().getInstructorLongTravelInMinutes()) 714 pref += Constants.sPreferenceLevelStronglyDiscouraged; 715 } 716 } 717 718 return pref; 719 } 720 } 721}