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