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.5. avoid course overlaps
088        if (getModel().getTimeOverlaps() != null) {
089            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
090            for (int idx = 0; idx < current.length; idx++) {
091                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest) {
092                    for (int x = 0; x < idx; x++) {
093                        if (best[x] != null && best[x].getAssignments() != null
094                                        && best[x].getRequest() instanceof CourseRequest)
095                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
096                    }
097                }
098                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest) {
099                    for (int x = 0; x < idx; x++) {
100                        if (current[x] != null && current[x].getAssignments() != null
101                                && current[x].getRequest() instanceof CourseRequest)
102                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
103                    }
104                }
105            }
106            if (currentTimeOverlaps < bestTimeOverlaps)
107                return -1;
108            if (bestTimeOverlaps < currentTimeOverlaps)
109                return 1;
110        }
111
112        // 1. minimize number of penalties
113        double bestPenalties = 0, currentPenalties = 0;
114        for (int idx = 0; idx < current.length; idx++) {
115            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
116                for (Section section : best[idx].getSections())
117                    bestPenalties += getModel().getOverExpected(assignment, section, best[idx].getRequest());
118            }
119            if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
120                for (Section section : current[idx].getSections())
121                    currentPenalties += getModel().getOverExpected(assignment, section, current[idx].getRequest());
122            }
123        }
124        if (currentPenalties < bestPenalties)
125            return -1;
126        if (bestPenalties < currentPenalties)
127            return 1;
128
129        // 2. best number of assigned requests (including free time requests)
130        if (currentAssignedRequests > bestAssignedRequests)
131            return -1;
132        if (bestAssignedRequests > currentAssignedRequests)
133            return 1;
134
135        // 3. maximize selection
136        int bestSelected = 0, currentSelected = 0;
137        for (int idx = 0; idx < current.length; idx++) {
138            Set<Section> preferred = getPreferredSections(getRequest(idx));
139            if (preferred != null && !preferred.isEmpty()) {
140                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
141                    for (Section section : best[idx].getSections())
142                        if (preferred.contains(section))
143                            bestSelected++;
144                }
145                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
146                    for (Section section : current[idx].getSections())
147                        if (preferred.contains(section))
148                            currentSelected++;
149                }
150            }
151        }
152        if (currentSelected > bestSelected)
153            return -1;
154        if (bestSelected > currentSelected)
155            return 1;
156        
157        // 3.5 maximize preferences
158        double bestSelectedConfigs = 0, currentSelectedConfigs = 0;
159        double bestSelectedSections = 0, currentSelectedSections = 0;
160        for (int idx = 0; idx < current.length; idx++) {
161            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
162                bestSelectedSections += best[idx].percentSelectedSameSection();
163                bestSelectedConfigs += best[idx].percentSelectedSameConfig();
164            }
165            if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
166                currentSelectedSections += current[idx].percentSelectedSameSection();
167                currentSelectedConfigs += current[idx].percentSelectedSameConfig();
168            }
169        }
170        if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return -1;
171        if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return 1;
172        
173        // 4. avoid time overlaps
174        if (getModel().getTimeOverlaps() != null) {
175            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
176            for (int idx = 0; idx < current.length; idx++) {
177                if (best[idx] != null && best[idx].getAssignments() != null) {
178                    for (int x = 0; x < idx; x++) {
179                        if (best[x] != null && best[x].getAssignments() != null)
180                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
181                        else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
182                            bestTimeOverlaps += getModel().getTimeOverlaps()
183                                    .nrConflicts(
184                                            ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
185                                            best[idx]);
186                    }
187                }
188                if (current[idx] != null && current[idx].getAssignments() != null) {
189                    for (int x = 0; x < idx; x++) {
190                        if (current[x] != null && current[x].getAssignments() != null)
191                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
192                        else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
193                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(
194                                    ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
195                                    current[idx]);
196                    }
197                }
198            }
199            if (currentTimeOverlaps < bestTimeOverlaps)
200                return -1;
201            if (bestTimeOverlaps < currentTimeOverlaps)
202                return 1;
203        }
204
205        // 5. avoid distance conflicts
206        if (getModel().getDistanceConflict() != null) {
207            int bestDistanceConf = 0, currentDistanceConf = 0;
208            for (int idx = 0; idx < current.length; idx++) {
209                if (best[idx] != null && best[idx].getAssignments() != null) {
210                    for (int x = 0; x < idx; x++) {
211                        if (best[x] != null && best[x].getAssignments() != null)
212                            bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], 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                            currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x],
219                                    current[idx]);
220                    }
221                }
222            }
223            if (currentDistanceConf < bestDistanceConf)
224                return -1;
225            if (bestDistanceConf < currentDistanceConf)
226                return 1;
227        }
228
229        // 6. avoid no-time sections
230        int bestNoTime = 0, currentNoTime = 0;
231        for (int idx = 0; idx < current.length; idx++) {
232            if (best[idx] != null && best[idx].getAssignments() != null) {
233                for (Section section : best[idx].getSections())
234                    if (section.getTime() == null)
235                        bestNoTime++;
236            }
237            if (current[idx] != null && current[idx].getAssignments() != null) {
238                for (Section section : current[idx].getSections())
239                    if (section.getTime() == null)
240                        currentNoTime++;
241            }
242        }
243        if (currentNoTime < bestNoTime)
244            return -1;
245        if (bestNoTime < currentNoTime)
246            return 1;
247
248        // 7. balance sections
249        double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0;
250        int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0;
251        for (int idx = 0; idx < current.length; idx++) {
252            if (best[idx] != null && best[idx].getAssignments() != null) {
253                for (Section section : best[idx].getSections()) {
254                    Subpart subpart = section.getSubpart();
255                    // skip unlimited and single section subparts
256                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
257                        continue;
258                    // average size
259                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
260                    // section is below average
261                    if (section.getLimit() < averageSize)
262                        bestUnavailableSize += (averageSize - section.getLimit()) / averageSize;
263                    bestAltSectionsWithLimit++;
264                }
265            }
266            if (current[idx] != null && current[idx].getAssignments() != null) {
267                for (Section section : current[idx].getSections()) {
268                    Subpart subpart = section.getSubpart();
269                    // skip unlimited and single section subparts
270                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
271                        continue;
272                    // average size
273                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
274                    // section is below average
275                    if (section.getLimit() < averageSize)
276                        currentUnavailableSize += (averageSize - section.getLimit()) / averageSize;
277                    currentAltSectionsWithLimit++;
278                }
279            }
280        }
281        double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit
282                : 0.0);
283        double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize
284                / currentAltSectionsWithLimit : 0.0);
285        if (currentUnavailableSizeFraction < bestUnavailableSizeFraction)
286            return -1;
287        if (bestUnavailableSizeFraction < currentUnavailableSizeFraction)
288            return 1;
289
290        // 8. average penalty sections
291        double bestPenalty = 0.0, currentPenalty = 0.0;
292        for (int idx = 0; idx < current.length; idx++) {
293            if (best[idx] != null && best[idx].getAssignments() != null) {
294                for (Section section : best[idx].getSections())
295                    bestPenalty += section.getPenalty();
296            }
297            if (current[idx] != null && current[idx].getAssignments() != null) {
298                for (Section section : current[idx].getSections())
299                    currentPenalty += section.getPenalty();
300            }
301        }
302        if (currentPenalty < bestPenalty)
303            return -1;
304        if (bestPenalty < currentPenalty)
305            return 1;
306
307        return 0;
308    }
309
310    @Override
311    public boolean canImprove(Assignment<Request, Enrollment> assignment, int maxIdx, Enrollment[] current,
312            Enrollment[] best) {
313        // 0. best number of assigned course requests (including alternativity &
314        // priority)
315        int currentAssignedCourseReq = 0, bestAssignedCourseReq = 0;
316        int currentAssignedRequests = 0, bestAssignedRequests = 0;
317        int currentAssignedPriority = 0, bestAssignedPriority = 0;
318        int currentAssignedAlternativity = 0, bestAssignedAlternativity = 0;
319        int alt = 0;
320        for (int idx = 0; idx < current.length; idx++) {
321            if (idx < maxIdx) {
322                if (current[idx] != null && current[idx].getAssignments() != null) {
323                    currentAssignedRequests++;
324                    if (current[idx].isCourseRequest())
325                        currentAssignedCourseReq++;
326                    currentAssignedPriority += current[idx].getPriority() * current[idx].getPriority();
327                    currentAssignedAlternativity += (current[idx].getRequest().isAlternative() ? 1 : 0);
328                } else if (!isFreeTime(idx) && !getRequest(idx).isAlternative()) {
329                    alt++;
330                }
331            } else {
332                if (!getRequest(idx).isAlternative()) {
333                    currentAssignedRequests++;
334                    if (!isFreeTime(idx))
335                        currentAssignedCourseReq++;
336                } else if (alt > 0) {
337                    currentAssignedRequests++;
338                    currentAssignedCourseReq++;
339                    alt--;
340                    currentAssignedAlternativity++;
341                }
342            }
343            if (best[idx] != null && best[idx].getAssignments() != null) {
344                bestAssignedRequests++;
345                if (best[idx].isCourseRequest())
346                    bestAssignedCourseReq++;
347                bestAssignedPriority += best[idx].getPriority() * best[idx].getPriority();
348                bestAssignedAlternativity += (best[idx].getRequest().isAlternative() ? 1 : 0);
349            }
350        }
351        if (currentAssignedCourseReq > bestAssignedCourseReq)
352            return true;
353        if (bestAssignedCourseReq > currentAssignedCourseReq)
354            return false;
355        if (currentAssignedPriority < bestAssignedPriority)
356            return true;
357        if (bestAssignedPriority < currentAssignedPriority)
358            return false;
359        if (currentAssignedAlternativity < bestAssignedAlternativity)
360            return true;
361        if (bestAssignedAlternativity < currentAssignedAlternativity)
362            return false;
363
364        // 0.5. avoid course time overlaps
365        if (getModel().getTimeOverlaps() != null) {
366            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
367            for (int idx = 0; idx < current.length; idx++) {
368                if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) {
369                    for (int x = 0; x < idx; x++) {
370                        if (best[x] != null && best[x].getRequest() instanceof CourseRequest)
371                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
372                    }
373                }
374                if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) {
375                    for (int x = 0; x < idx; x++) {
376                        if (current[x] != null && current[x].getRequest() instanceof CourseRequest)
377                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
378                    }
379                }
380            }
381            if (currentTimeOverlaps < bestTimeOverlaps)
382                return true;
383            if (bestTimeOverlaps < currentTimeOverlaps)
384                return false;
385        }
386
387        // 1. maximize number of penalties
388        double bestPenalties = 0, currentPenalties = 0;
389        for (int idx = 0; idx < current.length; idx++) {
390            if (best[idx] != null) {
391                for (Section section : best[idx].getSections())
392                    bestPenalties += getModel().getOverExpected(assignment, section, best[idx].getRequest());
393            }
394            if (current[idx] != null && idx < maxIdx) {
395                for (Section section : current[idx].getSections())
396                    currentPenalties += getModel().getOverExpected(assignment, section, current[idx].getRequest());
397            }
398        }
399        if (currentPenalties < bestPenalties)
400            return true;
401        if (bestPenalties < currentPenalties)
402            return false;
403
404        // 2. best number of assigned requests (including free time requests)
405        if (currentAssignedRequests > bestAssignedRequests)
406            return true;
407        if (bestAssignedRequests > currentAssignedRequests)
408            return false;
409
410        // 3. maximize selection
411        int bestSelected = 0, currentSelected = 0;
412        for (int idx = 0; idx < current.length; idx++) {
413            if (best[idx] != null && best[idx].isCourseRequest()) {
414                Set<Section> preferred = getPreferredSections(best[idx].getRequest());
415                if (preferred != null && !preferred.isEmpty()) {
416                    for (Section section : best[idx].getSections())
417                        if (preferred.contains(section)) {
418                            if (idx < maxIdx)
419                                bestSelected++;
420                        } else if (idx >= maxIdx)
421                            bestSelected--;
422                }
423            }
424            if (current[idx] != null && idx < maxIdx && current[idx].isCourseRequest()) {
425                Set<Section> preferred = getPreferredSections(current[idx].getRequest());
426                if (preferred != null && !preferred.isEmpty()) {
427                    for (Section section : current[idx].getSections())
428                        if (preferred.contains(section))
429                            currentSelected++;
430                }
431            }
432        }
433        if (currentSelected > bestSelected)
434            return true;
435        if (bestSelected > currentSelected)
436            return false;
437        
438        // 3.5 maximize preferences
439        double bestSelectedConfigs = 0, currentSelectedConfigs = 0;
440        double bestSelectedSections = 0, currentSelectedSections = 0;
441        for (int idx = 0; idx < current.length; idx++) {
442            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
443                bestSelectedSections += best[idx].percentSelectedSameSection();
444                bestSelectedConfigs += best[idx].percentSelectedSameConfig();
445                if (idx >= maxIdx) {
446                    bestSelectedSections -= 1.0;
447                    bestSelectedConfigs -= 1.0;
448                }
449            }
450            if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
451                currentSelectedSections += current[idx].percentSelectedSameSection();
452                currentSelectedConfigs += current[idx].percentSelectedSameConfig();
453            }
454        }
455        if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return true;
456        if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return false;
457        
458        // 4. avoid time overlaps
459        if (getModel().getTimeOverlaps() != null) {
460            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
461            for (int idx = 0; idx < current.length; idx++) {
462                if (best[idx] != null) {
463                    for (int x = 0; x < idx; x++) {
464                        if (best[x] != null)
465                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
466                        else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
467                            bestTimeOverlaps += getModel().getTimeOverlaps()
468                                    .nrConflicts(
469                                            ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
470                                            best[idx]);
471                    }
472                }
473                if (current[idx] != null && idx < maxIdx) {
474                    for (int x = 0; x < idx; x++) {
475                        if (current[x] != null)
476                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
477                        else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
478                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(
479                                    ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
480                                    current[idx]);
481                    }
482                }
483            }
484            if (currentTimeOverlaps < bestTimeOverlaps)
485                return true;
486            if (bestTimeOverlaps < currentTimeOverlaps)
487                return false;
488        }
489
490        // 5. avoid distance conflicts
491        if (getModel().getDistanceConflict() != null) {
492            int bestDistanceConf = 0, currentDistanceConf = 0;
493            for (int idx = 0; idx < current.length; idx++) {
494                if (best[idx] != null) {
495                    for (int x = 0; x < idx; x++) {
496                        if (best[x] != null)
497                            bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]);
498                    }
499                }
500                if (current[idx] != null && idx < maxIdx) {
501                    for (int x = 0; x < idx; x++) {
502                        if (current[x] != null)
503                            currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x],
504                                    current[idx]);
505                    }
506                }
507            }
508            if (currentDistanceConf < bestDistanceConf)
509                return true;
510            if (bestDistanceConf < currentDistanceConf)
511                return false;
512        }
513
514        // 6. avoid no-time sections
515        int bestNoTime = 0, currentNoTime = 0;
516        for (int idx = 0; idx < current.length; idx++) {
517            if (best[idx] != null) {
518                for (Section section : best[idx].getSections())
519                    if (section.getTime() == null)
520                        bestNoTime++;
521            }
522            if (current[idx] != null && idx < maxIdx) {
523                for (Section section : current[idx].getSections())
524                    if (section.getTime() == null)
525                        currentNoTime++;
526            }
527        }
528        if (currentNoTime < bestNoTime)
529            return true;
530        if (bestNoTime < currentNoTime)
531            return false;
532
533        // 7. balance sections
534        double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0;
535        int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0;
536        for (int idx = 0; idx < current.length; idx++) {
537            if (best[idx] != null) {
538                for (Section section : best[idx].getSections()) {
539                    Subpart subpart = section.getSubpart();
540                    // skip unlimited and single section subparts
541                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
542                        continue;
543                    // average size
544                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
545                    // section is below average
546                    if (section.getLimit() < averageSize)
547                        bestUnavailableSize += (averageSize - section.getLimit()) / averageSize;
548                    bestAltSectionsWithLimit++;
549                }
550            }
551            if (current[idx] != null && idx < maxIdx) {
552                for (Section section : current[idx].getSections()) {
553                    Subpart subpart = section.getSubpart();
554                    // skip unlimited and single section subparts
555                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
556                        continue;
557                    // average size
558                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
559                    // section is below average
560                    if (section.getLimit() < averageSize)
561                        currentUnavailableSize += (averageSize - section.getLimit()) / averageSize;
562                    currentAltSectionsWithLimit++;
563                }
564            }
565        }
566        double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit
567                : 0.0);
568        double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize
569                / currentAltSectionsWithLimit : 0.0);
570        if (currentUnavailableSizeFraction < bestUnavailableSizeFraction)
571            return true;
572        if (bestUnavailableSizeFraction < currentUnavailableSizeFraction)
573            return false;
574
575        // 8. average penalty sections
576        double bestPenalty = 0.0, currentPenalty = 0.0;
577        for (int idx = 0; idx < current.length; idx++) {
578            if (best[idx] != null) {
579                for (Section section : best[idx].getSections())
580                    bestPenalty += section.getPenalty();
581                if (idx >= maxIdx && best[idx].isCourseRequest())
582                    bestPenalty -= ((CourseRequest) best[idx].getRequest()).getMinPenalty();
583            }
584            if (current[idx] != null && idx < maxIdx) {
585                for (Section section : current[idx].getSections())
586                    currentPenalty += section.getPenalty();
587            }
588        }
589        if (currentPenalty < bestPenalty)
590            return true;
591        if (bestPenalty < currentPenalty)
592            return false;
593
594        return true;
595    }
596}