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 double bestPast = 0.0, currentPast = 0.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 += 1.0 / best[idx].getSections().size(); 245 } 246 } 247 if (current[idx] != null && current[idx].getAssignments() != null) { 248 for (Section section : current[idx].getSections()) { 249 if (section.isPast()) 250 currentPast += 1.0 / current[idx].getSections().size(); 251 } 252 } 253 } 254 if (Math.abs(currentPast - bestPast) > 0.0001) { 255 if (currentPast < bestPast) 256 return -1; 257 if (bestPast < currentPast) 258 return 1; 259 } 260 261 // 4-5. student quality 262 if (getModel().getStudentQuality() != null) { 263 double bestQuality = 0, currentQuality = 0; 264 for (StudentQuality.Type type: StudentQuality.Type.values()) { 265 for (int idx = 0; idx < current.length; idx++) { 266 if (best[idx] != null && best[idx].getAssignments() != null) { 267 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[idx]); 268 for (int x = 0; x < idx; x++) { 269 if (best[x] != null && best[x].getAssignments() != null) 270 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[x], best[idx]); 271 } 272 } 273 if (current[idx] != null && current[idx].getAssignments() != null) { 274 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[idx]); 275 for (int x = 0; x < idx; x++) { 276 if (current[x] != null && current[x].getAssignments() != null) 277 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[x], current[idx]); 278 } 279 } 280 } 281 } 282 if (currentQuality < bestQuality) 283 return -1; 284 if (bestQuality < currentQuality) 285 return 1; 286 } else { 287 // 4. avoid time overlaps 288 if (getModel().getTimeOverlaps() != null) { 289 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 290 for (int idx = 0; idx < current.length; idx++) { 291 if (best[idx] != null && best[idx].getAssignments() != null) { 292 for (int x = 0; x < idx; x++) { 293 if (best[x] != null && best[x].getAssignments() != null) 294 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]); 295 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 296 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), best[idx]); 297 } 298 } 299 if (current[idx] != null && current[idx].getAssignments() != null) { 300 for (int x = 0; x < idx; x++) { 301 if (current[x] != null && current[x].getAssignments() != null) 302 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]); 303 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 304 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts( 305 ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), 306 current[idx]); 307 } 308 } 309 } 310 for (int idx = 0; idx < current.length; idx++) { 311 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 312 bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]); 313 } 314 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 315 currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]); 316 } 317 } 318 if (currentTimeOverlaps < bestTimeOverlaps) 319 return -1; 320 if (bestTimeOverlaps < currentTimeOverlaps) 321 return 1; 322 } 323 324 // 5. avoid distance conflicts 325 if (getModel().getDistanceConflict() != null) { 326 int bestDistanceConf = 0, currentDistanceConf = 0; 327 for (int idx = 0; idx < current.length; idx++) { 328 if (best[idx] != null && best[idx].getAssignments() != null) { 329 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]); 330 for (int x = 0; x < idx; x++) { 331 if (best[x] != null && best[x].getAssignments() != null) 332 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]); 333 } 334 } 335 if (current[idx] != null && current[idx].getAssignments() != null) { 336 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]); 337 for (int x = 0; x < idx; x++) { 338 if (current[x] != null && current[x].getAssignments() != null) 339 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x], 340 current[idx]); 341 } 342 } 343 } 344 if (currentDistanceConf < bestDistanceConf) 345 return -1; 346 if (bestDistanceConf < currentDistanceConf) 347 return 1; 348 } 349 } 350 351 // 6. avoid no-time and online sections (no-time first, online second) 352 int bestNoTime = 0, currentNoTime = 0; 353 int bestOnline = 0, currentOnline = 0; 354 for (int idx = 0; idx < current.length; idx++) { 355 if (best[idx] != null && best[idx].getAssignments() != null) { 356 for (Section section : best[idx].getSections()) { 357 if (!section.hasTime()) 358 bestNoTime++; 359 if (section.isOnline()) 360 bestOnline++; 361 } 362 } 363 if (current[idx] != null && current[idx].getAssignments() != null) { 364 for (Section section : current[idx].getSections()) { 365 if (!section.hasTime()) 366 currentNoTime++; 367 if (section.isOnline()) 368 currentOnline++; 369 } 370 } 371 } 372 if (currentNoTime < bestNoTime) 373 return -1; 374 if (bestNoTime < currentNoTime) 375 return 1; 376 if (currentOnline < bestOnline) 377 return -1; 378 if (bestOnline < currentOnline) 379 return 1; 380 381 // 7. balance sections 382 double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0; 383 int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0; 384 for (int idx = 0; idx < current.length; idx++) { 385 if (best[idx] != null && best[idx].getAssignments() != null) { 386 for (Section section : best[idx].getSections()) { 387 Subpart subpart = section.getSubpart(); 388 // skip unlimited and single section subparts 389 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 390 continue; 391 // average size 392 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 393 // section is below average 394 if (section.getLimit() < averageSize) 395 bestUnavailableSize += (averageSize - section.getLimit()) / averageSize; 396 bestAltSectionsWithLimit++; 397 } 398 } 399 if (current[idx] != null && current[idx].getAssignments() != null) { 400 for (Section section : current[idx].getSections()) { 401 Subpart subpart = section.getSubpart(); 402 // skip unlimited and single section subparts 403 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 404 continue; 405 // average size 406 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 407 // section is below average 408 if (section.getLimit() < averageSize) 409 currentUnavailableSize += (averageSize - section.getLimit()) / averageSize; 410 currentAltSectionsWithLimit++; 411 } 412 } 413 } 414 double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit 415 : 0.0); 416 double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize 417 / currentAltSectionsWithLimit : 0.0); 418 if (currentUnavailableSizeFraction < bestUnavailableSizeFraction) 419 return -1; 420 if (bestUnavailableSizeFraction < currentUnavailableSizeFraction) 421 return 1; 422 423 // 8. average penalty sections 424 double bestPenalty = 0.0, currentPenalty = 0.0; 425 for (int idx = 0; idx < current.length; idx++) { 426 if (best[idx] != null && best[idx].getAssignments() != null) { 427 for (Section section : best[idx].getSections()) 428 bestPenalty += section.getPenalty() / best[idx].getSections().size(); 429 } 430 if (current[idx] != null && current[idx].getAssignments() != null) { 431 for (Section section : current[idx].getSections()) 432 currentPenalty += section.getPenalty() / current[idx].getSections().size(); 433 } 434 } 435 if (currentPenalty < bestPenalty) 436 return -1; 437 if (bestPenalty < currentPenalty) 438 return 1; 439 440 return 0; 441 } 442 443 @Override 444 public boolean canImprove(Assignment<Request, Enrollment> assignment, int maxIdx, Enrollment[] current, 445 Enrollment[] best) { 446 // 0. best number of assigned course requests (including alternativity & 447 // priority) 448 int currentAssignedCourseReq = 0, bestAssignedCourseReq = 0; 449 int currentAssignedRequests = 0, bestAssignedRequests = 0; 450 int currentAssignedPriority = 0, bestAssignedPriority = 0; 451 int currentAssignedAlternativity = 0, bestAssignedAlternativity = 0; 452 int currentNotFollowedReservations = 0, bestNotFollowedReservations = 0; 453 int alt = 0; 454 for (int idx = 0; idx < current.length; idx++) { 455 if (idx < maxIdx) { 456 if (current[idx] != null && current[idx].getAssignments() != null) { 457 currentAssignedRequests++; 458 if (current[idx].isCourseRequest()) 459 currentAssignedCourseReq++; 460 currentAssignedPriority += current[idx].getTruePriority() * current[idx].getTruePriority(); 461 currentAssignedAlternativity += (current[idx].getRequest().isAlternative() ? 1 : 0); 462 if (current[idx].getTruePriority() < current[idx].getPriority()) currentNotFollowedReservations ++; 463 } else if (!isFreeTime(idx) && !getRequest(idx).isAlternative()) { 464 alt++; 465 } 466 } else { 467 if (!getRequest(idx).isAlternative()) { 468 currentAssignedRequests++; 469 if (!isFreeTime(idx)) 470 currentAssignedCourseReq++; 471 } else if (alt > 0) { 472 currentAssignedRequests++; 473 currentAssignedCourseReq++; 474 alt--; 475 currentAssignedAlternativity++; 476 } 477 } 478 if (best[idx] != null && best[idx].getAssignments() != null) { 479 bestAssignedRequests++; 480 if (best[idx].isCourseRequest()) 481 bestAssignedCourseReq++; 482 bestAssignedPriority += best[idx].getTruePriority() * best[idx].getTruePriority(); 483 bestAssignedAlternativity += (best[idx].getRequest().isAlternative() ? 1 : 0); 484 if (best[idx].getTruePriority() < best[idx].getPriority()) bestNotFollowedReservations ++; 485 } 486 } 487 if (currentAssignedCourseReq > bestAssignedCourseReq) 488 return true; 489 if (bestAssignedCourseReq > currentAssignedCourseReq) 490 return false; 491 if (currentAssignedPriority < bestAssignedPriority) 492 return true; 493 if (bestAssignedPriority < currentAssignedPriority) 494 return false; 495 if (currentAssignedAlternativity < bestAssignedAlternativity) 496 return true; 497 if (bestAssignedAlternativity < currentAssignedAlternativity) 498 return false; 499 500 // 0.1. allowed, but not available sections 501 int notAvailable = 0; 502 for (int idx = 0; idx < current.length; idx++) { 503 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) { 504 for (Section section: best[idx].getSections()) { 505 if (section.getLimit() == 0) 506 notAvailable ++; 507 } 508 } 509 if (idx < maxIdx && current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) { 510 for (Section section: current[idx].getSections()) { 511 if (section.getLimit() == 0) 512 notAvailable --; 513 } 514 } 515 } 516 if (notAvailable > 0) { 517 return true; 518 } 519 520 // 0.5. avoid course time overlaps & unavailabilities 521 if (getModel().getStudentQuality() != null) { 522 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 523 for (int idx = 0; idx < current.length; idx++) { 524 if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) { 525 for (int x = 0; x < idx; x++) { 526 if (best[x] != null && best[x].getRequest() instanceof CourseRequest) 527 bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, best[x], best[idx]); 528 } 529 } 530 if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) { 531 for (int x = 0; x < idx; x++) { 532 if (current[x] != null && current[x].getRequest() instanceof CourseRequest) 533 currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, current[x], current[idx]); 534 } 535 } 536 } 537 for (int idx = 0; idx < current.length; idx++) { 538 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 539 bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, best[idx]); 540 } 541 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 542 currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, current[idx]); 543 } 544 } 545 if (currentTimeOverlaps < bestTimeOverlaps) 546 return true; 547 if (bestTimeOverlaps < currentTimeOverlaps) 548 return false; 549 } else if (getModel().getTimeOverlaps() != null) { 550 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 551 for (int idx = 0; idx < current.length; idx++) { 552 if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) { 553 for (int x = 0; x < idx; x++) { 554 if (best[x] != null && best[x].getRequest() instanceof CourseRequest) 555 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]); 556 } 557 } 558 if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) { 559 for (int x = 0; x < idx; x++) { 560 if (current[x] != null && current[x].getRequest() instanceof CourseRequest) 561 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]); 562 } 563 } 564 } 565 for (int idx = 0; idx < current.length; idx++) { 566 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 567 bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]); 568 } 569 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 570 currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]); 571 } 572 } 573 if (currentTimeOverlaps < bestTimeOverlaps) 574 return true; 575 if (bestTimeOverlaps < currentTimeOverlaps) 576 return false; 577 } 578 579 // 1. maximize number of penalties 580 double bestPenalties = 0, currentPenalties = 0; 581 for (int idx = 0; idx < current.length; idx++) { 582 if (best[idx] != null) { 583 for (Section section : best[idx].getSections()) 584 bestPenalties += getModel().getOverExpected(assignment, best, idx, section, best[idx].getRequest()); 585 } 586 if (current[idx] != null && idx < maxIdx) { 587 for (Section section : current[idx].getSections()) 588 currentPenalties += getModel().getOverExpected(assignment, current, idx, section, current[idx].getRequest()); 589 } 590 } 591 if (currentPenalties < bestPenalties) 592 return true; 593 if (bestPenalties < currentPenalties) 594 return false; 595 596 // 2. best number of assigned requests (including free time requests) 597 if (currentAssignedRequests > bestAssignedRequests) 598 return true; 599 if (bestAssignedRequests > currentAssignedRequests) 600 return false; 601 602 // 3. maximize selection 603 int bestSelected = 0, currentSelected = 0; 604 for (int idx = 0; idx < current.length; idx++) { 605 if (best[idx] != null && best[idx].isCourseRequest()) { 606 Set<Section> preferred = getPreferredSections(best[idx].getRequest()); 607 if (preferred != null && !preferred.isEmpty()) { 608 for (Section section : best[idx].getSections()) 609 if (preferred.contains(section)) { 610 if (idx < maxIdx) 611 bestSelected++; 612 } else if (idx >= maxIdx) 613 bestSelected--; 614 } 615 } 616 if (current[idx] != null && idx < maxIdx && current[idx].isCourseRequest()) { 617 Set<Section> preferred = getPreferredSections(current[idx].getRequest()); 618 if (preferred != null && !preferred.isEmpty()) { 619 for (Section section : current[idx].getSections()) 620 if (preferred.contains(section)) 621 currentSelected++; 622 } 623 } 624 } 625 if (currentSelected > bestSelected) 626 return true; 627 if (bestSelected > currentSelected) 628 return false; 629 630 // 3.5 maximize preferences 631 double bestSelectedConfigs = 0, currentSelectedConfigs = 0; 632 double bestSelectedSections = 0, currentSelectedSections = 0; 633 for (int idx = 0; idx < current.length; idx++) { 634 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 635 bestSelectedSections += best[idx].percentSelectedSameSection(); 636 bestSelectedConfigs += best[idx].percentSelectedSameConfig(); 637 if (idx >= maxIdx) { 638 bestSelectedSections -= 1.0; 639 bestSelectedConfigs -= 1.0; 640 } 641 } 642 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 643 currentSelectedSections += current[idx].percentSelectedSameSection(); 644 currentSelectedConfigs += current[idx].percentSelectedSameConfig(); 645 } 646 } 647 if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return true; 648 if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return false; 649 650 // 3.9 minimize enrollments where the reservation is not followed 651 if (currentNotFollowedReservations < bestNotFollowedReservations) 652 return true; 653 if (bestNotFollowedReservations < currentNotFollowedReservations) 654 return false; 655 656 // 3.95 avoid past sections 657 double bestPast = 0.0, currentPast = 0.0; 658 for (int idx = 0; idx < current.length; idx++) { 659 if (best[idx] != null && best[idx].getAssignments() != null) { 660 for (Section section : best[idx].getSections()) { 661 if (section.isPast()) 662 bestPast += 1.0 / best[idx].getSections().size(); 663 } 664 } 665 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null) { 666 for (Section section : current[idx].getSections()) { 667 if (section.isPast()) 668 currentPast += 1.0 / current[idx].getSections().size(); 669 } 670 } 671 } 672 if (Math.abs(currentPast - bestPast) > 0.0001) { 673 if (currentPast < bestPast) 674 return true; 675 if (bestPast < currentPast) 676 return false; 677 } 678 679 // 4-5. solution quality 680 if (getModel().getStudentQuality() != null) { 681 double bestQuality = 0, currentQuality = 0; 682 for (StudentQuality.Type type: StudentQuality.Type.values()) { 683 for (int idx = 0; idx < current.length; idx++) { 684 if (best[idx] != null) { 685 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[idx]); 686 for (int x = 0; x < idx; x++) { 687 if (best[x] != null) 688 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[x], best[idx]); 689 } 690 } 691 if (current[idx] != null && idx < maxIdx) { 692 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[idx]); 693 for (int x = 0; x < idx; x++) { 694 if (current[x] != null) 695 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[x], current[idx]); 696 } 697 } 698 } 699 } 700 if (currentQuality < bestQuality) 701 return true; 702 if (bestQuality < currentQuality) 703 return false; 704 } else { 705 // 4. avoid time overlaps 706 if (getModel().getTimeOverlaps() != null) { 707 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 708 for (int idx = 0; idx < current.length; idx++) { 709 if (best[idx] != null) { 710 for (int x = 0; x < idx; x++) { 711 if (best[x] != null) 712 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]); 713 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 714 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), best[idx]); 715 } 716 } 717 if (current[idx] != null && idx < maxIdx) { 718 for (int x = 0; x < idx; x++) { 719 if (current[x] != null) 720 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]); 721 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 722 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts( 723 ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), 724 current[idx]); 725 } 726 } 727 } 728 for (int idx = 0; idx < current.length; idx++) { 729 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 730 bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]); 731 } 732 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 733 currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]); 734 } 735 } 736 if (currentTimeOverlaps < bestTimeOverlaps) 737 return true; 738 if (bestTimeOverlaps < currentTimeOverlaps) 739 return false; 740 } 741 742 // 5. avoid distance conflicts 743 if (getModel().getDistanceConflict() != null) { 744 int bestDistanceConf = 0, currentDistanceConf = 0; 745 for (int idx = 0; idx < current.length; idx++) { 746 if (best[idx] != null) { 747 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]); 748 for (int x = 0; x < idx; x++) { 749 if (best[x] != null) 750 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]); 751 } 752 } 753 if (current[idx] != null && idx < maxIdx) { 754 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]); 755 for (int x = 0; x < idx; x++) { 756 if (current[x] != null) 757 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x], 758 current[idx]); 759 } 760 } 761 } 762 if (currentDistanceConf < bestDistanceConf) 763 return true; 764 if (bestDistanceConf < currentDistanceConf) 765 return false; 766 } 767 } 768 769 // 6. avoid no-time and online sections (no-time first, online second) 770 int bestNoTime = 0, currentNoTime = 0; 771 int bestOnline = 0, currentOnline = 0; 772 for (int idx = 0; idx < current.length; idx++) { 773 if (best[idx] != null) { 774 for (Section section : best[idx].getSections()) { 775 if (!section.hasTime()) 776 bestNoTime++; 777 if (section.isOnline()) 778 bestOnline++; 779 } 780 } 781 if (current[idx] != null && idx < maxIdx) { 782 for (Section section : current[idx].getSections()) { 783 if (!section.hasTime()) 784 currentNoTime++; 785 if (section.isOnline()) 786 currentOnline++; 787 } 788 } 789 } 790 if (currentNoTime < bestNoTime) 791 return true; 792 if (bestNoTime < currentNoTime) 793 return false; 794 if (currentOnline < bestOnline) 795 return true; 796 if (bestOnline < currentOnline) 797 return false; 798 799 // 7. balance sections 800 double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0; 801 int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0; 802 for (int idx = 0; idx < current.length; idx++) { 803 if (best[idx] != null) { 804 for (Section section : best[idx].getSections()) { 805 Subpart subpart = section.getSubpart(); 806 // skip unlimited and single section subparts 807 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 808 continue; 809 // average size 810 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 811 // section is below average 812 if (section.getLimit() < averageSize) 813 bestUnavailableSize += (averageSize - section.getLimit()) / averageSize; 814 bestAltSectionsWithLimit++; 815 } 816 } 817 if (current[idx] != null && idx < maxIdx) { 818 for (Section section : current[idx].getSections()) { 819 Subpart subpart = section.getSubpart(); 820 // skip unlimited and single section subparts 821 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 822 continue; 823 // average size 824 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 825 // section is below average 826 if (section.getLimit() < averageSize) 827 currentUnavailableSize += (averageSize - section.getLimit()) / averageSize; 828 currentAltSectionsWithLimit++; 829 } 830 } 831 } 832 double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit 833 : 0.0); 834 double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize 835 / currentAltSectionsWithLimit : 0.0); 836 if (currentUnavailableSizeFraction < bestUnavailableSizeFraction) 837 return true; 838 if (bestUnavailableSizeFraction < currentUnavailableSizeFraction) 839 return false; 840 841 // 8. average penalty sections 842 double bestPenalty = 0.0, currentPenalty = 0.0; 843 for (int idx = 0; idx < current.length; idx++) { 844 if (best[idx] != null) { 845 for (Section section : best[idx].getSections()) 846 bestPenalty += section.getPenalty() / best[idx].getSections().size(); 847 if (idx >= maxIdx && best[idx].isCourseRequest()) 848 bestPenalty -= ((CourseRequest) best[idx].getRequest()).getMinPenalty(); 849 } 850 if (current[idx] != null && idx < maxIdx) { 851 for (Section section : current[idx].getSections()) 852 currentPenalty += section.getPenalty() / current[idx].getSections().size(); 853 } 854 } 855 if (currentPenalty < bestPenalty) 856 return true; 857 if (bestPenalty < currentPenalty) 858 return false; 859 860 return true; 861 } 862}