001package org.cpsolver.studentsct.weights; 002 003import java.text.DecimalFormat; 004import java.util.ArrayList; 005import java.util.BitSet; 006import java.util.HashSet; 007import java.util.Set; 008 009import org.cpsolver.coursett.model.Placement; 010import org.cpsolver.coursett.model.RoomLocation; 011import org.cpsolver.coursett.model.TimeLocation; 012import org.cpsolver.ifs.assignment.Assignment; 013import org.cpsolver.ifs.assignment.DefaultSingleAssignment; 014import org.cpsolver.ifs.solution.Solution; 015import org.cpsolver.ifs.util.DataProperties; 016import org.cpsolver.ifs.util.ToolBox; 017import org.cpsolver.studentsct.StudentSectioningModel; 018import org.cpsolver.studentsct.extension.DistanceConflict; 019import org.cpsolver.studentsct.extension.StudentQuality; 020import org.cpsolver.studentsct.extension.StudentQuality.Conflict; 021import org.cpsolver.studentsct.extension.TimeOverlapsCounter; 022import org.cpsolver.studentsct.model.Choice; 023import org.cpsolver.studentsct.model.Config; 024import org.cpsolver.studentsct.model.Course; 025import org.cpsolver.studentsct.model.CourseRequest; 026import org.cpsolver.studentsct.model.Enrollment; 027import org.cpsolver.studentsct.model.Instructor; 028import org.cpsolver.studentsct.model.Offering; 029import org.cpsolver.studentsct.model.Request; 030import org.cpsolver.studentsct.model.RequestGroup; 031import org.cpsolver.studentsct.model.SctAssignment; 032import org.cpsolver.studentsct.model.Section; 033import org.cpsolver.studentsct.model.Student; 034import org.cpsolver.studentsct.model.Subpart; 035 036 037/** 038 * New weighting model. It tries to obey the following principles: 039 * <ul> 040 * <li> Total student weight is between zero and one (one means student got the best schedule) 041 * <li> Weight of the given priority course is higher than sum of the remaining weights the student can get 042 * <li> First alternative is better than the following course 043 * <li> Second alternative is better than the second following course 044 * <li> Distance conflicts are considered secondary (priorities should be maximized first) 045 * <li> If alternative sections are otherwise equal, use the better balanced one 046 * </ul> 047 * 048 * @version StudentSct 1.3 (Student Sectioning)<br> 049 * Copyright (C) 2007 - 2014 Tomas Muller<br> 050 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 051 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 052 * <br> 053 * This library is free software; you can redistribute it and/or modify 054 * it under the terms of the GNU Lesser General Public License as 055 * published by the Free Software Foundation; either version 3 of the 056 * License, or (at your option) any later version. <br> 057 * <br> 058 * This library is distributed in the hope that it will be useful, but 059 * WITHOUT ANY WARRANTY; without even the implied warranty of 060 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 061 * Lesser General Public License for more details. <br> 062 * <br> 063 * You should have received a copy of the GNU Lesser General Public 064 * License along with this library; if not see 065 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 066 */ 067 068public class PriorityStudentWeights implements StudentWeights { 069 protected double iPriorityFactor = 0.5010; 070 protected double iFirstAlternativeFactor = 0.5010; 071 protected double iSecondAlternativeFactor = 0.2510; 072 protected double iDistanceConflict = 0.0100; 073 protected double iShortDistanceConflict = 0.1000; 074 protected double iTimeOverlapFactor = 0.5000; 075 protected double iTimeOverlapMaxLimit = 0.5000; 076 protected boolean iLeftoverSpread = false; 077 protected double iBalancingFactor = 0.0050; 078 protected double iNoTimeFactor = 0.0100; 079 protected double iAlternativeRequestFactor = 0.1260; 080 protected double iProjectedStudentWeight = 0.0100; 081 protected boolean iMPP = false; 082 protected double iPerturbationFactor = 0.100; 083 protected double iSelectionFactor = 0.100; 084 protected double iSameChoiceWeight = 0.900; 085 protected double iSameTimeWeight = 0.700; 086 protected double iSameConfigWeight = 0.500; 087 protected double iGroupFactor = 0.100; 088 protected double iGroupBestRatio = 0.95; 089 protected double iGroupFillRatio = 0.05; 090 protected boolean iAdditiveWeights = false; 091 protected boolean iMaximizeAssignment = false; 092 protected boolean iPreciseComparison = false; 093 protected double[] iQalityWeights; 094 095 public PriorityStudentWeights(DataProperties config) { 096 iPriorityFactor = config.getPropertyDouble("StudentWeights.Priority", iPriorityFactor); 097 iFirstAlternativeFactor = config.getPropertyDouble("StudentWeights.FirstAlternative", iFirstAlternativeFactor); 098 iSecondAlternativeFactor = config.getPropertyDouble("StudentWeights.SecondAlternative", iSecondAlternativeFactor); 099 iDistanceConflict = config.getPropertyDouble("StudentWeights.DistanceConflict", iDistanceConflict); 100 iShortDistanceConflict = config.getPropertyDouble("StudentWeights.ShortDistanceConflict", iShortDistanceConflict); 101 iTimeOverlapFactor = config.getPropertyDouble("StudentWeights.TimeOverlapFactor", iTimeOverlapFactor); 102 iTimeOverlapMaxLimit = config.getPropertyDouble("StudentWeights.TimeOverlapMaxLimit", iTimeOverlapMaxLimit); 103 iLeftoverSpread = config.getPropertyBoolean("StudentWeights.LeftoverSpread", iLeftoverSpread); 104 iBalancingFactor = config.getPropertyDouble("StudentWeights.BalancingFactor", iBalancingFactor); 105 iAlternativeRequestFactor = config.getPropertyDouble("StudentWeights.AlternativeRequestFactor", iAlternativeRequestFactor); 106 iProjectedStudentWeight = config.getPropertyDouble("StudentWeights.ProjectedStudentWeight", iProjectedStudentWeight); 107 iMPP = config.getPropertyBoolean("General.MPP", false); 108 iPerturbationFactor = config.getPropertyDouble("StudentWeights.Perturbation", iPerturbationFactor); 109 iSelectionFactor = config.getPropertyDouble("StudentWeights.Selection", iSelectionFactor); 110 iSameChoiceWeight = config.getPropertyDouble("StudentWeights.SameChoice", iSameChoiceWeight); 111 iSameTimeWeight = config.getPropertyDouble("StudentWeights.SameTime", iSameTimeWeight); 112 iSameConfigWeight = config.getPropertyDouble("StudentWeights.SameConfig", iSameConfigWeight); 113 iGroupFactor = config.getPropertyDouble("StudentWeights.SameGroup", iGroupFactor); 114 iGroupBestRatio = config.getPropertyDouble("StudentWeights.GroupBestRatio", iGroupBestRatio); 115 iGroupFillRatio = config.getPropertyDouble("StudentWeights.GroupFillRatio", iGroupFillRatio); 116 iNoTimeFactor = config.getPropertyDouble("StudentWeights.NoTimeFactor", iNoTimeFactor); 117 iAdditiveWeights = config.getPropertyBoolean("StudentWeights.AdditiveWeights", iAdditiveWeights); 118 iMaximizeAssignment = config.getPropertyBoolean("StudentWeights.MaximizeAssignment", iMaximizeAssignment); 119 iPreciseComparison = config.getPropertyBoolean("StudentWeights.PreciseComparison", iPreciseComparison); 120 iQalityWeights = new double[StudentQuality.Type.values().length]; 121 for (StudentQuality.Type type: StudentQuality.Type.values()) { 122 iQalityWeights[type.ordinal()] = config.getPropertyDouble(type.getWeightName(), type.getWeightDefault()); 123 } 124 } 125 126 public double getWeight(Request request) { 127 if (request.getStudent().isDummy() && iProjectedStudentWeight >= 0.0) { 128 double weight = iProjectedStudentWeight; 129 if (request.isAlternative()) 130 weight *= iAlternativeRequestFactor; 131 return weight; 132 } 133 double total = 1000000.0; 134 int nrReq = request.getStudent().nrRequests(); 135 double remain = (iLeftoverSpread ? Math.floor(1000000.0 * Math.pow(iPriorityFactor, nrReq) / nrReq) : 0.0); 136 for (int idx = 0; idx < request.getStudent().getRequests().size(); idx++) { 137 Request r = request.getStudent().getRequests().get(idx); 138 boolean last = (idx + 1 == request.getStudent().getRequests().size()); 139 boolean lastNotAlt = !r.isAlternative() && (last || request.getStudent().getRequests().get(1 + idx).isAlternative()); 140 double w = Math.ceil(iPriorityFactor * total) + remain; 141 if (!iLeftoverSpread && lastNotAlt) { 142 w = total; 143 } else { 144 total -= w; 145 } 146 if (r.equals(request)) { 147 return w / 1000000.0; 148 } 149 } 150 return 0.0; 151 } 152 153 public double getCachedWeight(Request request) { 154 Double w = (Double)request.getExtra(); 155 if (w == null) { 156 w = getWeight(request); 157 request.setExtra(w); 158 } 159 return w; 160 } 161 162 /** 163 * Return how much the given enrollment is different from the initial enrollment 164 * @param enrollment given enrollment 165 * @return 0.0 when all the sections are the same, 1.0 when all the section are different (including different times) 166 */ 167 protected double getDifference(Enrollment enrollment) { 168 if (enrollment.getStudent().isDummy() || !enrollment.isCourseRequest()) return 1.0; 169 Enrollment other = enrollment.getRequest().getInitialAssignment(); 170 if (other != null) { 171 double similarSections = 0.0; 172 if (enrollment.getConfig().equals(other.getConfig())) { 173 // same configurations -- compare sections of matching subpart 174 for (Section section: enrollment.getSections()) { 175 for (Section initial: other.getSections()) { 176 if (section.getSubpart().equals(initial.getSubpart())) { 177 if (section.equals(initial)) { 178 similarSections += 1.0; 179 } else if (section.sameChoice(initial)) { 180 similarSections += iSameChoiceWeight; 181 } else if (section.sameTime(initial)) { 182 similarSections += iSameTimeWeight; 183 } 184 break; 185 } 186 } 187 } 188 } else { 189 // different configurations -- compare sections of matching itype 190 for (Section section: enrollment.getSections()) { 191 for (Section initial: other.getSections()) { 192 if (section.sameChoice(initial)) { 193 similarSections += iSameChoiceWeight; 194 break; 195 } else if (section.sameInstructionalType(initial) && section.sameTime(initial)) { 196 similarSections += iSameTimeWeight; 197 break; 198 } 199 } 200 } 201 } 202 return 1.0 - similarSections / enrollment.getAssignments().size(); 203 } 204 return 1.0; 205 } 206 207 /** 208 * Return how much the given enrollment is different from the selection (if any) 209 * @param enrollment given enrollment 210 * @return 0.0 when all the sections are the same, 1.0 when all the section are different (including different times) 211 */ 212 public double getSelection(Enrollment enrollment) { 213 if (enrollment.getStudent().isDummy()) return 1.0; 214 if (enrollment.isCourseRequest()) { 215 CourseRequest cr = (CourseRequest)enrollment.getRequest(); 216 if (!cr.getSelectedChoices().isEmpty()) { 217 double similarSections = 0.0; 218 for (Section section: enrollment.getSections()) { 219 double bestChoice = 0.0; 220 for (Choice ch: cr.getSelectedChoices()) { 221 if (bestChoice < 1.0 && ch.sameSection(section)) { 222 bestChoice = 1.0; 223 } else if (bestChoice < iSameChoiceWeight && ch.sameChoice(section)) { 224 bestChoice = iSameChoiceWeight; 225 } else if (bestChoice < iSameTimeWeight && ch.sameOffering(section) && ch.sameInstructionalType(section) && ch.sameTime(section)) { 226 bestChoice = iSameTimeWeight; 227 } else if (bestChoice < iSameConfigWeight && ch.sameConfiguration(section)) { 228 bestChoice = iSameConfigWeight; 229 } 230 } 231 similarSections += bestChoice; 232 } 233 return 1.0 - similarSections / enrollment.getAssignments().size(); 234 } else { 235 return 1.0; 236 } 237 } else { 238 return 1.0; 239 } 240 } 241 242 243 @Override 244 public double getBound(Request request) { 245 return getCachedWeight(request); 246 } 247 248 protected double round(double value) { 249 return Math.ceil(1000000.0 * value) / 1000000.0; 250 } 251 252 protected double getBaseWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 253 double weight = getCachedWeight(enrollment.getRequest()); 254 switch (enrollment.getPriority()) { 255 case 0: break; 256 case 1: weight *= iFirstAlternativeFactor; break; 257 case 2: weight *= iSecondAlternativeFactor; break; 258 default: 259 weight *= Math.pow(iFirstAlternativeFactor, enrollment.getPriority()); 260 } 261 return weight; 262 } 263 264 @Override 265 public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 266 if (iAdditiveWeights) 267 return getWeightAdditive(assignment, enrollment); 268 else 269 return getWeightMultiplicative(assignment, enrollment); 270 } 271 272 public double getWeightMultiplicative(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 273 double weight = getBaseWeight(assignment, enrollment); 274 if (enrollment.isCourseRequest() && iNoTimeFactor != 0.0) { 275 int noTimeSections = 0, total = 0; 276 for (Section section: enrollment.getSections()) { 277 if (section.getTime() == null) noTimeSections ++; 278 total ++; 279 } 280 if (noTimeSections > 0) 281 weight *= (1.0 - iNoTimeFactor * noTimeSections / total); 282 } 283 if (enrollment.isCourseRequest() && iBalancingFactor != 0.0) { 284 double configUsed = enrollment.getConfig().getEnrollmentTotalWeight(assignment, enrollment.getRequest()) + enrollment.getRequest().getWeight(); 285 double disbalanced = 0; 286 double total = 0; 287 for (Section section: enrollment.getSections()) { 288 Subpart subpart = section.getSubpart(); 289 if (subpart.getSections().size() <= 1) continue; 290 double used = section.getEnrollmentTotalWeight(assignment, enrollment.getRequest()) + enrollment.getRequest().getWeight(); 291 // sections have limits -> desired size is section limit x (total enrollment / total limit) 292 // unlimited sections -> desired size is total enrollment / number of sections 293 double desired = (subpart.getLimit() > 0 294 ? section.getLimit() * (configUsed / subpart.getLimit()) 295 : configUsed / subpart.getSections().size()); 296 if (used > desired) 297 disbalanced += Math.min(enrollment.getRequest().getWeight(), used - desired) / enrollment.getRequest().getWeight(); 298 else 299 disbalanced -= Math.min(enrollment.getRequest().getWeight(), desired - used) / enrollment.getRequest().getWeight(); 300 total ++; 301 } 302 if (disbalanced > 0) 303 weight *= (1.0 - disbalanced / total * iBalancingFactor); 304 } 305 if (iMPP) { 306 double difference = getDifference(enrollment); 307 if (difference > 0.0) 308 weight *= (1.0 - difference * iPerturbationFactor); 309 } 310 if (iSelectionFactor != 0.0) { 311 double selection = getSelection(enrollment); 312 if (selection > 0.0) 313 weight *= (1.0 - selection * iSelectionFactor); 314 } 315 if (enrollment.isCourseRequest() && iGroupFactor != 0.0) { 316 double sameGroup = 0.0; int groupCount = 0; 317 for (RequestGroup g: ((CourseRequest)enrollment.getRequest()).getRequestGroups()) { 318 if (g.getCourse().equals(enrollment.getCourse())) { 319 sameGroup += g.getEnrollmentSpread(assignment, enrollment, iGroupBestRatio, iGroupFillRatio); 320 groupCount ++; 321 } 322 } 323 if (groupCount > 0) { 324 double difference = 1.0 - sameGroup / groupCount; 325 weight *= (1.0 - difference * iGroupFactor); 326 } 327 } 328 return round(weight); 329 } 330 331 public double getWeightAdditive(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 332 double base = getBaseWeight(assignment, enrollment); 333 double weight = 0.0; 334 if (enrollment.isCourseRequest() && iNoTimeFactor != 0.0) { 335 int noTimeSections = 0, total = 0; 336 for (Section section: enrollment.getSections()) { 337 if (section.getTime() == null) noTimeSections ++; 338 total ++; 339 } 340 if (noTimeSections > 0) 341 weight += iNoTimeFactor * noTimeSections / total; 342 } 343 if (enrollment.isCourseRequest() && iBalancingFactor != 0.0) { 344 double configUsed = enrollment.getConfig().getEnrollmentTotalWeight(assignment, enrollment.getRequest()) + enrollment.getRequest().getWeight(); 345 double disbalanced = 0; 346 double total = 0; 347 for (Section section: enrollment.getSections()) { 348 Subpart subpart = section.getSubpart(); 349 if (subpart.getSections().size() <= 1) continue; 350 double used = section.getEnrollmentTotalWeight(assignment, enrollment.getRequest()) + enrollment.getRequest().getWeight(); 351 // sections have limits -> desired size is section limit x (total enrollment / total limit) 352 // unlimited sections -> desired size is total enrollment / number of sections 353 double desired = (subpart.getLimit() > 0 354 ? section.getLimit() * (configUsed / subpart.getLimit()) 355 : configUsed / subpart.getSections().size()); 356 if (used > desired) 357 disbalanced += Math.min(enrollment.getRequest().getWeight(), used - desired) / enrollment.getRequest().getWeight(); 358 else 359 disbalanced -= Math.min(enrollment.getRequest().getWeight(), desired - used) / enrollment.getRequest().getWeight(); 360 total ++; 361 } 362 if (disbalanced > 0) 363 weight += disbalanced / total * iBalancingFactor; 364 } 365 if (iMPP) { 366 double difference = getDifference(enrollment); 367 if (difference > 0.0) 368 weight += difference * iPerturbationFactor; 369 } 370 if (iSelectionFactor != 0.0) { 371 double selection = getSelection(enrollment); 372 if (selection > 0.0) 373 weight += selection * iSelectionFactor; 374 } 375 if (enrollment.isCourseRequest() && iGroupFactor != 0.0) { 376 double sameGroup = 0.0; int groupCount = 0; 377 for (RequestGroup g: ((CourseRequest)enrollment.getRequest()).getRequestGroups()) { 378 if (g.getCourse().equals(enrollment.getCourse())) { 379 sameGroup += g.getEnrollmentSpread(assignment, enrollment, iGroupBestRatio, iGroupFillRatio); 380 groupCount ++; 381 } 382 } 383 if (groupCount > 0) { 384 double difference = 1.0 - sameGroup / groupCount; 385 weight += difference * iGroupFactor; 386 } 387 } 388 return round(base * (1.0 - weight)); 389 } 390 391 @Override 392 public double getDistanceConflictWeight(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) { 393 if (iAdditiveWeights) { 394 if (c.getR1().getPriority() < c.getR2().getPriority()) { 395 return round(getBaseWeight(assignment, c.getE2()) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict)); 396 } else { 397 return round(getBaseWeight(assignment, c.getE1()) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict)); 398 } 399 } else { 400 if (c.getR1().getPriority() < c.getR2().getPriority()) { 401 return round(getWeightMultiplicative(assignment, c.getE2()) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict)); 402 } else { 403 return round(getWeightMultiplicative(assignment, c.getE1()) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict)); 404 } 405 } 406 } 407 408 @Override 409 public double getTimeOverlapConflictWeight(Assignment<Request, Enrollment> assignment, Enrollment e, TimeOverlapsCounter.Conflict c) { 410 if (e == null || e.getRequest() == null) return 0.0; 411 double toc = Math.min(iTimeOverlapMaxLimit * c.getShare() / e.getNrSlots(), iTimeOverlapMaxLimit); 412 if (iAdditiveWeights) { 413 return round(getBaseWeight(assignment, e) * toc); 414 } else { 415 return round(getWeightMultiplicative(assignment, e) * toc); 416 } 417 } 418 419 @Override 420 public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<DistanceConflict.Conflict> distanceConflicts, Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts) { 421 if (iAdditiveWeights) { 422 double base = getBaseWeight(assignment, enrollment); 423 double dc = 0.0; 424 if (distanceConflicts != null) { 425 for (DistanceConflict.Conflict c: distanceConflicts) { 426 Enrollment other = (c.getE1().equals(enrollment) ? c.getE2() : c.getE1()); 427 if (other.getRequest().getPriority() <= enrollment.getRequest().getPriority()) 428 dc += base * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict); 429 else 430 dc += getBaseWeight(assignment, other) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict); 431 } 432 } 433 double toc = 0.0; 434 if (timeOverlappingConflicts != null) { 435 for (TimeOverlapsCounter.Conflict c: timeOverlappingConflicts) { 436 toc += base * Math.min(iTimeOverlapFactor * c.getShare() / enrollment.getNrSlots(), iTimeOverlapMaxLimit); 437 Enrollment other = (c.getE1().equals(enrollment) ? c.getE2() : c.getE1()); 438 if (other.getRequest() != null) 439 toc += getBaseWeight(assignment, other) * Math.min(iTimeOverlapFactor * c.getShare() / other.getNrSlots(), iTimeOverlapMaxLimit); 440 } 441 } 442 return round(getWeight(assignment, enrollment) - dc - toc); 443 } else { 444 double base = getWeightMultiplicative(assignment, enrollment); 445 double dc = 0.0; 446 if (distanceConflicts != null) { 447 for (DistanceConflict.Conflict c: distanceConflicts) { 448 Enrollment other = (c.getE1().equals(enrollment) ? c.getE2() : c.getE1()); 449 if (other.getRequest().getPriority() <= enrollment.getRequest().getPriority()) 450 dc += base * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict); 451 else 452 dc += getWeightMultiplicative(assignment, other) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict); 453 } 454 } 455 double toc = 0.0; 456 if (timeOverlappingConflicts != null) { 457 for (TimeOverlapsCounter.Conflict c: timeOverlappingConflicts) { 458 toc += base * Math.min(iTimeOverlapFactor * c.getShare() / enrollment.getNrSlots(), iTimeOverlapMaxLimit); 459 Enrollment other = (c.getE1().equals(enrollment) ? c.getE2() : c.getE1()); 460 if (other.getRequest() != null) 461 toc += getWeightMultiplicative(assignment, other) * Math.min(iTimeOverlapFactor * c.getShare() / other.getNrSlots(), iTimeOverlapMaxLimit); 462 } 463 } 464 return round(base - dc - toc); 465 } 466 } 467 468 469 @Override 470 public boolean isBetterThanBestSolution(Solution<Request, Enrollment> currentSolution) { 471 if (currentSolution.getBestInfo() == null) return true; 472 if (iMaximizeAssignment) { 473 long acr = Math.round(((StudentSectioningModel)currentSolution.getModel()).getContext(currentSolution.getAssignment()).getAssignedCourseRequestWeight()); 474 long bcr = Math.round(((StudentSectioningModel)currentSolution.getModel()).getBestAssignedCourseRequestWeight()); 475 if (acr != bcr) 476 return acr > bcr; 477 } 478 return ((StudentSectioningModel)currentSolution.getModel()).getTotalValue(currentSolution.getAssignment(), iPreciseComparison) < currentSolution.getBestValue(); 479 } 480 481 @Override 482 public boolean isFreeTimeAllowOverlaps() { 483 return false; 484 } 485 486 /** 487 * Test case -- run to see the weights for a few courses 488 * @param args program arguments 489 */ 490 public static void main(String[] args) { 491 PriorityStudentWeights pw = new PriorityStudentWeights(new DataProperties()); 492 DecimalFormat df = new DecimalFormat("0.000000"); 493 Student s = new Student(0l); 494 new CourseRequest(1l, 0, false, s, ToolBox.toList( 495 new Course(1, "A", "1", new Offering(0, "A")), 496 new Course(1, "A", "2", new Offering(0, "A")), 497 new Course(1, "A", "3", new Offering(0, "A"))), false, null); 498 new CourseRequest(2l, 1, false, s, ToolBox.toList( 499 new Course(1, "B", "1", new Offering(0, "B")), 500 new Course(1, "B", "2", new Offering(0, "B")), 501 new Course(1, "B", "3", new Offering(0, "B"))), false, null); 502 new CourseRequest(3l, 2, false, s, ToolBox.toList( 503 new Course(1, "C", "1", new Offering(0, "C")), 504 new Course(1, "C", "2", new Offering(0, "C")), 505 new Course(1, "C", "3", new Offering(0, "C"))), false, null); 506 new CourseRequest(4l, 3, false, s, ToolBox.toList( 507 new Course(1, "D", "1", new Offering(0, "D")), 508 new Course(1, "D", "2", new Offering(0, "D")), 509 new Course(1, "D", "3", new Offering(0, "D"))), false, null); 510 new CourseRequest(5l, 4, false, s, ToolBox.toList( 511 new Course(1, "E", "1", new Offering(0, "E")), 512 new Course(1, "E", "2", new Offering(0, "E")), 513 new Course(1, "E", "3", new Offering(0, "E"))), false, null); 514 new CourseRequest(6l, 5, true, s, ToolBox.toList( 515 new Course(1, "F", "1", new Offering(0, "F")), 516 new Course(1, "F", "2", new Offering(0, "F")), 517 new Course(1, "F", "3", new Offering(0, "F"))), false, null); 518 new CourseRequest(7l, 6, true, s, ToolBox.toList( 519 new Course(1, "G", "1", new Offering(0, "G")), 520 new Course(1, "G", "2", new Offering(0, "G")), 521 new Course(1, "G", "3", new Offering(0, "G"))), false, null); 522 523 Assignment<Request, Enrollment> assignment = new DefaultSingleAssignment<Request, Enrollment>(); 524 Placement p = new Placement(null, new TimeLocation(1, 90, 12, 0, 0, null, null, new BitSet(), 10), new ArrayList<RoomLocation>()); 525 for (Request r: s.getRequests()) { 526 CourseRequest cr = (CourseRequest)r; 527 double[] w = new double[] {0.0, 0.0, 0.0}; 528 for (int i = 0; i < cr.getCourses().size(); i++) { 529 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 530 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 531 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 532 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 533 w[i] = pw.getWeight(assignment, e, null, null); 534 } 535 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 536 } 537 538 System.out.println("With one distance conflict:"); 539 for (Request r: s.getRequests()) { 540 CourseRequest cr = (CourseRequest)r; 541 double[] w = new double[] {0.0, 0.0, 0.0}; 542 for (int i = 0; i < cr.getCourses().size(); i++) { 543 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 544 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 545 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 546 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 547 Set<DistanceConflict.Conflict> dc = new HashSet<DistanceConflict.Conflict>(); 548 dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e, (Section)sections.iterator().next())); 549 w[i] = pw.getWeight(assignment, e, dc, null); 550 } 551 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 552 } 553 554 System.out.println("With two distance conflicts:"); 555 for (Request r: s.getRequests()) { 556 CourseRequest cr = (CourseRequest)r; 557 double[] w = new double[] {0.0, 0.0, 0.0}; 558 for (int i = 0; i < cr.getCourses().size(); i++) { 559 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 560 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 561 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 562 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 563 Set<DistanceConflict.Conflict> dc = new HashSet<DistanceConflict.Conflict>(); 564 dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e, (Section)sections.iterator().next())); 565 dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e, 566 new Section(1, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null))); 567 w[i] = pw.getWeight(assignment, e, dc, null); 568 } 569 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 570 } 571 572 System.out.println("With 25% time overlapping conflict:"); 573 for (Request r: s.getRequests()) { 574 CourseRequest cr = (CourseRequest)r; 575 double[] w = new double[] {0.0, 0.0, 0.0}; 576 for (int i = 0; i < cr.getCourses().size(); i++) { 577 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 578 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 579 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 580 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 581 Set<TimeOverlapsCounter.Conflict> toc = new HashSet<TimeOverlapsCounter.Conflict>(); 582 toc.add(new TimeOverlapsCounter.Conflict(s, 3, e, sections.iterator().next(), e, sections.iterator().next())); 583 w[i] = pw.getWeight(assignment, e, null, toc); 584 } 585 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 586 } 587 588 System.out.println("Disbalanced sections (by 2 / 10 students):"); 589 for (Request r: s.getRequests()) { 590 CourseRequest cr = (CourseRequest)r; 591 double[] w = new double[] {0.0, 0.0, 0.0}; 592 for (int i = 0; i < cr.getCourses().size(); i++) { 593 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 594 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 595 Subpart x = new Subpart(0, "Lec", "Lec", cfg, null); 596 Section a = new Section(0, 10, "x", x, p, null); 597 new Section(1, 10, "y", x, p, null); 598 sections.add(a); 599 a.assigned(assignment, new Enrollment(s.getRequests().get(0), i, cfg, sections, assignment)); 600 a.assigned(assignment, new Enrollment(s.getRequests().get(0), i, cfg, sections, assignment)); 601 cfg.getContext(assignment).assigned(assignment, new Enrollment(s.getRequests().get(0), i, cfg, sections, assignment)); 602 cfg.getContext(assignment).assigned(assignment, new Enrollment(s.getRequests().get(0), i, cfg, sections, assignment)); 603 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 604 w[i] = pw.getWeight(assignment, e, null, null); 605 } 606 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 607 } 608 609 System.out.println("Same choice sections:"); 610 pw.iMPP = true; 611 for (Request r: s.getRequests()) { 612 CourseRequest cr = (CourseRequest)r; 613 double[] w = new double[] {0.0, 0.0, 0.0}; 614 for (int i = 0; i < cr.getCourses().size(); i++) { 615 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 616 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 617 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 618 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 619 Set<SctAssignment> other = new HashSet<SctAssignment>(); 620 other.add(new Section(1, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 621 cr.setInitialAssignment(new Enrollment(cr, i, cfg, other, assignment)); 622 w[i] = pw.getWeight(assignment, e, null, null); 623 } 624 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 625 } 626 627 System.out.println("Same time sections:"); 628 for (Request r: s.getRequests()) { 629 CourseRequest cr = (CourseRequest)r; 630 double[] w = new double[] {0.0, 0.0, 0.0}; 631 for (int i = 0; i < cr.getCourses().size(); i++) { 632 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 633 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 634 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 635 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 636 Set<SctAssignment> other = new HashSet<SctAssignment>(); 637 other.add(new Section(1, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null, new Instructor(1l, null, "Josef Novak", null))); 638 cr.setInitialAssignment(new Enrollment(cr, i, cfg, other, assignment)); 639 w[i] = pw.getWeight(assignment, e, null, null); 640 } 641 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 642 } 643 644 System.out.println("Different time sections:"); 645 Placement q = new Placement(null, new TimeLocation(1, 102, 12, 0, 0, null, null, new BitSet(), 10), new ArrayList<RoomLocation>()); 646 for (Request r: s.getRequests()) { 647 CourseRequest cr = (CourseRequest)r; 648 double[] w = new double[] {0.0, 0.0, 0.0}; 649 for (int i = 0; i < cr.getCourses().size(); i++) { 650 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 651 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 652 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 653 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 654 Set<SctAssignment> other = new HashSet<SctAssignment>(); 655 other.add(new Section(1, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), q, null)); 656 cr.setInitialAssignment(new Enrollment(cr, i, cfg, other, assignment)); 657 w[i] = pw.getWeight(assignment, e, null, null); 658 } 659 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 660 } 661 662 System.out.println("Two sections, one same choice, one same time:"); 663 for (Request r: s.getRequests()) { 664 CourseRequest cr = (CourseRequest)r; 665 double[] w = new double[] {0.0, 0.0, 0.0}; 666 for (int i = 0; i < cr.getCourses().size(); i++) { 667 Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering()); 668 Set<SctAssignment> sections = new HashSet<SctAssignment>(); 669 sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 670 sections.add(new Section(1, 1, "y", new Subpart(1, "Rec", "Rec", cfg, null), p, null)); 671 Enrollment e = new Enrollment(cr, i, cfg, sections, assignment); 672 Set<SctAssignment> other = new HashSet<SctAssignment>(); 673 other.add(new Section(2, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)); 674 other.add(new Section(3, 1, "y", new Subpart(1, "Rec", "Rec", cfg, null), p, null, new Instructor(1l, null, "Josef Novak", null))); 675 cr.setInitialAssignment(new Enrollment(cr, i, cfg, other, assignment)); 676 w[i] = pw.getWeight(assignment, e, null, null); 677 } 678 System.out.println(cr + ": " + df.format(w[0]) + " " + df.format(w[1]) + " " + df.format(w[2])); 679 } 680 681 } 682 683 @Override 684 public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<StudentQuality.Conflict> qualityConflicts) { 685 if (iAdditiveWeights) { 686 double base = getBaseWeight(assignment, enrollment); 687 double penalty = 0.0; 688 if (qualityConflicts != null) { 689 for (StudentQuality.Conflict c: qualityConflicts) { 690 switch (c.getType().getType()) { 691 case REQUEST: 692 if (enrollment.isCourseRequest()) 693 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 694 break; 695 case BOTH: 696 Enrollment other = c.getOther(enrollment); 697 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 698 penalty += getBaseWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other); 699 break; 700 case LOWER: 701 other = c.getOther(enrollment); 702 if (other.getRequest().getPriority() <= enrollment.getRequest().getPriority()) 703 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 704 else 705 penalty += getBaseWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other); 706 break; 707 case HIGHER: 708 other = c.getOther(enrollment); 709 if (other.getRequest().getPriority() >= enrollment.getRequest().getPriority()) 710 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 711 else 712 penalty += getBaseWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other); 713 } 714 } 715 } 716 return round(getWeight(assignment, enrollment) - penalty); 717 } else { 718 double base = getWeightMultiplicative(assignment, enrollment); 719 double penalty = 0.0; 720 if (qualityConflicts != null) { 721 for (StudentQuality.Conflict c: qualityConflicts) { 722 Enrollment other = c.getOther(enrollment); 723 switch (c.getType().getType()) { 724 case REQUEST: 725 if (enrollment.isCourseRequest()) 726 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 727 else if (other.isCourseRequest()) 728 penalty += getWeightMultiplicative(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other); 729 break; 730 case BOTH: 731 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 732 if (other.getRequest() != null) 733 penalty += getWeightMultiplicative(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other); 734 break; 735 case LOWER: 736 other = c.getOther(enrollment); 737 if (other.getRequest().getPriority() <= enrollment.getRequest().getPriority()) 738 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 739 else if (other.getRequest() != null) 740 penalty += getWeightMultiplicative(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other); 741 break; 742 case HIGHER: 743 other = c.getOther(enrollment); 744 if (other.getRequest().getPriority() >= enrollment.getRequest().getPriority()) 745 penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment); 746 else if (other.getRequest() != null) 747 penalty += getWeightMultiplicative(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other); 748 } 749 } 750 } 751 return round(base - penalty); 752 } 753 } 754 755 @Override 756 public double getStudentQualityConflictWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Conflict conflict) { 757 switch (conflict.getType().getType()) { 758 case BOTH: 759 if (enrollment == null || enrollment.getRequest() == null) return 0.0; 760 if (iAdditiveWeights) { 761 return round(getBaseWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment)); 762 } else { 763 return round(getWeightMultiplicative(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment)); 764 } 765 case REQUEST: 766 if (enrollment == null || enrollment.getRequest() == null || !enrollment.isCourseRequest()) return 0.0; 767 if (iAdditiveWeights) { 768 return round(getBaseWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment)); 769 } else { 770 return round(getWeightMultiplicative(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment)); 771 } 772 case LOWER: 773 if (iAdditiveWeights) { 774 if (conflict.getR1().getPriority() < conflict.getR2().getPriority()) { 775 return round(getBaseWeight(assignment, conflict.getE2()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2())); 776 } else { 777 return round(getBaseWeight(assignment, conflict.getE1()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1())); 778 } 779 } else { 780 if (conflict.getR1().getPriority() < conflict.getR2().getPriority()) { 781 return round(getWeightMultiplicative(assignment, conflict.getE2()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2())); 782 } else { 783 return round(getWeightMultiplicative(assignment, conflict.getE1()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1())); 784 } 785 } 786 case HIGHER: 787 if (iAdditiveWeights) { 788 if (conflict.getR1().getPriority() > conflict.getR2().getPriority()) { 789 return round(getBaseWeight(assignment, conflict.getE2()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2())); 790 } else { 791 return round(getBaseWeight(assignment, conflict.getE1()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1())); 792 } 793 } else { 794 if (conflict.getR1().getPriority() < conflict.getR2().getPriority()) { 795 return round(getWeightMultiplicative(assignment, conflict.getE2()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2())); 796 } else { 797 return round(getWeightMultiplicative(assignment, conflict.getE1()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1())); 798 } 799 } 800 default: 801 return 0.0; 802 } 803 } 804}