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 for (int idx = 0; idx < current.length; idx++) { 060 if (current[idx] != null && current[idx].getAssignments() != null) { 061 currentAssignedRequests++; 062 if (current[idx].isCourseRequest()) 063 currentAssignedCourseReq++; 064 currentAssignedPriority += current[idx].getPriority() * current[idx].getPriority(); 065 currentAssignedAlternativity += (current[idx].getRequest().isAlternative() ? 1 : 0); 066 } 067 if (best[idx] != null && best[idx].getAssignments() != null) { 068 bestAssignedRequests++; 069 if (best[idx].isCourseRequest()) 070 bestAssignedCourseReq++; 071 bestAssignedPriority += best[idx].getPriority() * best[idx].getPriority(); 072 bestAssignedAlternativity += (best[idx].getRequest().isAlternative() ? 1 : 0); 073 } 074 } 075 if (currentAssignedCourseReq > bestAssignedCourseReq) 076 return -1; 077 if (bestAssignedCourseReq > currentAssignedCourseReq) 078 return 1; 079 if (currentAssignedPriority < bestAssignedPriority) 080 return -1; 081 if (bestAssignedPriority < currentAssignedPriority) 082 return 1; 083 if (currentAssignedAlternativity < bestAssignedAlternativity) 084 return -1; 085 if (bestAssignedAlternativity < currentAssignedAlternativity) 086 return 1; 087 088 // 0.1. allowed, but not available sections 089 int bestNotAvailable = 0, currentNotAvailable = 0; 090 for (int idx = 0; idx < current.length; idx++) { 091 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) { 092 for (Section section: best[idx].getSections()) { 093 if (section.getLimit() == 0) 094 bestNotAvailable ++; 095 } 096 } 097 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) { 098 for (Section section: current[idx].getSections()) { 099 if (section.getLimit() == 0) 100 currentNotAvailable ++; 101 } 102 } 103 } 104 if (bestNotAvailable > currentNotAvailable) return -1; 105 if (bestNotAvailable < currentNotAvailable) return 1; 106 107 // 0.5. avoid course overlaps & unavailabilities 108 if (getModel().getStudentQuality() != null) { 109 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 110 for (int idx = 0; idx < current.length; idx++) { 111 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest) { 112 for (int x = 0; x < idx; x++) { 113 if (best[x] != null && best[x].getAssignments() != null && best[x].getRequest() instanceof CourseRequest) 114 bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, best[x], best[idx]); 115 } 116 } 117 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest) { 118 for (int x = 0; x < idx; x++) { 119 if (current[x] != null && current[x].getAssignments() != null && current[x].getRequest() instanceof CourseRequest) 120 currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, current[x], current[idx]); 121 } 122 } 123 } 124 for (int idx = 0; idx < current.length; idx++) { 125 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 126 bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, best[idx]); 127 } 128 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 129 currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, current[idx]); 130 } 131 } 132 if (currentTimeOverlaps < bestTimeOverlaps) 133 return -1; 134 if (bestTimeOverlaps < currentTimeOverlaps) 135 return 1; 136 } else if (getModel().getTimeOverlaps() != null) { 137 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 138 for (int idx = 0; idx < current.length; idx++) { 139 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest) { 140 for (int x = 0; x < idx; x++) { 141 if (best[x] != null && best[x].getAssignments() != null 142 && best[x].getRequest() instanceof CourseRequest) 143 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]); 144 } 145 } 146 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest) { 147 for (int x = 0; x < idx; x++) { 148 if (current[x] != null && current[x].getAssignments() != null 149 && current[x].getRequest() instanceof CourseRequest) 150 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]); 151 } 152 } 153 } 154 for (int idx = 0; idx < current.length; idx++) { 155 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 156 bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]); 157 } 158 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 159 currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]); 160 } 161 } 162 if (currentTimeOverlaps < bestTimeOverlaps) 163 return -1; 164 if (bestTimeOverlaps < currentTimeOverlaps) 165 return 1; 166 } 167 168 // 1. minimize number of penalties 169 double bestPenalties = 0, currentPenalties = 0; 170 for (int idx = 0; idx < current.length; idx++) { 171 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 172 for (Section section : best[idx].getSections()) 173 bestPenalties += getModel().getOverExpected(assignment, best, idx, section, best[idx].getRequest()); 174 } 175 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 176 for (Section section : current[idx].getSections()) 177 currentPenalties += getModel().getOverExpected(assignment, current, idx, section, current[idx].getRequest()); 178 } 179 } 180 if (currentPenalties < bestPenalties) 181 return -1; 182 if (bestPenalties < currentPenalties) 183 return 1; 184 185 // 2. best number of assigned requests (including free time requests) 186 if (currentAssignedRequests > bestAssignedRequests) 187 return -1; 188 if (bestAssignedRequests > currentAssignedRequests) 189 return 1; 190 191 // 3. maximize selection 192 int bestSelected = 0, currentSelected = 0; 193 for (int idx = 0; idx < current.length; idx++) { 194 Set<Section> preferred = getPreferredSections(getRequest(idx)); 195 if (preferred != null && !preferred.isEmpty()) { 196 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 197 for (Section section : best[idx].getSections()) 198 if (preferred.contains(section)) 199 bestSelected++; 200 } 201 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 202 for (Section section : current[idx].getSections()) 203 if (preferred.contains(section)) 204 currentSelected++; 205 } 206 } 207 } 208 if (currentSelected > bestSelected) 209 return -1; 210 if (bestSelected > currentSelected) 211 return 1; 212 213 // 3.5 maximize preferences 214 double bestSelectedConfigs = 0, currentSelectedConfigs = 0; 215 double bestSelectedSections = 0, currentSelectedSections = 0; 216 for (int idx = 0; idx < current.length; idx++) { 217 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 218 bestSelectedSections += best[idx].percentSelectedSameSection(); 219 bestSelectedConfigs += best[idx].percentSelectedSameConfig(); 220 } 221 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 222 currentSelectedSections += current[idx].percentSelectedSameSection(); 223 currentSelectedConfigs += current[idx].percentSelectedSameConfig(); 224 } 225 } 226 if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return -1; 227 if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return 1; 228 229 // 4-5. student quality 230 if (getModel().getStudentQuality() != null) { 231 double bestQuality = 0, currentQuality = 0; 232 for (StudentQuality.Type type: StudentQuality.Type.values()) { 233 for (int idx = 0; idx < current.length; idx++) { 234 if (best[idx] != null && best[idx].getAssignments() != null) { 235 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[idx]); 236 for (int x = 0; x < idx; x++) { 237 if (best[x] != null && best[x].getAssignments() != null) 238 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[x], best[idx]); 239 } 240 } 241 if (current[idx] != null && current[idx].getAssignments() != null) { 242 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[idx]); 243 for (int x = 0; x < idx; x++) { 244 if (current[x] != null && current[x].getAssignments() != null) 245 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[x], current[idx]); 246 } 247 } 248 } 249 } 250 if (currentQuality < bestQuality) 251 return -1; 252 if (bestQuality < currentQuality) 253 return 1; 254 } else { 255 // 4. avoid time overlaps 256 if (getModel().getTimeOverlaps() != null) { 257 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 258 for (int idx = 0; idx < current.length; idx++) { 259 if (best[idx] != null && best[idx].getAssignments() != null) { 260 for (int x = 0; x < idx; x++) { 261 if (best[x] != null && best[x].getAssignments() != null) 262 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]); 263 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 264 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), best[idx]); 265 } 266 } 267 if (current[idx] != null && current[idx].getAssignments() != null) { 268 for (int x = 0; x < idx; x++) { 269 if (current[x] != null && current[x].getAssignments() != null) 270 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]); 271 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 272 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts( 273 ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), 274 current[idx]); 275 } 276 } 277 } 278 for (int idx = 0; idx < current.length; idx++) { 279 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 280 bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]); 281 } 282 if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 283 currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]); 284 } 285 } 286 if (currentTimeOverlaps < bestTimeOverlaps) 287 return -1; 288 if (bestTimeOverlaps < currentTimeOverlaps) 289 return 1; 290 } 291 292 // 5. avoid distance conflicts 293 if (getModel().getDistanceConflict() != null) { 294 int bestDistanceConf = 0, currentDistanceConf = 0; 295 for (int idx = 0; idx < current.length; idx++) { 296 if (best[idx] != null && best[idx].getAssignments() != null) { 297 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]); 298 for (int x = 0; x < idx; x++) { 299 if (best[x] != null && best[x].getAssignments() != null) 300 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]); 301 } 302 } 303 if (current[idx] != null && current[idx].getAssignments() != null) { 304 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]); 305 for (int x = 0; x < idx; x++) { 306 if (current[x] != null && current[x].getAssignments() != null) 307 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x], 308 current[idx]); 309 } 310 } 311 } 312 if (currentDistanceConf < bestDistanceConf) 313 return -1; 314 if (bestDistanceConf < currentDistanceConf) 315 return 1; 316 } 317 } 318 319 // 6. avoid no-time sections 320 int bestNoTime = 0, currentNoTime = 0; 321 for (int idx = 0; idx < current.length; idx++) { 322 if (best[idx] != null && best[idx].getAssignments() != null) { 323 for (Section section : best[idx].getSections()) 324 if (section.getTime() == null) 325 bestNoTime++; 326 } 327 if (current[idx] != null && current[idx].getAssignments() != null) { 328 for (Section section : current[idx].getSections()) 329 if (section.getTime() == null) 330 currentNoTime++; 331 } 332 } 333 if (currentNoTime < bestNoTime) 334 return -1; 335 if (bestNoTime < currentNoTime) 336 return 1; 337 338 // 7. balance sections 339 double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0; 340 int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0; 341 for (int idx = 0; idx < current.length; idx++) { 342 if (best[idx] != null && best[idx].getAssignments() != null) { 343 for (Section section : best[idx].getSections()) { 344 Subpart subpart = section.getSubpart(); 345 // skip unlimited and single section subparts 346 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 347 continue; 348 // average size 349 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 350 // section is below average 351 if (section.getLimit() < averageSize) 352 bestUnavailableSize += (averageSize - section.getLimit()) / averageSize; 353 bestAltSectionsWithLimit++; 354 } 355 } 356 if (current[idx] != null && current[idx].getAssignments() != null) { 357 for (Section section : current[idx].getSections()) { 358 Subpart subpart = section.getSubpart(); 359 // skip unlimited and single section subparts 360 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 361 continue; 362 // average size 363 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 364 // section is below average 365 if (section.getLimit() < averageSize) 366 currentUnavailableSize += (averageSize - section.getLimit()) / averageSize; 367 currentAltSectionsWithLimit++; 368 } 369 } 370 } 371 double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit 372 : 0.0); 373 double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize 374 / currentAltSectionsWithLimit : 0.0); 375 if (currentUnavailableSizeFraction < bestUnavailableSizeFraction) 376 return -1; 377 if (bestUnavailableSizeFraction < currentUnavailableSizeFraction) 378 return 1; 379 380 // 8. average penalty sections 381 double bestPenalty = 0.0, currentPenalty = 0.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 bestPenalty += section.getPenalty() / best[idx].getSections().size(); 386 } 387 if (current[idx] != null && current[idx].getAssignments() != null) { 388 for (Section section : current[idx].getSections()) 389 currentPenalty += section.getPenalty() / current[idx].getSections().size(); 390 } 391 } 392 if (currentPenalty < bestPenalty) 393 return -1; 394 if (bestPenalty < currentPenalty) 395 return 1; 396 397 return 0; 398 } 399 400 @Override 401 public boolean canImprove(Assignment<Request, Enrollment> assignment, int maxIdx, Enrollment[] current, 402 Enrollment[] best) { 403 // 0. best number of assigned course requests (including alternativity & 404 // priority) 405 int currentAssignedCourseReq = 0, bestAssignedCourseReq = 0; 406 int currentAssignedRequests = 0, bestAssignedRequests = 0; 407 int currentAssignedPriority = 0, bestAssignedPriority = 0; 408 int currentAssignedAlternativity = 0, bestAssignedAlternativity = 0; 409 int alt = 0; 410 for (int idx = 0; idx < current.length; idx++) { 411 if (idx < maxIdx) { 412 if (current[idx] != null && current[idx].getAssignments() != null) { 413 currentAssignedRequests++; 414 if (current[idx].isCourseRequest()) 415 currentAssignedCourseReq++; 416 currentAssignedPriority += current[idx].getPriority() * current[idx].getPriority(); 417 currentAssignedAlternativity += (current[idx].getRequest().isAlternative() ? 1 : 0); 418 } else if (!isFreeTime(idx) && !getRequest(idx).isAlternative()) { 419 alt++; 420 } 421 } else { 422 if (!getRequest(idx).isAlternative()) { 423 currentAssignedRequests++; 424 if (!isFreeTime(idx)) 425 currentAssignedCourseReq++; 426 } else if (alt > 0) { 427 currentAssignedRequests++; 428 currentAssignedCourseReq++; 429 alt--; 430 currentAssignedAlternativity++; 431 } 432 } 433 if (best[idx] != null && best[idx].getAssignments() != null) { 434 bestAssignedRequests++; 435 if (best[idx].isCourseRequest()) 436 bestAssignedCourseReq++; 437 bestAssignedPriority += best[idx].getPriority() * best[idx].getPriority(); 438 bestAssignedAlternativity += (best[idx].getRequest().isAlternative() ? 1 : 0); 439 } 440 } 441 if (currentAssignedCourseReq > bestAssignedCourseReq) 442 return true; 443 if (bestAssignedCourseReq > currentAssignedCourseReq) 444 return false; 445 if (currentAssignedPriority < bestAssignedPriority) 446 return true; 447 if (bestAssignedPriority < currentAssignedPriority) 448 return false; 449 if (currentAssignedAlternativity < bestAssignedAlternativity) 450 return true; 451 if (bestAssignedAlternativity < currentAssignedAlternativity) 452 return false; 453 454 // 0.1. allowed, but not available sections 455 int notAvailable = 0; 456 for (int idx = 0; idx < current.length; idx++) { 457 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) { 458 for (Section section: best[idx].getSections()) { 459 if (section.getLimit() == 0) 460 notAvailable ++; 461 } 462 } 463 if (idx < maxIdx && current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) { 464 for (Section section: current[idx].getSections()) { 465 if (section.getLimit() == 0) 466 notAvailable --; 467 } 468 } 469 } 470 if (notAvailable > 0) { 471 return true; 472 } 473 474 // 0.5. avoid course time overlaps & unavailabilities 475 if (getModel().getStudentQuality() != null) { 476 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 477 for (int idx = 0; idx < current.length; idx++) { 478 if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) { 479 for (int x = 0; x < idx; x++) { 480 if (best[x] != null && best[x].getRequest() instanceof CourseRequest) 481 bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, best[x], best[idx]); 482 } 483 } 484 if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) { 485 for (int x = 0; x < idx; x++) { 486 if (current[x] != null && current[x].getRequest() instanceof CourseRequest) 487 currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, current[x], current[idx]); 488 } 489 } 490 } 491 for (int idx = 0; idx < current.length; idx++) { 492 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 493 bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, best[idx]); 494 } 495 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 496 currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, current[idx]); 497 } 498 } 499 if (currentTimeOverlaps < bestTimeOverlaps) 500 return true; 501 if (bestTimeOverlaps < currentTimeOverlaps) 502 return false; 503 } else if (getModel().getTimeOverlaps() != null) { 504 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 505 for (int idx = 0; idx < current.length; idx++) { 506 if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) { 507 for (int x = 0; x < idx; x++) { 508 if (best[x] != null && best[x].getRequest() instanceof CourseRequest) 509 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]); 510 } 511 } 512 if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) { 513 for (int x = 0; x < idx; x++) { 514 if (current[x] != null && current[x].getRequest() instanceof CourseRequest) 515 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]); 516 } 517 } 518 } 519 for (int idx = 0; idx < current.length; idx++) { 520 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 521 bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]); 522 } 523 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 524 currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]); 525 } 526 } 527 if (currentTimeOverlaps < bestTimeOverlaps) 528 return true; 529 if (bestTimeOverlaps < currentTimeOverlaps) 530 return false; 531 } 532 533 // 1. maximize number of penalties 534 double bestPenalties = 0, currentPenalties = 0; 535 for (int idx = 0; idx < current.length; idx++) { 536 if (best[idx] != null) { 537 for (Section section : best[idx].getSections()) 538 bestPenalties += getModel().getOverExpected(assignment, best, idx, section, best[idx].getRequest()); 539 } 540 if (current[idx] != null && idx < maxIdx) { 541 for (Section section : current[idx].getSections()) 542 currentPenalties += getModel().getOverExpected(assignment, current, idx, section, current[idx].getRequest()); 543 } 544 } 545 if (currentPenalties < bestPenalties) 546 return true; 547 if (bestPenalties < currentPenalties) 548 return false; 549 550 // 2. best number of assigned requests (including free time requests) 551 if (currentAssignedRequests > bestAssignedRequests) 552 return true; 553 if (bestAssignedRequests > currentAssignedRequests) 554 return false; 555 556 // 3. maximize selection 557 int bestSelected = 0, currentSelected = 0; 558 for (int idx = 0; idx < current.length; idx++) { 559 if (best[idx] != null && best[idx].isCourseRequest()) { 560 Set<Section> preferred = getPreferredSections(best[idx].getRequest()); 561 if (preferred != null && !preferred.isEmpty()) { 562 for (Section section : best[idx].getSections()) 563 if (preferred.contains(section)) { 564 if (idx < maxIdx) 565 bestSelected++; 566 } else if (idx >= maxIdx) 567 bestSelected--; 568 } 569 } 570 if (current[idx] != null && idx < maxIdx && current[idx].isCourseRequest()) { 571 Set<Section> preferred = getPreferredSections(current[idx].getRequest()); 572 if (preferred != null && !preferred.isEmpty()) { 573 for (Section section : current[idx].getSections()) 574 if (preferred.contains(section)) 575 currentSelected++; 576 } 577 } 578 } 579 if (currentSelected > bestSelected) 580 return true; 581 if (bestSelected > currentSelected) 582 return false; 583 584 // 3.5 maximize preferences 585 double bestSelectedConfigs = 0, currentSelectedConfigs = 0; 586 double bestSelectedSections = 0, currentSelectedSections = 0; 587 for (int idx = 0; idx < current.length; idx++) { 588 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 589 bestSelectedSections += best[idx].percentSelectedSameSection(); 590 bestSelectedConfigs += best[idx].percentSelectedSameConfig(); 591 if (idx >= maxIdx) { 592 bestSelectedSections -= 1.0; 593 bestSelectedConfigs -= 1.0; 594 } 595 } 596 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 597 currentSelectedSections += current[idx].percentSelectedSameSection(); 598 currentSelectedConfigs += current[idx].percentSelectedSameConfig(); 599 } 600 } 601 if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return true; 602 if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return false; 603 604 // 4-5. solution quality 605 if (getModel().getStudentQuality() != null) { 606 double bestQuality = 0, currentQuality = 0; 607 for (StudentQuality.Type type: StudentQuality.Type.values()) { 608 for (int idx = 0; idx < current.length; idx++) { 609 if (best[idx] != null) { 610 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[idx]); 611 for (int x = 0; x < idx; x++) { 612 if (best[x] != null) 613 bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[x], best[idx]); 614 } 615 } 616 if (current[idx] != null && idx < maxIdx) { 617 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[idx]); 618 for (int x = 0; x < idx; x++) { 619 if (current[x] != null) 620 currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[x], current[idx]); 621 } 622 } 623 } 624 } 625 if (currentQuality < bestQuality) 626 return true; 627 if (bestQuality < currentQuality) 628 return false; 629 } else { 630 // 4. avoid time overlaps 631 if (getModel().getTimeOverlaps() != null) { 632 int bestTimeOverlaps = 0, currentTimeOverlaps = 0; 633 for (int idx = 0; idx < current.length; idx++) { 634 if (best[idx] != null) { 635 for (int x = 0; x < idx; x++) { 636 if (best[x] != null) 637 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]); 638 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 639 bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), best[idx]); 640 } 641 } 642 if (current[idx] != null && idx < maxIdx) { 643 for (int x = 0; x < idx; x++) { 644 if (current[x] != null) 645 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]); 646 else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest) 647 currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts( 648 ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), 649 current[idx]); 650 } 651 } 652 } 653 for (int idx = 0; idx < current.length; idx++) { 654 if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) { 655 bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]); 656 } 657 if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) { 658 currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]); 659 } 660 } 661 if (currentTimeOverlaps < bestTimeOverlaps) 662 return true; 663 if (bestTimeOverlaps < currentTimeOverlaps) 664 return false; 665 } 666 667 // 5. avoid distance conflicts 668 if (getModel().getDistanceConflict() != null) { 669 int bestDistanceConf = 0, currentDistanceConf = 0; 670 for (int idx = 0; idx < current.length; idx++) { 671 if (best[idx] != null) { 672 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]); 673 for (int x = 0; x < idx; x++) { 674 if (best[x] != null) 675 bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]); 676 } 677 } 678 if (current[idx] != null && idx < maxIdx) { 679 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]); 680 for (int x = 0; x < idx; x++) { 681 if (current[x] != null) 682 currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x], 683 current[idx]); 684 } 685 } 686 } 687 if (currentDistanceConf < bestDistanceConf) 688 return true; 689 if (bestDistanceConf < currentDistanceConf) 690 return false; 691 } 692 } 693 694 // 6. avoid no-time sections 695 int bestNoTime = 0, currentNoTime = 0; 696 for (int idx = 0; idx < current.length; idx++) { 697 if (best[idx] != null) { 698 for (Section section : best[idx].getSections()) 699 if (section.getTime() == null) 700 bestNoTime++; 701 } 702 if (current[idx] != null && idx < maxIdx) { 703 for (Section section : current[idx].getSections()) 704 if (section.getTime() == null) 705 currentNoTime++; 706 } 707 } 708 if (currentNoTime < bestNoTime) 709 return true; 710 if (bestNoTime < currentNoTime) 711 return false; 712 713 // 7. balance sections 714 double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0; 715 int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0; 716 for (int idx = 0; idx < current.length; idx++) { 717 if (best[idx] != null) { 718 for (Section section : best[idx].getSections()) { 719 Subpart subpart = section.getSubpart(); 720 // skip unlimited and single section subparts 721 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 722 continue; 723 // average size 724 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 725 // section is below average 726 if (section.getLimit() < averageSize) 727 bestUnavailableSize += (averageSize - section.getLimit()) / averageSize; 728 bestAltSectionsWithLimit++; 729 } 730 } 731 if (current[idx] != null && idx < maxIdx) { 732 for (Section section : current[idx].getSections()) { 733 Subpart subpart = section.getSubpart(); 734 // skip unlimited and single section subparts 735 if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0) 736 continue; 737 // average size 738 double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size(); 739 // section is below average 740 if (section.getLimit() < averageSize) 741 currentUnavailableSize += (averageSize - section.getLimit()) / averageSize; 742 currentAltSectionsWithLimit++; 743 } 744 } 745 } 746 double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit 747 : 0.0); 748 double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize 749 / currentAltSectionsWithLimit : 0.0); 750 if (currentUnavailableSizeFraction < bestUnavailableSizeFraction) 751 return true; 752 if (bestUnavailableSizeFraction < currentUnavailableSizeFraction) 753 return false; 754 755 // 8. average penalty sections 756 double bestPenalty = 0.0, currentPenalty = 0.0; 757 for (int idx = 0; idx < current.length; idx++) { 758 if (best[idx] != null) { 759 for (Section section : best[idx].getSections()) 760 bestPenalty += section.getPenalty() / best[idx].getSections().size(); 761 if (idx >= maxIdx && best[idx].isCourseRequest()) 762 bestPenalty -= ((CourseRequest) best[idx].getRequest()).getMinPenalty(); 763 } 764 if (current[idx] != null && idx < maxIdx) { 765 for (Section section : current[idx].getSections()) 766 currentPenalty += section.getPenalty() / current[idx].getSections().size(); 767 } 768 } 769 if (currentPenalty < bestPenalty) 770 return true; 771 if (bestPenalty < currentPenalty) 772 return false; 773 774 return true; 775 } 776}