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