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