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}