001package org.cpsolver.studentsct.online.selection;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.Hashtable;
006import java.util.List;
007import java.util.Set;
008
009import org.cpsolver.coursett.model.TimeLocation;
010import org.cpsolver.ifs.assignment.Assignment;
011import org.cpsolver.studentsct.extension.DistanceConflict;
012import org.cpsolver.studentsct.extension.StudentQuality;
013import org.cpsolver.studentsct.extension.TimeOverlapsCounter;
014import org.cpsolver.studentsct.model.CourseRequest;
015import org.cpsolver.studentsct.model.Enrollment;
016import org.cpsolver.studentsct.model.FreeTimeRequest;
017import org.cpsolver.studentsct.model.Request;
018import org.cpsolver.studentsct.model.Section;
019import org.cpsolver.studentsct.model.Student;
020import org.cpsolver.studentsct.model.Subpart;
021import org.cpsolver.studentsct.model.Unavailability;
022import org.cpsolver.studentsct.online.OnlineSectioningModel;
023import org.cpsolver.studentsct.online.selection.MultiCriteriaBranchAndBoundSelection.SelectionCriterion;
024import org.cpsolver.studentsct.weights.StudentWeights;
025
026/**
027* Multi-criteria selection criterion. This provides a lexicographical order of solutions using the
028* following criteria:
029* <ul>
030* <li>best priority & alternativity ignoring free time requests (a better solution has a higher priority course assigned or does not use alternative request if possible)
031* <li>avoid or minimize course time overlaps
032* <li>minimise use of over-expected classes (this prevents students of getting into classes that we know will be needed later in the process)
033* <li>best priority & alternativity including free time requests (this is to prevent students of gaming the system by adding free time requests)
034* <li>maximise selection (preferred sections)
035* <li>avoid or minimise time overlaps (for classes that are allowed to overlap and for free time requests)
036* <li>avoid or minimise distance conflicts
037* <li>avoid classes that have no time assignment (arranged hours)
038* <li>balance sections
039* <li>minimise class penalties (expressing how much a class is over-expected)
040* </ul>
041* 
042* @version StudentSct 1.3 (Student Sectioning)<br>
043*          Copyright (C) 2014 Tomáš Müller<br>
044*          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
045*          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
046* <br>
047*          This library is free software; you can redistribute it and/or modify
048*          it under the terms of the GNU Lesser General Public License as
049*          published by the Free Software Foundation; either version 3 of the
050*          License, or (at your option) any later version. <br>
051* <br>
052*          This library is distributed in the hope that it will be useful, but
053*          WITHOUT ANY WARRANTY; without even the implied warranty of
054*          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
055*          Lesser General Public License for more details. <br>
056* <br>
057*          You should have received a copy of the GNU Lesser General Public
058*          License along with this library; if not see <a
059*          href='http://www.gnu.org/licenses'>http://www.gnu.org/licenses</a>.
060* 
061*/
062public class OnlineSectioningCriterion implements SelectionCriterion {
063    private Hashtable<CourseRequest, Set<Section>> iPreferredSections = null;
064    private List<TimeToAvoid> iTimesToAvoid = null;
065    private OnlineSectioningModel iModel;
066    private Student iStudent;
067    protected double[] iQalityWeights;
068
069    /**
070     * Constructor
071     * @param student current student
072     * @param model online student sectioning model
073     * @param assignment current assignment
074     * @param preferredSections preferred sections
075     */
076    public OnlineSectioningCriterion(Student student, OnlineSectioningModel model,
077            Assignment<Request, Enrollment> assignment, Hashtable<CourseRequest, Set<Section>> preferredSections) {
078        iStudent = student;
079        iModel = model;
080        iPreferredSections = preferredSections;
081        if (model.getProperties().getPropertyBoolean("OnlineStudentSectioning.TimesToAvoidHeuristics", true)) {
082            iTimesToAvoid = new ArrayList<TimeToAvoid>();
083            for (Request r : iStudent.getRequests()) {
084                if (r instanceof CourseRequest) {
085                    List<Enrollment> enrollments = ((CourseRequest) r).getAvaiableEnrollmentsSkipSameTime(assignment);
086                    if (enrollments.size() <= 5) {
087                        int penalty = (7 - enrollments.size()) * (r.isAlternative() ? 1 : 7 - enrollments.size());
088                        for (Enrollment enrollment : enrollments)
089                            for (Section section : enrollment.getSections())
090                                if (section.getTime() != null)
091                                    iTimesToAvoid.add(new TimeToAvoid(section.getTime(), penalty, r.getPriority()));
092                    }
093                } else if (r instanceof FreeTimeRequest) {
094                    iTimesToAvoid.add(new TimeToAvoid(((FreeTimeRequest) r).getTime(), 1, Integer.MAX_VALUE));
095                }
096            }
097            for (Unavailability unavailability: iStudent.getUnavailabilities())
098                if (unavailability.getTime() != null)
099                    iTimesToAvoid.add(new TimeToAvoid(unavailability.getTime(), 1, Integer.MAX_VALUE));
100        }
101        iQalityWeights = new double[StudentQuality.Type.values().length];
102        for (StudentQuality.Type type: StudentQuality.Type.values()) {
103            iQalityWeights[type.ordinal()] = model.getProperties().getPropertyDouble(type.getWeightName(), type.getWeightDefault());
104        }
105    }
106
107    protected OnlineSectioningModel getModel() {
108        return iModel;
109    }
110
111    protected Student getStudent() {
112        return iStudent;
113    }
114
115    protected Set<Section> getPreferredSections(Request request) {
116        return iPreferredSections.get(request);
117    }
118
119    protected List<TimeToAvoid> getTimesToAvoid() {
120        return iTimesToAvoid;
121    }
122
123    /**
124     * Distance conflicts of idx-th assignment of the current schedule
125     */
126    public Set<DistanceConflict.Conflict> getDistanceConflicts(Enrollment[] assignment, int idx) {
127        if (getModel().getDistanceConflict() == null || assignment[idx] == null)
128            return null;
129        Set<DistanceConflict.Conflict> dist = getModel().getDistanceConflict().conflicts(assignment[idx]);
130        for (int x = 0; x < idx; x++)
131            if (assignment[x] != null)
132                dist.addAll(getModel().getDistanceConflict().conflicts(assignment[x], assignment[idx]));
133        return dist;
134    }
135
136    /**
137     * Time overlapping conflicts of idx-th assignment of the current schedule
138     */
139    public Set<TimeOverlapsCounter.Conflict> getTimeOverlappingConflicts(Enrollment[] assignment, int idx) {
140        if (getModel().getTimeOverlaps() == null || assignment[idx] == null)
141            return null;
142        Set<TimeOverlapsCounter.Conflict> overlaps = new HashSet<TimeOverlapsCounter.Conflict>();
143        for (int x = 0; x < idx; x++)
144            if (assignment[x] != null) {
145                overlaps.addAll(getModel().getTimeOverlaps().conflicts(assignment[x], assignment[idx]));
146            } else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
147                overlaps.addAll(getModel().getTimeOverlaps().conflicts(
148                        ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), assignment[idx]));
149        overlaps.addAll(getModel().getTimeOverlaps().notAvailableTimeConflicts(assignment[idx]));
150        return overlaps;
151    }
152    
153    public Set<StudentQuality.Conflict> getStudentQualityConflicts(Enrollment[] assignment, int idx) {
154        if (getModel().getStudentQuality() == null || assignment[idx] == null)
155            return null;
156        Set<StudentQuality.Conflict> conflicts = new HashSet<StudentQuality.Conflict>();
157        for (StudentQuality.Type t: StudentQuality.Type.values()) {
158            for (int x = 0; x < idx; x++)
159                if (assignment[x] != null)
160                    conflicts.addAll(getModel().getStudentQuality().conflicts(t, assignment[x], assignment[idx]));
161            conflicts.addAll(getModel().getStudentQuality().conflicts(t, assignment[idx]));
162        }
163        return conflicts;
164    }
165
166    /**
167     * Weight of an assignment. Unlike
168     * {@link StudentWeights#getWeight(Assignment, Enrollment, Set, Set)}, only count this
169     * side of distance conflicts and time overlaps.
170     **/
171    @Deprecated
172    protected double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment,
173            Set<DistanceConflict.Conflict> distanceConflicts, Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts) {
174        double weight = -getModel().getStudentWeights().getWeight(assignment, enrollment);
175        if (distanceConflicts != null)
176            for (DistanceConflict.Conflict c : distanceConflicts) {
177                Enrollment other = (c.getE1().equals(enrollment) ? c.getE2() : c.getE1());
178                if (other.getRequest().getPriority() <= enrollment.getRequest().getPriority())
179                    weight += getModel().getStudentWeights().getDistanceConflictWeight(assignment, c);
180            }
181        if (timeOverlappingConflicts != null)
182            for (TimeOverlapsCounter.Conflict c : timeOverlappingConflicts) {
183                weight += getModel().getStudentWeights().getTimeOverlapConflictWeight(assignment, enrollment, c);
184            }
185        return enrollment.getRequest().getWeight() * weight;
186    }
187    
188    protected double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<StudentQuality.Conflict> conflicts) {
189        double weight = -getModel().getStudentWeights().getWeight(assignment, enrollment);
190        if (conflicts != null)
191            for (StudentQuality.Conflict c : conflicts) {
192                weight += getModel().getStudentWeights().getStudentQualityConflictWeight(assignment, enrollment, c);
193            }
194        return enrollment.getRequest().getWeight() * weight;
195    }
196
197    public Request getRequest(int index) {
198        return (index < 0 || index >= getStudent().getRequests().size() ? null : getStudent().getRequests().get(index));
199    }
200
201    public boolean isFreeTime(int index) {
202        Request r = getRequest(index);
203        return r != null && r instanceof FreeTimeRequest;
204    }
205    
206    @Override
207    public int compare(Assignment<Request, Enrollment> assignment, Enrollment[] current, Enrollment[] best) {
208        if (best == null)
209            return -1;
210
211        // 0. best priority & alternativity ignoring free time requests
212        boolean ft = false;
213        boolean res = false;
214        for (int idx = 0; idx < current.length; idx++) {
215            if (isFreeTime(idx)) {
216                ft = true;
217                continue;
218            }
219            Request request = getRequest(idx);
220            if (request instanceof CourseRequest && ((CourseRequest)request).hasReservations()) res = true;
221            if (best[idx] != null && best[idx].getAssignments() != null) {
222                if (current[idx] == null || current[idx].getSections() == null)
223                    return 1; // higher priority request assigned
224                if (best[idx].getTruePriority() < current[idx].getTruePriority())
225                    return 1; // less alternative request assigned
226                if (best[idx].getTruePriority() > current[idx].getTruePriority())
227                    return -1; // less alternative request assigned
228            } else {
229                if (current[idx] != null && current[idx].getAssignments() != null)
230                    return -1; // higher priority request assigned
231            }
232        }
233        
234        // 0.1. allowed, but not available sections
235        int bestNotAvailable = 0, currentNotAvailable = 0;
236        for (int idx = 0; idx < current.length; idx++) {
237            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) {
238                for (Section section: best[idx].getSections()) {
239                    if (section.getLimit() == 0)
240                        bestNotAvailable ++;
241                }
242            }
243            if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) {
244                for (Section section: current[idx].getSections()) {
245                    if (section.getLimit() == 0)
246                        currentNotAvailable ++;
247                }
248            }
249        }
250        if (bestNotAvailable > currentNotAvailable) return -1;
251        if (bestNotAvailable < currentNotAvailable) return 1;
252
253        // 0.5. avoid course time overlaps & unavailabilities
254        if (getModel().getStudentQuality() != null) {
255            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
256            for (int idx = 0; idx < current.length; idx++) {
257                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest) {
258                    for (int x = 0; x < idx; x++) {
259                        if (best[x] != null && best[x].getAssignments() != null && best[x].getRequest() instanceof CourseRequest)
260                            bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, best[x], best[idx]);
261                    }
262                }
263                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest) {
264                    for (int x = 0; x < idx; x++) {
265                        if (current[x] != null && current[x].getAssignments() != null && current[x].getRequest() instanceof CourseRequest)
266                            currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, current[x], current[idx]);
267                    }
268                }
269            }
270            for (int idx = 0; idx < current.length; idx++) {
271                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
272                    bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, best[idx]);
273                }
274                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
275                    currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, current[idx]);
276                }
277            }
278            if (currentTimeOverlaps < bestTimeOverlaps)
279                return -1;
280            if (bestTimeOverlaps < currentTimeOverlaps)
281                return 1;
282        } else if (getModel().getTimeOverlaps() != null) {
283            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
284            for (int idx = 0; idx < current.length; idx++) {
285                if (best[idx] != null && best[idx].getAssignments() != null
286                                && best[idx].getRequest() instanceof CourseRequest) {
287                    for (int x = 0; x < idx; x++) {
288                        if (best[x] != null && best[x].getAssignments() != null
289                                        && best[x].getRequest() instanceof CourseRequest)
290                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
291                    }
292                    for (int x = 0; x < idx; x++) {
293                        if (current[x] != null && current[x].getAssignments() != null
294                                        && current[x].getRequest() instanceof CourseRequest)
295                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
296                    }
297                }
298            }
299            for (int idx = 0; idx < current.length; idx++) {
300                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
301                    bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
302                }
303                if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
304                    currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
305                }
306            }
307            if (currentTimeOverlaps < bestTimeOverlaps)
308                return -1;
309            if (bestTimeOverlaps < currentTimeOverlaps)
310                return 1;
311        }
312
313        // 1. minimize number of penalties
314        double bestPenalties = 0, currentPenalties = 0;
315        for (int idx = 0; idx < current.length; idx++) {
316            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
317                for (Section section : best[idx].getSections())
318                    bestPenalties += getModel().getOverExpected(assignment, best, idx, section, best[idx].getRequest());
319                for (Section section : current[idx].getSections())
320                    currentPenalties += getModel().getOverExpected(assignment, current, idx, section, current[idx].getRequest());
321            }
322        }
323        if (currentPenalties < bestPenalties)
324            return -1;
325        if (bestPenalties < currentPenalties)
326            return 1;
327
328        // 2. best priority & alternativity including free time requests
329        if (ft) {
330            for (int idx = 0; idx < current.length; idx++) {
331                if (best[idx] != null && best[idx].getAssignments() != null) {
332                    if (current[idx] == null || current[idx].getSections() == null)
333                        return 1; // higher priority request assigned
334                    if (best[idx].getTruePriority() < current[idx].getTruePriority())
335                        return 1; // less alternative request assigned
336                    if (best[idx].getTruePriority() > current[idx].getTruePriority())
337                        return -1; // less alternative request assigned
338                } else {
339                    if (current[idx] != null && current[idx].getAssignments() != null)
340                        return -1; // higher priority request assigned
341                }
342            }
343        }
344
345        // 3. maximize selection
346        int bestSelected = 0, currentSelected = 0;
347        for (int idx = 0; idx < current.length; idx++) {
348            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
349                Set<Section> preferred = getPreferredSections(best[idx].getRequest());
350                if (preferred != null && !preferred.isEmpty()) {
351                    for (Section section : best[idx].getSections())
352                        if (preferred.contains(section))
353                            bestSelected++;
354                    for (Section section : current[idx].getSections())
355                        if (preferred.contains(section))
356                            currentSelected++;
357                }
358            }
359        }
360        if (currentSelected > bestSelected)
361            return -1;
362        if (bestSelected > currentSelected)
363            return 1;
364        
365        // 3.5 maximize preferences
366        double bestSelectedConfigs = 0, currentSelectedConfigs = 0;
367        double bestSelectedSections = 0, currentSelectedSections = 0;
368        for (int idx = 0; idx < current.length; idx++) {
369            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
370                bestSelectedSections += best[idx].percentSelectedSameSection();
371                bestSelectedConfigs += best[idx].percentSelectedSameConfig();
372            }
373            if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
374                currentSelectedSections += current[idx].percentSelectedSameSection();
375                currentSelectedConfigs += current[idx].percentSelectedSameConfig();
376            }
377        }
378        if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return -1;
379        if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return 1;
380        
381        // 3.9 maximize selection with penalization for not followed reservations
382        if (res) {
383            for (int idx = 0; idx < current.length; idx++) {
384                if (best[idx] != null && best[idx].getAssignments() != null) {
385                    if (current[idx] == null || current[idx].getSections() == null)
386                        return 1; // higher priority request assigned
387                    if (best[idx].getAdjustedPriority() < current[idx].getAdjustedPriority())
388                        return 1; // less alternative request assigned
389                    if (best[idx].getAdjustedPriority() > current[idx].getAdjustedPriority())
390                        return -1; // less alternative request assigned
391                } else {
392                    if (current[idx] != null && current[idx].getAssignments() != null)
393                        return -1; // higher priority request assigned
394                }
395            }
396        }
397        
398        // 3.95 avoid past sections
399        double bestPast = 0.0, currentPast = 0.0;
400        for (int idx = 0; idx < current.length; idx++) {
401            if (best[idx] != null && best[idx].getAssignments() != null) {
402                for (Section section : best[idx].getSections()) {
403                    if (section.isPast())
404                        bestPast += 1.0 / best[idx].getSections().size();
405                }
406            }
407            if (current[idx] != null && current[idx].getAssignments() != null) {
408                for (Section section : current[idx].getSections()) {
409                    if (section.isPast())
410                        currentPast += 1.0 / current[idx].getSections().size();
411                }
412            }
413        }
414        if (Math.abs(currentPast - bestPast) > 0.0001) {
415            if (currentPast < bestPast)
416                return -1;
417            if (bestPast < currentPast)
418                return 1;
419        }
420
421        // 4-5. student quality
422        if (getModel().getStudentQuality() != null) {
423            double bestQuality = 0, currentQuality = 0;
424            for (StudentQuality.Type type: StudentQuality.Type.values()) {
425                for (int idx = 0; idx < current.length; idx++) {
426                    if (best[idx] != null && best[idx].getAssignments() != null) {
427                        bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[idx]);
428                        for (int x = 0; x < idx; x++) {
429                            if (best[x] != null && best[x].getAssignments() != null)
430                                bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[x], best[idx]);
431                        }
432                    }
433                    if (current[idx] != null && current[idx].getAssignments() != null) {
434                        currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[idx]);
435                        for (int x = 0; x < idx; x++) {
436                            if (current[x] != null && current[x].getAssignments() != null)
437                                currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[x], current[idx]);
438                        }
439                    }
440                }
441            }
442            if (currentQuality < bestQuality)
443                return -1;
444            if (bestQuality < currentQuality)
445                return 1;
446        } else {
447            // 4. avoid time overlaps
448            if (getModel().getTimeOverlaps() != null) {
449                int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
450                for (int idx = 0; idx < current.length; idx++) {
451                    if (best[idx] != null && best[idx].getAssignments() != null) {
452                        for (int x = 0; x < idx; x++) {
453                            if (best[x] != null && best[x].getAssignments() != null)
454                                bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
455                            else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
456                                bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), best[idx]);
457                        }
458                        for (int x = 0; x < idx; x++) {
459                            if (current[x] != null && current[x].getAssignments() != null)
460                                currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
461                            else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
462                                currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(), current[idx]);
463                        }
464                    }
465                }
466                for (int idx = 0; idx < current.length; idx++) {
467                    if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
468                        bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
469                    }
470                    if (current[idx] != null && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
471                        currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
472                    }
473                }
474                if (currentTimeOverlaps < bestTimeOverlaps)
475                    return -1;
476                if (bestTimeOverlaps < currentTimeOverlaps)
477                    return 1;
478            }
479
480            // 5. avoid distance conflicts
481            if (getModel().getDistanceConflict() != null) {
482                int bestDistanceConf = 0, currentDistanceConf = 0;
483                for (int idx = 0; idx < current.length; idx++) {
484                    if (best[idx] != null && best[idx].getAssignments() != null) {
485                        bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]);
486                        for (int x = 0; x < idx; x++) {
487                            if (best[x] != null && best[x].getAssignments() != null)
488                                bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]);
489                        }
490                    }
491                    if (current[idx] != null && current[idx].getAssignments() != null) {
492                        currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]);
493                        for (int x = 0; x < idx; x++) {
494                            if (current[x] != null && current[x].getAssignments() != null)
495                                currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x], current[idx]);
496                        }
497                    }
498                }
499                if (currentDistanceConf < bestDistanceConf)
500                    return -1;
501                if (bestDistanceConf < currentDistanceConf)
502                    return 1;
503            }
504        }
505
506        // 6. avoid no-time and online sections (no-time first, online second)
507        int bestNoTime = 0, currentNoTime = 0;
508        int bestOnline = 0, currentOnline = 0;
509        for (int idx = 0; idx < current.length; idx++) {
510            if (best[idx] != null && best[idx].getAssignments() != null) {
511                for (Section section : best[idx].getSections()) {
512                    if (!section.hasTime())
513                        bestNoTime++;
514                    if (section.isOnline())
515                        bestOnline++;
516                }
517                for (Section section : current[idx].getSections()) {
518                    if (!section.hasTime())
519                        currentNoTime++;
520                    if (section.isOnline())
521                        currentOnline++;
522                }
523            }
524        }
525        if (currentNoTime < bestNoTime)
526            return -1;
527        if (bestNoTime < currentNoTime)
528            return 1;
529        if (currentOnline < bestOnline)
530            return -1;
531        if (bestOnline < currentOnline)
532            return 1;
533
534        // 7. balance sections
535        double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0;
536        int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0;
537        for (int idx = 0; idx < current.length; idx++) {
538            if (best[idx] != null && best[idx].getAssignments() != null) {
539                for (Section section : best[idx].getSections()) {
540                    Subpart subpart = section.getSubpart();
541                    // skip unlimited and single section subparts
542                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
543                        continue;
544                    // average size
545                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
546                    // section is below average
547                    if (section.getLimit() < averageSize)
548                        bestUnavailableSize += (averageSize - section.getLimit()) / averageSize;
549                    bestAltSectionsWithLimit++;
550                }
551                for (Section section : current[idx].getSections()) {
552                    Subpart subpart = section.getSubpart();
553                    // skip unlimited and single section subparts
554                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
555                        continue;
556                    // average size
557                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
558                    // section is below average
559                    if (section.getLimit() < averageSize)
560                        currentUnavailableSize += (averageSize - section.getLimit()) / averageSize;
561                    currentAltSectionsWithLimit++;
562                }
563            }
564        }
565        double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit
566                : 0.0);
567        double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize
568                / currentAltSectionsWithLimit : 0.0);
569        if (currentUnavailableSizeFraction < bestUnavailableSizeFraction)
570            return -1;
571        if (bestUnavailableSizeFraction < currentUnavailableSizeFraction)
572            return 1;
573
574        // 8. average penalty sections
575        double bestPenalty = 0.0, currentPenalty = 0.0;
576        for (int idx = 0; idx < current.length; idx++) {
577            if (best[idx] != null && best[idx].getAssignments() != null) {
578                for (Section section : best[idx].getSections())
579                    bestPenalty += section.getPenalty() / best[idx].getSections().size();
580                for (Section section : current[idx].getSections())
581                    currentPenalty += section.getPenalty() / current[idx].getSections().size();
582            }
583        }
584        if (currentPenalty < bestPenalty)
585            return -1;
586        if (bestPenalty < currentPenalty)
587            return 1;
588
589        return 0;
590    }
591
592    @Override
593    public boolean canImprove(Assignment<Request, Enrollment> assignment, int maxIdx, Enrollment[] current,
594            Enrollment[] best) {
595        // 0. best priority & alternativity ignoring free time requests
596        int alt = 0;
597        boolean ft = false;
598        boolean res = false;
599        for (int idx = 0; idx < current.length; idx++) {
600            if (isFreeTime(idx)) {
601                ft = true;
602                continue;
603            }
604            Request request = getRequest(idx);
605            if (request instanceof CourseRequest && ((CourseRequest)request).hasReservations()) res = true;
606            if (idx < maxIdx) {
607                if (best[idx] != null) {
608                    if (current[idx] == null)
609                        return false; // higher priority request assigned
610                    if (best[idx].getTruePriority() < current[idx].getTruePriority())
611                        return false; // less alternative request assigned
612                    if (best[idx].getTruePriority() > current[idx].getTruePriority())
613                        return true; // less alternative request assigned
614                    if (request.isAlternative())
615                        alt--;
616                } else {
617                    if (current[idx] != null)
618                        return true; // higher priority request assigned
619                    if (!request.isAlternative())
620                        alt++;
621                }
622            } else {
623                if (best[idx] != null) {
624                    if (best[idx].getTruePriority() > 0)
625                        return true; // alternativity can be improved
626                } else {
627                    if (!request.isAlternative() || alt > 0)
628                        return true; // priority can be improved
629                }
630            }
631        }
632        
633        // 0.1. allowed, but not available sections
634        int notAvailable = 0;
635        for (int idx = 0; idx < current.length; idx++) {
636            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].getRequest() instanceof CourseRequest && best[idx].getReservation() != null && best[idx].getReservation().canAssignOverLimit()) {
637                for (Section section: best[idx].getSections()) {
638                    if (section.getLimit() == 0)
639                        notAvailable ++;
640                }
641            }
642            if (idx < maxIdx && current[idx] != null && current[idx].getAssignments() != null && current[idx].getRequest() instanceof CourseRequest && current[idx].getReservation() != null && current[idx].getReservation().canAssignOverLimit()) {
643                for (Section section: current[idx].getSections()) {
644                    if (section.getLimit() == 0)
645                        notAvailable --;
646                }
647            }
648        }
649        if (notAvailable > 0) {
650            return true;
651        }
652
653        // 0.5. avoid course time overlaps & unavailability overlaps
654        if (getModel().getStudentQuality() != null) {
655            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
656            for (int idx = 0; idx < current.length; idx++) {
657                if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) {
658                    for (int x = 0; x < idx; x++) {
659                        if (best[x] != null && best[x].getRequest() instanceof CourseRequest)
660                            bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, best[x], best[idx]);
661                    }
662                }
663                if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) {
664                    for (int x = 0; x < idx; x++) {
665                        if (current[x] != null && current[x].getRequest() instanceof CourseRequest)
666                            currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.CourseTimeOverlap, current[x], current[idx]);
667                    }
668                }
669            }
670            for (int idx = 0; idx < current.length; idx++) {
671                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
672                    bestTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, best[idx]);
673                }
674                if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
675                    currentTimeOverlaps += getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, current[idx]);
676                }
677            }
678            if (currentTimeOverlaps < bestTimeOverlaps)
679                return true;
680            if (bestTimeOverlaps < currentTimeOverlaps)
681                return false;
682        } else if (getModel().getTimeOverlaps() != null) {
683            int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
684            for (int idx = 0; idx < current.length; idx++) {
685                if (best[idx] != null && best[idx].getRequest() instanceof CourseRequest) {
686                    for (int x = 0; x < idx; x++) {
687                        if (best[x] != null && best[x].getRequest() instanceof CourseRequest)
688                            bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
689                    }
690                }
691                if (current[idx] != null && idx < maxIdx && current[idx].getRequest() instanceof CourseRequest) {
692                    for (int x = 0; x < idx; x++) {
693                        if (current[x] != null && current[x].getRequest() instanceof CourseRequest)
694                            currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
695                    }
696                }
697            }
698            for (int idx = 0; idx < current.length; idx++) {
699                if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
700                    bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
701                }
702                if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
703                    currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
704                }
705            }
706            if (currentTimeOverlaps < bestTimeOverlaps)
707                return true;
708            if (bestTimeOverlaps < currentTimeOverlaps)
709                return false;
710        }
711
712        // 1. maximize number of penalties
713        double bestPenalties = 0, currentPenalties = 0;
714        for (int idx = 0; idx < current.length; idx++) {
715            if (best[idx] != null) {
716                for (Section section : best[idx].getSections())
717                    bestPenalties += getModel().getOverExpected(assignment, best, idx, section, best[idx].getRequest());
718            }
719            if (current[idx] != null && idx < maxIdx) {
720                for (Section section : current[idx].getSections())
721                    currentPenalties += getModel().getOverExpected(assignment, current, idx, section, current[idx].getRequest());
722            }
723        }
724        if (currentPenalties < bestPenalties)
725            return true;
726        if (bestPenalties < currentPenalties)
727            return false;
728
729        // 2. best priority & alternativity including free times
730        if (ft) {
731            alt = 0;
732            for (int idx = 0; idx < current.length; idx++) {
733                Request request = getStudent().getRequests().get(idx);
734                if (idx < maxIdx) {
735                    if (best[idx] != null) {
736                        if (current[idx] == null)
737                            return false; // higher priority request assigned
738                        if (best[idx].getTruePriority() < current[idx].getTruePriority())
739                            return false; // less alternative request assigned
740                        if (best[idx].getTruePriority() > current[idx].getTruePriority())
741                            return true; // less alternative request assigned
742                        if (request.isAlternative())
743                            alt--;
744                    } else {
745                        if (current[idx] != null)
746                            return true; // higher priority request assigned
747                        if (request instanceof CourseRequest && !request.isAlternative())
748                            alt++;
749                    }
750                } else {
751                    if (best[idx] != null) {
752                        if (best[idx].getTruePriority() > 0)
753                            return true; // alternativity can be improved
754                    } else {
755                        if (!request.isAlternative() || alt > 0)
756                            return true; // priority can be improved
757                    }
758                }
759            }
760        }
761
762        // 3. maximize selection
763        int bestSelected = 0, currentSelected = 0;
764        for (int idx = 0; idx < current.length; idx++) {
765            if (best[idx] != null && best[idx].isCourseRequest()) {
766                Set<Section> preferred = getPreferredSections(best[idx].getRequest());
767                if (preferred != null && !preferred.isEmpty()) {
768                    for (Section section : best[idx].getSections())
769                        if (preferred.contains(section)) {
770                            if (idx < maxIdx)
771                                bestSelected++;
772                        } else if (idx >= maxIdx)
773                            bestSelected--;
774                }
775            }
776            if (current[idx] != null && idx < maxIdx && current[idx].isCourseRequest()) {
777                Set<Section> preferred = getPreferredSections(current[idx].getRequest());
778                if (preferred != null && !preferred.isEmpty()) {
779                    for (Section section : current[idx].getSections())
780                        if (preferred.contains(section))
781                            currentSelected++;
782                }
783            }
784        }
785        if (currentSelected > bestSelected)
786            return true;
787        if (bestSelected > currentSelected)
788            return false;
789
790        // 3.5 maximize preferences
791        double bestSelectedConfigs = 0, currentSelectedConfigs = 0;
792        double bestSelectedSections = 0, currentSelectedSections = 0;
793        for (int idx = 0; idx < current.length; idx++) {
794            if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
795                bestSelectedSections += best[idx].percentSelectedSameSection();
796                bestSelectedConfigs += best[idx].percentSelectedSameConfig();
797                if (idx >= maxIdx) {
798                    bestSelectedSections -= 1.0;
799                    bestSelectedConfigs -= 1.0;
800                }
801            }
802            if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
803                currentSelectedSections += current[idx].percentSelectedSameSection();
804                currentSelectedConfigs += current[idx].percentSelectedSameConfig();
805            }
806        }
807        if (0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections > 0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections) return true;
808        if (0.3 * bestSelectedConfigs + 0.7 * bestSelectedSections > 0.3 * currentSelectedConfigs + 0.7 * currentSelectedSections) return false;
809        
810        // 3.9 maximize selection with penalization for not followed reservations
811        if (res) {
812            alt = 0;
813            for (int idx = 0; idx < current.length; idx++) {
814                Request request = getStudent().getRequests().get(idx);
815                if (idx < maxIdx) {
816                    if (best[idx] != null) {
817                        if (current[idx] == null)
818                            return false; // higher priority request assigned
819                        if (best[idx].getAdjustedPriority() < current[idx].getAdjustedPriority())
820                            return false; // less alternative request assigned
821                        if (best[idx].getAdjustedPriority() > current[idx].getAdjustedPriority())
822                            return true; // less alternative request assigned
823                        if (request.isAlternative())
824                            alt--;
825                    } else {
826                        if (current[idx] != null)
827                            return true; // higher priority request assigned
828                        if (request instanceof CourseRequest && !request.isAlternative())
829                            alt++;
830                    }
831                } else {
832                    if (best[idx] != null) {
833                        if (best[idx].getTruePriority() > 0)
834                            return true; // alternativity can be improved
835                    } else {
836                        if (!request.isAlternative() || alt > 0)
837                            return true; // priority can be improved
838                    }
839                }
840            }
841        }
842        
843        // 3.95 avoid past sections
844        double bestPast = 0.0, currentPast = 0.0;
845        for (int idx = 0; idx < current.length; idx++) {
846            if (best[idx] != null && best[idx].getAssignments() != null) {
847                for (Section section : best[idx].getSections()) {
848                    if (section.isPast())
849                        bestPast += 1.0 / best[idx].getSections().size();
850                }
851            }
852            if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null) {
853                for (Section section : current[idx].getSections()) {
854                    if (section.isPast())
855                        currentPast += 1.0 / current[idx].getSections().size();
856                }
857            }
858        }
859        if (Math.abs(currentPast - bestPast) > 0.0001) {
860            if (currentPast < bestPast)
861                return true;
862            if (bestPast < currentPast)
863                return false;
864        }
865        
866        // 4-5. student quality
867        if (getModel().getStudentQuality() != null) {
868            double bestQuality = 0, currentQuality = 0;
869            for (StudentQuality.Type type: StudentQuality.Type.values()) {
870                for (int idx = 0; idx < current.length; idx++) {
871                    if (best[idx] != null) {
872                        bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[idx]);
873                        for (int x = 0; x < idx; x++) {
874                            if (best[x] != null)
875                                bestQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, best[x], best[idx]);
876                        }
877                    }
878                    if (current[idx] != null && idx < maxIdx) {
879                        currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[idx]);
880                        for (int x = 0; x < idx; x++) {
881                            if (current[x] != null)
882                                currentQuality += iQalityWeights[type.ordinal()] * getModel().getStudentQuality().penalty(type, current[x], current[idx]);
883                        }
884                    }
885                }
886            }
887            if (currentQuality < bestQuality)
888                return true;
889            if (bestQuality < currentQuality)
890                return false;
891        } else {
892            // 4. avoid time overlaps
893            if (getModel().getTimeOverlaps() != null) {
894                int bestTimeOverlaps = 0, currentTimeOverlaps = 0;
895                for (int idx = 0; idx < current.length; idx++) {
896                    if (best[idx] != null) {
897                        for (int x = 0; x < idx; x++) {
898                            if (best[x] != null)
899                                bestTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(best[x], best[idx]);
900                            else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
901                                bestTimeOverlaps += getModel().getTimeOverlaps()
902                                        .nrConflicts(
903                                                ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
904                                                best[idx]);
905                        }
906                    }
907                    if (current[idx] != null && idx < maxIdx) {
908                        for (int x = 0; x < idx; x++) {
909                            if (current[x] != null)
910                                currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(current[x], current[idx]);
911                            else if (getStudent().getRequests().get(x) instanceof FreeTimeRequest)
912                                currentTimeOverlaps += getModel().getTimeOverlaps().nrConflicts(
913                                        ((FreeTimeRequest) getStudent().getRequests().get(x)).createEnrollment(),
914                                        current[idx]);
915                        }
916                    }
917                }
918                for (int idx = 0; idx < current.length; idx++) {
919                    if (best[idx] != null && best[idx].getAssignments() != null && best[idx].isCourseRequest()) {
920                        bestTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(best[idx]);
921                    }
922                    if (current[idx] != null && idx < maxIdx && current[idx].getAssignments() != null && current[idx].isCourseRequest()) {
923                        currentTimeOverlaps += getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(current[idx]);
924                    }
925                }
926                if (currentTimeOverlaps < bestTimeOverlaps)
927                    return true;
928                if (bestTimeOverlaps < currentTimeOverlaps)
929                    return false;
930            }
931
932            // 5. avoid distance conflicts
933            if (getModel().getDistanceConflict() != null) {
934                int bestDistanceConf = 0, currentDistanceConf = 0;
935                for (int idx = 0; idx < current.length; idx++) {
936                    if (best[idx] != null) {
937                        bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[idx]);
938                        for (int x = 0; x < idx; x++) {
939                            if (best[x] != null)
940                                bestDistanceConf += getModel().getDistanceConflict().nrConflicts(best[x], best[idx]);
941                        }
942                    }
943                    if (current[idx] != null && idx < maxIdx) {
944                        currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[idx]);
945                        for (int x = 0; x < idx; x++) {
946                            if (current[x] != null)
947                                currentDistanceConf += getModel().getDistanceConflict().nrConflicts(current[x],
948                                        current[idx]);
949                        }
950                    }
951                }
952                if (currentDistanceConf < bestDistanceConf)
953                    return true;
954                if (bestDistanceConf < currentDistanceConf)
955                    return false;
956            }
957        }
958
959        // 6. avoid no-time and online sections (no-time first, online second)
960        int bestNoTime = 0, currentNoTime = 0;
961        int bestOnline = 0, currentOnline = 0;
962        for (int idx = 0; idx < current.length; idx++) {
963            if (best[idx] != null) {
964                for (Section section : best[idx].getSections()) {
965                    if (!section.hasTime())
966                        bestNoTime++;
967                    if (section.isOnline())
968                        bestOnline++;
969                }
970            }
971            if (current[idx] != null && idx < maxIdx) {
972                for (Section section : current[idx].getSections()) {
973                    if (!section.hasTime())
974                        currentNoTime++;
975                    if (section.isOnline())
976                        currentOnline++;
977                }
978            }
979        }
980        if (currentNoTime < bestNoTime)
981            return true;
982        if (bestNoTime < currentNoTime)
983            return false;
984        if (currentOnline < bestOnline)
985            return true;
986        if (bestOnline < currentOnline)
987            return false;
988
989        // 7. balance sections
990        double bestUnavailableSize = 0.0, currentUnavailableSize = 0.0;
991        int bestAltSectionsWithLimit = 0, currentAltSectionsWithLimit = 0;
992        for (int idx = 0; idx < current.length; idx++) {
993            if (best[idx] != null) {
994                for (Section section : best[idx].getSections()) {
995                    Subpart subpart = section.getSubpart();
996                    // skip unlimited and single section subparts
997                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
998                        continue;
999                    // average size
1000                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
1001                    // section is below average
1002                    if (section.getLimit() < averageSize)
1003                        bestUnavailableSize += (averageSize - section.getLimit()) / averageSize;
1004                    bestAltSectionsWithLimit++;
1005                }
1006            }
1007            if (current[idx] != null && idx < maxIdx) {
1008                for (Section section : current[idx].getSections()) {
1009                    Subpart subpart = section.getSubpart();
1010                    // skip unlimited and single section subparts
1011                    if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
1012                        continue;
1013                    // average size
1014                    double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
1015                    // section is below average
1016                    if (section.getLimit() < averageSize)
1017                        currentUnavailableSize += (averageSize - section.getLimit()) / averageSize;
1018                    currentAltSectionsWithLimit++;
1019                }
1020            }
1021        }
1022        double bestUnavailableSizeFraction = (bestUnavailableSize > 0 ? bestUnavailableSize / bestAltSectionsWithLimit
1023                : 0.0);
1024        double currentUnavailableSizeFraction = (currentUnavailableSize > 0 ? currentUnavailableSize
1025                / currentAltSectionsWithLimit : 0.0);
1026        if (currentUnavailableSizeFraction < bestUnavailableSizeFraction)
1027            return true;
1028        if (bestUnavailableSizeFraction < currentUnavailableSizeFraction)
1029            return false;
1030
1031        // 8. average penalty sections
1032        double bestPenalty = 0.0, currentPenalty = 0.0;
1033        for (int idx = 0; idx < current.length; idx++) {
1034            if (best[idx] != null) {
1035                for (Section section : best[idx].getSections())
1036                    bestPenalty += section.getPenalty() / best[idx].getSections().size();
1037                if (idx >= maxIdx && best[idx].isCourseRequest())
1038                    bestPenalty -= ((CourseRequest) best[idx].getRequest()).getMinPenalty();
1039            }
1040            if (current[idx] != null && idx < maxIdx) {
1041                for (Section section : current[idx].getSections())
1042                    currentPenalty += section.getPenalty() / current[idx].getSections().size();
1043            }
1044        }
1045        if (currentPenalty < bestPenalty)
1046            return true;
1047        if (bestPenalty < currentPenalty)
1048            return false;
1049
1050        return true;
1051    }
1052
1053    @Override
1054    public double getTotalWeight(Assignment<Request, Enrollment> assignment, Enrollment[] enrollemnts) {
1055        if (enrollemnts == null)
1056            return 0.0;
1057        double value = 0.0;
1058        for (int idx = 0; idx < enrollemnts.length; idx++) {
1059            if (enrollemnts[idx] != null)
1060                if (getModel().getStudentQuality() != null) {
1061                    value += getWeight(assignment, enrollemnts[idx], getStudentQualityConflicts(enrollemnts, idx));
1062                } else { 
1063                    value += getWeight(assignment, enrollemnts[idx], getDistanceConflicts(enrollemnts, idx), getTimeOverlappingConflicts(enrollemnts, idx));
1064                }
1065        }
1066        return value;
1067    }
1068
1069    @Override
1070    public int compare(Assignment<Request, Enrollment> assignment, Enrollment e1, Enrollment e2) {
1071        // 1. alternativity
1072        if (e1.getTruePriority() < e2.getTruePriority())
1073            return -1;
1074        if (e1.getTruePriority() > e2.getTruePriority())
1075            return 1;
1076        
1077        // 1.5 not available sections
1078        int na1 = 0, na2 = 0;
1079        for (Section section: e1.getSections())
1080            if (section.getLimit() == 0) na1++;
1081        for (Section section: e2.getSections())
1082            if (section.getLimit() == 0) na2++;
1083        if (na1 < na2) return -1;
1084        if (na1 > na2) return 1;
1085        
1086        // 2. maximize number of penalties
1087        double p1 = 0, p2 = 0;
1088        for (Section section : e1.getSections())
1089            p1 += getModel().getOverExpected(assignment, section, e1.getRequest());
1090        for (Section section : e2.getSections())
1091            p2 += getModel().getOverExpected(assignment, section, e2.getRequest());
1092        if (p1 < p2)
1093            return -1;
1094        if (p2 < p1)
1095            return 1;
1096
1097        // 3. maximize selection
1098        if (e1.isCourseRequest()) {
1099            Set<Section> preferred = getPreferredSections(e1.getRequest());
1100            if (preferred != null && !preferred.isEmpty()) {
1101                int s1 = 0, s2 = 0;
1102                for (Section section : e1.getSections())
1103                    if (preferred.contains(section))
1104                        s1++;
1105                for (Section section : e2.getSections())
1106                    if (preferred.contains(section))
1107                        s2++;
1108                if (s2 > s1)
1109                    return -1;
1110                if (s1 > s2)
1111                    return 1;
1112            }
1113        }
1114        
1115        // 3.5 maximize preferences
1116        if (e1.isCourseRequest()) {
1117            double s1 = 0.3 * e1.percentSelectedSameConfig() + 0.7 * e1.percentSelectedSameSection();
1118            double s2 = 0.3 * e2.percentSelectedSameConfig() + 0.7 * e2.percentSelectedSameSection();
1119            if (s1 > s2) return -1;
1120            if (s2 > s1) return 1;
1121        }
1122        
1123        // 3.9 maximize selection with penalization for not followed reservations
1124        if (e1.getAdjustedPriority() < e2.getAdjustedPriority())
1125            return -1;
1126        if (e1.getAdjustedPriority() > e2.getAdjustedPriority())
1127            return 1;
1128        
1129        // 3.95 avoid past sections
1130        double w1 = 0, w2 = 0;
1131        for (Section section : e1.getSections()) {
1132            if (section.isPast())
1133                w1 += 1.0 / e1.getSections().size();
1134        }
1135        for (Section section : e2.getSections()) {
1136            if (section.isPast())
1137                w2 += 1.0 / e2.getSections().size();
1138        }
1139        if (Math.abs(w1 - w2) > 0.0001) {
1140            if (w1 < w2)
1141                return -1;
1142            if (w2 < w1)
1143                return 1;
1144        }
1145
1146        // 4. avoid time overlaps
1147        if (getTimesToAvoid() == null) {
1148            if (getModel().getStudentQuality() != null) {
1149                int o1 = getModel().getStudentQuality().penalty(StudentQuality.Type.FreeTimeOverlap, e1) + getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, e1);
1150                int o2 = getModel().getStudentQuality().penalty(StudentQuality.Type.FreeTimeOverlap, e2) + getModel().getStudentQuality().penalty(StudentQuality.Type.Unavailability, e2);
1151                if (o1 < o2)
1152                    return -1;
1153                if (o2 < o1)
1154                    return 1;
1155            } else if (getModel().getTimeOverlaps() != null) {
1156                int o1 = getModel().getTimeOverlaps().nrFreeTimeConflicts(e1) + getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(e1);
1157                int o2 = getModel().getTimeOverlaps().nrFreeTimeConflicts(e2) + getModel().getTimeOverlaps().nrNotAvailableTimeConflicts(e2);
1158                if (o1 < o2)
1159                    return -1;
1160                if (o2 < o1)
1161                    return 1;
1162            }
1163        } else {
1164            if (e1.getRequest().equals(e2.getRequest()) && e1.isCourseRequest()) {
1165                double o1 = 0.0, o2 = 0.0;
1166                for (Section s : e1.getSections()) {
1167                    if (s.getTime() != null)
1168                        for (TimeToAvoid avoid : getTimesToAvoid()) {
1169                            if (avoid.priority() > e1.getRequest().getPriority())
1170                                o1 += avoid.overlap(s.getTime());
1171                        }
1172                }
1173                for (Section s : e2.getSections()) {
1174                    if (s.getTime() != null)
1175                        for (TimeToAvoid avoid : getTimesToAvoid()) {
1176                            if (avoid.priority() > e2.getRequest().getPriority())
1177                                o2 += avoid.overlap(s.getTime());
1178                        }
1179                }
1180                if (o1 < o2)
1181                    return -1;
1182                if (o2 < o1)
1183                    return 1;
1184            }
1185        }
1186
1187        // 5. avoid distance conflicts
1188        if (getModel().getDistanceConflict() != null) {
1189            int c1 = getModel().getDistanceConflict().nrConflicts(e1);
1190            int c2 = getModel().getDistanceConflict().nrConflicts(e2);
1191            if (c1 < c2)
1192                return -1;
1193            if (c2 < c1)
1194                return 1;
1195        }
1196
1197        // 6. avoid no-time and online sections (no-time first, online second)
1198        int n1 = 0, n2 = 0;
1199        int o1 = 0, o2 = 0;
1200        for (Section section : e1.getSections()) {
1201            if (!section.hasTime())
1202                n1++;
1203            if (section.isOnline())
1204                o1++;
1205        }
1206        for (Section section : e2.getSections()) {
1207            if (!section.hasTime())
1208                n2++;
1209            if (section.isOnline())
1210                o2++;
1211        }
1212        if (n1 < n2)
1213            return -1;
1214        if (n2 < n1)
1215            return 1;
1216        if (o1 < o2)
1217            return -1;
1218        if (o2 < o1)
1219            return 1;
1220
1221        // 7. balance sections
1222        double u1 = 0.0, u2 = 0.0;
1223        int a1 = 0, a2 = 0;
1224        for (Section section : e1.getSections()) {
1225            Subpart subpart = section.getSubpart();
1226            // skip unlimited and single section subparts
1227            if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
1228                continue;
1229            // average size
1230            double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
1231            // section is below average
1232            if (section.getLimit() < averageSize)
1233                u1 += (averageSize - section.getLimit()) / averageSize;
1234            a1++;
1235        }
1236        for (Section section : e2.getSections()) {
1237            Subpart subpart = section.getSubpart();
1238            // skip unlimited and single section subparts
1239            if (subpart.getSections().size() <= 1 || subpart.getLimit() <= 0)
1240                continue;
1241            // average size
1242            double averageSize = ((double) subpart.getLimit()) / subpart.getSections().size();
1243            // section is below average
1244            if (section.getLimit() < averageSize)
1245                u2 += (averageSize - section.getLimit()) / averageSize;
1246            a2++;
1247        }
1248        double f1 = (u1 > 0 ? u1 / a1 : 0.0);
1249        double f2 = (u2 > 0 ? u2 / a2 : 0.0);
1250        if (f1 < f2)
1251            return -1;
1252        if (f2 < f1)
1253            return 1;
1254
1255        // 8. average penalty sections
1256        double x1 = 0.0, x2 = 0.0;
1257        for (Section section : e1.getSections())
1258            x1 += section.getPenalty() / e1.getSections().size();
1259        for (Section section : e2.getSections())
1260            x2 += section.getPenalty() / e2.getSections().size();
1261        if (x1 < x2)
1262            return -1;
1263        if (x2 < x1)
1264            return 1;
1265
1266        return 0;
1267    }
1268
1269    /**
1270     * Time to be avoided.
1271     */
1272    public static class TimeToAvoid {
1273        private TimeLocation iTime;
1274        private double iPenalty;
1275        private int iPriority;
1276
1277        public TimeToAvoid(TimeLocation time, int penalty, int priority) {
1278            iTime = time;
1279            iPenalty = penalty;
1280            iPriority = priority;
1281        }
1282
1283        public int priority() {
1284            return iPriority;
1285        }
1286
1287        public double overlap(TimeLocation time) {
1288            if (time.hasIntersection(iTime)) {
1289                return iPenalty * (time.nrSharedDays(iTime) * time.nrSharedHours(iTime))
1290                        / (iTime.getNrMeetings() * iTime.getLength());
1291            } else {
1292                return 0.0;
1293            }
1294        }
1295
1296        @Override
1297        public String toString() {
1298            return iTime.getLongName(true) + " (" + iPriority + "/" + iPenalty + ")";
1299        }
1300    }
1301}