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