    001package org.cpsolver.studentsct.model;
    002
    003import java.text.DecimalFormat;
    004import java.util.ArrayList;
    005import java.util.Collection;
    006import java.util.Collections;
    007import java.util.HashMap;
    008import java.util.HashSet;
    009import java.util.List;
    010import java.util.Map;
    011import java.util.Set;
    012import java.util.TreeSet;
    013
    014import org.cpsolver.coursett.model.TimeLocation;
    015import org.cpsolver.ifs.assignment.Assignment;
    016import org.cpsolver.ifs.assignment.AssignmentComparator;
    017import org.cpsolver.ifs.util.ToolBox;
    018import org.cpsolver.studentsct.StudentSectioningModel;
    019import org.cpsolver.studentsct.constraint.ConfigLimit;
    020import org.cpsolver.studentsct.constraint.CourseLimit;
    021import org.cpsolver.studentsct.constraint.LinkedSections;
    022import org.cpsolver.studentsct.constraint.SectionLimit;
    023import org.cpsolver.studentsct.reservation.Reservation;
    024import org.cpsolver.studentsct.reservation.Restriction;
    025
    026
    027/**
    028 * Representation of a request of a student for one or more course. A student
    029 * requests one of the given courses, preferably the first one. <br>
    030 * <br>
    031 *
    032 * @author  Tomáš Müller
    033 * @version StudentSct 1.3 (Student Sectioning)<br>
    034 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    035 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    036 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    037 * <br>
    038 *          This library is free software; you can redistribute it and/or modify
    039 *          it under the terms of the GNU Lesser General Public License as
    040 *          published by the Free Software Foundation; either version 3 of the
    041 *          License, or (at your option) any later version. <br>
    042 * <br>
    043 *          This library is distributed in the hope that it will be useful, but
    044 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    045 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    046 *          Lesser General Public License for more details. <br>
    047 * <br>
    048 *          You should have received a copy of the GNU Lesser General Public
    049 *          License along with this library; if not see
    050 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    051 */
    052public class CourseRequest extends Request {
    053    private static DecimalFormat sDF = new DecimalFormat("0.000");
    054    private List<Course> iCourses = null;
    055    private Set<Choice> iWaitlistedChoices = new HashSet<Choice>();
    056    private Set<Choice> iSelectedChoices = new HashSet<Choice>();
    057    private Set<Choice> iRequiredChoices = new HashSet<Choice>();
    058    private boolean iWaitlist = false;
    059    private Long iTimeStamp = null;
    060    private Double iCachedMinPenalty = null, iCachedMaxPenalty = null;
    061    public static boolean sSameTimePrecise = false;
    062    private Set<RequestGroup> iRequestGroups = new HashSet<RequestGroup>();
    063    private RequestPriority iPriority = RequestPriority.Normal;
    064    private Enrollment iFixed = null;
    065
    066    /**
    067     * Constructor
    068     *
    069     * @param id
    070     *            request unique id
    071     * @param priority
    072     *            request priority
    073     * @param alternative
    074     *            true if the request is alternative (alternative request can be
    075     *            assigned instead of a non-alternative course requests, if it
    076     *            is left unassigned)
    077     * @param student
    078     *            appropriate student
    079     * @param courses
    080     *            list of requested courses (in the correct order -- first is
    081     *            the requested course, second is the first alternative, etc.)
    082     * @param waitlist
    083     *            time stamp of the request if the student can be put on a wait-list (no alternative
    084     *            course request will be given instead)
    085     * @param critical
    086     *            is the course request is critical for the student in order to move forward in their degree
    087     * @param timeStamp request time stamp
    088     */
    089    public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses, boolean waitlist, boolean critical, Long timeStamp) {
    090        super(id, priority, alternative, student);
    091        iCourses = new ArrayList<Course>(courses);
    092        for (Course course: iCourses)
    093            course.getRequests().add(this);
    094        iWaitlist = waitlist;
    095        iPriority = (critical ? RequestPriority.Critical : RequestPriority.Normal);
    096        iTimeStamp = timeStamp;
    097    }
    098
    099    /**
    100     * Constructor
    101     *
    102     * @param id
    103     *            request unique id
    104     * @param priority
    105     *            request priority
    106     * @param alternative
    107     *            true if the request is alternative (alternative request can be
    108     *            assigned instead of a non-alternative course requests, if it
    109     *            is left unassigned)
    110     * @param student
    111     *            appropriate student
    112     * @param courses
    113     *            list of requested courses (in the correct order -- first is
    114     *            the requested course, second is the first alternative, etc.)
    115     * @param waitlist
    116     *            time stamp of the request if the student can be put on a wait-list (no alternative
    117     *            course request will be given instead)
    118     * @param importance
    119     *            request priority
    120     * @param timeStamp request time stamp
    121     */
    122    public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses, boolean waitlist, RequestPriority importance, Long timeStamp) {
    123        super(id, priority, alternative, student);
    124        iCourses = new ArrayList<Course>(courses);
    125        for (Course course: iCourses)
    126            course.getRequests().add(this);
    127        iWaitlist = waitlist;
    128        iPriority = importance;
    129        iTimeStamp = timeStamp;
    130    }
    131
    132    /**
    133     * Constructor
    134     *
    135     * @param id
    136     *            request unique id
    137     * @param priority
    138     *            request priority
    139     * @param alternative
    140     *            true if the request is alternative (alternative request can be
    141     *            assigned instead of a non-alternative course requests, if it
    142     *            is left unassigned)
    143     * @param student
    144     *            appropriate student
    145     * @param courses
    146     *            list of requested courses (in the correct order -- first is
    147     *            the requested course, second is the first alternative, etc.)
    148     * @param waitlist
    149     *            time stamp of the request if the student can be put on a wait-list (no alternative
    150     *            course request will be given instead)
    151     * @param timeStamp request time stamp
    152     */
    153    public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses, boolean waitlist, Long timeStamp) {
    154        this(id, priority, alternative, student, courses, waitlist, false, timeStamp);
    155    }
    156
    157    /**
    158     * List of requested courses (in the correct order -- first is the requested
    159     * course, second is the first alternative, etc.)
    160     * @return requested course offerings
    161     */
    162    public List<Course> getCourses() {
    163        return iCourses;
    164    }
    165
    166    /**
    167     * Create enrollment for the given list of sections. The list of sections
    168     * needs to be correct, i.e., a section for each subpart of a configuration
    169     * of one of the requested courses.
    170     * @param sections selected sections
    171     * @param reservation selected reservation
    172     * @return enrollment
    173     */
    174    public Enrollment createEnrollment(Set<? extends SctAssignment> sections, Reservation reservation) {
    175        if (sections == null || sections.isEmpty())
    176            return null;
    177        Config config = ((Section) sections.iterator().next()).getSubpart().getConfig();
    178        Course course = null;
    179        for (Course c: iCourses) {
    180            if (c.getOffering().getConfigs().contains(config)) {
    181                course = c;
    182                break;
    183            }
    184        }
    185        return new Enrollment(this, iCourses.indexOf(course), course, config, sections, reservation);
    186    }
    187
    188    /**
    189     * Create enrollment for the given list of sections. The list of sections
    190     * needs to be correct, i.e., a section for each subpart of a configuration
    191     * of one of the requested courses.
    192     * @param course selected course
    193     * @param sections selected sections
    194     * @param reservation selected reservation
    195     * @return enrollment
    196     */
    197    public Enrollment createEnrollment(Course course, Set<? extends SctAssignment> sections, Reservation reservation) {
    198        if (sections == null || sections.isEmpty())
    199            return null;
    200        Config config = ((Section) sections.iterator().next()).getSubpart().getConfig();
    201        return new Enrollment(this, iCourses.indexOf(course), course, config, sections, reservation);
    202    }
    203
    204    /**
    205     * Create enrollment for the given list of sections. The list of sections
    206     * needs to be correct, i.e., a section for each subpart of a configuration
    207     * of one of the requested courses.
    208     * @param assignment current assignment (to guess the reservation)
    209     * @param sections selected sections
    210     * @return enrollment
    211     */
    212    public Enrollment createEnrollment(Assignment<Request, Enrollment> assignment, Set<? extends SctAssignment> sections) {
    213        Enrollment ret = createEnrollment(sections, null);
    214        ret.guessReservation(assignment, true);
    215        return ret;
    216
    217    }
    218
    219    /**
    220     * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit.
    221     * @return maximal domain size, -1 if unlimited
    222     */
    223    protected int getMaxDomainSize() {
    224        StudentSectioningModel model = (StudentSectioningModel) getModel();
    225        return model == null ? -1 : model.getMaxDomainSize();
    226    }
    227
    228    /**
    229     * Return all possible enrollments.
    230     */
    231    @Override
    232    public List<Enrollment> computeEnrollments(Assignment<Request, Enrollment> assignment) {
    233        List<Enrollment> ret = new ArrayList<Enrollment>();
    234        if (isFixed()) {
    235            ret.add(getFixedValue());
    236            return ret;
    237        }
    238        if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) {
    239            ret.add(getInitialAssignment());
    240            return ret;
    241        }
    242        int idx = 0;
    243        for (Course course : iCourses) {
    244            for (Config config : course.getOffering().getConfigs()) {
    245                computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false,
    246                        false, false, getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), false);
    247            }
    248            idx++;
    249        }
    250        return ret;
    251    }
    252
    253    /**
    254     * Return a subset of all enrollments -- randomly select only up to
    255     * limitEachConfig enrollments of each config.
    256     * @param assignment current assignment
    257     * @param limitEachConfig maximal number of enrollments in each configuration
    258     * @return computed enrollments
    259     */
    260    public List<Enrollment> computeRandomEnrollments(Assignment<Request, Enrollment> assignment, int limitEachConfig) {
    261        List<Enrollment> ret = new ArrayList<Enrollment>();
    262        if (isFixed()) {
    263            ret.add(getFixedValue());
    264            return ret;
    265        }
    266        if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) {
    267            ret.add(getInitialAssignment());
    268            return ret;
    269        }
    270        int idx = 0;
    271        for (Course course : iCourses) {
    272            for (Config config : course.getOffering().getConfigs()) {
    273                computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false,
    274                        false, true, (limitEachConfig <= 0 ? limitEachConfig : ret.size() + limitEachConfig), false);
    275            }
    276            idx++;
    277        }
    278        return ret;
    279    }
    280
    281    /**
    282     * Return true if the both sets of sections contain sections of the same
    283     * subparts, and each pair of sections of the same subpart is offered at the
    284     * same time.
    285     */
    286    private boolean sameTimes(Set<Section> sections1, Set<Section> sections2) {
    287        for (Section s1 : sections1) {
    288            Section s2 = null;
    289            for (Section s : sections2) {
    290                if (s.getSubpart().equals(s1.getSubpart())) {
    291                    s2 = s;
    292                    break;
    293                }
    294            }
    295            if (s2 == null)
    296                return false;
    297            if (!ToolBox.equals(s1.getTime(), s2.getTime()))
    298                return false;
    299        }
    300        return true;
    301    }
    302
    303    /**
    304     * Recursive computation of enrollments
    305     *
    306     * @param enrollments
    307     *            list of enrollments to be returned
    308     * @param priority
    309     *            zero for the course, one for the first alternative, two for the second alternative
    310     * @param penalty
    311     *            penalty of the selected sections
    312     * @param course
    313     *            selected course
    314     * @param config
    315     *            selected configuration
    316     * @param sections
    317     *            sections selected so far
    318     * @param idx
    319     *            index of the subparts (a section of 0..idx-1 subparts has been
    320     *            already selected)
    321     * @param availableOnly
    322     *            only use available sections
    323     * @param skipSameTime
    324     *            for each possible times, pick only one section
    325     * @param selectedOnly
    326     *            select only sections that are selected (
    327     *            {@link CourseRequest#isSelected(Section)} is true)
    328     * @param random
    329     *            pick sections in a random order (useful when limit is used)
    330     * @param limit
    331     *            when above zero, limit the number of selected enrollments to
    332     *            this limit
    333     * @param ignoreDisabled
    334     *            are sections that are disabled for student scheduling allowed to be used
    335     * @param reservations
    336     *            list of applicable reservations
    337     */
    338    private void computeEnrollments(Assignment<Request, Enrollment> assignment, Collection<Enrollment> enrollments, int priority, double penalty, Course course, Config config,
    339            HashSet<Section> sections, int idx, boolean availableOnly, boolean skipSameTime, boolean selectedOnly,
    340            boolean random, int limit, boolean checkParent) {
    341        if (limit > 0 && enrollments.size() >= limit)
    342            return;
    343        if (checkParent && course.hasParent()) {
    344            Course parent = course.getParent();
    345            for (Request r: getStudent().getRequests()) {
    346                if (r.hasCourse(parent)) {
    347                    Enrollment e = assignment.getValue(r);
    348                    if (e == null || !parent.equals(e.getCourse())) return;
    349                }
    350            }
    351        }
    352        if (idx == 0) { // run only once for each configuration
    353            if (isNotAllowed(course, config)) return;
    354            boolean canOverLimit = false;
    355            if (availableOnly) {
    356                for (Reservation r: getReservations(course)) {
    357                    if (!r.canBatchAssignOverLimit()) continue;
    358                    if (r.neverIncluded()) continue;
    359                    if (!r.getConfigs().isEmpty() && !r.getConfigs().contains(config)) continue;
    360                    if (r.getReservedAvailableSpace(assignment, this) < getWeight()) continue;
    361                    if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
    362                    canOverLimit = true; break;
    363                }
    364            }
    365            if (!canOverLimit) {
    366                if (availableOnly && config.getLimit() >= 0 && ConfigLimit.getEnrollmentWeight(assignment, config, this) > config.getLimit())
    367                    return;
    368                if (availableOnly && course.getLimit() >= 0 && CourseLimit.getEnrollmentWeight(assignment, course, this) > course.getLimit())
    369                    return;
    370                if (config.getOffering().hasReservations()) {
    371                    boolean hasReservation = false, hasConfigReservation = false, reservationMustBeUsed = false;
    372                    for (Reservation r: getReservations(course)) {
    373                        if (r.mustBeUsed()) reservationMustBeUsed = true;
    374                        if (availableOnly && r.getReservedAvailableSpace(assignment, this) < getWeight()) continue;
    375                        if (availableOnly && r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
    376                        if (r.neverIncluded()) {
    377                        } else if (r.getConfigs().isEmpty()) {
    378                            hasReservation = true;
    379                        } else if (r.getConfigs().contains(config)) {
    380                            hasReservation = true;
    381                            hasConfigReservation = true;
    382                        } else if (!r.areRestrictionsInclusive()) {
    383                            hasReservation = true;
    384                        }
    385                    }
    386                    if (!hasConfigReservation && config.getTotalUnreservedSpace() < getWeight())
    387                        return;
    388                    if (!hasReservation && config.getOffering().getTotalUnreservedSpace() < getWeight())
    389                        return;
    390                    if (availableOnly && !hasReservation && config.getOffering().getUnreservedSpace(assignment, this) < getWeight())
    391                        return;
    392                    if (availableOnly && !hasConfigReservation && config.getUnreservedSpace(assignment, this) < getWeight())
    393                        return;
    394                    if (!hasReservation && reservationMustBeUsed)
    395                        return;
    396                }
    397            }
    398        }
    399        if (config.getSubparts().size() == idx) {
    400            if (skipSameTime && sSameTimePrecise) {
    401                boolean waitListedOrSelected = false;
    402                if (!getSelectedChoices().isEmpty() || !getWaitlistedChoices().isEmpty()) {
    403                    for (Section section : sections) {
    404                        if (isWaitlisted(section) || isSelected(section)) {
    405                            waitListedOrSelected = true;
    406                            break;
    407                        }
    408                    }
    409                }
    410                if (!waitListedOrSelected) {
    411                    for (Enrollment enrollment : enrollments) {
    412                        if (sameTimes(enrollment.getSections(), sections))
    413                            return;
    414                    }
    415                }
    416            }
    417            Enrollment e = new Enrollment(this, priority, course, config, new HashSet<SctAssignment>(sections), null);
    418            if (isNotAllowed(e)) {
    419            } else if (!config.getOffering().hasReservations()) {
    420                enrollments.add(e);
    421            } else {
    422                boolean mustHaveReservation = config.getOffering().getTotalUnreservedSpace() < getWeight();
    423                boolean mustHaveConfigReservation = config.getTotalUnreservedSpace() < getWeight();
    424                boolean mustHaveSectionReservation = false;
    425                boolean containDisabledSection = false;
    426                for (Section s: sections) {
    427                    if (s.getTotalUnreservedSpace() < getWeight()) {
    428                        mustHaveSectionReservation = true;
    429                    }
    430                    if (!getStudent().isAllowDisabled() && !s.isEnabled(getStudent())) {
    431                        containDisabledSection = true;
    432                    }
    433                }
    434                boolean canOverLimit = false;
    435                if (availableOnly) {
    436                    for (Reservation r: getReservations(course)) {
    437                        if (!r.canBatchAssignOverLimit() || !r.isIncluded(e)) continue;
    438                        if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
    439                        if (containDisabledSection && !r.isAllowDisabled()) continue;
    440                        enrollments.add(new Enrollment(this, priority, null, config, new HashSet<SctAssignment>(sections), r));
    441                        canOverLimit = true;
    442                    }
    443                }
    444                if (!canOverLimit) {
    445                    boolean reservationMustBeUsed = false;
    446                    reservations: for (Reservation r: (availableOnly ? getSortedReservations(assignment, course) : getReservations(course))) {
    447                        if (r.mustBeUsed()) reservationMustBeUsed = true;
    448                        if (!r.isIncluded(e)) continue;
    449                        if (availableOnly && r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
    450                        if (mustHaveConfigReservation && r.getConfigs().isEmpty()) continue;
    451                        if (mustHaveSectionReservation)
    452                            for (Section s: sections)
    453                                if (r.getSections(s.getSubpart()) == null && s.getTotalUnreservedSpace() < getWeight()) continue reservations;
    454                        if (containDisabledSection && !r.isAllowDisabled()) continue;
    455                        enrollments.add(new Enrollment(this, priority, null, config, new HashSet<SctAssignment>(sections), r));
    456                        if (availableOnly) return; // only one available reservation suffice (the best matching one)
    457                    }
    458                    // a case w/o reservation
    459                    if (!(mustHaveReservation || mustHaveConfigReservation || mustHaveSectionReservation) &&
    460                        !(availableOnly && config.getOffering().getUnreservedSpace(assignment, this) < getWeight()) &&
    461                        !reservationMustBeUsed && !containDisabledSection) {
    462                        enrollments.add(new Enrollment(this, priority, !getReservations(course).isEmpty(), null, config, new HashSet<SctAssignment>(sections), null));
    463                    }
    464                }
    465            }
    466        } else {
    467            Subpart subpart = config.getSubparts().get(idx);
    468            HashSet<TimeLocation> times = (skipSameTime ? new HashSet<TimeLocation>() : null);
    469            List<Section> sectionsThisSubpart = subpart.getSections();
    470            if (skipSameTime) {
    471                sectionsThisSubpart = new ArrayList<Section>(subpart.getSections());
    472                Collections.sort(sectionsThisSubpart, new AssignmentComparator<Section, Request, Enrollment>(assignment));
    473            }
    474            List<Section> matchingSectionsThisSubpart = new ArrayList<Section>(subpart.getSections().size());
    475            boolean hasChildren = !subpart.getChildren().isEmpty();
    476            for (Section section : sectionsThisSubpart) {
    477                if (section.isCancelled())
    478                    continue;
    479                if (!isRequired(section))
    480                    continue;
    481                if (getInitialAssignment() != null && (getModel() != null && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) &&
    482                        !getInitialAssignment().getAssignments().contains(section))
    483                    continue;
    484                if (isFixed() && !getFixedValue().getAssignments().contains(section))
    485                    continue;
    486                if (section.getParent() != null && !sections.contains(section.getParent()))
    487                    continue;
    488                if (section.isOverlapping(sections))
    489                    continue;
    490                if (selectedOnly && hasSelection(section) && !isSelected(section))
    491                    continue;
    492                if (isNotAllowed(course, section))
    493                    continue;
    494                if (!getStudent().isAvailable(section)) {
    495                    boolean canOverlap = false;
    496                    for (Reservation r: getReservations(course)) {
    497                        if (!r.isAllowOverlap()) continue;
    498                        if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue;
    499                        if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
    500                        canOverlap = true; break;
    501                    }
    502                    if (!canOverlap) continue;
    503                }
    504                boolean canOverLimit = false;
    505                if (availableOnly) {
    506                    for (Reservation r: getReservations(course)) {
    507                        if (!r.canBatchAssignOverLimit()) continue;
    508                        if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue;
    509                        if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
    510                        canOverLimit = true; break;
    511                    }
    512                }
    513                if (!canOverLimit) {
    514                    if (availableOnly && section.getLimit() >= 0
    515                            && SectionLimit.getEnrollmentWeight(assignment, section, this) > section.getLimit())
    516                        continue;
    517                    if (config.getOffering().hasReservations()) {
    518                        boolean hasReservation = false, hasSectionReservation = false, reservationMustBeUsed = false;
    519                        for (Reservation r: getReservations(course)) {
    520                            if (r.mustBeUsed()) reservationMustBeUsed = true;
    521                            if (availableOnly && r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue;
    522                            if (r.getSections(subpart) == null) {
    523                                hasReservation = true;
    524                            } else if (r.getSections(subpart).contains(section)) {
    525                                hasReservation = true;
    526                                hasSectionReservation = true;
    527                            }
    528                        }
    529                        if (!hasSectionReservation && section.getTotalUnreservedSpace() < getWeight())
    530                            continue;
    531                        if (availableOnly && !hasSectionReservation && section.getUnreservedSpace(assignment, this) < getWeight())
    532                            continue;
    533                        if (!hasReservation && reservationMustBeUsed)
    534                            continue;
    535                    }
    536                }
    537                if (!getStudent().isAllowDisabled() && !section.isEnabled(getStudent())) {
    538                    boolean allowDisabled = false;
    539                    for (Reservation r: getReservations(course)) {
    540                        if (!r.isAllowDisabled()) continue;
    541                        if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue;
    542                        if (!r.getConfigs().isEmpty() && !r.getConfigs().contains(config)) continue;
    543                        allowDisabled = true; break;
    544                    }
    545                    if (!allowDisabled) continue;
    546                }
    547                if (skipSameTime && section.getTime() != null && !hasChildren && !times.add(section.getTime()) && !isSelected(section) && !isWaitlisted(section) &&
    548                        (section.getIgnoreConflictWithSectionIds() == null || section.getIgnoreConflictWithSectionIds().isEmpty()))
    549                    continue;
    550                matchingSectionsThisSubpart.add(section);
    551            }
    552            if (random || limit > 0) {
    553                sectionsThisSubpart = new ArrayList<Section>(sectionsThisSubpart);
    554                Collections.shuffle(sectionsThisSubpart);
    555            }
    556            int i = 0;
    557            for (Section section: matchingSectionsThisSubpart) {
    558                sections.add(section);
    559                computeEnrollments(assignment, enrollments, priority, penalty + section.getPenalty(), course, config, sections, idx + 1,
    560                        availableOnly, skipSameTime, selectedOnly, random,
    561                        limit < 0 ? limit : Math.max(1, limit * (1 + i) / matchingSectionsThisSubpart.size()),
    562                        checkParent);
    563                sections.remove(section);
    564                i++;
    565            }
    566        }
    567    }
    568
    569    /** Return all enrollments that are available
    570     * @param assignment current assignment
    571     * @return all available enrollments
    572     **/
    573    public List<Enrollment> getAvaiableEnrollments(Assignment<Request, Enrollment> assignment) {
    574        return getAvaiableEnrollments(assignment, true);
    575    }
    576
    577    /** Return all enrollments that are available
    578     * @param assignment current assignment
    579     * @return all available enrollments
    580     **/
    581    public List<Enrollment> getAvaiableEnrollments(Assignment<Request, Enrollment> assignment, boolean checkParent) {
    582        List<Enrollment> ret = new ArrayList<Enrollment>();
    583        if (isFixed()) {
    584            ret.add(getFixedValue());
    585            return ret;
    586        }
    587        if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) {
    588            ret.add(getInitialAssignment());
    589            return ret;
    590        }
    591        int idx = 0;
    592        for (Course course : iCourses) {
    593            for (Config config : course.getOffering().getConfigs()) {
    594                computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, true, false, false, false,
    595                        getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), checkParent);
    596            }
    597            idx++;
    598        }
    599        return ret;
    600    }
    601
    602    /**
    603     * Return all enrollments of the first course that are selected (
    604     * {@link CourseRequest#isSelected(Section)} is true)
    605     *
    606     * @param assignment current assignment
    607     * @param availableOnly
    608     *            pick only available sections
    609     * @return selected enrollments
    610     */
    611    public List<Enrollment> getSelectedEnrollments(Assignment<Request, Enrollment> assignment, boolean availableOnly) {
    612        if (getSelectedChoices().isEmpty())
    613            return null;
    614        List<Enrollment> enrollments = new ArrayList<Enrollment>();
    615        if (isFixed()) return enrollments;
    616        if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments())
    617            return enrollments;
    618        for (Course course : iCourses) {
    619            boolean hasChoice = false;
    620            for (Choice choice: getSelectedChoices())
    621                if (course.getOffering().equals(choice.getOffering())) { hasChoice = true; break; }
    622            if (hasChoice)
    623                for (Config config : course.getOffering().getConfigs()) {
    624                    computeEnrollments(assignment, enrollments, 0, 0, course, config, new HashSet<Section>(), 0, availableOnly, false, true, false, -1, false);
    625                }
    626            break;
    627        }
    628        return enrollments;
    629    }
    630
    631    /**
    632     * Return all enrollments that are available, pick only the first section of
    633     * the sections with the same time (of each subpart, {@link Section}
    634     * comparator is used)
    635     * @param assignment current assignment
    636     * @return available enrollments
    637     */
    638    public List<Enrollment> getAvaiableEnrollmentsSkipSameTime(Assignment<Request, Enrollment> assignment) {
    639        return getAvaiableEnrollmentsSkipSameTime(assignment, true);
    640    }
    641
    642    /**
    643     * Return all enrollments that are available, pick only the first section of
    644     * the sections with the same time (of each subpart, {@link Section}
    645     * comparator is used)
    646     * @param assignment current assignment
    647     * @param checkParent check course dependency with other assigned courses
    648     * @return available enrollments
    649     */
    650    public List<Enrollment> getAvaiableEnrollmentsSkipSameTime(Assignment<Request, Enrollment> assignment, boolean checkParent) {
    651        List<Enrollment> ret = new ArrayList<Enrollment>();
    652        if (isFixed()) {
    653            ret.add(getFixedValue());
    654            return ret;
    655        }
    656        if (getInitialAssignment() != null) {
    657            ret.add(getInitialAssignment());
    658            if (getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments())
    659                return ret;
    660        }
    661        int idx = 0;
    662        for (Course course : iCourses) {
    663            boolean skipSameTime = true;
    664            for (LinkedSections link: getStudent().getLinkedSections())
    665                if (link.getOfferings().contains(course.getOffering())) { skipSameTime = false; break; }
    666            for (Config config : course.getOffering().getConfigs()) {
    667                computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, true, skipSameTime, false, false,
    668                        getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), checkParent);
    669            }
    670            idx++;
    671        }
    672        return ret;
    673    }
    674
    675    /**
    676     * Return all possible enrollments, but pick only the first section of
    677     * the sections with the same time (of each subpart, {@link Section}
    678     * comparator is used).
    679     * @param assignment current assignment
    680     * @return computed enrollments
    681     */
    682    public List<Enrollment> getEnrollmentsSkipSameTime(Assignment<Request, Enrollment> assignment) {
    683        List<Enrollment> ret = new ArrayList<Enrollment>();
    684        if (isFixed()) {
    685            ret.add(getFixedValue());
    686            return ret;
    687        }
    688        if (getInitialAssignment() != null) {
    689            ret.add(getInitialAssignment());
    690            if (getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments())
    691                return ret;
    692        }
    693        int idx = 0;
    694        for (Course course : iCourses) {
    695            for (Config config : course.getOffering().getConfigs()) {
    696                boolean skipSameTime = true;
    697                for (LinkedSections link: getStudent().getLinkedSections())
    698                    if (link.getOfferings().contains(course.getOffering())) { skipSameTime = false; break; }
    699                computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, skipSameTime, false, false,
    700                        getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), false);
    701            }
    702            idx++;
    703        }
    704        return ret;
    705    }
    706
    707    /** Wait-listed choices
    708     * @return wait-listed choices
    709     **/
    710    public Set<Choice> getWaitlistedChoices() {
    711        return iWaitlistedChoices;
    712    }
    713
    714    /**
    715     * Return true when the given section is wait-listed (i.e., its choice is
    716     * among wait-listed choices)
    717     * @param section given section
    718     * @return true if the given section matches the wait-listed choices
    719     */
    720    public boolean isWaitlisted(Section section) {
    721        for (Choice choice: iWaitlistedChoices)
    722            if (choice.sameChoice(section)) return true;
    723        return false;
    724    }
    725
    726    /** Selected choices
    727     * @return selected choices
    728     **/
    729    public Set<Choice> getSelectedChoices() {
    730        return iSelectedChoices;
    731    }
    732
    733    /**
    734     * Return true when the given section is selected (i.e., its choice is among
    735     * selected choices)
    736     * @param section given section
    737     * @return true if the given section matches the selected choices
    738     */
    739    public boolean isSelected(Section section) {
    740        for (Choice choice: iSelectedChoices)
    741            if (choice.sameSection(section) || choice.sameConfiguration(section)) return true;
    742        return false;
    743    }
    744
    745    /**
    746     * Return true when the given section has a preference (i.e., there is a matching selection),
    747     * or when there is a section preference for a different configuration
    748     * @param section given section
    749     * @return true if the there is a matching choice for the given section that is of the same offering
    750     */
    751    public boolean hasSelection(Section section) {
    752        boolean hasSectionChoices = false, hasSectionChoicesThisConfig = false;
    753        for (Choice choice: iSelectedChoices) {
    754            if (choice.sameOffering(section)) {
    755                if (choice.isMatching(section)) return true;
    756                if (choice.getSubpartId() != null) {
    757                    hasSectionChoices = true;
    758                    for (Subpart subpart: section.getSubpart().getConfig().getSubparts()) {
    759                        if (choice.getSubpartId().equals(subpart.getId())) { hasSectionChoicesThisConfig = true; }
    760                    }
    761                }
    762            }
    763        }
    764        return (hasSectionChoices && !hasSectionChoicesThisConfig);
    765    }
    766
    767    /**
    768     * Required choices
    769     * @return required choices
    770     */
    771    public Set<Choice> getRequiredChoices() {
    772        return iRequiredChoices;
    773    }
    774
    775    /**
    776     * Return true when the given section is required (i.e., its choice is among required choices, or there are no requirements)
    777     * @param section given section
    778     * @return true if the given section matches the required choices
    779     */
    780    public boolean isRequired(Section section) {
    781        if (iRequiredChoices.isEmpty()) return true;
    782        boolean hasConfig = false, hasMatchingConfig = false;
    783        boolean hasSubpart = false, hasMatchingSection = false;
    784        boolean hasSectionReq = false;
    785        for (Choice choice: iRequiredChoices) {
    786            // different offering -> skip
    787            if (!choice.getOffering().equals(section.getSubpart().getConfig().getOffering())) continue;
    788            // has config -> check config
    789            if (choice.getConfigId() != null) {
    790                hasConfig = true;
    791                if (choice.sameConfiguration(section)) hasMatchingConfig = true;
    792            }
    793            // has section of the matching subpart -> check section
    794            if (choice.getSubpartId() != null) {
    795                hasSectionReq = true;
    796                if (choice.getSubpartId().equals(section.getSubpart().getId())) {
    797                    hasSubpart = true;
    798                    if (choice.sameSection(section)) hasMatchingSection = true;
    799                } else if (!hasMatchingConfig) {
    800                    for (Subpart subpart: section.getSubpart().getConfig().getSubparts()) {
    801                        if (choice.getSubpartId().equals(subpart.getId())) {
    802                            hasMatchingConfig = true;
    803                            break;
    804                        }
    805                    }
    806                }
    807            }
    808        }
    809        if (hasConfig && !hasMatchingConfig) return false;
    810        if (hasSubpart && !hasMatchingSection) return false;
    811        // no match, but there are section requirements for a different config -> not satisfied
    812        if (!hasMatchingConfig && !hasMatchingSection && hasSectionReq) return false;
    813        return true;
    814    }
    815
    816    /**
    817     * Request name: A for alternative, 1 + priority, (w) when wait-list, list of
    818     * course names
    819     */
    820    @Override
    821    public String getName() {
    822        String ret = (isAlternative() ? "A" : "")
    823                + (1 + getPriority() + (isAlternative() ? -getStudent().nrRequests() : 0)) + ". "
    824                + (getRequestPriority() != RequestPriority.Normal ?
    825                  (isWaitlist() ? "(" + getRequestPriority().getAbbreviation() + "w) " : "(" + getRequestPriority().getAbbreviation() + ") ")
    826                  : isWaitlist() ? "(w) " : "");
    827        int idx = 0;
    828        for (Course course : iCourses) {
    829            if (idx == 0)
    830                ret += course.getName();
    831            else
    832                ret += ", " + idx + ". alt " + course.getName();
    833            idx++;
    834        }
    835        if (getStudent().getExternalId() != null)
    836            ret = "[" + getStudent().getExternalId() + "] " + ret;
    837        return ret;
    838    }
    839
    840    /**
    841     * True if the student can be put on a wait-list (no alternative course
    842     * request will be given instead)
    843     * @return true if the request can be wait-listed
    844     */
    845    public boolean isWaitlist() {
    846        return iWaitlist;
    847    }
    848
    849    /**
    850     * True if the student can be put on a wait-list (no alternative course
    851     * request will be given instead)
    852     * @param waitlist true if the request can be wait-listed
    853     */
    854    public void setWaitlist(boolean waitlist) {
    855        iWaitlist = waitlist;
    856    }
    857
    858    /**
    859     * True if the course request is critical for the student in order to move forward in their degree
    860     * @param critical true if the request is critical
    861     */
    862    @Deprecated
    863    public void setCritical(boolean critical) {
    864        iPriority = (critical ? RequestPriority.Critical : RequestPriority.Normal);
    865    }
    866
    867    /**
    868     * Time stamp of the request
    869     * @return request time stamp
    870     */
    871    public Long getTimeStamp() {
    872        return iTimeStamp;
    873    }
    874
    875    @Override
    876    public String toString() {
    877        return getName() + (getWeight() != 1.0 ? " (W:" + sDF.format(getWeight()) + ")" : "");
    878    }
    879
    880    /** Return course of the requested courses with the given id
    881     * @param courseId course offering id
    882     * @return course of the given id
    883     **/
    884    public Course getCourse(long courseId) {
    885        for (Course course : iCourses) {
    886            if (course.getId() == courseId)
    887                return course;
    888        }
    889        return null;
    890    }
    891
    892    /** Return configuration of the requested courses with the given id
    893     * @param configId instructional offering configuration unique id
    894     * @return config of the given id
    895     **/
    896    public Config getConfig(long configId) {
    897        for (Course course : iCourses) {
    898            for (Config config : course.getOffering().getConfigs()) {
    899                if (config.getId() == configId)
    900                    return config;
    901            }
    902        }
    903        return null;
    904    }
    905
    906    /** Return subpart of the requested courses with the given id
    907     * @param subpartId scheduling subpart unique id
    908     * @return subpart of the given id
    909     **/
    910    public Subpart getSubpart(long subpartId) {
    911        for (Course course : iCourses) {
    912            for (Config config : course.getOffering().getConfigs()) {
    913                for (Subpart subpart : config.getSubparts()) {
    914                    if (subpart.getId() == subpartId)
    915                        return subpart;
    916                }
    917            }
    918        }
    919        return null;
    920    }
    921
    922    /** Return section of the requested courses with the given id
    923     * @param sectionId class unique id
    924     * @return section of the given id
    925     **/
    926    public Section getSection(long sectionId) {
    927        for (Course course : iCourses) {
    928            for (Config config : course.getOffering().getConfigs()) {
    929                for (Subpart subpart : config.getSubparts()) {
    930                    for (Section section : subpart.getSections()) {
    931                        if (section.getId() == sectionId)
    932                            return section;
    933                    }
    934                }
    935            }
    936        }
    937        return null;
    938    }
    939
    940    /**
    941     * Minimal penalty (minimum of {@link Offering#getMinPenalty()} among
    942     * requested courses)
    943     * @return minimal penalty
    944     */
    945    public double getMinPenalty() {
    946        if (iCachedMinPenalty == null) {
    947            double min = Double.MAX_VALUE;
    948            for (Course course : iCourses) {
    949                min = Math.min(min, course.getOffering().getMinPenalty());
    950            }
    951            iCachedMinPenalty = Double.valueOf(min);
    952        }
    953        return iCachedMinPenalty.doubleValue();
    954    }
    955
    956    /**
    957     * Maximal penalty (maximum of {@link Offering#getMaxPenalty()} among
    958     * requested courses)
    959     * @return maximal penalty
    960     */
    961    public double getMaxPenalty() {
    962        if (iCachedMaxPenalty == null) {
    963            double max = Double.MIN_VALUE;
    964            for (Course course : iCourses) {
    965                max = Math.max(max, course.getOffering().getMaxPenalty());
    966            }
    967            iCachedMaxPenalty = Double.valueOf(max);
    968        }
    969        return iCachedMaxPenalty.doubleValue();
    970    }
    971
    972    /** Clear cached min/max penalties and cached bound */
    973    public void clearCache() {
    974        iCachedMaxPenalty = null;
    975        iCachedMinPenalty = null;
    976    }
    977
    978    /**
    979     * Estimated bound for this request -- it estimates the smallest value among
    980     * all possible enrollments
    981     */
    982    @Override
    983    public double getBound() {
    984        return - getWeight() * ((StudentSectioningModel)getModel()).getStudentWeights().getBound(this);
    985        /*
    986        if (iCachedBound == null) {
    987            iCachedBound = Double.valueOf(-Math.pow(Enrollment.sPriorityWeight, getPriority())
    988                    * (isAlternative() ? Enrollment.sAlterativeWeight : 1.0)
    989                    * Math.pow(Enrollment.sInitialWeight, (getInitialAssignment() == null ? 0 : 1))
    990                    * Math.pow(Enrollment.sSelectedWeight, (iSelectedChoices.isEmpty() ? 0 : 1))
    991                    * Math.pow(Enrollment.sWaitlistedWeight, (iWaitlistedChoices.isEmpty() ? 0 : 1))
    992                    *
    993                    // Math.max(Enrollment.sMinWeight,getWeight()) *
    994                    (getStudent().isDummy() ? Student.sDummyStudentWeight : 1.0)
    995                    * Enrollment.normalizePenalty(getMinPenalty()));
    996        }
    997        return iCachedBound.doubleValue();
    998        */
    999    }
    1000
    1001    /** Return true if request is assigned. */
    1002    @Override
    1003    public boolean isAssigned(Assignment<Request, Enrollment> assignment) {
    1004        Enrollment e = assignment.getValue(this);
    1005        return e != null && !e.getAssignments().isEmpty();
    1006    }
    1007
    1008    @Override
    1009    public boolean equals(Object o) {
    1010        return super.equals(o) && (o instanceof CourseRequest);
    1011    }
    1012
    1013    /**
    1014     * Get reservations for this course requests
    1015     * @param course given course
    1016     * @return reservations for this course requests and the given course
    1017     */
    1018    public synchronized List<Reservation> getReservations(Course course) {
    1019        if (iReservations == null)
    1020            iReservations = new HashMap<Course, List<Reservation>>();
    1021        List<Reservation> reservations = iReservations.get(course);
    1022        if (reservations == null) {
    1023            reservations = new ArrayList<Reservation>();
    1024            boolean mustBeUsed = false;
    1025            for (Reservation r: course.getOffering().getReservations()) {
    1026                if (!r.isApplicable(getStudent(), course)) continue;
    1027                if (!mustBeUsed && r.mustBeUsed()) { reservations.clear(); mustBeUsed = true; }
    1028                if (mustBeUsed && !r.mustBeUsed()) continue;
    1029                reservations.add(r);
    1030            }
    1031            iReservations.put(course, reservations);
    1032        }
    1033        return reservations;
    1034    }
    1035    private Map<Course, List<Reservation>> iReservations = null;
    1036
    1037    /**
    1038     * Get reservations for this course requests ordered using {@link Reservation#compareTo(Assignment, Reservation)}
    1039     * @param course given course
    1040     * @return reservations for this course requests and the given course
    1041     */
    1042    public TreeSet<Reservation> getSortedReservations(Assignment<Request, Enrollment> assignment, Course course) {
    1043        TreeSet<Reservation> reservations = new TreeSet<Reservation>(new AssignmentComparator<Reservation, Request, Enrollment>(assignment));
    1044        reservations.addAll(getReservations(course));
    1045        return reservations;
    1046    }
    1047
    1048    /**
    1049     * Return true if there is a reservation for a course of this request
    1050     * @return true if there is a reservation for a course of this request
    1051     */
    1052    public boolean hasReservations() {
    1053        for (Course course: getCourses())
    1054            if (!getReservations(course).isEmpty())
    1055                return true;
    1056        return false;
    1057    }
    1058
    1059    /**
    1060     * Clear reservation information that was cached on this section
    1061     */
    1062    public synchronized void clearReservationCache() {
    1063        if (iReservations != null) iReservations.clear();
    1064    }
    1065
    1066    /**
    1067     * Get restrictions for this course requests
    1068     * @param course given course
    1069     * @return restrictions for this course requests and the given course
    1070     */
    1071    public synchronized List<Restriction> getRestrictions(Course course) {
    1072        if (iRestrictions == null)
    1073            iRestrictions = new HashMap<Course, List<Restriction>>();
    1074        List<Restriction> restrictions = iRestrictions.get(course);
    1075        if (restrictions == null) {
    1076            restrictions = new ArrayList<Restriction>();
    1077            for (Restriction r: course.getOffering().getRestrictions()) {
    1078                if (r.isApplicable(getStudent(), course))
    1079                    restrictions.add(r);
    1080            }
    1081            iRestrictions.put(course, restrictions);
    1082        }
    1083        return restrictions;
    1084    }
    1085    private Map<Course, List<Restriction>> iRestrictions = null;
    1086
    1087    /**
    1088     * Return true if there is a restriction for a course of this request
    1089     * @return true if there is a restriction for a course of this request
    1090     */
    1091    public boolean hasRestrictions(Course course) {
    1092        return !getRestrictions(course).isEmpty();
    1093    }
    1094
    1095    /**
    1096     * Return true when there are restrictions for a course of this course request and the given config does not meet any of them
    1097     */
    1098    public boolean isNotAllowed(Course course, Config config) {
    1099        List<Restriction> restrictions = getRestrictions(course);
    1100        if (restrictions.isEmpty()) return false;
    1101        for (Restriction r: restrictions)
    1102            if (r.isIncluded(config)) return false;
    1103        return true;
    1104    }
    1105
    1106    /**
    1107     * Return true when there are restrictions for a course of this course request and the given section does not meet any of them
    1108     */
    1109    public boolean isNotAllowed(Course course, Section section) {
    1110        List<Restriction> restrictions = getRestrictions(course);
    1111        if (restrictions.isEmpty()) return false;
    1112        for (Restriction r: restrictions)
    1113            if (r.isIncluded(section)) return false;
    1114        return true;
    1115    }
    1116
    1117    /**
    1118     * Return true when there are restrictions for a course of this course request and the given enrollment does not meet any of them
    1119     */
    1120    public boolean isNotAllowed(Enrollment e) {
    1121        List<Restriction> restrictions = getRestrictions(e.getCourse());
    1122        if (restrictions.isEmpty()) return false;
    1123        for (Restriction r: restrictions)
    1124            if (r.isIncluded(e)) return false;
    1125        return true;
    1126    }
    1127
    1128    /**
    1129     * Clear restriction information that was cached on this request
    1130     */
    1131    public synchronized void clearRestrictionCache() {
    1132        if (iRestrictions != null) iRestrictions.clear();
    1133    }
    1134
    1135    /**
    1136     * Return true if this request can track MPP
    1137     * @return true if the request is course request and it either has an initial enrollment
    1138     */
    1139    @Override
    1140    public boolean isMPP() {
    1141        StudentSectioningModel model = (StudentSectioningModel) getModel();
    1142        if (model == null || !model.isMPP()) return false;
    1143        return !getStudent().isDummy() && getInitialAssignment() != null;
    1144    }
    1145
    1146    /**
    1147     * Return true if this request has any selection
    1148     * @return true if the request is course request and has some selected choices.
    1149     */
    1150    @Override
    1151    public boolean hasSelection() {
    1152        if (getStudent().isDummy() || getSelectedChoices().isEmpty()) return false;
    1153        for (Choice choice: getSelectedChoices())
    1154            if (choice.getSectionId() != null || choice.getConfigId() != null) return true;
    1155        return false;
    1156    }
    1157
    1158    /**
    1159     * Add request group to this request.
    1160     * @param group request group to be added
    1161     */
    1162    public void addRequestGroup(RequestGroup group) {
    1163        iRequestGroups.add(group);
    1164        group.addRequest(this);
    1165    }
    1166
    1167    /**
    1168     * Removed request group from this request.
    1169     * @param group request group to be removed
    1170     */
    1171    public void removeRequestGroup(RequestGroup group) {
    1172        iRequestGroups.remove(group);
    1173        group.removeRequest(this);
    1174    }
    1175
    1176    /**
    1177     * Lists request groups of this request
    1178     * @return request groups of this course requests
    1179     */
    1180    public Set<RequestGroup> getRequestGroups() {
    1181        return iRequestGroups;
    1182    }
    1183
    1184    @Override
    1185    public void variableAssigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment enrollment) {
    1186        super.variableAssigned(assignment, iteration, enrollment);
    1187        for (RequestGroup g: getRequestGroups())
    1188            if (g.getCourse().equals(enrollment.getCourse()))
    1189                g.assigned(assignment, enrollment);
    1190    }
    1191
    1192    @Override
    1193    public void variableUnassigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment enrollment) {
    1194        super.variableUnassigned(assignment, iteration, enrollment);
    1195        for (RequestGroup g: getRequestGroups())
    1196            if (g.getCourse().equals(enrollment.getCourse()))
    1197                g.unassigned(assignment, enrollment);
    1198        /*
    1199        if (enrollment != null && enrollment.getCourse() != null && enrollment.getCourse().hasChildren()) {
    1200            for (Request r: enrollment.getStudent().getRequests()) {
    1201                if (r.equals(enrollment.getRequest())) continue;
    1202                Enrollment e = assignment.getValue(r);
    1203                if (e != null && e.getCourse() != null && enrollment.getCourse().equals(e.getCourse().getParent())) {
    1204                    assignment.unassign(iteration, r);
    1205                }
    1206            }
    1207        }*/
    1208    }
    1209
    1210    @Override
    1211    public float getMinCredit() {
    1212        Float credit = null;
    1213        for (Course course: getCourses()) {
    1214            if (course.hasCreditValue() && (credit == null || credit > course.getCreditValue()))
    1215                    credit = course.getCreditValue();
    1216            for (Config config: course.getOffering().getConfigs()) {
    1217                Float configCredit = config.getCreditValue();
    1218                if (configCredit != null && (credit == null || credit > configCredit))
    1219                        credit = configCredit;
    1220            }
    1221        }
    1222        return (credit == null ? 0 : credit.floatValue());
    1223    }
    1224
    1225    @Override
    1226    public RequestPriority getRequestPriority() {
    1227        return iPriority;
    1228    }
    1229
    1230    public void setRequestPriority(RequestPriority priority) {
    1231        iPriority = priority;
    1232    }
    1233
    1234    public boolean isFixed() { return iFixed != null; }
    1235    public Enrollment getFixedValue() { return iFixed; }
    1236    public void setFixedValue(Enrollment constant) { iFixed = constant; }
    1237
    1238    @Override
    1239    public boolean hasCourse(Course course) {
    1240        return iCourses.contains(course);
    1241    }
    1242
    1243    @Override
    1244    public boolean hasChildren() {
    1245        for (Course course: iCourses)
    1246            if (course.hasChildren()) return true;
    1247        return false;
    1248    }
    1249}