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