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.model.CourseRequest;
008import org.cpsolver.studentsct.model.Enrollment;
009import org.cpsolver.studentsct.model.FreeTimeRequest;
010import org.cpsolver.studentsct.model.Request;
011import org.cpsolver.studentsct.model.Section;
012import org.cpsolver.studentsct.model.Student;
013import org.cpsolver.studentsct.model.Subpart;
014import org.cpsolver.studentsct.online.OnlineSectioningModel;
015
016/**
017* Equal weighting multi-criteria selection criterion. Much like the {@link OnlineSectioningCriterion}, but
018* course request priorities are ignored. Most complete solution is preferred instead.
019* 
020* @version StudentSct 1.3 (Student Sectioning)<br>
021*          Copyright (C) 2014 Tomas Muller<br>
022*          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
023*          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
024* <br>
025*          This library is free software; you can redistribute it and/or modify
026*          it under the terms of the GNU Lesser General Public License as
027*          published by the Free Software Foundation; either version 3 of the
028*          License, or (at your option) any later version. <br>
029* <br>
030*          This library is distributed in the hope that it will be useful, but
031*          WITHOUT ANY WARRANTY; without even the implied warranty of
032*          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
033*          Lesser General Public License for more details. <br>
034* <br>
035*          You should have received a copy of the GNU Lesser General Public
036*          License along with this library; if not see <a
037*          href='http://www.gnu.org/licenses'>http://www.gnu.org/licenses</a>.
038* 
039*/
040public class EqualWeightCriterion extends OnlineSectioningCriterion {
041
042    public EqualWeightCriterion(Student student, OnlineSectioningModel model,
043            Assignment<Request, Enrollment> assignment, Hashtable<CourseRequest, Set<Section>> preferredSections) {
044        super(student, model, assignment, preferredSections);
045    }
046
047    @Override
048    public int compare(Assignment<Request, Enrollment> assignment, Enrollment[] current, Enrollment[] best) {
049        if (best == null)
050            return -1;
051
052        // 0. best number of assigned course requests (including alternativity &
053        // priority)
054        int currentAssignedCourseReq = 0, bestAssignedCourseReq = 0;
055        int currentAssignedRequests = 0, bestAssignedRequests = 0;
056        int currentAssignedPriority = 0, bestAssignedPriority = 0;
057        int currentAssignedAlternativity = 0, bestAssignedAlternativity = 0;
058        for (int idx = 0; idx < current.length; idx++) {
059            if (current[idx] != null && current[idx].getAssignments() != null) {
060                currentAssignedRequests++;
061                if (current[idx].isCourseRequest())
062                    currentAssignedCourseReq++;
063                currentAssignedPriority += current[idx].getPriority() * current[idx].getPriority();
064                currentAssignedAlternativity += (current[idx].getRequest().isAlternative() ? 1 : 0);
065            }
066            if (best[idx] != null && best[idx].getAssignments() != null) {
067                bestAssignedRequests++;
068                if (best[idx].isCourseRequest())
069                    bestAssignedCourseReq++;
070                bestAssignedPriority += best[idx].getPriority() * best[idx].getPriority();
071                bestAssignedAlternativity += (best[idx].getRequest().isAlternative() ? 1 : 0);
072            }
073        }
074        if (currentAssignedCourseReq > bestAssignedCourseReq)
075            return -1;
076        if (bestAssignedCourseReq > currentAssignedCourseReq)
077            return 1;
078        if (currentAssignedPriority < bestAssignedPriority)
079            return -1;
080        if (bestAssignedPriority < currentAssignedPriority)
081            return 1;
082        if (currentAssignedAlternativity < bestAssignedAlternativity)
083            return -1;
084        if (bestAssignedAlternativity < currentAssignedAlternativity)
085            return 1;
086
087        // 0.1. allowed, but not available sections
088        int bestNotAvailable = 0, currentNotAvailable = 0;
089        for (int idx = 0; idx < current.length; idx++) {
090            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) {
091                for (Section section: best[idx].getSections()) {
092                    if (section.getLimit() == 0)
093                        bestNotAvailable ++;
094                }
095            }
096            if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) {
097                for (Section section: current[idx].getSections()) {
098                    if (section.getLimit() == 0)
099                        currentNotAvailable ++;
100                }
101            }
102        }
103        if (bestNotAvailable > currentNotAvailable) return -1;
104        if (bestNotAvailable < currentNotAvailable) return 1;
105
106        // 0.5. avoid course overlaps & unavailabilities
107        if (getModel().getTimeOverlaps() != null) {
108            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
109            for (int idx = 0; idx < current.length; idx++) {
110                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest) {
111                    for (int x = 0; x < idx; x++) {
112                        if (best[x] != null && best[x].getAssignments() != null
113                                        && best[x].getRequest() instanceof CourseRequest)
114                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
115                    }
116                }
117                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest) {
118                    for (int x = 0; x < idx; x++) {
119                        if (current[x] != null && current[x].getAssignments() != null
120                                && current[x].getRequest() instanceof CourseRequest)
121                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
122                    }
123                }
124            }
125            for (int idx = 0; idx < current.length; idx++) {
126                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
127                    bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
128                }
129                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
130                    currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
131                }
132            }
133            if (currentTimeOverlaps < bestTimeOverlaps)
134                return -1;
135            if (bestTimeOverlaps < currentTimeOverlaps)
136                return 1;
137        }
138
139        // 1. minimize number of penalties
140        double bestPenalties = 0, currentPenalties = 0;
141        for (int idx = 0; idx < current.length; idx++) {
142            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
143                for (Section section : best[idx].getSections())
144                    bestPenalties += getModel().getOverExpected(assignment, section, best[idx].getRequest());
145            }
146            if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
147                for (Section section : current[idx].getSections())
148                    currentPenalties += getModel().getOverExpected(assignment, section, current[idx].getRequest());
149            }
150        }
151        if (currentPenalties < bestPenalties)
152            return -1;
153        if (bestPenalties < currentPenalties)
154            return 1;
155
156        // 2. best number of assigned requests (including free time requests)
157        if (currentAssignedRequests > bestAssignedRequests)
158            return -1;
159        if (bestAssignedRequests > currentAssignedRequests)
160            return 1;
161
162        // 3. maximize selection
163        int bestSelected = 0, currentSelected = 0;
164        for (int idx = 0; idx < current.length; idx++) {
165            Set<Section> preferred = getPreferredSections(getRequest(idx));
166            if (preferred != null && !preferred.isEmpty()) {
167                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
168                    for (Section section : best[idx].getSections())
169                        if (preferred.contains(section))
170                            bestSelected++;
171                }
172                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
173                    for (Section section : current[idx].getSections())
174                        if (preferred.contains(section))
175                            currentSelected++;
176                }
177            }
178        }
179        if (currentSelected > bestSelected)
180            return -1;
181        if (bestSelected > currentSelected)
182            return 1;
183        
184        // 3.5 maximize preferences
185        double bestSelectedConfigs = 0, currentSelectedConfigs = 0;
186        double bestSelectedSections = 0, currentSelectedSections = 0;
187        for (int idx = 0; idx < current.length; idx++) {
188            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
189                bestSelectedSections += best[idx].percentSelectedSameSection();
190                bestSelectedConfigs += best[idx].percentSelectedSameConfig();
191            }
192            if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
193                currentSelectedSections += current[idx].percentSelectedSameSection();
194                currentSelectedConfigs += current[idx].percentSelectedSameConfig();
195            }
196        }
197        if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return -1;
198        if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return 1;
199        
200        // 4. avoid time overlaps
201        if (getModel().getTimeOverlaps() != null) {
202            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
203            for (int idx = 0; idx < current.length; idx++) {
204                if (best[idx] != null && best[idx].getAssignments() != null) {
205                    for (int x = 0; x < idx; x++) {
206                        if (best[x] != null && best[x].getAssignments() != null)
207                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
208                        else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
209                            bestTimeOverlaps += getModel().getTimeOverlaps()
210                                    .nrConflicts(
211                                            ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
212                                            best[idx]);
213                    }
214                }
215                if (current[idx] != null && current[idx].getAssignments() != null) {
216                    for (int x = 0; x < idx; x++) {
217                        if (current[x] != null && current[x].getAssignments() != null)
218                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
219                        else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
220                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(
221                                    ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
222                                    current[idx]);
223                    }
224                }
225            }
226            for (int idx = 0; idx < current.length; idx++) {
227                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
228                    bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
229                }
230                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
231                    currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
232                }
233            }
234            if (currentTimeOverlaps < bestTimeOverlaps)
235                return -1;
236            if (bestTimeOverlaps < currentTimeOverlaps)
237                return 1;
238        }
239
240        // 5. avoid distance conflicts
241        if (getModel().getDistanceConflict() != null) {
242            int bestDistanceConf = 0, currentDistanceConf = 0;
243            for (int idx = 0; idx < current.length; idx++) {
244                if (best[idx] != null && best[idx].getAssignments() != null) {
245                    for (int x = 0; x < idx; x++) {
246                        if (best[x] != null && best[x].getAssignments() != null)
247                            bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]);
248                    }
249                }
250                if (current[idx] != null && current[idx].getAssignments() != null) {
251                    for (int x = 0; x < idx; x++) {
252                        if (current[x] != null && current[x].getAssignments() != null)
253                            currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x],
254                                    current[idx]);
255                    }
256                }
257            }
258            if (currentDistanceConf < bestDistanceConf)
259                return -1;
260            if (bestDistanceConf < currentDistanceConf)
261                return 1;
262        }
263
264        // 6. avoid no-time sections
265        int bestNoTime = 0, currentNoTime = 0;
266        for (int idx = 0; idx < current.length; idx++) {
267            if (best[idx] != null && best[idx].getAssignments() != null) {
268                for (Section section : best[idx].getSections())
269                    if (section.getTime() == null)
270                        bestNoTime++;
271            }
272            if (current[idx] != null && current[idx].getAssignments() != null) {
273                for (Section section : current[idx].getSections())
274                    if (section.getTime() == null)
275                        currentNoTime++;
276            }
277        }
278        if (currentNoTime < bestNoTime)
279            return -1;
280        if (bestNoTime < currentNoTime)
281            return 1;
282
283        // 7. balance sections
284        double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0;
285        int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0;
286        for (int idx = 0; idx < current.length; idx++) {
287            if (best[idx] != null && best[idx].getAssignments() != null) {
288                for (Section section : best[idx].getSections()) {
289                    Subpart subpart = section.getSubpart();
290                    // skip unlimited and single section subparts
291                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
292                        continue;
293                    // average size
294                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
295                    // section is below average
296                    if (section.getLimit() < averageSize)
297                        bestUnavailableSize += (averageSize - section.getLimit()) / averageSize;
298                    bestAltSectionsWithLimit++;
299                }
300            }
301            if (current[idx] != null && current[idx].getAssignments() != null) {
302                for (Section section : current[idx].getSections()) {
303                    Subpart subpart = section.getSubpart();
304                    // skip unlimited and single section subparts
305                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
306                        continue;
307                    // average size
308                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
309                    // section is below average
310                    if (section.getLimit() < averageSize)
311                        currentUnavailableSize += (averageSize - section.getLimit()) / averageSize;
312                    currentAltSectionsWithLimit++;
313                }
314            }
315        }
316        double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit
317                : 0.0);
318        double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize
319                / currentAltSectionsWithLimit : 0.0);
320        if (currentUnavailableSizeFraction < bestUnavailableSizeFraction)
321            return -1;
322        if (bestUnavailableSizeFraction < currentUnavailableSizeFraction)
323            return 1;
324
325        // 8. average penalty sections
326        double bestPenalty = 0.0, currentPenalty = 0.0;
327        for (int idx = 0; idx < current.length; idx++) {
328            if (best[idx] != null && best[idx].getAssignments() != null) {
329                for (Section section : best[idx].getSections())
330                    bestPenalty += section.getPenalty() / best[idx].getSections().size();
331            }
332            if (current[idx] != null && current[idx].getAssignments() != null) {
333                for (Section section : current[idx].getSections())
334                    currentPenalty += section.getPenalty() / current[idx].getSections().size();
335            }
336        }
337        if (currentPenalty < bestPenalty)
338            return -1;
339        if (bestPenalty < currentPenalty)
340            return 1;
341
342        return 0;
343    }
344
345    @Override
346    public boolean canImprove(Assignment<Request, Enrollment> assignment, int maxIdx, Enrollment[] current,
347            Enrollment[] best) {
348        // 0. best number of assigned course requests (including alternativity &
349        // priority)
350        int currentAssignedCourseReq = 0, bestAssignedCourseReq = 0;
351        int currentAssignedRequests = 0, bestAssignedRequests = 0;
352        int currentAssignedPriority = 0, bestAssignedPriority = 0;
353        int currentAssignedAlternativity = 0, bestAssignedAlternativity = 0;
354        int alt = 0;
355        for (int idx = 0; idx < current.length; idx++) {
356            if (idx < maxIdx) {
357                if (current[idx] != null && current[idx].getAssignments() != null) {
358                    currentAssignedRequests++;
359                    if (current[idx].isCourseRequest())
360                        currentAssignedCourseReq++;
361                    currentAssignedPriority += current[idx].getPriority() * current[idx].getPriority();
362                    currentAssignedAlternativity += (current[idx].getRequest().isAlternative() ? 1 : 0);
363                } else if (!isFreeTime(idx) && !getRequest(idx).isAlternative()) {
364                    alt++;
365                }
366            } else {
367                if (!getRequest(idx).isAlternative()) {
368                    currentAssignedRequests++;
369                    if (!isFreeTime(idx))
370                        currentAssignedCourseReq++;
371                } else if (alt > 0) {
372                    currentAssignedRequests++;
373                    currentAssignedCourseReq++;
374                    alt--;
375                    currentAssignedAlternativity++;
376                }
377            }
378            if (best[idx] != null && best[idx].getAssignments() != null) {
379                bestAssignedRequests++;
380                if (best[idx].isCourseRequest())
381                    bestAssignedCourseReq++;
382                bestAssignedPriority += best[idx].getPriority() * best[idx].getPriority();
383                bestAssignedAlternativity += (best[idx].getRequest().isAlternative() ? 1 : 0);
384            }
385        }
386        if (currentAssignedCourseReq > bestAssignedCourseReq)
387            return true;
388        if (bestAssignedCourseReq > currentAssignedCourseReq)
389            return false;
390        if (currentAssignedPriority < bestAssignedPriority)
391            return true;
392        if (bestAssignedPriority < currentAssignedPriority)
393            return false;
394        if (currentAssignedAlternativity < bestAssignedAlternativity)
395            return true;
396        if (bestAssignedAlternativity < currentAssignedAlternativity)
397            return false;
398
399        // 0.1. allowed, but not available sections
400        int notAvailable = 0;
401        for (int idx = 0; idx < current.length; idx++) {
402            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) {
403                for (Section section: best[idx].getSections()) {
404                    if (section.getLimit() == 0)
405                        notAvailable ++;
406                }
407            }
408            if (idx < maxIdx && current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) {
409                for (Section section: current[idx].getSections()) {
410                    if (section.getLimit() == 0)
411                        notAvailable --;
412                }
413            }
414        }
415        if (notAvailable > 0) {
416            return true;
417        }
418
419        // 0.5. avoid course time overlaps & unavailabilities
420        if (getModel().getTimeOverlaps() != null) {
421            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
422            for (int idx = 0; idx < current.length; idx++) {
423                if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) {
424                    for (int x = 0; x < idx; x++) {
425                        if (best[x] != null && best[x].getRequest() instanceof CourseRequest)
426                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
427                    }
428                }
429                if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) {
430                    for (int x = 0; x < idx; x++) {
431                        if (current[x] != null && current[x].getRequest() instanceof CourseRequest)
432                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
433                    }
434                }
435            }
436            for (int idx = 0; idx < current.length; idx++) {
437                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
438                    bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
439                }
440                if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
441                    currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
442                }
443            }
444            if (currentTimeOverlaps < bestTimeOverlaps)
445                return true;
446            if (bestTimeOverlaps < currentTimeOverlaps)
447                return false;
448        }
449
450        // 1. maximize number of penalties
451        double bestPenalties = 0, currentPenalties = 0;
452        for (int idx = 0; idx < current.length; idx++) {
453            if (best[idx] != null) {
454                for (Section section : best[idx].getSections())
455                    bestPenalties += getModel().getOverExpected(assignment, section, best[idx].getRequest());
456            }
457            if (current[idx] != null && idx < maxIdx) {
458                for (Section section : current[idx].getSections())
459                    currentPenalties += getModel().getOverExpected(assignment, section, current[idx].getRequest());
460            }
461        }
462        if (currentPenalties < bestPenalties)
463            return true;
464        if (bestPenalties < currentPenalties)
465            return false;
466
467        // 2. best number of assigned requests (including free time requests)
468        if (currentAssignedRequests > bestAssignedRequests)
469            return true;
470        if (bestAssignedRequests > currentAssignedRequests)
471            return false;
472
473        // 3. maximize selection
474        int bestSelected = 0, currentSelected = 0;
475        for (int idx = 0; idx < current.length; idx++) {
476            if (best[idx] != null && best[idx].isCourseRequest()) {
477                Set<Section> preferred = getPreferredSections(best[idx].getRequest());
478                if (preferred != null && !preferred.isEmpty()) {
479                    for (Section section : best[idx].getSections())
480                        if (preferred.contains(section)) {
481                            if (idx < maxIdx)
482                                bestSelected++;
483                        } else if (idx >= maxIdx)
484                            bestSelected--;
485                }
486            }
487            if (current[idx] != null && idx < maxIdx && current[idx].isCourseRequest()) {
488                Set<Section> preferred = getPreferredSections(current[idx].getRequest());
489                if (preferred != null && !preferred.isEmpty()) {
490                    for (Section section : current[idx].getSections())
491                        if (preferred.contains(section))
492                            currentSelected++;
493                }
494            }
495        }
496        if (currentSelected > bestSelected)
497            return true;
498        if (bestSelected > currentSelected)
499            return false;
500        
501        // 3.5 maximize preferences
502        double bestSelectedConfigs = 0, currentSelectedConfigs = 0;
503        double bestSelectedSections = 0, currentSelectedSections = 0;
504        for (int idx = 0; idx < current.length; idx++) {
505            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
506                bestSelectedSections += best[idx].percentSelectedSameSection();
507                bestSelectedConfigs += best[idx].percentSelectedSameConfig();
508                if (idx >= maxIdx) {
509                    bestSelectedSections -= 1.0;
510                    bestSelectedConfigs -= 1.0;
511                }
512            }
513            if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
514                currentSelectedSections += current[idx].percentSelectedSameSection();
515                currentSelectedConfigs += current[idx].percentSelectedSameConfig();
516            }
517        }
518        if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return true;
519        if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return false;
520        
521        // 4. avoid time overlaps
522        if (getModel().getTimeOverlaps() != null) {
523            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
524            for (int idx = 0; idx < current.length; idx++) {
525                if (best[idx] != null) {
526                    for (int x = 0; x < idx; x++) {
527                        if (best[x] != null)
528                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
529                        else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
530                            bestTimeOverlaps += getModel().getTimeOverlaps()
531                                    .nrConflicts(
532                                            ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
533                                            best[idx]);
534                    }
535                }
536                if (current[idx] != null && idx < maxIdx) {
537                    for (int x = 0; x < idx; x++) {
538                        if (current[x] != null)
539                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
540                        else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
541                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(
542                                    ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
543                                    current[idx]);
544                    }
545                }
546            }
547            for (int idx = 0; idx < current.length; idx++) {
548                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
549                    bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
550                }
551                if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
552                    currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
553                }
554            }
555            if (currentTimeOverlaps < bestTimeOverlaps)
556                return true;
557            if (bestTimeOverlaps < currentTimeOverlaps)
558                return false;
559        }
560
561        // 5. avoid distance conflicts
562        if (getModel().getDistanceConflict() != null) {
563            int bestDistanceConf = 0, currentDistanceConf = 0;
564            for (int idx = 0; idx < current.length; idx++) {
565                if (best[idx] != null) {
566                    for (int x = 0; x < idx; x++) {
567                        if (best[x] != null)
568                            bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]);
569                    }
570                }
571                if (current[idx] != null && idx < maxIdx) {
572                    for (int x = 0; x < idx; x++) {
573                        if (current[x] != null)
574                            currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x],
575                                    current[idx]);
576                    }
577                }
578            }
579            if (currentDistanceConf < bestDistanceConf)
580                return true;
581            if (bestDistanceConf < currentDistanceConf)
582                return false;
583        }
584
585        // 6. avoid no-time sections
586        int bestNoTime = 0, currentNoTime = 0;
587        for (int idx = 0; idx < current.length; idx++) {
588            if (best[idx] != null) {
589                for (Section section : best[idx].getSections())
590                    if (section.getTime() == null)
591                        bestNoTime++;
592            }
593            if (current[idx] != null && idx < maxIdx) {
594                for (Section section : current[idx].getSections())
595                    if (section.getTime() == null)
596                        currentNoTime++;
597            }
598        }
599        if (currentNoTime < bestNoTime)
600            return true;
601        if (bestNoTime < currentNoTime)
602            return false;
603
604        // 7. balance sections
605        double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0;
606        int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0;
607        for (int idx = 0; idx < current.length; idx++) {
608            if (best[idx] != null) {
609                for (Section section : best[idx].getSections()) {
610                    Subpart subpart = section.getSubpart();
611                    // skip unlimited and single section subparts
612                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
613                        continue;
614                    // average size
615                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
616                    // section is below average
617                    if (section.getLimit() < averageSize)
618                        bestUnavailableSize += (averageSize - section.getLimit()) / averageSize;
619                    bestAltSectionsWithLimit++;
620                }
621            }
622            if (current[idx] != null && idx < maxIdx) {
623                for (Section section : current[idx].getSections()) {
624                    Subpart subpart = section.getSubpart();
625                    // skip unlimited and single section subparts
626                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
627                        continue;
628                    // average size
629                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
630                    // section is below average
631                    if (section.getLimit() < averageSize)
632                        currentUnavailableSize += (averageSize - section.getLimit()) / averageSize;
633                    currentAltSectionsWithLimit++;
634                }
635            }
636        }
637        double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit
638                : 0.0);
639        double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize
640                / currentAltSectionsWithLimit : 0.0);
641        if (currentUnavailableSizeFraction < bestUnavailableSizeFraction)
642            return true;
643        if (bestUnavailableSizeFraction < currentUnavailableSizeFraction)
644            return false;
645
646        // 8. average penalty sections
647        double bestPenalty = 0.0, currentPenalty = 0.0;
648        for (int idx = 0; idx < current.length; idx++) {
649            if (best[idx] != null) {
650                for (Section section : best[idx].getSections())
651                    bestPenalty += section.getPenalty() / best[idx].getSections().size();
652                if (idx >= maxIdx && best[idx].isCourseRequest())
653                    bestPenalty -= ((CourseRequest) best[idx].getRequest()).getMinPenalty();
654            }
655            if (current[idx] != null && idx < maxIdx) {
656                for (Section section : current[idx].getSections())
657                    currentPenalty += section.getPenalty() / current[idx].getSections().size();
658            }
659        }
660        if (currentPenalty < bestPenalty)
661            return true;
662        if (bestPenalty < currentPenalty)
663            return false;
664
665        return true;
666    }
667}