001package org.cpsolver.coursett.criteria.additional; 002 003import java.util.Arrays; 004import java.util.Collection; 005import java.util.HashSet; 006import java.util.List; 007import java.util.Map; 008import java.util.Set; 009import java.util.TreeMap; 010import org.cpsolver.coursett.constraint.InstructorConstraint; 011import org.cpsolver.coursett.criteria.TimePreferences; 012import org.cpsolver.coursett.criteria.TimetablingCriterion; 013import org.cpsolver.coursett.model.Lecture; 014import org.cpsolver.coursett.model.Placement; 015import org.cpsolver.ifs.assignment.Assignment; 016import org.cpsolver.ifs.util.DataProperties; 017 018/** 019 * This class represent fairness criterion for instructors. 020 * Criterion iteratively evaluate instructors fairness, based on absolute 021 * deviation of the individual satisfaction of instructors time (or time and 022 * room) requirements. 023 * @author Rostislav Burget<br> 024 * implemented criterion: Instructor Fairness <br> 025 * @version CourseTT 1.3 (University Course Timetabling)<br> 026 * Copyright (C) 2015 Rostislav Burget<br> 027 * <a href="mailto:BurgetRostislav@gmail.com">BurgetRostislav@gmail.com</a><br> 028 * <br> 029 * This library is free software; you can redistribute it and/or modify 030 * it under the terms of the GNU Lesser General Public License as 031 * published by the Free Software Foundation; either version 3 of the 032 * License, or (at your option) any later version. <br> 033 * <br> 034 * This library is distributed in the hope that it will be useful, but 035 * WITHOUT ANY WARRANTY; without even the implied warranty of 036 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 037 * Lesser General Public License for more details. <br> 038 * <br> 039 * You should have received a copy of the GNU Lesser General Public 040 * License along with this library; if not see 041 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/ 042 * </a>. 043 */ 044public class InstructorFairness extends TimetablingCriterion { 045 046 /** 047 * 048 */ 049 public InstructorFairness() { 050 setValueUpdateType(ValueUpdateType.BeforeUnassignedAfterAssigned); 051 } 052 053 @Override 054 public double getWeightDefault(DataProperties config) { 055 return config.getPropertyDouble("Comparator.InstructorFairnessPreferenceWeight", 1.0); 056 } 057 058 @Override 059 public String getPlacementSelectionWeightName() { 060 return "Placement.InstructorFairnessPreferenceWeight"; 061 } 062 063 @Override 064 public void bestSaved(Assignment<Lecture, Placement> assignment) { 065 iBest = getValue(assignment); 066 } 067 068 @Override 069 public double getValue(Assignment<Lecture, Placement> assignment, Placement value, Set<Placement> conflicts) { 070 double ret = 0.0; 071 InstructorFairnessContext context = (InstructorFairnessContext) getContext(assignment); 072 if (context.allInstructorsAssigned(assignment) && !value.variable().getInstructorConstraints().isEmpty()) { 073 List<InstructorConstraint> insConstraints = value.variable().getInstructorConstraints(); 074 ret = (context.getDiffInstrValue(insConstraints, context.fairnessDouble(assignment, value))) / insConstraints.size(); 075 if (conflicts != null) { 076 for (Placement conflict : conflicts) { 077 if (!conflict.variable().getInstructorConstraints().isEmpty()) { 078 List<InstructorConstraint> insConstraints2 = conflict.variable().getInstructorConstraints(); 079 ret -= (context.getDiffInstrValue(insConstraints2, context.fairnessDouble(assignment, conflict))) / insConstraints2.size(); 080 } 081 } 082 } 083 } 084 return ret; 085 } 086 087 @Override 088 public double getValue(Assignment<Lecture, Placement> assignment) { 089 InstructorFairnessContext context = (InstructorFairnessContext) getContext(assignment); 090 if (context.allInstructorsAssigned(assignment)) 091 return context.getObjectiveValue(); 092 return 0.0; 093 } 094 095 @Override 096 public double getValue(Assignment<Lecture, Placement> assignment, Collection<Lecture> variables) { 097 InstructorFairnessContext context = (InstructorFairnessContext) getContext(assignment); 098 if (context.allInstructorsAssigned(assignment)) { 099 Set<InstructorConstraint> constraints = new HashSet<InstructorConstraint>(); 100 for (Lecture lecture : variables) { 101 constraints.addAll(lecture.getInstructorConstraints()); 102 } 103 return context.getObjectiveValue(constraints); 104 } 105 return 0.0; 106 } 107 108 @Override 109 public void getInfo(Assignment<Lecture, Placement> assignment, Map<String, String> info) { 110 double value = getValue(assignment); 111 if (value != 0.0) 112 info.put(getName(), sDoubleFormat.format(value)); 113 } 114 115 @Override 116 public void getInfo(Assignment<Lecture, Placement> assignment, Map<String, String> info, Collection<Lecture> variables) { 117 double value = getValue(assignment, variables); 118 if (value != 0.0) 119 info.put(getName(), sDoubleFormat.format(value)); 120 } 121 122 @Override 123 public void getExtendedInfo(Assignment<Lecture, Placement> assignment, Map<String, String> info) { 124 if (iDebug) { 125 InstructorFairnessContext context = (InstructorFairnessContext) getContext(assignment); 126 String[] fairnessInfo = context.testFairness(assignment); 127 info.put(getName() + " Details", 128 fairnessInfo[8] + " (avg: " + fairnessInfo[0] + ", rms: " + fairnessInfo[1] + 129 ", Pmax: " + fairnessInfo[2] + ", Pdev: " + fairnessInfo[3] + ", Perror: " + fairnessInfo[4] + 130 ", Pss: " + fairnessInfo[5] + ", Jain's index: " + fairnessInfo[6] + ", max: " + fairnessInfo[7] + ")"); 131 } 132 } 133 134 /** 135 * Context for InstructorFairness 136 */ 137 public class InstructorFairnessContext extends ValueContext { 138 private TreeMap<Long, Instructor> iId2Instructor = new TreeMap<Long, Instructor>(); 139 private double iInstrMeanFairValue = 0.0; 140 private boolean iFullTreeMap = false; 141 private boolean iFirstIterDone = false; 142 143 /** 144 * 145 * @param assignment 146 * current assignment 147 */ 148 public InstructorFairnessContext(Assignment<Lecture, Placement> assignment) { 149 countInstructorFair(assignment); 150 } 151 152 @Override 153 protected void assigned(Assignment<Lecture, Placement> assignment, Placement value) { 154 if (isFirstIterDone()) { 155 countInstructorAssignedFair(assignment, value); 156 } else { 157 countInstructorFair(assignment); 158 } 159 } 160 161 @Override 162 protected void unassigned(Assignment<Lecture, Placement> assignment, Placement value) { 163 if (isFirstIterDone()) { 164 countInstructorUnassignedFair(assignment, value); 165 } else { 166 if (countInstructorFair(assignment)) 167 countInstructorUnassignedFair(assignment, value); 168 } 169 } 170 171 /** 172 * This method set fairness values to all instructors 173 * 174 * @param assignment current assignment 175 * @return false if complete solution wasn't found 176 */ 177 public boolean countInstructorFair(Assignment<Lecture, Placement> assignment) { 178 if (allInstructorsAssigned(assignment)) { 179 iId2Instructor.clear(); 180 for (Lecture lecture : getModel().variables()) { 181 Double bestPossibleValue = null; 182 183 for (Placement t : lecture.values(assignment)) { 184 double f = fairnessDouble(assignment, t); 185 if (bestPossibleValue == null || f < bestPossibleValue) 186 bestPossibleValue = f; 187 } 188 189 Placement placement = assignment.getValue(lecture); 190 for (InstructorConstraint ic : lecture.getInstructorConstraints()) { 191 Instructor s = iId2Instructor.get(ic.getResourceId()); 192 if (s == null) { 193 s = new Instructor(ic.getResourceId()); 194 iId2Instructor.put(ic.getResourceId(), s); 195 } 196 if (bestPossibleValue != null) 197 s.addBestValue(bestPossibleValue); 198 if (placement != null) { 199 s.addValue(fairnessDouble(assignment, placement)); 200 s.incNumOfClasses(); 201 } 202 } 203 } 204 countInstrMeanFairValue(); 205 setFirstIterDone(); 206 return true; 207 } else { 208 return false; 209 } 210 } 211 212 /** 213 * Method actualize values of instructors whose lecture was just 214 * assigned 215 * 216 * @param assignment current assignment 217 * @param value placement of lecture 218 */ 219 220 public void countInstructorAssignedFair(Assignment<Lecture, Placement> assignment, Placement value) { 221 Lecture lec = value.variable(); 222 if (lec.getInstructorConstraints() != null) { 223 List<InstructorConstraint> insConstraints = lec.getInstructorConstraints(); 224 double critValue = fairnessDouble(assignment, lec.getAssignment(assignment)); 225 for (InstructorConstraint ic : insConstraints) { 226 if (!addInstructorValue(ic.getResourceId(), critValue)) { 227 throw new IllegalArgumentException("Instructor " + ic.getResourceId() + " is not present in the context."); 228 } 229 } 230 } 231 countInstrMeanFairValue(); 232 } 233 234 /** 235 * Method actualize values of instructors whose lecture will be 236 * unassigned 237 * 238 * @param assignment current assignment 239 * @param value placement of lecture 240 */ 241 242 public void countInstructorUnassignedFair(Assignment<Lecture, Placement> assignment, Placement value) { 243 Lecture lec = value.variable(); 244 if (lec.getInstructorConstraints() != null) { 245 List<InstructorConstraint> insConstraints = lec.getInstructorConstraints(); 246 double critValue = fairnessDouble(assignment, lec.getAssignment(assignment)); 247 for (InstructorConstraint ic : insConstraints) { 248 if (!decreaseInstructorValue(ic.getResourceId(), critValue)) { 249 throw new IllegalArgumentException("Instructor " + ic.getResourceId() + " is not present in the context."); 250 } 251 } 252 } 253 countInstrMeanFairValue(); 254 } 255 256 /** 257 * 258 * @return instructor mean fair value 259 */ 260 public double getInstrMeanFairValue() { 261 return iInstrMeanFairValue; 262 } 263 264 /** 265 * 266 * @return if first iteration was done 267 */ 268 public boolean isFirstIterDone() { 269 return iFirstIterDone; 270 } 271 272 /** 273 * set first iteration done to true 274 */ 275 public void setFirstIterDone() { 276 this.iFirstIterDone = true; 277 } 278 279 /** 280 * 281 * @return number of instructors 282 */ 283 public int getNumOfIstructors() { 284 return iId2Instructor.size(); 285 } 286 287 /** 288 * 289 * @return Collection of instructors in context 290 */ 291 public Collection<Instructor> getInstructorsWithAssig() { 292 return iId2Instructor.values(); 293 } 294 295 /** 296 * Return if complete solution (all variables assigned) was found 297 * in this context 298 * @param assignment current assignment 299 * @return true if in this context were all variables assigned 300 * false otherwise 301 */ 302 public boolean allInstructorsAssigned(Assignment<Lecture, Placement> assignment) { 303 if (!iFullTreeMap) { 304 iFullTreeMap = (assignment.nrAssignedVariables() > 0 && assignment.nrUnassignedVariables(getModel()) == 0 && getModel().getBestUnassignedVariables() == 0); 305 } 306 return iFullTreeMap; 307 } 308 309 /** 310 * adding value to instructor in stringInstMap 311 * 312 * @param insID instructor ID 313 * @param value that should be added 314 * @return false if instructor is not in iId2Instructor 315 */ 316 317 public boolean addInstructorValue(Long insID, double value) { 318 Instructor s = iId2Instructor.get(insID); 319 if (s != null) { 320 s.addValue(value); 321 s.incNumOfClasses(); 322 return true; 323 } else { 324 return false; 325 } 326 } 327 328 /** 329 * adding value to instructor in stringInstMap 330 * 331 * @param insID instructor ID 332 * @param value value that should be subtracted 333 * @return false if instructor is not iId2Instructor 334 */ 335 336 public boolean decreaseInstructorValue(Long insID, double value) { 337 Instructor s = iId2Instructor.get(insID); 338 if (s != null) { 339 s.removeValue(value); 340 s.decNumOfClasses(); 341 return true; 342 } else { 343 return false; 344 } 345 } 346 347 /** 348 * compute and return mean fair value of instructors in iId2Instructor 349 */ 350 351 public void countInstrMeanFairValue() { 352 if (!iId2Instructor.isEmpty()) { 353 double sum = 0.0; 354 for (Instructor ins: iId2Instructor.values()) { 355 sum += ins.getFinalValue(); 356 } 357 iInstrMeanFairValue = sum / iId2Instructor.size(); 358 } 359 } 360 361 /** 362 * Method estimates value of placement for instructors in entry list 363 * 364 * @param instructorsList instructor list 365 * @param placementValue given placement 366 * @return estimated value of placement for instructors in entry list 367 */ 368 369 public double getDiffInstrValue(List<InstructorConstraint> instructorsList, double placementValue) { 370 double ret = 0.0; 371 for (InstructorConstraint ic : instructorsList) { 372 Instructor i = iId2Instructor.get(ic.getResourceId()); 373 if (i != null) { 374 if (i.getFinalValue() > iInstrMeanFairValue) { 375 ret += ((i.getFinalValue() - iInstrMeanFairValue) / i.getNumOfClasses()) + (placementValue - iInstrMeanFairValue); 376 } else { 377 ret -= ((iInstrMeanFairValue - i.getFinalValue()) / i.getNumOfClasses()) + (iInstrMeanFairValue - placementValue); 378 } 379 } 380 } 381 return ret; 382 } 383 384 /** 385 * Fairness value based on pdev (pdev sec. part) of all instructors 386 * 387 * @return Objective value of all instructors 388 */ 389 390 public double getObjectiveValue() { 391 double ret = 0.0; 392 for (Map.Entry<Long, Instructor> entry : iId2Instructor.entrySet()) { 393 Instructor ins = entry.getValue(); 394 ret += Math.abs(ins.getFinalValue() - iInstrMeanFairValue); 395 } 396 return ret; 397 } 398 399 /** 400 * Fairness value based on pdev (pdev sec. part) of instructors 401 * @param instructors instructor list 402 * @return Objective value of instructors 403 */ 404 public double getObjectiveValue(Collection<InstructorConstraint> instructors) { 405 double ret = 0.0; 406 for (InstructorConstraint ic : instructors) { 407 Instructor ins = iId2Instructor.get(ic.getResourceId()); 408 if (ins != null) 409 ret += Math.abs(ins.getFinalValue() - iInstrMeanFairValue); 410 } 411 return ret; 412 } 413 414 /** 415 * fairness value with squared P and avg.P 416 * 417 * @return fairness value with squared P and avg.P 418 */ 419 420 public double getDiffAllInstrValueSquared() { 421 double ret = 0.0; 422 for (Map.Entry<Long, Instructor> entry : iId2Instructor.entrySet()) { 423 Instructor ins = entry.getValue(); 424 ret += Math.sqrt(Math.abs(ins.getFinalValue() * ins.getFinalValue() - iInstrMeanFairValue * iInstrMeanFairValue)); 425 } 426 return ret; 427 } 428 429 /** 430 * refresh of all instructors in iId2Instructor 431 * 432 */ 433 434 public void refreshInstructors() { 435 for (Map.Entry<Long, Instructor> entry : iId2Instructor.entrySet()) { 436 Instructor ins = entry.getValue(); 437 ins.refresh(); 438 } 439 } 440 441 /** 442 * Metod count and return time (and room) preferences in placement 443 * 444 * @param assignment current assignment 445 * @param placement given placement 446 * @return time (and room) preferences in placement 447 */ 448 449 public double fairnessDouble(Assignment<Lecture, Placement> assignment, Placement placement) { 450 double critValue = 0.0; 451 // critValue += getModel().getCriterion(RoomPreferences.class).getWeightedValue(assignment,placement,null); 452 critValue += getModel().getCriterion(TimePreferences.class).getWeightedValue(assignment, placement, null); 453 return critValue; 454 } 455 456 /** 457 * Method for whole evaluation of fairness criteria 458 * 459 * @param assignment current assignment 460 * @return String[] with informations about solution [0-Avarage 461 * instructor penalty, 1-Sum of squared penalities, 2-Pmax, 462 * 3-Pdev, 4-Perror, 5-Pss, 6-Jain, 7-worst instructor fairness 463 * value,8-Instructors fairness value] 464 */ 465 466 public String[] testFairness(Assignment<Lecture, Placement> assignment) { 467 String[] dataForEval = new String[9]; 468 Collection<Lecture> assignedLectures = assignment.assignedVariables(); 469 refreshInstructors(); 470 for (Lecture lec : assignedLectures) { 471 if (lec.getInstructorConstraints() != null) { 472 List<InstructorConstraint> insConstraints = lec.getInstructorConstraints(); 473 double critValue = fairnessDouble(assignment, lec.getAssignment(assignment)); 474 for (InstructorConstraint ic : insConstraints) { 475 addInstructorValue(ic.getResourceId(), critValue); 476 } 477 } 478 } 479 countInstrMeanFairValue(); 480 dataForEval[8] = sDoubleFormat.format(getObjectiveValue()); 481 482 double[] instructorsValue = new double[iId2Instructor.values().size()]; 483 int counter = 0; 484 double sumOfSquaredPen = 0.0; 485 double min = 100000; 486 double max = -100000; 487 double sum = 0.0; 488 double pdevSecPart = 0.0; 489 double pssSecPart = 0.0; 490 491 for (Instructor s : iId2Instructor.values()) { 492 instructorsValue[counter] = s.getFinalValue(); 493 sumOfSquaredPen = sumOfSquaredPen + (s.getFinalValue() * s.getFinalValue()); 494 if (min > s.getFinalValue()) { 495 min = s.getFinalValue(); 496 } 497 if (max < s.getFinalValue()) { 498 max = s.getFinalValue(); 499 } 500 sum += s.getFinalValue(); 501 counter++; 502 } 503 Arrays.sort(instructorsValue); 504 505 for (double d : instructorsValue) { 506 pdevSecPart += Math.abs(d - sum / instructorsValue.length); 507 pssSecPart += d * d; 508 } 509 510 // Worst instructor penalty: 511 dataForEval[7] = sDoubleFormat.format(max); 512 // "Avarage instructor penalty: 513 dataForEval[0] = sDoubleFormat.format(sum / instructorsValue.length); 514 // Sum of squared penalities: 515 dataForEval[1] = sDoubleFormat.format(Math.sqrt(sumOfSquaredPen)); 516 // Fairness W1: 517 // Pmax 518 dataForEval[2] = sDoubleFormat.format(iBest - pdevSecPart * iWeight + (instructorsValue.length) * max); 519 // Pdev 520 dataForEval[3] = sDoubleFormat.format(iBest); 521 // PError 522 dataForEval[4] = sDoubleFormat.format(iBest - pdevSecPart * iWeight + sum + ((instructorsValue.length) * (max - min))); 523 // Pss: 524 dataForEval[5] = sDoubleFormat.format(Math.sqrt(((iBest - pdevSecPart * iWeight) * iBest - pdevSecPart * iWeight) + pssSecPart)); 525 526 if (sumOfSquaredPen != 0.0) { 527 // Jain's index: 528 dataForEval[6] = sDoubleFormat.format((sum * sum) / ((instructorsValue.length) * sumOfSquaredPen)); 529 } else { 530 dataForEval[6] = sDoubleFormat.format(1); 531 } 532 return dataForEval; 533 } 534 535 /** 536 * Class representing instructor 537 * 538 */ 539 public class Instructor { 540 private Long iId; 541 private double iValue = 0.0; 542 private double iBestValue = 0.0; 543 private int iNumOfClasses = 0; 544 545 // could be used to change how important instructors requiremets are 546 private double coef = 1; 547 548 /** 549 * Create instructor with ID(instructorId) 550 * @param instructorId instructor ID 551 */ 552 public Instructor(Long instructorId) { 553 iId = instructorId; 554 } 555 556 /** 557 * representation how well instructors requirements are met 558 * 559 * @return Instructor final value 560 */ 561 562 public double getFinalValue() { 563 if (iNumOfClasses == 0) return 0.0; 564 return ((iValue - iBestValue) / iNumOfClasses) * coef; 565 } 566 567 /** 568 * 569 * @return iId - instructor ID 570 */ 571 public Long getId() { 572 return iId; 573 } 574 575 /** 576 * 577 * @return iValue - instructor value 578 */ 579 public double getValue() { 580 return iValue; 581 } 582 583 /** 584 * Add value to instructor value 585 * @param value value that should be added to instructor value 586 */ 587 public void addValue(double value) { 588 iValue += value; 589 } 590 591 /** 592 * Subtract value from instructor value 593 * @param value value that should be subtracted from instructor value 594 */ 595 public void removeValue(double value) { 596 iValue -= value; 597 } 598 599 /** 600 * 601 * @return iBestValue - instructor best value 602 */ 603 public double getBestValue() { 604 return iBestValue; 605 } 606 607 /** 608 * Add value to instructor best value 609 * @param bestValue value that should be added to instructor best value 610 */ 611 public void addBestValue(double bestValue) { 612 iBestValue += bestValue; 613 } 614 615 /** 616 * 617 * @return iNumOfClasses - number of instructor classes 618 */ 619 public int getNumOfClasses() { 620 return iNumOfClasses; 621 } 622 623 /** 624 * Increase number of classes by 1 625 */ 626 public void incNumOfClasses() { 627 this.iNumOfClasses += 1; 628 } 629 630 /** 631 * Decrease number of instructor classes by 1 632 */ 633 public void decNumOfClasses() { 634 this.iNumOfClasses -= 1; 635 } 636 637 /** 638 * 639 * @return coef - coefficient of instructor 640 */ 641 public double getCoef() { 642 return coef; 643 } 644 645 /** 646 * Set instructor coefficient to double value 647 * @param coef coefficient of instructor 648 */ 649 public void setCoef(double coef) { 650 this.coef = coef; 651 } 652 653 /** 654 * Set instructor value and number of classes to 0 655 */ 656 public void refresh() { 657 iValue = 0.0; 658 iNumOfClasses = 0; 659 } 660 661 @Override 662 public int hashCode() { 663 return iId.hashCode(); 664 } 665 666 @Override 667 public boolean equals(Object obj) { 668 if (obj == null || !(obj instanceof Instructor)) return false; 669 return iId.equals(((Instructor) obj).iId); 670 } 671 } 672 } 673 674 @Override 675 public ValueContext createAssignmentContext(Assignment<Lecture, Placement> assignment) { 676 return new InstructorFairnessContext(assignment); 677 } 678}