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