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