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