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, best, idx, 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, current, idx, 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                    bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]);
246                    for (int x = 0; x < idx; x++) {
247                        if (best[x] != null && best[x].getAssignments() != null)
248                            bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]);
249                    }
250                }
251                if (current[idx] != null && current[idx].getAssignments() != null) {
252                    currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]);
253                    for (int x = 0; x < idx; x++) {
254                        if (current[x] != null && current[x].getAssignments() != null)
255                            currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x],
256                                    current[idx]);
257                    }
258                }
259            }
260            if (currentDistanceConf < bestDistanceConf)
261                return -1;
262            if (bestDistanceConf < currentDistanceConf)
263                return 1;
264        }
265
266        // 6. avoid no-time sections
267        int bestNoTime = 0, currentNoTime = 0;
268        for (int idx = 0; idx < current.length; idx++) {
269            if (best[idx] != null && best[idx].getAssignments() != null) {
270                for (Section section : best[idx].getSections())
271                    if (section.getTime() == null)
272                        bestNoTime++;
273            }
274            if (current[idx] != null && current[idx].getAssignments() != null) {
275                for (Section section : current[idx].getSections())
276                    if (section.getTime() == null)
277                        currentNoTime++;
278            }
279        }
280        if (currentNoTime < bestNoTime)
281            return -1;
282        if (bestNoTime < currentNoTime)
283            return 1;
284
285        // 7. balance sections
286        double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0;
287        int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0;
288        for (int idx = 0; idx < current.length; idx++) {
289            if (best[idx] != null && best[idx].getAssignments() != null) {
290                for (Section section : best[idx].getSections()) {
291                    Subpart subpart = section.getSubpart();
292                    // skip unlimited and single section subparts
293                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
294                        continue;
295                    // average size
296                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
297                    // section is below average
298                    if (section.getLimit() < averageSize)
299                        bestUnavailableSize += (averageSize - section.getLimit()) / averageSize;
300                    bestAltSectionsWithLimit++;
301                }
302            }
303            if (current[idx] != null && current[idx].getAssignments() != null) {
304                for (Section section : current[idx].getSections()) {
305                    Subpart subpart = section.getSubpart();
306                    // skip unlimited and single section subparts
307                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
308                        continue;
309                    // average size
310                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
311                    // section is below average
312                    if (section.getLimit() < averageSize)
313                        currentUnavailableSize += (averageSize - section.getLimit()) / averageSize;
314                    currentAltSectionsWithLimit++;
315                }
316            }
317        }
318        double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit
319                : 0.0);
320        double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize
321                / currentAltSectionsWithLimit : 0.0);
322        if (currentUnavailableSizeFraction < bestUnavailableSizeFraction)
323            return -1;
324        if (bestUnavailableSizeFraction < currentUnavailableSizeFraction)
325            return 1;
326
327        // 8. average penalty sections
328        double bestPenalty = 0.0, currentPenalty = 0.0;
329        for (int idx = 0; idx < current.length; idx++) {
330            if (best[idx] != null && best[idx].getAssignments() != null) {
331                for (Section section : best[idx].getSections())
332                    bestPenalty += section.getPenalty() / best[idx].getSections().size();
333            }
334            if (current[idx] != null && current[idx].getAssignments() != null) {
335                for (Section section : current[idx].getSections())
336                    currentPenalty += section.getPenalty() / current[idx].getSections().size();
337            }
338        }
339        if (currentPenalty < bestPenalty)
340            return -1;
341        if (bestPenalty < currentPenalty)
342            return 1;
343
344        return 0;
345    }
346
347    @Override
348    public boolean canImprove(Assignment<Request, Enrollment> assignment, int maxIdx, Enrollment[] current,
349            Enrollment[] best) {
350        // 0. best number of assigned course requests (including alternativity &
351        // priority)
352        int currentAssignedCourseReq = 0, bestAssignedCourseReq = 0;
353        int currentAssignedRequests = 0, bestAssignedRequests = 0;
354        int currentAssignedPriority = 0, bestAssignedPriority = 0;
355        int currentAssignedAlternativity = 0, bestAssignedAlternativity = 0;
356        int alt = 0;
357        for (int idx = 0; idx < current.length; idx++) {
358            if (idx < maxIdx) {
359                if (current[idx] != null && current[idx].getAssignments() != null) {
360                    currentAssignedRequests++;
361                    if (current[idx].isCourseRequest())
362                        currentAssignedCourseReq++;
363                    currentAssignedPriority += current[idx].getPriority() * current[idx].getPriority();
364                    currentAssignedAlternativity += (current[idx].getRequest().isAlternative() ? 1 : 0);
365                } else if (!isFreeTime(idx) && !getRequest(idx).isAlternative()) {
366                    alt++;
367                }
368            } else {
369                if (!getRequest(idx).isAlternative()) {
370                    currentAssignedRequests++;
371                    if (!isFreeTime(idx))
372                        currentAssignedCourseReq++;
373                } else if (alt > 0) {
374                    currentAssignedRequests++;
375                    currentAssignedCourseReq++;
376                    alt--;
377                    currentAssignedAlternativity++;
378                }
379            }
380            if (best[idx] != null && best[idx].getAssignments() != null) {
381                bestAssignedRequests++;
382                if (best[idx].isCourseRequest())
383                    bestAssignedCourseReq++;
384                bestAssignedPriority += best[idx].getPriority() * best[idx].getPriority();
385                bestAssignedAlternativity += (best[idx].getRequest().isAlternative() ? 1 : 0);
386            }
387        }
388        if (currentAssignedCourseReq > bestAssignedCourseReq)
389            return true;
390        if (bestAssignedCourseReq > currentAssignedCourseReq)
391            return false;
392        if (currentAssignedPriority < bestAssignedPriority)
393            return true;
394        if (bestAssignedPriority < currentAssignedPriority)
395            return false;
396        if (currentAssignedAlternativity < bestAssignedAlternativity)
397            return true;
398        if (bestAssignedAlternativity < currentAssignedAlternativity)
399            return false;
400
401        // 0.1. allowed, but not available sections
402        int notAvailable = 0;
403        for (int idx = 0; idx < current.length; idx++) {
404            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) {
405                for (Section section: best[idx].getSections()) {
406                    if (section.getLimit() == 0)
407                        notAvailable ++;
408                }
409            }
410            if (idx < maxIdx && current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) {
411                for (Section section: current[idx].getSections()) {
412                    if (section.getLimit() == 0)
413                        notAvailable --;
414                }
415            }
416        }
417        if (notAvailable > 0) {
418            return true;
419        }
420
421        // 0.5. avoid course time overlaps & unavailabilities
422        if (getModel().getTimeOverlaps() != null) {
423            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
424            for (int idx = 0; idx < current.length; idx++) {
425                if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) {
426                    for (int x = 0; x < idx; x++) {
427                        if (best[x] != null && best[x].getRequest() instanceof CourseRequest)
428                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
429                    }
430                }
431                if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) {
432                    for (int x = 0; x < idx; x++) {
433                        if (current[x] != null && current[x].getRequest() instanceof CourseRequest)
434                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
435                    }
436                }
437            }
438            for (int idx = 0; idx < current.length; idx++) {
439                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
440                    bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
441                }
442                if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
443                    currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
444                }
445            }
446            if (currentTimeOverlaps < bestTimeOverlaps)
447                return true;
448            if (bestTimeOverlaps < currentTimeOverlaps)
449                return false;
450        }
451
452        // 1. maximize number of penalties
453        double bestPenalties = 0, currentPenalties = 0;
454        for (int idx = 0; idx < current.length; idx++) {
455            if (best[idx] != null) {
456                for (Section section : best[idx].getSections())
457                    bestPenalties += getModel().getOverExpected(assignment, best, idx, section, best[idx].getRequest());
458            }
459            if (current[idx] != null && idx < maxIdx) {
460                for (Section section : current[idx].getSections())
461                    currentPenalties += getModel().getOverExpected(assignment, current, idx, section, current[idx].getRequest());
462            }
463        }
464        if (currentPenalties < bestPenalties)
465            return true;
466        if (bestPenalties < currentPenalties)
467            return false;
468
469        // 2. best number of assigned requests (including free time requests)
470        if (currentAssignedRequests > bestAssignedRequests)
471            return true;
472        if (bestAssignedRequests > currentAssignedRequests)
473            return false;
474
475        // 3. maximize selection
476        int bestSelected = 0, currentSelected = 0;
477        for (int idx = 0; idx < current.length; idx++) {
478            if (best[idx] != null && best[idx].isCourseRequest()) {
479                Set<Section> preferred = getPreferredSections(best[idx].getRequest());
480                if (preferred != null && !preferred.isEmpty()) {
481                    for (Section section : best[idx].getSections())
482                        if (preferred.contains(section)) {
483                            if (idx < maxIdx)
484                                bestSelected++;
485                        } else if (idx >= maxIdx)
486                            bestSelected--;
487                }
488            }
489            if (current[idx] != null && idx < maxIdx && current[idx].isCourseRequest()) {
490                Set<Section> preferred = getPreferredSections(current[idx].getRequest());
491                if (preferred != null && !preferred.isEmpty()) {
492                    for (Section section : current[idx].getSections())
493                        if (preferred.contains(section))
494                            currentSelected++;
495                }
496            }
497        }
498        if (currentSelected > bestSelected)
499            return true;
500        if (bestSelected > currentSelected)
501            return false;
502        
503        // 3.5 maximize preferences
504        double bestSelectedConfigs = 0, currentSelectedConfigs = 0;
505        double bestSelectedSections = 0, currentSelectedSections = 0;
506        for (int idx = 0; idx < current.length; idx++) {
507            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
508                bestSelectedSections += best[idx].percentSelectedSameSection();
509                bestSelectedConfigs += best[idx].percentSelectedSameConfig();
510                if (idx >= maxIdx) {
511                    bestSelectedSections -= 1.0;
512                    bestSelectedConfigs -= 1.0;
513                }
514            }
515            if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
516                currentSelectedSections += current[idx].percentSelectedSameSection();
517                currentSelectedConfigs += current[idx].percentSelectedSameConfig();
518            }
519        }
520        if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return true;
521        if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return false;
522        
523        // 4. avoid time overlaps
524        if (getModel().getTimeOverlaps() != null) {
525            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
526            for (int idx = 0; idx < current.length; idx++) {
527                if (best[idx] != null) {
528                    for (int x = 0; x < idx; x++) {
529                        if (best[x] != null)
530                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
531                        else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
532                            bestTimeOverlaps += getModel().getTimeOverlaps()
533                                    .nrConflicts(
534                                            ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
535                                            best[idx]);
536                    }
537                }
538                if (current[idx] != null && idx < maxIdx) {
539                    for (int x = 0; x < idx; x++) {
540                        if (current[x] != null)
541                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
542                        else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
543                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(
544                                    ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
545                                    current[idx]);
546                    }
547                }
548            }
549            for (int idx = 0; idx < current.length; idx++) {
550                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
551                    bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
552                }
553                if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
554                    currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
555                }
556            }
557            if (currentTimeOverlaps < bestTimeOverlaps)
558                return true;
559            if (bestTimeOverlaps < currentTimeOverlaps)
560                return false;
561        }
562
563        // 5. avoid distance conflicts
564        if (getModel().getDistanceConflict() != null) {
565            int bestDistanceConf = 0, currentDistanceConf = 0;
566            for (int idx = 0; idx < current.length; idx++) {
567                if (best[idx] != null) {
568                    bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]);
569                    for (int x = 0; x < idx; x++) {
570                        if (best[x] != null)
571                            bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]);
572                    }
573                }
574                if (current[idx] != null && idx < maxIdx) {
575                    currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]);
576                    for (int x = 0; x < idx; x++) {
577                        if (current[x] != null)
578                            currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x],
579                                    current[idx]);
580                    }
581                }
582            }
583            if (currentDistanceConf < bestDistanceConf)
584                return true;
585            if (bestDistanceConf < currentDistanceConf)
586                return false;
587        }
588
589        // 6. avoid no-time sections
590        int bestNoTime = 0, currentNoTime = 0;
591        for (int idx = 0; idx < current.length; idx++) {
592            if (best[idx] != null) {
593                for (Section section : best[idx].getSections())
594                    if (section.getTime() == null)
595                        bestNoTime++;
596            }
597            if (current[idx] != null && idx < maxIdx) {
598                for (Section section : current[idx].getSections())
599                    if (section.getTime() == null)
600                        currentNoTime++;
601            }
602        }
603        if (currentNoTime < bestNoTime)
604            return true;
605        if (bestNoTime < currentNoTime)
606            return false;
607
608        // 7. balance sections
609        double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0;
610        int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0;
611        for (int idx = 0; idx < current.length; idx++) {
612            if (best[idx] != null) {
613                for (Section section : best[idx].getSections()) {
614                    Subpart subpart = section.getSubpart();
615                    // skip unlimited and single section subparts
616                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
617                        continue;
618                    // average size
619                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
620                    // section is below average
621                    if (section.getLimit() < averageSize)
622                        bestUnavailableSize += (averageSize - section.getLimit()) / averageSize;
623                    bestAltSectionsWithLimit++;
624                }
625            }
626            if (current[idx] != null && idx < maxIdx) {
627                for (Section section : current[idx].getSections()) {
628                    Subpart subpart = section.getSubpart();
629                    // skip unlimited and single section subparts
630                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
631                        continue;
632                    // average size
633                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
634                    // section is below average
635                    if (section.getLimit() < averageSize)
636                        currentUnavailableSize += (averageSize - section.getLimit()) / averageSize;
637                    currentAltSectionsWithLimit++;
638                }
639            }
640        }
641        double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit
642                : 0.0);
643        double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize
644                / currentAltSectionsWithLimit : 0.0);
645        if (currentUnavailableSizeFraction < bestUnavailableSizeFraction)
646            return true;
647        if (bestUnavailableSizeFraction < currentUnavailableSizeFraction)
648            return false;
649
650        // 8. average penalty sections
651        double bestPenalty = 0.0, currentPenalty = 0.0;
652        for (int idx = 0; idx < current.length; idx++) {
653            if (best[idx] != null) {
654                for (Section section : best[idx].getSections())
655                    bestPenalty += section.getPenalty() / best[idx].getSections().size();
656                if (idx >= maxIdx && best[idx].isCourseRequest())
657                    bestPenalty -= ((CourseRequest) best[idx].getRequest()).getMinPenalty();
658            }
659            if (current[idx] != null && idx < maxIdx) {
660                for (Section section : current[idx].getSections())
661                    currentPenalty += section.getPenalty() / current[idx].getSections().size();
662            }
663        }
664        if (currentPenalty < bestPenalty)
665            return true;
666        if (bestPenalty < currentPenalty)
667            return false;
668
669        return true;
670    }
671}