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())) 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()))
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}