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        for (int idx = 0; idx < current.length; idx++) {
060            if (current[idx] != null && current[idx].getAssignments() != null) {
061                currentAssignedRequests++;
062                if (current[idx].isCourseRequest())
063                    currentAssignedCourseReq++;
064                currentAssignedPriority += current[idx].getPriority() * current[idx].getPriority();
065                currentAssignedAlternativity += (current[idx].getRequest().isAlternative() ? 1 : 0);
066            }
067            if (best[idx] != null && best[idx].getAssignments() != null) {
068                bestAssignedRequests++;
069                if (best[idx].isCourseRequest())
070                    bestAssignedCourseReq++;
071                bestAssignedPriority += best[idx].getPriority() * best[idx].getPriority();
072                bestAssignedAlternativity += (best[idx].getRequest().isAlternative() ? 1 : 0);
073            }
074        }
075        if (currentAssignedCourseReq > bestAssignedCourseReq)
076            return -1;
077        if (bestAssignedCourseReq > currentAssignedCourseReq)
078            return 1;
079        if (currentAssignedPriority < bestAssignedPriority)
080            return -1;
081        if (bestAssignedPriority < currentAssignedPriority)
082            return 1;
083        if (currentAssignedAlternativity < bestAssignedAlternativity)
084            return -1;
085        if (bestAssignedAlternativity < currentAssignedAlternativity)
086            return 1;
087
088        // 0.1. allowed, but not available sections
089        int bestNotAvailable = 0, currentNotAvailable = 0;
090        for (int idx = 0; idx < current.length; idx++) {
091            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) {
092                for (Section section: best[idx].getSections()) {
093                    if (section.getLimit() == 0)
094                        bestNotAvailable ++;
095                }
096            }
097            if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) {
098                for (Section section: current[idx].getSections()) {
099                    if (section.getLimit() == 0)
100                        currentNotAvailable ++;
101                }
102            }
103        }
104        if (bestNotAvailable > currentNotAvailable) return -1;
105        if (bestNotAvailable < currentNotAvailable) return 1;
106
107        // 0.5. avoid course overlaps & unavailabilities
108        if (getModel().getStudentQuality() != null) {
109            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
110            for (int idx = 0; idx < current.length; idx++) {
111                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest) {
112                    for (int x = 0; x < idx; x++) {
113                        if (best[x] != null && best[x].getAssignments() != null && best[x].getRequest() instanceof CourseRequest)
114                            bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, 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 && current[x].getRequest() instanceof CourseRequest)
120                            currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, current[x], current[idx]);
121                    }
122                }
123            }
124            for (int idx = 0; idx < current.length; idx++) {
125                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
126                    bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, best[idx]);
127                }
128                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
129                    currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, current[idx]);
130                }
131            }
132            if (currentTimeOverlaps < bestTimeOverlaps)
133                return -1;
134            if (bestTimeOverlaps < currentTimeOverlaps)
135                return 1;
136        } else if (getModel().getTimeOverlaps() != null) {
137            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
138            for (int idx = 0; idx < current.length; idx++) {
139                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest) {
140                    for (int x = 0; x < idx; x++) {
141                        if (best[x] != null && best[x].getAssignments() != null
142                                        && best[x].getRequest() instanceof CourseRequest)
143                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
144                    }
145                }
146                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest) {
147                    for (int x = 0; x < idx; x++) {
148                        if (current[x] != null && current[x].getAssignments() != null
149                                && current[x].getRequest() instanceof CourseRequest)
150                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
151                    }
152                }
153            }
154            for (int idx = 0; idx < current.length; idx++) {
155                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
156                    bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
157                }
158                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
159                    currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
160                }
161            }
162            if (currentTimeOverlaps < bestTimeOverlaps)
163                return -1;
164            if (bestTimeOverlaps < currentTimeOverlaps)
165                return 1;
166        }
167
168        // 1. minimize number of penalties
169        double bestPenalties = 0, currentPenalties = 0;
170        for (int idx = 0; idx < current.length; idx++) {
171            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
172                for (Section section : best[idx].getSections())
173                    bestPenalties += getModel().getOverExpected(assignment, best, idx, section, best[idx].getRequest());
174            }
175            if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
176                for (Section section : current[idx].getSections())
177                    currentPenalties += getModel().getOverExpected(assignment, current, idx, section, current[idx].getRequest());
178            }
179        }
180        if (currentPenalties < bestPenalties)
181            return -1;
182        if (bestPenalties < currentPenalties)
183            return 1;
184
185        // 2. best number of assigned requests (including free time requests)
186        if (currentAssignedRequests > bestAssignedRequests)
187            return -1;
188        if (bestAssignedRequests > currentAssignedRequests)
189            return 1;
190
191        // 3. maximize selection
192        int bestSelected = 0, currentSelected = 0;
193        for (int idx = 0; idx < current.length; idx++) {
194            Set<Section> preferred = getPreferredSections(getRequest(idx));
195            if (preferred != null && !preferred.isEmpty()) {
196                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
197                    for (Section section : best[idx].getSections())
198                        if (preferred.contains(section))
199                            bestSelected++;
200                }
201                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
202                    for (Section section : current[idx].getSections())
203                        if (preferred.contains(section))
204                            currentSelected++;
205                }
206            }
207        }
208        if (currentSelected > bestSelected)
209            return -1;
210        if (bestSelected > currentSelected)
211            return 1;
212        
213        // 3.5 maximize preferences
214        double bestSelectedConfigs = 0, currentSelectedConfigs = 0;
215        double bestSelectedSections = 0, currentSelectedSections = 0;
216        for (int idx = 0; idx < current.length; idx++) {
217            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
218                bestSelectedSections += best[idx].percentSelectedSameSection();
219                bestSelectedConfigs += best[idx].percentSelectedSameConfig();
220            }
221            if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
222                currentSelectedSections += current[idx].percentSelectedSameSection();
223                currentSelectedConfigs += current[idx].percentSelectedSameConfig();
224            }
225        }
226        if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return -1;
227        if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return 1;
228        
229        // 4-5. student quality
230        if (getModel().getStudentQuality() != null) {
231            double bestQuality = 0, currentQuality = 0;
232            for (StudentQuality.Type type: StudentQuality.Type.values()) {
233                for (int idx = 0; idx < current.length; idx++) {
234                    if (best[idx] != null && best[idx].getAssignments() != null) {
235                        bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[idx]);
236                        for (int x = 0; x < idx; x++) {
237                            if (best[x] != null && best[x].getAssignments() != null)
238                                bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[x], best[idx]);
239                        }
240                    }
241                    if (current[idx] != null && current[idx].getAssignments() != null) {
242                        currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[idx]);
243                        for (int x = 0; x < idx; x++) {
244                            if (current[x] != null && current[x].getAssignments() != null)
245                                currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[x], current[idx]);
246                        }
247                    }
248                }
249            }
250            if (currentQuality < bestQuality)
251                return -1;
252            if (bestQuality < currentQuality)
253                return 1;
254        } else {
255            // 4. avoid time overlaps
256            if (getModel().getTimeOverlaps() != null) {
257                int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
258                for (int idx = 0; idx < current.length; idx++) {
259                    if (best[idx] != null && best[idx].getAssignments() != null) {
260                        for (int x = 0; x < idx; x++) {
261                            if (best[x] != null && best[x].getAssignments() != null)
262                                bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
263                            else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
264                                bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), best[idx]);
265                        }
266                    }
267                    if (current[idx] != null && current[idx].getAssignments() != null) {
268                        for (int x = 0; x < idx; x++) {
269                            if (current[x] != null && current[x].getAssignments() != null)
270                                currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
271                            else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
272                                currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(
273                                        ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
274                                        current[idx]);
275                        }
276                    }
277                }
278                for (int idx = 0; idx < current.length; idx++) {
279                    if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
280                        bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
281                    }
282                    if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
283                        currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
284                    }
285                }
286                if (currentTimeOverlaps < bestTimeOverlaps)
287                    return -1;
288                if (bestTimeOverlaps < currentTimeOverlaps)
289                    return 1;
290            }
291
292            // 5. avoid distance conflicts
293            if (getModel().getDistanceConflict() != null) {
294                int bestDistanceConf = 0, currentDistanceConf = 0;
295                for (int idx = 0; idx < current.length; idx++) {
296                    if (best[idx] != null && best[idx].getAssignments() != null) {
297                        bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]);
298                        for (int x = 0; x < idx; x++) {
299                            if (best[x] != null && best[x].getAssignments() != null)
300                                bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]);
301                        }
302                    }
303                    if (current[idx] != null && current[idx].getAssignments() != null) {
304                        currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]);
305                        for (int x = 0; x < idx; x++) {
306                            if (current[x] != null && current[x].getAssignments() != null)
307                                currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x],
308                                        current[idx]);
309                        }
310                    }
311                }
312                if (currentDistanceConf < bestDistanceConf)
313                    return -1;
314                if (bestDistanceConf < currentDistanceConf)
315                    return 1;
316            }            
317        }
318
319        // 6. avoid no-time sections
320        int bestNoTime = 0, currentNoTime = 0;
321        for (int idx = 0; idx < current.length; idx++) {
322            if (best[idx] != null && best[idx].getAssignments() != null) {
323                for (Section section : best[idx].getSections())
324                    if (section.getTime() == null)
325                        bestNoTime++;
326            }
327            if (current[idx] != null && current[idx].getAssignments() != null) {
328                for (Section section : current[idx].getSections())
329                    if (section.getTime() == null)
330                        currentNoTime++;
331            }
332        }
333        if (currentNoTime < bestNoTime)
334            return -1;
335        if (bestNoTime < currentNoTime)
336            return 1;
337
338        // 7. balance sections
339        double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0;
340        int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0;
341        for (int idx = 0; idx < current.length; idx++) {
342            if (best[idx] != null && best[idx].getAssignments() != null) {
343                for (Section section : best[idx].getSections()) {
344                    Subpart subpart = section.getSubpart();
345                    // skip unlimited and single section subparts
346                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
347                        continue;
348                    // average size
349                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
350                    // section is below average
351                    if (section.getLimit() < averageSize)
352                        bestUnavailableSize += (averageSize - section.getLimit()) / averageSize;
353                    bestAltSectionsWithLimit++;
354                }
355            }
356            if (current[idx] != null && current[idx].getAssignments() != null) {
357                for (Section section : current[idx].getSections()) {
358                    Subpart subpart = section.getSubpart();
359                    // skip unlimited and single section subparts
360                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
361                        continue;
362                    // average size
363                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
364                    // section is below average
365                    if (section.getLimit() < averageSize)
366                        currentUnavailableSize += (averageSize - section.getLimit()) / averageSize;
367                    currentAltSectionsWithLimit++;
368                }
369            }
370        }
371        double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit
372                : 0.0);
373        double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize
374                / currentAltSectionsWithLimit : 0.0);
375        if (currentUnavailableSizeFraction < bestUnavailableSizeFraction)
376            return -1;
377        if (bestUnavailableSizeFraction < currentUnavailableSizeFraction)
378            return 1;
379
380        // 8. average penalty sections
381        double bestPenalty = 0.0, currentPenalty = 0.0;
382        for (int idx = 0; idx < current.length; idx++) {
383            if (best[idx] != null && best[idx].getAssignments() != null) {
384                for (Section section : best[idx].getSections())
385                    bestPenalty += section.getPenalty() / best[idx].getSections().size();
386            }
387            if (current[idx] != null && current[idx].getAssignments() != null) {
388                for (Section section : current[idx].getSections())
389                    currentPenalty += section.getPenalty() / current[idx].getSections().size();
390            }
391        }
392        if (currentPenalty < bestPenalty)
393            return -1;
394        if (bestPenalty < currentPenalty)
395            return 1;
396
397        return 0;
398    }
399
400    @Override
401    public boolean canImprove(Assignment<Request, Enrollment> assignment, int maxIdx, Enrollment[] current,
402            Enrollment[] best) {
403        // 0. best number of assigned course requests (including alternativity &
404        // priority)
405        int currentAssignedCourseReq = 0, bestAssignedCourseReq = 0;
406        int currentAssignedRequests = 0, bestAssignedRequests = 0;
407        int currentAssignedPriority = 0, bestAssignedPriority = 0;
408        int currentAssignedAlternativity = 0, bestAssignedAlternativity = 0;
409        int alt = 0;
410        for (int idx = 0; idx < current.length; idx++) {
411            if (idx < maxIdx) {
412                if (current[idx] != null && current[idx].getAssignments() != null) {
413                    currentAssignedRequests++;
414                    if (current[idx].isCourseRequest())
415                        currentAssignedCourseReq++;
416                    currentAssignedPriority += current[idx].getPriority() * current[idx].getPriority();
417                    currentAssignedAlternativity += (current[idx].getRequest().isAlternative() ? 1 : 0);
418                } else if (!isFreeTime(idx) && !getRequest(idx).isAlternative()) {
419                    alt++;
420                }
421            } else {
422                if (!getRequest(idx).isAlternative()) {
423                    currentAssignedRequests++;
424                    if (!isFreeTime(idx))
425                        currentAssignedCourseReq++;
426                } else if (alt > 0) {
427                    currentAssignedRequests++;
428                    currentAssignedCourseReq++;
429                    alt--;
430                    currentAssignedAlternativity++;
431                }
432            }
433            if (best[idx] != null && best[idx].getAssignments() != null) {
434                bestAssignedRequests++;
435                if (best[idx].isCourseRequest())
436                    bestAssignedCourseReq++;
437                bestAssignedPriority += best[idx].getPriority() * best[idx].getPriority();
438                bestAssignedAlternativity += (best[idx].getRequest().isAlternative() ? 1 : 0);
439            }
440        }
441        if (currentAssignedCourseReq > bestAssignedCourseReq)
442            return true;
443        if (bestAssignedCourseReq > currentAssignedCourseReq)
444            return false;
445        if (currentAssignedPriority < bestAssignedPriority)
446            return true;
447        if (bestAssignedPriority < currentAssignedPriority)
448            return false;
449        if (currentAssignedAlternativity < bestAssignedAlternativity)
450            return true;
451        if (bestAssignedAlternativity < currentAssignedAlternativity)
452            return false;
453
454        // 0.1. allowed, but not available sections
455        int notAvailable = 0;
456        for (int idx = 0; idx < current.length; idx++) {
457            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) {
458                for (Section section: best[idx].getSections()) {
459                    if (section.getLimit() == 0)
460                        notAvailable ++;
461                }
462            }
463            if (idx < maxIdx && current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) {
464                for (Section section: current[idx].getSections()) {
465                    if (section.getLimit() == 0)
466                        notAvailable --;
467                }
468            }
469        }
470        if (notAvailable > 0) {
471            return true;
472        }
473
474        // 0.5. avoid course time overlaps & unavailabilities
475        if (getModel().getStudentQuality() != null) {
476            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
477            for (int idx = 0; idx < current.length; idx++) {
478                if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) {
479                    for (int x = 0; x < idx; x++) {
480                        if (best[x] != null && best[x].getRequest() instanceof CourseRequest)
481                            bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, best[x], best[idx]);
482                    }
483                }
484                if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) {
485                    for (int x = 0; x < idx; x++) {
486                        if (current[x] != null && current[x].getRequest() instanceof CourseRequest)
487                            currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, current[x], current[idx]);
488                    }
489                }
490            }
491            for (int idx = 0; idx < current.length; idx++) {
492                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
493                    bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, best[idx]);
494                }
495                if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
496                    currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, current[idx]);
497                }
498            }
499            if (currentTimeOverlaps < bestTimeOverlaps)
500                return true;
501            if (bestTimeOverlaps < currentTimeOverlaps)
502                return false;
503        } else if (getModel().getTimeOverlaps() != null) {
504            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
505            for (int idx = 0; idx < current.length; idx++) {
506                if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) {
507                    for (int x = 0; x < idx; x++) {
508                        if (best[x] != null && best[x].getRequest() instanceof CourseRequest)
509                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
510                    }
511                }
512                if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) {
513                    for (int x = 0; x < idx; x++) {
514                        if (current[x] != null && current[x].getRequest() instanceof CourseRequest)
515                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
516                    }
517                }
518            }
519            for (int idx = 0; idx < current.length; idx++) {
520                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
521                    bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
522                }
523                if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
524                    currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
525                }
526            }
527            if (currentTimeOverlaps < bestTimeOverlaps)
528                return true;
529            if (bestTimeOverlaps < currentTimeOverlaps)
530                return false;
531        }
532
533        // 1. maximize number of penalties
534        double bestPenalties = 0, currentPenalties = 0;
535        for (int idx = 0; idx < current.length; idx++) {
536            if (best[idx] != null) {
537                for (Section section : best[idx].getSections())
538                    bestPenalties += getModel().getOverExpected(assignment, best, idx, section, best[idx].getRequest());
539            }
540            if (current[idx] != null && idx < maxIdx) {
541                for (Section section : current[idx].getSections())
542                    currentPenalties += getModel().getOverExpected(assignment, current, idx, section, current[idx].getRequest());
543            }
544        }
545        if (currentPenalties < bestPenalties)
546            return true;
547        if (bestPenalties < currentPenalties)
548            return false;
549
550        // 2. best number of assigned requests (including free time requests)
551        if (currentAssignedRequests > bestAssignedRequests)
552            return true;
553        if (bestAssignedRequests > currentAssignedRequests)
554            return false;
555
556        // 3. maximize selection
557        int bestSelected = 0, currentSelected = 0;
558        for (int idx = 0; idx < current.length; idx++) {
559            if (best[idx] != null && best[idx].isCourseRequest()) {
560                Set<Section> preferred = getPreferredSections(best[idx].getRequest());
561                if (preferred != null && !preferred.isEmpty()) {
562                    for (Section section : best[idx].getSections())
563                        if (preferred.contains(section)) {
564                            if (idx < maxIdx)
565                                bestSelected++;
566                        } else if (idx >= maxIdx)
567                            bestSelected--;
568                }
569            }
570            if (current[idx] != null && idx < maxIdx && current[idx].isCourseRequest()) {
571                Set<Section> preferred = getPreferredSections(current[idx].getRequest());
572                if (preferred != null && !preferred.isEmpty()) {
573                    for (Section section : current[idx].getSections())
574                        if (preferred.contains(section))
575                            currentSelected++;
576                }
577            }
578        }
579        if (currentSelected > bestSelected)
580            return true;
581        if (bestSelected > currentSelected)
582            return false;
583        
584        // 3.5 maximize preferences
585        double bestSelectedConfigs = 0, currentSelectedConfigs = 0;
586        double bestSelectedSections = 0, currentSelectedSections = 0;
587        for (int idx = 0; idx < current.length; idx++) {
588            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
589                bestSelectedSections += best[idx].percentSelectedSameSection();
590                bestSelectedConfigs += best[idx].percentSelectedSameConfig();
591                if (idx >= maxIdx) {
592                    bestSelectedSections -= 1.0;
593                    bestSelectedConfigs -= 1.0;
594                }
595            }
596            if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
597                currentSelectedSections += current[idx].percentSelectedSameSection();
598                currentSelectedConfigs += current[idx].percentSelectedSameConfig();
599            }
600        }
601        if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return true;
602        if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return false;
603        
604        // 4-5. solution quality
605        if (getModel().getStudentQuality() != null) {
606            double bestQuality = 0, currentQuality = 0;
607            for (StudentQuality.Type type: StudentQuality.Type.values()) {
608                for (int idx = 0; idx < current.length; idx++) {
609                    if (best[idx] != null) {
610                        bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[idx]);
611                        for (int x = 0; x < idx; x++) {
612                            if (best[x] != null)
613                                bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[x], best[idx]);
614                        }
615                    }
616                    if (current[idx] != null && idx < maxIdx) {
617                        currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[idx]);
618                        for (int x = 0; x < idx; x++) {
619                            if (current[x] != null)
620                                currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[x], current[idx]);
621                        }
622                    }
623                }
624            }
625            if (currentQuality < bestQuality)
626                return true;
627            if (bestQuality < currentQuality)
628                return false;
629        } else {
630            // 4. avoid time overlaps
631            if (getModel().getTimeOverlaps() != null) {
632                int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
633                for (int idx = 0; idx < current.length; idx++) {
634                    if (best[idx] != null) {
635                        for (int x = 0; x < idx; x++) {
636                            if (best[x] != null)
637                                bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
638                            else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
639                                bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), best[idx]);
640                        }
641                    }
642                    if (current[idx] != null && idx < maxIdx) {
643                        for (int x = 0; x < idx; x++) {
644                            if (current[x] != null)
645                                currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
646                            else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
647                                currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(
648                                        ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
649                                        current[idx]);
650                        }
651                    }
652                }
653                for (int idx = 0; idx < current.length; idx++) {
654                    if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
655                        bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
656                    }
657                    if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
658                        currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
659                    }
660                }
661                if (currentTimeOverlaps < bestTimeOverlaps)
662                    return true;
663                if (bestTimeOverlaps < currentTimeOverlaps)
664                    return false;
665            }
666
667            // 5. avoid distance conflicts
668            if (getModel().getDistanceConflict() != null) {
669                int bestDistanceConf = 0, currentDistanceConf = 0;
670                for (int idx = 0; idx < current.length; idx++) {
671                    if (best[idx] != null) {
672                        bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]);
673                        for (int x = 0; x < idx; x++) {
674                            if (best[x] != null)
675                                bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]);
676                        }
677                    }
678                    if (current[idx] != null && idx < maxIdx) {
679                        currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]);
680                        for (int x = 0; x < idx; x++) {
681                            if (current[x] != null)
682                                currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x],
683                                        current[idx]);
684                        }
685                    }
686                }
687                if (currentDistanceConf < bestDistanceConf)
688                    return true;
689                if (bestDistanceConf < currentDistanceConf)
690                    return false;
691            }
692        }
693
694        // 6. avoid no-time sections
695        int bestNoTime = 0, currentNoTime = 0;
696        for (int idx = 0; idx < current.length; idx++) {
697            if (best[idx] != null) {
698                for (Section section : best[idx].getSections())
699                    if (section.getTime() == null)
700                        bestNoTime++;
701            }
702            if (current[idx] != null && idx < maxIdx) {
703                for (Section section : current[idx].getSections())
704                    if (section.getTime() == null)
705                        currentNoTime++;
706            }
707        }
708        if (currentNoTime < bestNoTime)
709            return true;
710        if (bestNoTime < currentNoTime)
711            return false;
712
713        // 7. balance sections
714        double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0;
715        int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0;
716        for (int idx = 0; idx < current.length; idx++) {
717            if (best[idx] != null) {
718                for (Section section : best[idx].getSections()) {
719                    Subpart subpart = section.getSubpart();
720                    // skip unlimited and single section subparts
721                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
722                        continue;
723                    // average size
724                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
725                    // section is below average
726                    if (section.getLimit() < averageSize)
727                        bestUnavailableSize += (averageSize - section.getLimit()) / averageSize;
728                    bestAltSectionsWithLimit++;
729                }
730            }
731            if (current[idx] != null && idx < maxIdx) {
732                for (Section section : current[idx].getSections()) {
733                    Subpart subpart = section.getSubpart();
734                    // skip unlimited and single section subparts
735                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
736                        continue;
737                    // average size
738                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
739                    // section is below average
740                    if (section.getLimit() < averageSize)
741                        currentUnavailableSize += (averageSize - section.getLimit()) / averageSize;
742                    currentAltSectionsWithLimit++;
743                }
744            }
745        }
746        double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit
747                : 0.0);
748        double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize
749                / currentAltSectionsWithLimit : 0.0);
750        if (currentUnavailableSizeFraction < bestUnavailableSizeFraction)
751            return true;
752        if (bestUnavailableSizeFraction < currentUnavailableSizeFraction)
753            return false;
754
755        // 8. average penalty sections
756        double bestPenalty = 0.0, currentPenalty = 0.0;
757        for (int idx = 0; idx < current.length; idx++) {
758            if (best[idx] != null) {
759                for (Section section : best[idx].getSections())
760                    bestPenalty += section.getPenalty() / best[idx].getSections().size();
761                if (idx >= maxIdx && best[idx].isCourseRequest())
762                    bestPenalty -= ((CourseRequest) best[idx].getRequest()).getMinPenalty();
763            }
764            if (current[idx] != null && idx < maxIdx) {
765                for (Section section : current[idx].getSections())
766                    currentPenalty += section.getPenalty() / current[idx].getSections().size();
767            }
768        }
769        if (currentPenalty < bestPenalty)
770            return true;
771        if (bestPenalty < currentPenalty)
772            return false;
773
774        return true;
775    }
776}