    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}