001package org.cpsolver.instructor.model; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.List; 006import java.util.Set; 007 008import org.cpsolver.coursett.Constants; 009import org.cpsolver.coursett.model.TimeLocation; 010import org.cpsolver.coursett.preference.PreferenceCombination; 011import org.cpsolver.coursett.preference.SumPreferenceCombination; 012import org.cpsolver.ifs.assignment.Assignment; 013 014/** 015 * Teaching request. A set of sections of a course to be assigned to an instructor. 016 * Each teaching request has a teaching load. The maximal teaching load of an instructor 017 * cannot be breached. 018 * 019 * @author Tomáš Müller 020 * @version IFS 1.3 (Instructor Sectioning)<br> 021 * Copyright (C) 2016 Tomáš Müller<br> 022 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 023 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 024 * <br> 025 * This library is free software; you can redistribute it and/or modify 026 * it under the terms of the GNU Lesser General Public License as 027 * published by the Free Software Foundation; either version 3 of the 028 * License, or (at your option) any later version. <br> 029 * <br> 030 * This library is distributed in the hope that it will be useful, but 031 * WITHOUT ANY WARRANTY; without even the implied warranty of 032 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 033 * Lesser General Public License for more details. <br> 034 * <br> 035 * You should have received a copy of the GNU Lesser General Public 036 * License along with this library; if not see 037 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 038 */ 039public class TeachingRequest { 040 private long iRequestId; 041 private Course iCourse; 042 private float iLoad; 043 private List<Section> iSections = new ArrayList<Section>(); 044 private List<Preference<Attribute>> iAttributePreferences = new ArrayList<Preference<Attribute>>(); 045 private List<Preference<Instructor>> iInstructorPreferences = new ArrayList<Preference<Instructor>>(); 046 private Variable[] iVariables; 047 private int iSameCoursePreference, iSameCommonPreference; 048 049 /** 050 * Constructor 051 * @param requestId teaching request id 052 * @param nrVariables number of instructors for this teaching request 053 * @param course course 054 * @param load teaching load 055 * @param sections list of sections 056 * @param sameCoursePreference same course preference 057 * @param sameCommonPreference same common preference (two requests of the same course share the common part) 058 */ 059 public TeachingRequest(long requestId, int nrVariables, Course course, float load, Collection<Section> sections, int sameCoursePreference, int sameCommonPreference) { 060 super(); 061 iRequestId = requestId; 062 iCourse = course; 063 iLoad = load; 064 iSections.addAll(sections); 065 iVariables = new Variable[nrVariables]; 066 for (int i = 0; i < nrVariables; i++) 067 iVariables[i] = new Variable(i); 068 iSameCoursePreference = sameCoursePreference; 069 iSameCommonPreference = sameCommonPreference; 070 } 071 072 /** 073 * Teaching request id that was provided in the constructor 074 * @return request id 075 */ 076 public long getRequestId() { 077 return iRequestId; 078 } 079 080 081 /** 082 * Preference of an instructor taking this request together with some other request of the same / different course. 083 * @return same course preference 084 */ 085 public int getSameCoursePreference() { return iSameCoursePreference; } 086 087 /** 088 * Is same course required? 089 * @return same course preference is required 090 */ 091 public boolean isSameCourseRequired() { 092 return Constants.sPreferenceRequired.equals(Constants.preferenceLevel2preference(iSameCoursePreference)); 093 } 094 095 /** 096 * Is same course prohibited? 097 * @return same course preference is prohibited 098 */ 099 public boolean isSameCourseProhibited() { 100 return Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(iSameCoursePreference)); 101 } 102 103 /** 104 * Whether to ensure that multiple assignments given to the same instructor share the common part. If required, all assignments of this 105 * course that are given to the same student must share the sections that are marked as common (see {@link Section#isCommon()}). 106 * @return same common preference 107 */ 108 public int getSameCommonPreference() { return iSameCommonPreference; } 109 110 /** 111 * Is same common required? 112 * @return same common preference is required 113 */ 114 public boolean isSameCommonRequired() { 115 return Constants.sPreferenceRequired.equals(Constants.preferenceLevel2preference(iSameCommonPreference)); 116 } 117 118 /** 119 * Is same common prohibited? 120 * @return same common preference is prohibited 121 */ 122 public boolean isSameCommonProhibited() { 123 return Constants.sPreferenceProhibited.equals(Constants.preferenceLevel2preference(iSameCommonPreference)); 124 } 125 126 /** 127 * Get single instructor assignment variables 128 * @return variables for this request 129 */ 130 public Variable[] getVariables() { 131 return iVariables; 132 } 133 134 /** 135 * Get single instructor assignment variable 136 * @param index index of the variable 137 * @return variable for the index-th instructor assignment 138 */ 139 public Variable getVariable(int index) { 140 return iVariables[index]; 141 } 142 143 /** 144 * Get number of instructors needed 145 * @return number of variables for this request 146 */ 147 public int getNrInstructors() { 148 return iVariables.length; 149 } 150 151 /** 152 * Return attribute preferences for this request 153 * @return attribute preferences 154 */ 155 public List<Preference<Attribute>> getAttributePreferences() { return iAttributePreferences; } 156 157 /** 158 * Add attribute preference 159 * @param pref attribute preference 160 */ 161 public void addAttributePreference(Preference<Attribute> pref) { iAttributePreferences.add(pref); } 162 163 /** 164 * Compute attribute preference for the given instructor and attribute type 165 * @param instructor an instructor 166 * @param type an attribute type 167 * @return combined preference using {@link Attribute.Type#isConjunctive()} and {@link Attribute.Type#isRequired()} properties 168 */ 169 public int getAttributePreference(Instructor instructor, Attribute.Type type) { 170 Set<Attribute> attributes = instructor.getAttributes(type); 171 boolean hasReq = false, hasPref = false, needReq = false, hasType = false; 172 PreferenceCombination ret = new SumPreferenceCombination(); 173 for (Preference<Attribute> pref: iAttributePreferences) { 174 if (!type.equals(pref.getTarget().getType())) continue; 175 hasType = true; 176 if (pref.isRequired()) needReq = true; 177 if (attributes.contains(pref.getTarget())) { 178 if (pref.isProhibited()) return Constants.sPreferenceLevelProhibited; 179 else if (pref.isRequired()) hasReq = true; 180 else ret.addPreferenceInt(pref.getPreference()); 181 hasPref = true; 182 } else { 183 if (pref.isRequired() && type.isConjunctive()) return Constants.sPreferenceLevelProhibited; 184 } 185 } 186 if (needReq && !hasReq) return Constants.sPreferenceLevelProhibited; 187 if (type.isRequired() && hasType && !hasPref) return Constants.sPreferenceLevelProhibited; 188 if (!type.isRequired() && hasType && !hasPref) return 16; 189 return ret.getPreferenceInt(); 190 } 191 192 /** 193 * Compute attribute preference for the given instructor 194 * @param instructor an instructor 195 * @return using {@link SumPreferenceCombination} for the preferences of each attribute type (using {@link TeachingRequest#getAttributePreference(Instructor, org.cpsolver.instructor.model.Attribute.Type)}) 196 */ 197 public PreferenceCombination getAttributePreference(Instructor instructor) { 198 PreferenceCombination preference = new SumPreferenceCombination(); 199 for (Attribute.Type type: ((InstructorSchedulingModel)getVariables()[0].getModel()).getAttributeTypes()) 200 preference.addPreferenceInt(getAttributePreference(instructor, type)); 201 return preference; 202 } 203 204 /** 205 * Return instructor preferences for this request 206 * @return instructor preferences 207 */ 208 public List<Preference<Instructor>> getInstructorPreferences() { return iInstructorPreferences; } 209 210 /** 211 * Add instructor preference 212 * @param pref instructor preference 213 */ 214 public void addInstructorPreference(Preference<Instructor> pref) { iInstructorPreferences.add(pref); } 215 216 /** 217 * Return instructor preference for the given instructor 218 * @param instructor an instructor 219 * @return instructor preference for the given instructor 220 */ 221 public Preference<Instructor> getInstructorPreference(Instructor instructor) { 222 boolean hasRequired = false; 223 for (Preference<Instructor> pref: iInstructorPreferences) 224 if (pref.isRequired()) { hasRequired = true; break; } 225 for (Preference<Instructor> pref: iInstructorPreferences) 226 if (pref.getTarget().equals(instructor)) { 227 if (hasRequired && !pref.isRequired()) continue; 228 return pref; 229 } 230 if (hasRequired) 231 return new Preference<Instructor>(instructor, Constants.sPreferenceLevelProhibited); 232 return new Preference<Instructor>(instructor, Constants.sPreferenceLevelNeutral); 233 } 234 235 /** 236 * Course of the request that was provided in the constructor 237 * @return course of the request 238 */ 239 public Course getCourse() { 240 return iCourse; 241 } 242 243 /** 244 * Sections of the request that was provided in the constructor 245 * @return sections of the request 246 */ 247 public List<Section> getSections() { return iSections; } 248 249 /** 250 * Return teaching load of the request 251 * @return teaching load 252 */ 253 public float getLoad() { return iLoad; } 254 255 /** 256 * Set teaching load of the request 257 * @param load teaching load 258 */ 259 public void setLoad(float load) { iLoad = load; } 260 261 @Override 262 public String toString() { 263 return iCourse.getCourseName() + " " + getSections(); 264 } 265 266 /** 267 * Check if the given request fully share the common sections with this request 268 * @param request the other teaching request 269 * @return true, if all common sections of this request are also present in the other request 270 */ 271 public boolean sameCommon(TeachingRequest request) { 272 for (Section section: getSections()) 273 if (section.isCommon() && !request.getSections().contains(section)) 274 return false; 275 for (Section section: request.getSections()) 276 if (section.isCommon() && !getSections().contains(section)) 277 return false; 278 return true; 279 } 280 281 /** 282 * Check if the given request (partially) share the common sections with this request 283 * @param request the other teaching request 284 * @return true, if there is at least one common section of this request that is also present in the other request 285 */ 286 public boolean shareCommon(TeachingRequest request) { 287 for (Section section: getSections()) 288 if (section.isCommon() && request.getSections().contains(section)) 289 return true; 290 for (Section section: request.getSections()) 291 if (section.isCommon() && getSections().contains(section)) 292 return true; 293 return false; 294 } 295 296 /** 297 * Check if this request and the given one can be assigned to the same instructor without violating the same common constraint 298 * @param request the other teaching request 299 * @return same common constraint is violated 300 */ 301 public boolean isSameCommonViolated(TeachingRequest request) { 302 if (!sameCourse(request)) return false; 303 if ((isSameCommonRequired() || request.isSameCommonRequired()) && !sameCommon(request)) 304 return true; 305 if ((isSameCommonProhibited() || request.isSameCommonProhibited()) && shareCommon(request)) 306 return true; 307 return false; 308 } 309 310 /** 311 * Return same common penalty of this request and the given request being assigned to the same instructor 312 * @param request the other teaching request 313 * @return same common penalty between the two teaching requests 314 */ 315 public double getSameCommonPenalty(TeachingRequest request) { 316 if (!sameCourse(request)) return 0; // not applicable 317 int penalty = 0; 318 // preferred and same 319 if ((getSameCommonPreference() < 0 || request.getSameCommonPreference() < 0) && sameCommon(request)) { 320 penalty += (!isSameCommonRequired() && getSameCommonPreference() < 0 ? getSameCommonPreference() : 0) 321 + (!request.isSameCommonRequired() && request.getSameCommonPreference() < 0 ? request.getSameCommonPreference() : 0); 322 } 323 // discouraged and sharing common 324 if ((getSameCommonPreference() > 0 || request.getSameCommonPreference() > 0) && shareCommon(request)) { 325 penalty += (getSameCommonPreference() > 0 ? getSameCommonPreference() : 0) + (request.getSameCommonPreference() > 0 ? request.getSameCommonPreference() : 0); 326 } 327 return penalty; 328 } 329 330 /** 331 * Count the number of common sections that the given request share with this request 332 * @param request the other teaching request 333 * @return the number of shared common sections 334 */ 335 public double nrSameLectures(TeachingRequest request) { 336 if (!sameCourse(request)) return 0.0; 337 double same = 0; int common = 0; 338 for (Section section: getSections()) 339 if (section.isCommon()) { 340 common ++; 341 if (request.getSections().contains(section)) same++; 342 } 343 return (common == 0 ? 1.0 : same / common); 344 } 345 346 /** 347 * Check if this request and the given request are of the same course 348 * @param request the other teaching request 349 * @return true, if the course of the given request is the same as the course of this request 350 */ 351 public boolean sameCourse(TeachingRequest request) { 352 return getCourse().equals(request.getCourse()); 353 } 354 355 /** 356 * Check if this request and the given one can be assigned to the same instructor without violating the same course constraint 357 * @param request the other teaching request 358 * @return same course constraint is violated 359 */ 360 public boolean isSameCourseViolated(TeachingRequest request) { 361 if (sameCourse(request)) { // same course 362 return isSameCourseProhibited() || request.isSameCourseProhibited(); 363 } else { // not same course 364 return isSameCourseRequired() || request.isSameCourseRequired(); 365 } 366 } 367 368 /** 369 * Return same course penalty of this request and the given request being assigned to the same instructor 370 * @param request the other teaching request 371 * @return same course penalty between the two teaching requests 372 */ 373 public double getSameCoursePenalty(TeachingRequest request) { 374 if (!sameCourse(request)) return 0; 375 return (isSameCourseRequired() ? 0 : getSameCoursePreference()) + (request.isSameCourseRequired() ? 0 : request.getSameCoursePreference()); 376 } 377 378 /** 379 * Check if this request overlaps with the given one 380 * @param request the other teaching request 381 * @return true, if there are two sections that are overlapping in time (that are not allowed to overlap) 382 */ 383 public boolean overlaps(TeachingRequest request) { 384 for (Section section: getSections()) { 385 if (section.isAllowOverlap() || section.getTime() == null || request.getSections().contains(section)) continue; 386 for (Section other: request.getSections()) { 387 if (other.isAllowOverlap() || other.getTime() == null || getSections().contains(other)) continue; 388 if (section.getTime().hasIntersection(other.getTime())) return true; 389 } 390 } 391 return false; 392 } 393 394 /** 395 * Count the number of (allowed) overlapping time slots between this request and the given one 396 * @param request the other teaching request 397 * @return the number of overlapping time slots 398 */ 399 public int share(TeachingRequest request) { 400 int ret = 0; 401 for (Section section: getSections()) 402 ret += section.share(request.getSections()); 403 return ret; 404 } 405 406 /** 407 * Count the number of overlapping time slots between this request and the given time 408 * @param time a time 409 * @return the number of overlapping time slots 410 */ 411 public int share(TimeLocation time) { 412 int ret = 0; 413 for (Section section: getSections()) 414 ret += section.share(time); 415 return ret; 416 } 417 418 /** 419 * Average value of the back-to-backs between this request and the given one 420 * @param request the other teaching request 421 * @param diffRoomWeight different room penalty 422 * @param diffTypeWeight different instructional type penalty 423 * @return average value of {@link Section#countBackToBacks(Collection, double, double)} between the two, common sections are ignored 424 */ 425 public double countBackToBacks(TeachingRequest request, double diffRoomWeight, double diffTypeWeight) { 426 double b2b = 0.0; 427 int count = 0; 428 for (Section section: getSections()) { 429 if (!section.isCommon() || !sameCourse(request) || !request.getSections().contains(section)) { 430 b2b += section.countBackToBacks(request.getSections(), diffRoomWeight, diffTypeWeight); 431 count ++; 432 } 433 } 434 return (count == 0 ? 0.0 : b2b / count); 435 } 436 437 /** 438 * Average value of the same days between this request and the given one 439 * @param request the other teaching request 440 * @param diffRoomWeight different room penalty 441 * @param diffTypeWeight different instructional type penalty 442 * @return average value of {@link Section#countSameDays(Collection, double, double)} between the two, common sections are ignored 443 */ 444 public double countSameDays(TeachingRequest request, double diffRoomWeight, double diffTypeWeight) { 445 double sd = 0.0; 446 int count = 0; 447 for (Section section: getSections()) { 448 if (!section.isCommon() || !sameCourse(request) || !request.getSections().contains(section)) { 449 sd += section.countSameDays(request.getSections(), diffRoomWeight, diffTypeWeight); 450 count ++; 451 } 452 } 453 return (count == 0 ? 0.0 : sd / count); 454 } 455 456 /** 457 * Average value of the same rooms between this request and the given one 458 * @param request the other teaching request 459 * @param diffTypeWeight different instructional type penalty 460 * @return average value of {@link Section#countSameRooms(Collection, double)} between the two, common sections are ignored 461 */ 462 public double countSameRooms(TeachingRequest request, double diffTypeWeight) { 463 double sr = 0.0; 464 int count = 0; 465 for (Section section: getSections()) { 466 if (!section.isCommon() || !sameCourse(request) || !request.getSections().contains(section)) { 467 sr += section.countSameRooms(request.getSections(), diffTypeWeight); 468 count ++; 469 } 470 } 471 return (count == 0 ? 0.0 : sr / count); 472 } 473 474 @Override 475 public int hashCode() { 476 return (int)(iRequestId ^ (iRequestId >>> 32)); 477 } 478 479 @Override 480 public boolean equals(Object o) { 481 if (o == null || !(o instanceof TeachingRequest)) return false; 482 TeachingRequest tr = (TeachingRequest)o; 483 return getRequestId() == tr.getRequestId(); 484 } 485 486 /** 487 * Single instructor assignment to this teaching request 488 */ 489 public class Variable extends org.cpsolver.ifs.model.Variable<TeachingRequest.Variable, TeachingAssignment> { 490 private int iIndex; 491 492 /** 493 * Constructor 494 * @param index instructor index (if a class can be taught by multiple instructors, the index identifies the particular request) 495 */ 496 public Variable(int index) { 497 iId = (iRequestId << 8) + index; 498 iIndex = index; 499 } 500 501 /** 502 * Instructor index that was provided in the constructor 503 * @return instructor index 504 */ 505 public int getInstructorIndex() { 506 return iIndex; 507 } 508 509 /** 510 * Teaching request for this variable 511 * @return teaching request 512 */ 513 public TeachingRequest getRequest() { 514 return TeachingRequest.this; 515 } 516 517 /** 518 * Course of the request that was provided in the constructor 519 * @return course of the request 520 */ 521 public Course getCourse() { 522 return iCourse; 523 } 524 525 /** 526 * Sections of the request that was provided in the constructor 527 * @return sections of the request 528 */ 529 public List<Section> getSections() { return iSections; } 530 531 @Override 532 public List<TeachingAssignment> values(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) { 533 List<TeachingAssignment> values = super.values(assignment); 534 if (values == null) { 535 values = new ArrayList<TeachingAssignment>(); 536 for (Instructor instructor: ((InstructorSchedulingModel)getModel()).getInstructors()) { 537 if (instructor.canTeach(getRequest())) { 538 PreferenceCombination attributePref = getAttributePreference(instructor); 539 if (attributePref.isProhibited()) continue; 540 values.add(new TeachingAssignment(this, instructor, attributePref.getPreferenceInt())); 541 } 542 } 543 setValues(values); 544 } 545 return values; 546 } 547 548 @Override 549 public void variableAssigned(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, long iteration, TeachingAssignment ta) { 550 super.variableAssigned(assignment, iteration, ta); 551 ta.getInstructor().getContext(assignment).assigned(assignment, ta); 552 } 553 554 @Override 555 public void variableUnassigned(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, long iteration, TeachingAssignment ta) { 556 super.variableUnassigned(assignment, iteration, ta); 557 ta.getInstructor().getContext(assignment).unassigned(assignment, ta); 558 } 559 560 @Override 561 public int hashCode() { 562 return Long.valueOf(iRequestId << 8 + iIndex).hashCode(); 563 } 564 565 @Override 566 public boolean equals(Object o) { 567 if (o == null || !(o instanceof Variable)) return false; 568 Variable tr = (Variable)o; 569 return getRequest().getRequestId() == tr.getRequest().getRequestId() && getInstructorIndex() == tr.getInstructorIndex(); 570 } 571 572 @Override 573 public String getName() { 574 return iCourse.getCourseName() + (getNrInstructors() > 1 ? "[" + getInstructorIndex() + "]" : "") + " " + getSections(); 575 } 576 } 577}