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}