001package org.cpsolver.studentsct.online.selection; 002 003import java.util.Hashtable; 004import java.util.Set; 005 006import org.cpsolver.ifs.assignment.Assignment; 007import org.cpsolver.studentsct.extension.StudentQuality; 008import org.cpsolver.studentsct.model.CourseRequest; 009import org.cpsolver.studentsct.model.Enrollment; 010import org.cpsolver.studentsct.model.FreeTimeRequest; 011import org.cpsolver.studentsct.model.Request; 012import org.cpsolver.studentsct.model.Section; 013import org.cpsolver.studentsct.model.Student; 014import org.cpsolver.studentsct.model.Subpart; 015import org.cpsolver.studentsct.online.OnlineSectioningModel; 016 017/** 018* Equal weighting multi-criteria selection criterion. Much like the {@link OnlineSectioningCriterion}, but 019* course request priorities are ignored. Most complete solution is preferred instead. 020* 021* @version StudentSct 1.3 (Student Sectioning)<br> 022* Copyright (C) 2014 Tomáš Müller<br> 023* <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 024* <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 025* <br> 026* This library is free software; you can redistribute it and/or modify 027* it under the terms of the GNU Lesser General Public License as 028* published by the Free Software Foundation; either version 3 of the 029* License, or (at your option) any later version. <br> 030* <br> 031* This library is distributed in the hope that it will be useful, but 032* WITHOUT ANY WARRANTY; without even the implied warranty of 033* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 034* Lesser General Public License for more details. <br> 035* <br> 036* You should have received a copy of the GNU Lesser General Public 037* License along with this library; if not see <a 038* href='http://www.gnu.org/licenses'>http://www.gnu.org/licenses</a>. 039* 040*/ 041public class EqualWeightCriterion extends OnlineSectioningCriterion { 042 043 public EqualWeightCriterion(Student student, OnlineSectioningModel model, 044 Assignment<Request, Enrollment> assignment, Hashtable<CourseRequest, Set<Section>> preferredSections) { 045 super(student, model, assignment, preferredSections); 046 } 047 048 @Override 049 public int compare(Assignment<Request, Enrollment> assignment, Enrollment[] current, Enrollment[] best) { 050 if (best == null) 051 return -1; 052 053 // 0. best number of assigned course requests (including alternativity & 054 // priority) 055 int currentAssignedCourseReq = 0, bestAssignedCourseReq = 0; 056 int currentAssignedRequests = 0, bestAssignedRequests = 0; 057 int currentAssignedPriority = 0, bestAssignedPriority = 0; 058 int currentAssignedAlternativity = 0, bestAssignedAlternativity = 0; 059 int currentNotFollowedReservations = 0, bestNotFollowedReservations = 0; 060 for (int idx = 0; idx < current.length; idx++) { 061 if (current[idx] != null && current[idx].getAssignments() != null) { 062 currentAssignedRequests++; 063 if (current[idx].isCourseRequest()) 064 currentAssignedCourseReq++; 065 currentAssignedPriority += current[idx].getTruePriority() * current[idx].getTruePriority(); 066 currentAssignedAlternativity += (current[idx].getRequest().isAlternative() ? 1 : 0); 067 if (current[idx].getTruePriority() < current[idx].getPriority()) currentNotFollowedReservations ++; 068 } 069 if (best[idx] != null && best[idx].getAssignments() != null) { 070 bestAssignedRequests++; 071 if (best[idx].isCourseRequest()) 072 bestAssignedCourseReq++; 073 bestAssignedPriority += best[idx].getTruePriority() * best[idx].getTruePriority(); 074 bestAssignedAlternativity += (best[idx].getRequest().isAlternative() ? 1 : 0); 075 if (best[idx].getTruePriority() < best[idx].getPriority()) bestNotFollowedReservations ++; 076 } 077 } 078 if (currentAssignedCourseReq > bestAssignedCourseReq) 079 return -1; 080 if (bestAssignedCourseReq > currentAssignedCourseReq) 081 return 1; 082 if (currentAssignedPriority < bestAssignedPriority) 083 return -1; 084 if (bestAssignedPriority < currentAssignedPriority) 085 return 1; 086 if (currentAssignedAlternativity < bestAssignedAlternativity) 087 return -1; 088 if (bestAssignedAlternativity < currentAssignedAlternativity) 089 return 1; 090 091 // 0.1. allowed, but not available sections 092 int bestNotAvailable = 0, currentNotAvailable = 0; 093 for (int idx = 0; idx < current.length; idx++) { 094 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) { 095 for (Section section: best[idx].getSections()) { 096 if (section.getLimit() == 0) 097 bestNotAvailable ++; 098 } 099 } 100 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) { 101 for (Section section: current[idx].getSections()) { 102 if (section.getLimit() == 0) 103 currentNotAvailable ++; 104 } 105 } 106 } 107 if (bestNotAvailable > currentNotAvailable) return -1; 108 if (bestNotAvailable < currentNotAvailable) return 1; 109 110 // 0.5. avoid course overlaps & unavailabilities 111 if (getModel().getStudentQuality() != null) { 112 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 113 for (int idx = 0; idx < current.length; idx++) { 114 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest) { 115 for (int x = 0; x < idx; x++) { 116 if (best[x] != null && best[x].getAssignments() != null && best[x].getRequest() instanceof CourseRequest) 117 bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, best[x], best[idx]); 118 } 119 } 120 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest) { 121 for (int x = 0; x < idx; x++) { 122 if (current[x] != null && current[x].getAssignments() != null && current[x].getRequest() instanceof CourseRequest) 123 currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, current[x], current[idx]); 124 } 125 } 126 } 127 for (int idx = 0; idx < current.length; idx++) { 128 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 129 bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, best[idx]); 130 } 131 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 132 currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, current[idx]); 133 } 134 } 135 if (currentTimeOverlaps < bestTimeOverlaps) 136 return -1; 137 if (bestTimeOverlaps < currentTimeOverlaps) 138 return 1; 139 } else if (getModel().getTimeOverlaps() != null) { 140 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 141 for (int idx = 0; idx < current.length; idx++) { 142 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest) { 143 for (int x = 0; x < idx; x++) { 144 if (best[x] != null && best[x].getAssignments() != null 145 && best[x].getRequest() instanceof CourseRequest) 146 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]); 147 } 148 } 149 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest) { 150 for (int x = 0; x < idx; x++) { 151 if (current[x] != null && current[x].getAssignments() != null 152 && current[x].getRequest() instanceof CourseRequest) 153 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]); 154 } 155 } 156 } 157 for (int idx = 0; idx < current.length; idx++) { 158 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 159 bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]); 160 } 161 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 162 currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]); 163 } 164 } 165 if (currentTimeOverlaps < bestTimeOverlaps) 166 return -1; 167 if (bestTimeOverlaps < currentTimeOverlaps) 168 return 1; 169 } 170 171 // 1. minimize number of penalties 172 double bestPenalties = 0, currentPenalties = 0; 173 for (int idx = 0; idx < current.length; idx++) { 174 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 175 for (Section section : best[idx].getSections()) 176 bestPenalties += getModel().getOverExpected(assignment, best, idx, section, best[idx].getRequest()); 177 } 178 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 179 for (Section section : current[idx].getSections()) 180 currentPenalties += getModel().getOverExpected(assignment, current, idx, section, current[idx].getRequest()); 181 } 182 } 183 if (currentPenalties < bestPenalties) 184 return -1; 185 if (bestPenalties < currentPenalties) 186 return 1; 187 188 // 2. best number of assigned requests (including free time requests) 189 if (currentAssignedRequests > bestAssignedRequests) 190 return -1; 191 if (bestAssignedRequests > currentAssignedRequests) 192 return 1; 193 194 // 3. maximize selection 195 int bestSelected = 0, currentSelected = 0; 196 for (int idx = 0; idx < current.length; idx++) { 197 Set<Section> preferred = getPreferredSections(getRequest(idx)); 198 if (preferred != null && !preferred.isEmpty()) { 199 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 200 for (Section section : best[idx].getSections()) 201 if (preferred.contains(section)) 202 bestSelected++; 203 } 204 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 205 for (Section section : current[idx].getSections()) 206 if (preferred.contains(section)) 207 currentSelected++; 208 } 209 } 210 } 211 if (currentSelected > bestSelected) 212 return -1; 213 if (bestSelected > currentSelected) 214 return 1; 215 216 // 3.5 maximize preferences 217 double bestSelectedConfigs = 0, currentSelectedConfigs = 0; 218 double bestSelectedSections = 0, currentSelectedSections = 0; 219 for (int idx = 0; idx < current.length; idx++) { 220 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 221 bestSelectedSections += best[idx].percentSelectedSameSection(); 222 bestSelectedConfigs += best[idx].percentSelectedSameConfig(); 223 } 224 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 225 currentSelectedSections += current[idx].percentSelectedSameSection(); 226 currentSelectedConfigs += current[idx].percentSelectedSameConfig(); 227 } 228 } 229 if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return -1; 230 if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return 1; 231 232 // 3.9 minimize enrollments where the reservation is not followed 233 if (currentNotFollowedReservations < bestNotFollowedReservations) 234 return -1; 235 if (bestNotFollowedReservations < currentNotFollowedReservations) 236 return 1; 237 238 // 3.95 avoid past sections 239 int bestPast = 0, currentPast = 0; 240 for (int idx = 0; idx < current.length; idx++) { 241 if (best[idx] != null && best[idx].getAssignments() != null) { 242 for (Section section : best[idx].getSections()) { 243 if (section.isPast()) 244 bestPast++; 245 } 246 } 247 if (current[idx] != null && current[idx].getAssignments() != null) { 248 for (Section section : current[idx].getSections()) { 249 if (section.isPast()) 250 currentPast++; 251 } 252 } 253 } 254 if (currentPast < bestPast) 255 return -1; 256 if (bestPast < currentPast) 257 return 1; 258 259 // 4-5. student quality 260 if (getModel().getStudentQuality() != null) { 261 double bestQuality = 0, currentQuality = 0; 262 for (StudentQuality.Type type: StudentQuality.Type.values()) { 263 for (int idx = 0; idx < current.length; idx++) { 264 if (best[idx] != null && best[idx].getAssignments() != null) { 265 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[idx]); 266 for (int x = 0; x < idx; x++) { 267 if (best[x] != null && best[x].getAssignments() != null) 268 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[x], best[idx]); 269 } 270 } 271 if (current[idx] != null && current[idx].getAssignments() != null) { 272 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[idx]); 273 for (int x = 0; x < idx; x++) { 274 if (current[x] != null && current[x].getAssignments() != null) 275 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[x], current[idx]); 276 } 277 } 278 } 279 } 280 if (currentQuality < bestQuality) 281 return -1; 282 if (bestQuality < currentQuality) 283 return 1; 284 } else { 285 // 4. avoid time overlaps 286 if (getModel().getTimeOverlaps() != null) { 287 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 288 for (int idx = 0; idx < current.length; idx++) { 289 if (best[idx] != null && best[idx].getAssignments() != null) { 290 for (int x = 0; x < idx; x++) { 291 if (best[x] != null && best[x].getAssignments() != null) 292 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]); 293 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 294 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), best[idx]); 295 } 296 } 297 if (current[idx] != null && current[idx].getAssignments() != null) { 298 for (int x = 0; x < idx; x++) { 299 if (current[x] != null && current[x].getAssignments() != null) 300 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]); 301 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 302 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts( 303 ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), 304 current[idx]); 305 } 306 } 307 } 308 for (int idx = 0; idx < current.length; idx++) { 309 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 310 bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]); 311 } 312 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 313 currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]); 314 } 315 } 316 if (currentTimeOverlaps < bestTimeOverlaps) 317 return -1; 318 if (bestTimeOverlaps < currentTimeOverlaps) 319 return 1; 320 } 321 322 // 5. avoid distance conflicts 323 if (getModel().getDistanceConflict() != null) { 324 int bestDistanceConf = 0, currentDistanceConf = 0; 325 for (int idx = 0; idx < current.length; idx++) { 326 if (best[idx] != null && best[idx].getAssignments() != null) { 327 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]); 328 for (int x = 0; x < idx; x++) { 329 if (best[x] != null && best[x].getAssignments() != null) 330 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]); 331 } 332 } 333 if (current[idx] != null && current[idx].getAssignments() != null) { 334 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]); 335 for (int x = 0; x < idx; x++) { 336 if (current[x] != null && current[x].getAssignments() != null) 337 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x], 338 current[idx]); 339 } 340 } 341 } 342 if (currentDistanceConf < bestDistanceConf) 343 return -1; 344 if (bestDistanceConf < currentDistanceConf) 345 return 1; 346 } 347 } 348 349 // 6. avoid no-time and online sections (no-time first, online second) 350 int bestNoTime = 0, currentNoTime = 0; 351 int bestOnline = 0, currentOnline = 0; 352 for (int idx = 0; idx < current.length; idx++) { 353 if (best[idx] != null && best[idx].getAssignments() != null) { 354 for (Section section : best[idx].getSections()) { 355 if (!section.hasTime()) 356 bestNoTime++; 357 if (section.isOnline()) 358 bestOnline++; 359 } 360 } 361 if (current[idx] != null && current[idx].getAssignments() != null) { 362 for (Section section : current[idx].getSections()) { 363 if (!section.hasTime()) 364 currentNoTime++; 365 if (section.isOnline()) 366 currentOnline++; 367 } 368 } 369 } 370 if (currentNoTime < bestNoTime) 371 return -1; 372 if (bestNoTime < currentNoTime) 373 return 1; 374 if (currentOnline < bestOnline) 375 return -1; 376 if (bestOnline < currentOnline) 377 return 1; 378 379 // 7. balance sections 380 double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0; 381 int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0; 382 for (int idx = 0; idx < current.length; idx++) { 383 if (best[idx] != null && best[idx].getAssignments() != null) { 384 for (Section section : best[idx].getSections()) { 385 Subpart subpart = section.getSubpart(); 386 // skip unlimited and single section subparts 387 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 388 continue; 389 // average size 390 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 391 // section is below average 392 if (section.getLimit() < averageSize) 393 bestUnavailableSize += (averageSize - section.getLimit()) / averageSize; 394 bestAltSectionsWithLimit++; 395 } 396 } 397 if (current[idx] != null && current[idx].getAssignments() != null) { 398 for (Section section : current[idx].getSections()) { 399 Subpart subpart = section.getSubpart(); 400 // skip unlimited and single section subparts 401 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 402 continue; 403 // average size 404 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 405 // section is below average 406 if (section.getLimit() < averageSize) 407 currentUnavailableSize += (averageSize - section.getLimit()) / averageSize; 408 currentAltSectionsWithLimit++; 409 } 410 } 411 } 412 double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit 413 : 0.0); 414 double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize 415 / currentAltSectionsWithLimit : 0.0); 416 if (currentUnavailableSizeFraction < bestUnavailableSizeFraction) 417 return -1; 418 if (bestUnavailableSizeFraction < currentUnavailableSizeFraction) 419 return 1; 420 421 // 8. average penalty sections 422 double bestPenalty = 0.0, currentPenalty = 0.0; 423 for (int idx = 0; idx < current.length; idx++) { 424 if (best[idx] != null && best[idx].getAssignments() != null) { 425 for (Section section : best[idx].getSections()) 426 bestPenalty += section.getPenalty() / best[idx].getSections().size(); 427 } 428 if (current[idx] != null && current[idx].getAssignments() != null) { 429 for (Section section : current[idx].getSections()) 430 currentPenalty += section.getPenalty() / current[idx].getSections().size(); 431 } 432 } 433 if (currentPenalty < bestPenalty) 434 return -1; 435 if (bestPenalty < currentPenalty) 436 return 1; 437 438 return 0; 439 } 440 441 @Override 442 public boolean canImprove(Assignment<Request, Enrollment> assignment, int maxIdx, Enrollment[] current, 443 Enrollment[] best) { 444 // 0. best number of assigned course requests (including alternativity & 445 // priority) 446 int currentAssignedCourseReq = 0, bestAssignedCourseReq = 0; 447 int currentAssignedRequests = 0, bestAssignedRequests = 0; 448 int currentAssignedPriority = 0, bestAssignedPriority = 0; 449 int currentAssignedAlternativity = 0, bestAssignedAlternativity = 0; 450 int currentNotFollowedReservations = 0, bestNotFollowedReservations = 0; 451 int alt = 0; 452 for (int idx = 0; idx < current.length; idx++) { 453 if (idx < maxIdx) { 454 if (current[idx] != null && current[idx].getAssignments() != null) { 455 currentAssignedRequests++; 456 if (current[idx].isCourseRequest()) 457 currentAssignedCourseReq++; 458 currentAssignedPriority += current[idx].getTruePriority() * current[idx].getTruePriority(); 459 currentAssignedAlternativity += (current[idx].getRequest().isAlternative() ? 1 : 0); 460 if (current[idx].getTruePriority() < current[idx].getPriority()) currentNotFollowedReservations ++; 461 } else if (!isFreeTime(idx) && !getRequest(idx).isAlternative()) { 462 alt++; 463 } 464 } else { 465 if (!getRequest(idx).isAlternative()) { 466 currentAssignedRequests++; 467 if (!isFreeTime(idx)) 468 currentAssignedCourseReq++; 469 } else if (alt > 0) { 470 currentAssignedRequests++; 471 currentAssignedCourseReq++; 472 alt--; 473 currentAssignedAlternativity++; 474 } 475 } 476 if (best[idx] != null && best[idx].getAssignments() != null) { 477 bestAssignedRequests++; 478 if (best[idx].isCourseRequest()) 479 bestAssignedCourseReq++; 480 bestAssignedPriority += best[idx].getTruePriority() * best[idx].getTruePriority(); 481 bestAssignedAlternativity += (best[idx].getRequest().isAlternative() ? 1 : 0); 482 if (best[idx].getTruePriority() < best[idx].getPriority()) bestNotFollowedReservations ++; 483 } 484 } 485 if (currentAssignedCourseReq > bestAssignedCourseReq) 486 return true; 487 if (bestAssignedCourseReq > currentAssignedCourseReq) 488 return false; 489 if (currentAssignedPriority < bestAssignedPriority) 490 return true; 491 if (bestAssignedPriority < currentAssignedPriority) 492 return false; 493 if (currentAssignedAlternativity < bestAssignedAlternativity) 494 return true; 495 if (bestAssignedAlternativity < currentAssignedAlternativity) 496 return false; 497 498 // 0.1. allowed, but not available sections 499 int notAvailable = 0; 500 for (int idx = 0; idx < current.length; idx++) { 501 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) { 502 for (Section section: best[idx].getSections()) { 503 if (section.getLimit() == 0) 504 notAvailable ++; 505 } 506 } 507 if (idx < maxIdx && current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) { 508 for (Section section: current[idx].getSections()) { 509 if (section.getLimit() == 0) 510 notAvailable --; 511 } 512 } 513 } 514 if (notAvailable > 0) { 515 return true; 516 } 517 518 // 0.5. avoid course time overlaps & unavailabilities 519 if (getModel().getStudentQuality() != null) { 520 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 521 for (int idx = 0; idx < current.length; idx++) { 522 if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) { 523 for (int x = 0; x < idx; x++) { 524 if (best[x] != null && best[x].getRequest() instanceof CourseRequest) 525 bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, best[x], best[idx]); 526 } 527 } 528 if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) { 529 for (int x = 0; x < idx; x++) { 530 if (current[x] != null && current[x].getRequest() instanceof CourseRequest) 531 currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, current[x], current[idx]); 532 } 533 } 534 } 535 for (int idx = 0; idx < current.length; idx++) { 536 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 537 bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, best[idx]); 538 } 539 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 540 currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, current[idx]); 541 } 542 } 543 if (currentTimeOverlaps < bestTimeOverlaps) 544 return true; 545 if (bestTimeOverlaps < currentTimeOverlaps) 546 return false; 547 } else if (getModel().getTimeOverlaps() != null) { 548 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 549 for (int idx = 0; idx < current.length; idx++) { 550 if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) { 551 for (int x = 0; x < idx; x++) { 552 if (best[x] != null && best[x].getRequest() instanceof CourseRequest) 553 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]); 554 } 555 } 556 if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) { 557 for (int x = 0; x < idx; x++) { 558 if (current[x] != null && current[x].getRequest() instanceof CourseRequest) 559 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]); 560 } 561 } 562 } 563 for (int idx = 0; idx < current.length; idx++) { 564 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 565 bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]); 566 } 567 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 568 currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]); 569 } 570 } 571 if (currentTimeOverlaps < bestTimeOverlaps) 572 return true; 573 if (bestTimeOverlaps < currentTimeOverlaps) 574 return false; 575 } 576 577 // 1. maximize number of penalties 578 double bestPenalties = 0, currentPenalties = 0; 579 for (int idx = 0; idx < current.length; idx++) { 580 if (best[idx] != null) { 581 for (Section section : best[idx].getSections()) 582 bestPenalties += getModel().getOverExpected(assignment, best, idx, section, best[idx].getRequest()); 583 } 584 if (current[idx] != null && idx < maxIdx) { 585 for (Section section : current[idx].getSections()) 586 currentPenalties += getModel().getOverExpected(assignment, current, idx, section, current[idx].getRequest()); 587 } 588 } 589 if (currentPenalties < bestPenalties) 590 return true; 591 if (bestPenalties < currentPenalties) 592 return false; 593 594 // 2. best number of assigned requests (including free time requests) 595 if (currentAssignedRequests > bestAssignedRequests) 596 return true; 597 if (bestAssignedRequests > currentAssignedRequests) 598 return false; 599 600 // 3. maximize selection 601 int bestSelected = 0, currentSelected = 0; 602 for (int idx = 0; idx < current.length; idx++) { 603 if (best[idx] != null && best[idx].isCourseRequest()) { 604 Set<Section> preferred = getPreferredSections(best[idx].getRequest()); 605 if (preferred != null && !preferred.isEmpty()) { 606 for (Section section : best[idx].getSections()) 607 if (preferred.contains(section)) { 608 if (idx < maxIdx) 609 bestSelected++; 610 } else if (idx >= maxIdx) 611 bestSelected--; 612 } 613 } 614 if (current[idx] != null && idx < maxIdx && current[idx].isCourseRequest()) { 615 Set<Section> preferred = getPreferredSections(current[idx].getRequest()); 616 if (preferred != null && !preferred.isEmpty()) { 617 for (Section section : current[idx].getSections()) 618 if (preferred.contains(section)) 619 currentSelected++; 620 } 621 } 622 } 623 if (currentSelected > bestSelected) 624 return true; 625 if (bestSelected > currentSelected) 626 return false; 627 628 // 3.5 maximize preferences 629 double bestSelectedConfigs = 0, currentSelectedConfigs = 0; 630 double bestSelectedSections = 0, currentSelectedSections = 0; 631 for (int idx = 0; idx < current.length; idx++) { 632 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 633 bestSelectedSections += best[idx].percentSelectedSameSection(); 634 bestSelectedConfigs += best[idx].percentSelectedSameConfig(); 635 if (idx >= maxIdx) { 636 bestSelectedSections -= 1.0; 637 bestSelectedConfigs -= 1.0; 638 } 639 } 640 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 641 currentSelectedSections += current[idx].percentSelectedSameSection(); 642 currentSelectedConfigs += current[idx].percentSelectedSameConfig(); 643 } 644 } 645 if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return true; 646 if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return false; 647 648 // 3.9 minimize enrollments where the reservation is not followed 649 if (currentNotFollowedReservations < bestNotFollowedReservations) 650 return true; 651 if (bestNotFollowedReservations < currentNotFollowedReservations) 652 return false; 653 654 // 3.95 avoid past sections 655 int bestPast = 0, currentPast = 0; 656 for (int idx = 0; idx < current.length; idx++) { 657 if (best[idx] != null && best[idx].getAssignments() != null) { 658 for (Section section : best[idx].getSections()) { 659 if (section.isPast()) 660 bestPast++; 661 } 662 } 663 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null) { 664 for (Section section : current[idx].getSections()) { 665 if (section.isPast()) 666 currentPast++; 667 } 668 } 669 } 670 if (currentPast < bestPast) 671 return true; 672 if (bestPast < currentPast) 673 return false; 674 675 // 4-5. solution quality 676 if (getModel().getStudentQuality() != null) { 677 double bestQuality = 0, currentQuality = 0; 678 for (StudentQuality.Type type: StudentQuality.Type.values()) { 679 for (int idx = 0; idx < current.length; idx++) { 680 if (best[idx] != null) { 681 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[idx]); 682 for (int x = 0; x < idx; x++) { 683 if (best[x] != null) 684 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[x], best[idx]); 685 } 686 } 687 if (current[idx] != null && idx < maxIdx) { 688 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[idx]); 689 for (int x = 0; x < idx; x++) { 690 if (current[x] != null) 691 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[x], current[idx]); 692 } 693 } 694 } 695 } 696 if (currentQuality < bestQuality) 697 return true; 698 if (bestQuality < currentQuality) 699 return false; 700 } else { 701 // 4. avoid time overlaps 702 if (getModel().getTimeOverlaps() != null) { 703 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 704 for (int idx = 0; idx < current.length; idx++) { 705 if (best[idx] != null) { 706 for (int x = 0; x < idx; x++) { 707 if (best[x] != null) 708 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]); 709 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 710 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), best[idx]); 711 } 712 } 713 if (current[idx] != null && idx < maxIdx) { 714 for (int x = 0; x < idx; x++) { 715 if (current[x] != null) 716 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]); 717 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 718 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts( 719 ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), 720 current[idx]); 721 } 722 } 723 } 724 for (int idx = 0; idx < current.length; idx++) { 725 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 726 bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]); 727 } 728 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 729 currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]); 730 } 731 } 732 if (currentTimeOverlaps < bestTimeOverlaps) 733 return true; 734 if (bestTimeOverlaps < currentTimeOverlaps) 735 return false; 736 } 737 738 // 5. avoid distance conflicts 739 if (getModel().getDistanceConflict() != null) { 740 int bestDistanceConf = 0, currentDistanceConf = 0; 741 for (int idx = 0; idx < current.length; idx++) { 742 if (best[idx] != null) { 743 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]); 744 for (int x = 0; x < idx; x++) { 745 if (best[x] != null) 746 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]); 747 } 748 } 749 if (current[idx] != null && idx < maxIdx) { 750 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]); 751 for (int x = 0; x < idx; x++) { 752 if (current[x] != null) 753 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x], 754 current[idx]); 755 } 756 } 757 } 758 if (currentDistanceConf < bestDistanceConf) 759 return true; 760 if (bestDistanceConf < currentDistanceConf) 761 return false; 762 } 763 } 764 765 // 6. avoid no-time and online sections (no-time first, online second) 766 int bestNoTime = 0, currentNoTime = 0; 767 int bestOnline = 0, currentOnline = 0; 768 for (int idx = 0; idx < current.length; idx++) { 769 if (best[idx] != null) { 770 for (Section section : best[idx].getSections()) { 771 if (!section.hasTime()) 772 bestNoTime++; 773 if (section.isOnline()) 774 bestOnline++; 775 } 776 } 777 if (current[idx] != null && idx < maxIdx) { 778 for (Section section : current[idx].getSections()) { 779 if (!section.hasTime()) 780 currentNoTime++; 781 if (section.isOnline()) 782 currentOnline++; 783 } 784 } 785 } 786 if (currentNoTime < bestNoTime) 787 return true; 788 if (bestNoTime < currentNoTime) 789 return false; 790 if (currentOnline < bestOnline) 791 return true; 792 if (bestOnline < currentOnline) 793 return false; 794 795 // 7. balance sections 796 double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0; 797 int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0; 798 for (int idx = 0; idx < current.length; idx++) { 799 if (best[idx] != null) { 800 for (Section section : best[idx].getSections()) { 801 Subpart subpart = section.getSubpart(); 802 // skip unlimited and single section subparts 803 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 804 continue; 805 // average size 806 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 807 // section is below average 808 if (section.getLimit() < averageSize) 809 bestUnavailableSize += (averageSize - section.getLimit()) / averageSize; 810 bestAltSectionsWithLimit++; 811 } 812 } 813 if (current[idx] != null && idx < maxIdx) { 814 for (Section section : current[idx].getSections()) { 815 Subpart subpart = section.getSubpart(); 816 // skip unlimited and single section subparts 817 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 818 continue; 819 // average size 820 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 821 // section is below average 822 if (section.getLimit() < averageSize) 823 currentUnavailableSize += (averageSize - section.getLimit()) / averageSize; 824 currentAltSectionsWithLimit++; 825 } 826 } 827 } 828 double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit 829 : 0.0); 830 double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize 831 / currentAltSectionsWithLimit : 0.0); 832 if (currentUnavailableSizeFraction < bestUnavailableSizeFraction) 833 return true; 834 if (bestUnavailableSizeFraction < currentUnavailableSizeFraction) 835 return false; 836 837 // 8. average penalty sections 838 double bestPenalty = 0.0, currentPenalty = 0.0; 839 for (int idx = 0; idx < current.length; idx++) { 840 if (best[idx] != null) { 841 for (Section section : best[idx].getSections()) 842 bestPenalty += section.getPenalty() / best[idx].getSections().size(); 843 if (idx >= maxIdx && best[idx].isCourseRequest()) 844 bestPenalty -= ((CourseRequest) best[idx].getRequest()).getMinPenalty(); 845 } 846 if (current[idx] != null && idx < maxIdx) { 847 for (Section section : current[idx].getSections()) 848 currentPenalty += section.getPenalty() / current[idx].getSections().size(); 849 } 850 } 851 if (currentPenalty < bestPenalty) 852 return true; 853 if (bestPenalty < currentPenalty) 854 return false; 855 856 return true; 857 } 858}