001package net.sf.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 net.sf.cpsolver.coursett.model.TimeLocation;
015import net.sf.cpsolver.ifs.util.ToolBox;
016import net.sf.cpsolver.studentsct.StudentSectioningModel;
017import net.sf.cpsolver.studentsct.constraint.ConfigLimit;
018import net.sf.cpsolver.studentsct.constraint.CourseLimit;
019import net.sf.cpsolver.studentsct.constraint.SectionLimit;
020import net.sf.cpsolver.studentsct.reservation.Reservation;
021
022/**
023 * Representation of a request of a student for one or more course. A student
024 * requests one of the given courses, preferably the first one. <br>
025 * <br>
026 * 
027 * @version StudentSct 1.2 (Student Sectioning)<br>
028 *          Copyright (C) 2007 - 2010 Tomáš Müller<br>
029 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
030 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
031 * <br>
032 *          This library is free software; you can redistribute it and/or modify
033 *          it under the terms of the GNU Lesser General Public License as
034 *          published by the Free Software Foundation; either version 3 of the
035 *          License, or (at your option) any later version. <br>
036 * <br>
037 *          This library is distributed in the hope that it will be useful, but
038 *          WITHOUT ANY WARRANTY; without even the implied warranty of
039 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
040 *          Lesser General Public License for more details. <br>
041 * <br>
042 *          You should have received a copy of the GNU Lesser General Public
043 *          License along with this library; if not see
044 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
045 */
046public class CourseRequest extends Request {
047    private static DecimalFormat sDF = new DecimalFormat("0.000");
048    private List<Course> iCourses = null;
049    private Set<Choice> iWaitlistedChoices = new HashSet<Choice>();
050    private Set<Choice> iSelectedChoices = new HashSet<Choice>();
051    private boolean iWaitlist = false;
052    private Long iTimeStamp = null;
053    private Double iCachedMinPenalty = null, iCachedMaxPenalty = null;
054    public static boolean sSameTimePrecise = false;
055
056    /**
057     * Constructor
058     * 
059     * @param id
060     *            request unique id
061     * @param priority
062     *            request priority
063     * @param alternative
064     *            true if the request is alternative (alternative request can be
065     *            assigned instead of a non-alternative course requests, if it
066     *            is left unassigned)
067     * @param student
068     *            appropriate student
069     * @param courses
070     *            list of requested courses (in the correct order -- first is
071     *            the requested course, second is the first alternative, etc.)
072     * @param waitlist
073     *            time stamp of the request if the student can be put on a wait-list (no alternative
074     *            course request will be given instead)
075     */
076    public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses,
077            boolean waitlist, Long timeStamp) {
078        super(id, priority, alternative, student);
079        iCourses = new ArrayList<Course>(courses);
080        for (Course course: iCourses)
081            course.getRequests().add(this);
082        iWaitlist = waitlist;
083        iTimeStamp = timeStamp;
084    }
085
086    /**
087     * List of requested courses (in the correct order -- first is the requested
088     * course, second is the first alternative, etc.)
089     */
090    public List<Course> getCourses() {
091        return iCourses;
092    }
093
094    /**
095     * Create enrollment for the given list of sections. The list of sections
096     * needs to be correct, i.e., a section for each subpart of a configuration
097     * of one of the requested courses.
098     */
099    public Enrollment createEnrollment(Set<? extends Assignment> sections, Reservation reservation) {
100        if (sections == null || sections.isEmpty())
101            return null;
102        Config config = ((Section) sections.iterator().next()).getSubpart().getConfig();
103        Course course = null;
104        for (Course c: iCourses) {
105            if (c.getOffering().getConfigs().contains(config)) {
106                course = c;
107                break;
108            }
109        }
110        return new Enrollment(this, iCourses.indexOf(course), course, config, sections, reservation);
111    }
112
113    /**
114     * Create enrollment for the given list of sections. The list of sections
115     * needs to be correct, i.e., a section for each subpart of a configuration
116     * of one of the requested courses.
117     */
118    public Enrollment createEnrollment(Set<? extends Assignment> sections) {
119        Enrollment ret = createEnrollment(sections, null);
120        ret.guessReservation(true);
121        return ret;
122        
123    }
124    
125    /**
126     * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit.
127     */
128    protected int getMaxDomainSize() {
129        StudentSectioningModel model = (StudentSectioningModel) getModel();
130        return model == null ? null : model.getMaxDomainSize();
131    }
132
133    /**
134     * Return all possible enrollments.
135     */
136    @Override
137    public List<Enrollment> computeEnrollments() {
138        List<Enrollment> ret = new ArrayList<Enrollment>();
139        int idx = 0;
140        for (Course course : iCourses) {
141            for (Config config : course.getOffering().getConfigs()) {
142                computeEnrollments(ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false,
143                        false, false, getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize());
144            }
145            idx++;
146        }
147        return ret;
148    }
149
150    /**
151     * Return a subset of all enrollments -- randomly select only up to
152     * limitEachConfig enrollments of each config.
153     */
154    public List<Enrollment> computeRandomEnrollments(int limitEachConfig) {
155        List<Enrollment> ret = new ArrayList<Enrollment>();
156        int idx = 0;
157        for (Course course : iCourses) {
158            for (Config config : course.getOffering().getConfigs()) {
159                computeEnrollments(ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false,
160                        false, true, (limitEachConfig <= 0 ? limitEachConfig : ret.size() + limitEachConfig));
161            }
162            idx++;
163        }
164        return ret;
165    }
166
167    /**
168     * Return true if the both sets of sections contain sections of the same
169     * subparts, and each pair of sections of the same subpart is offered at the
170     * same time.
171     */
172    private boolean sameTimes(Set<Section> sections1, Set<Section> sections2) {
173        for (Section s1 : sections1) {
174            Section s2 = null;
175            for (Section s : sections2) {
176                if (s.getSubpart().equals(s1.getSubpart())) {
177                    s2 = s;
178                    break;
179                }
180            }
181            if (s2 == null)
182                return false;
183            if (!ToolBox.equals(s1.getTime(), s2.getTime()))
184                return false;
185        }
186        return true;
187    }
188
189    /**
190     * Recursive computation of enrollments
191     * 
192     * @param enrollments
193     *            list of enrollments to be returned
194     * @param priority
195     *            zero for the course, one for the first alternative, two for the second alternative
196     * @param penalty
197     *            penalty of the selected sections
198     * @param course
199     *            selected course
200     * @param config
201     *            selected configuration
202     * @param sections
203     *            sections selected so far
204     * @param idx
205     *            index of the subparts (a section of 0..idx-1 subparts has been
206     *            already selected)
207     * @param availableOnly
208     *            only use available sections
209     * @param skipSameTime
210     *            for each possible times, pick only one section
211     * @param selectedOnly
212     *            select only sections that are selected (
213     *            {@link CourseRequest#isSelected(Section)} is true)
214     * @param random
215     *            pick sections in a random order (useful when limit is used)
216     * @param limit
217     *            when above zero, limit the number of selected enrollments to
218     *            this limit
219     * @param reservations
220     *            list of applicable reservations
221     */
222    private void computeEnrollments(Collection<Enrollment> enrollments, int priority, double penalty, Course course, Config config,
223            HashSet<Section> sections, int idx, boolean availableOnly, boolean skipSameTime, boolean selectedOnly,
224            boolean random, int limit) {
225        if (limit > 0 && enrollments.size() >= limit)
226            return;
227        if (idx == 0) { // run only once for each configuration
228            boolean canOverLimit = false;
229            if (availableOnly && (getModel() == null || ((StudentSectioningModel)getModel()).getReservationCanAssignOverTheLimit())) {
230                for (Reservation r: getReservations(course)) {
231                    if (!r.canAssignOverLimit()) continue;
232                    if (!r.getConfigs().isEmpty() && !r.getConfigs().contains(config)) continue;
233                    if (r.getReservedAvailableSpace(this) < getWeight()) continue;
234                    canOverLimit = true; break;
235                }
236            }
237            if (!canOverLimit) {
238                if (availableOnly && config.getLimit() >= 0 && ConfigLimit.getEnrollmentWeight(config, this) > config.getLimit())
239                    return;
240                if (availableOnly && course.getLimit() >= 0 && CourseLimit.getEnrollmentWeight(course, this) > course.getLimit())
241                    return;
242                if (config.getOffering().hasReservations()) {
243                    boolean hasReservation = false, hasOtherReservation = false, hasConfigReservation = false, reservationMustBeUsed = false;
244                    for (Reservation r: getReservations(course)) {
245                        if (r.mustBeUsed()) reservationMustBeUsed = true;
246                        if (availableOnly && r.getReservedAvailableSpace(this) < getWeight()) continue;
247                        if (r.getConfigs().isEmpty()) {
248                            hasReservation = true;
249                        } else if (r.getConfigs().contains(config)) {
250                            hasReservation = true;
251                            hasConfigReservation = true;
252                        } else {
253                            hasOtherReservation = true;
254                        }
255                    }
256                    if (!hasConfigReservation && config.getTotalUnreservedSpace() < getWeight())
257                        return;
258                    if (!hasReservation && config.getOffering().getTotalUnreservedSpace() < getWeight())
259                        return;
260                    if (hasOtherReservation && !hasReservation)
261                        return;
262                    if (availableOnly && !hasReservation && config.getOffering().getUnreservedSpace(this) < getWeight())
263                        return;
264                    if (availableOnly && !hasConfigReservation && config.getUnreservedSpace(this) < getWeight())
265                        return;
266                    if (!hasReservation && reservationMustBeUsed)
267                        return;
268                }
269            }
270        }
271        if (config.getSubparts().size() == idx) {
272            if (skipSameTime && sSameTimePrecise) {
273                boolean waitListedOrSelected = false;
274                if (!getSelectedChoices().isEmpty() || !getWaitlistedChoices().isEmpty()) {
275                    for (Section section : sections) {
276                        if (isWaitlisted(section) || isSelected(section)) {
277                            waitListedOrSelected = true;
278                            break;
279                        }
280                    }
281                }
282                if (!waitListedOrSelected) {
283                    for (Enrollment enrollment : enrollments) {
284                        if (sameTimes(enrollment.getSections(), sections))
285                            return;
286                    }
287                }
288            }
289            if (!config.getOffering().hasReservations()) {
290                enrollments.add(new Enrollment(this, priority, null, config, new HashSet<Assignment>(sections), null));
291            } else {
292                Enrollment e = new Enrollment(this, priority, null, config, new HashSet<Assignment>(sections), null);
293                boolean mustHaveReservation = config.getOffering().getTotalUnreservedSpace() < getWeight();
294                boolean mustHaveConfigReservation = config.getTotalUnreservedSpace() < getWeight();
295                boolean mustHaveSectionReservation = false;
296                for (Section s: sections) {
297                    if (s.getTotalUnreservedSpace() < getWeight()) {
298                        mustHaveSectionReservation = true;
299                        break;
300                    }
301                }
302                boolean canOverLimit = false;
303                if (availableOnly &&  (getModel() == null || ((StudentSectioningModel)getModel()).getReservationCanAssignOverTheLimit())) {
304                    for (Reservation r: getReservations(course)) {
305                        if (!r.canAssignOverLimit() || !r.isIncluded(e)) continue;
306                        if (r.getReservedAvailableSpace(this) < getWeight()) continue;
307                        enrollments.add(new Enrollment(this, priority, null, config, new HashSet<Assignment>(sections), r));
308                        canOverLimit = true;
309                    }
310                }
311                if (!canOverLimit) {
312                    boolean reservationMustBeUsed = false;
313                    reservations: for (Reservation r: (availableOnly ? new TreeSet<Reservation>(getReservations(course)) : getReservations(course))) {
314                        if (r.mustBeUsed()) reservationMustBeUsed = true;
315                        if (!r.isIncluded(e)) continue;
316                        if (availableOnly && r.getReservedAvailableSpace(this) < getWeight()) continue;
317                        if (mustHaveConfigReservation && r.getConfigs().isEmpty()) continue;
318                        if (mustHaveSectionReservation)
319                            for (Section s: sections)
320                                if (r.getSections(s.getSubpart()) == null && s.getTotalUnreservedSpace() < getWeight()) continue reservations;
321                        enrollments.add(new Enrollment(this, priority, null, config, new HashSet<Assignment>(sections), r));
322                        if (availableOnly) return; // only one available reservation suffice (the best matching one)
323                    }
324                    // a case w/o reservation
325                    if (!(mustHaveReservation || mustHaveConfigReservation || mustHaveSectionReservation) &&
326                        !(availableOnly && config.getOffering().getUnreservedSpace(this) < getWeight()) &&
327                        !reservationMustBeUsed) {
328                        enrollments.add(new Enrollment(this, (getReservations(course).isEmpty() ? 0 : 1) + priority, null, config, new HashSet<Assignment>(sections), null));
329                    }
330                }
331            }
332        } else {
333            Subpart subpart = config.getSubparts().get(idx);
334            HashSet<TimeLocation> times = (skipSameTime ? new HashSet<TimeLocation>() : null);
335            List<Section> sectionsThisSubpart = subpart.getSections();
336            if (skipSameTime) {
337                sectionsThisSubpart = new ArrayList<Section>(subpart.getSections());
338                Collections.sort(sectionsThisSubpart);
339            }
340            List<Section> matchingSectionsThisSubpart = new ArrayList<Section>(subpart.getSections().size());
341            boolean hasChildren = !subpart.getChildren().isEmpty();
342            for (Section section : sectionsThisSubpart) {
343                if (section.getParent() != null && !sections.contains(section.getParent()))
344                    continue;
345                if (section.isOverlapping(sections))
346                    continue;
347                if (selectedOnly && !isSelected(section))
348                    continue;
349                boolean canOverLimit = false;
350                if (availableOnly && (getModel() == null || ((StudentSectioningModel)getModel()).getReservationCanAssignOverTheLimit())) {
351                    for (Reservation r: getReservations(course)) {
352                        if (!r.canAssignOverLimit()) continue;
353                        if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue;
354                        if (r.getReservedAvailableSpace(this) < getWeight()) continue;
355                        canOverLimit = true; break;
356                    }
357                }
358                if (!canOverLimit) {
359                    if (availableOnly && section.getLimit() >= 0
360                            && SectionLimit.getEnrollmentWeight(section, this) > section.getLimit())
361                        continue;
362                    if (config.getOffering().hasReservations()) {
363                        boolean hasReservation = false, hasSectionReservation = false, hasOtherReservation = false, reservationMustBeUsed = false;
364                        for (Reservation r: getReservations(course)) {
365                            if (r.mustBeUsed()) reservationMustBeUsed = true;
366                            if (availableOnly && r.getReservedAvailableSpace(this) < getWeight()) continue;
367                            if (r.getSections(subpart) == null) {
368                                hasReservation = true;
369                            } else if (r.getSections(subpart).contains(section)) {
370                                hasReservation = true;
371                                hasSectionReservation = true;
372                            } else {
373                                hasOtherReservation = true;
374                            }
375                        }
376                        if (!hasSectionReservation && section.getTotalUnreservedSpace() < getWeight())
377                            continue;
378                        if (hasOtherReservation && !hasReservation)
379                            continue;
380                        if (availableOnly && !hasSectionReservation && section.getUnreservedSpace(this) < getWeight())
381                            continue;
382                        if (!hasReservation && reservationMustBeUsed)
383                            continue;
384                    }
385                }
386                if (skipSameTime && section.getTime() != null && !hasChildren && !times.add(section.getTime()) && !isSelected(section) && !isWaitlisted(section))
387                    continue;
388                matchingSectionsThisSubpart.add(section);
389            }
390            if (random || limit > 0)
391                Collections.shuffle(sectionsThisSubpart);
392            int i = 0;
393            for (Section section: matchingSectionsThisSubpart) {
394                sections.add(section);
395                computeEnrollments(enrollments, priority, penalty + section.getPenalty(), course, config, sections, idx + 1,
396                        availableOnly, skipSameTime, selectedOnly, random,
397                        limit < 0 ? limit : Math.max(1, limit * (1 + i) / matchingSectionsThisSubpart.size())
398                        );
399                sections.remove(section);
400                i++;
401            }
402        }
403    }
404
405    /** Return all enrollments that are available */
406    public List<Enrollment> getAvaiableEnrollments() {
407        List<Enrollment> ret = new ArrayList<Enrollment>();
408        int idx = 0;
409        for (Course course : iCourses) {
410            for (Config config : course.getOffering().getConfigs()) {
411                computeEnrollments(ret, idx, 0, course, config, new HashSet<Section>(), 0, true, false, false, false,
412                        getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize());
413            }
414            idx++;
415        }
416        return ret;
417    }
418
419    /**
420     * Return all enrollments that are selected (
421     * {@link CourseRequest#isSelected(Section)} is true)
422     * 
423     * @param availableOnly
424     *            pick only available sections
425     */
426    public List<Enrollment> getSelectedEnrollments(boolean availableOnly) {
427        if (getSelectedChoices().isEmpty())
428            return null;
429        Choice firstChoice = getSelectedChoices().iterator().next();
430        List<Enrollment> enrollments = new ArrayList<Enrollment>();
431        for (Course course : iCourses) {
432            if (!course.getOffering().equals(firstChoice.getOffering()))
433                continue;
434            for (Config config : course.getOffering().getConfigs()) {
435                computeEnrollments(enrollments, 0, 0, course, config, new HashSet<Section>(), 0, availableOnly, false, true, false, -1);
436            }
437        }
438        return enrollments;
439    }
440
441    /**
442     * Return all enrollments that are available, pick only the first section of
443     * the sections with the same time (of each subpart, {@link Section}
444     * comparator is used)
445     */
446    public List<Enrollment> getAvaiableEnrollmentsSkipSameTime() {
447        List<Enrollment> ret = new ArrayList<Enrollment>();
448        if (getInitialAssignment() != null)
449            ret.add(getInitialAssignment());
450        int idx = 0;
451        for (Course course : iCourses) {
452            for (Config config : course.getOffering().getConfigs()) {
453                computeEnrollments(ret, idx, 0, course, config, new HashSet<Section>(), 0, true, true, false, false,
454                        getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize());
455            }
456            idx++;
457        }
458        return ret;
459    }
460
461    /**
462     * Return all possible enrollments.
463     */
464    public List<Enrollment> getEnrollmentsSkipSameTime() {
465        List<Enrollment> ret = new ArrayList<Enrollment>();
466        int idx = 0;
467        for (Course course : iCourses) {
468            for (Config config : course.getOffering().getConfigs()) {
469                computeEnrollments(ret, idx, 0, course, config, new HashSet<Section>(), 0, false, true, false, false,
470                        getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize());
471            }
472            idx++;
473        }
474        return ret;
475    }
476
477    /** Wait-listed choices */
478    public Set<Choice> getWaitlistedChoices() {
479        return iWaitlistedChoices;
480    }
481
482    /**
483     * Return true when the given section is wait-listed (i.e., its choice is
484     * among wait-listed choices)
485     */
486    public boolean isWaitlisted(Section section) {
487        return iWaitlistedChoices.contains(section.getChoice());
488    }
489
490    /** Selected choices */
491    public Set<Choice> getSelectedChoices() {
492        return iSelectedChoices;
493    }
494
495    /**
496     * Return true when the given section is selected (i.e., its choice is among
497     * selected choices)
498     */
499    public boolean isSelected(Section section) {
500        return iSelectedChoices.contains(section.getChoice());
501    }
502
503    /**
504     * Request name: A for alternative, 1 + priority, (w) when waitlist, list of
505     * course names
506     */
507    @Override
508    public String getName() {
509        String ret = (isAlternative() ? "A" : "")
510                + (1 + getPriority() + (isAlternative() ? -getStudent().nrRequests() : 0)) + ". "
511                + (isWaitlist() ? "(w) " : "");
512        int idx = 0;
513        for (Course course : iCourses) {
514            if (idx == 0)
515                ret += course.getName();
516            else
517                ret += ", " + idx + ". alt " + course.getName();
518            idx++;
519        }
520        return ret;
521    }
522
523    /**
524     * True if the student can be put on a wait-list (no alternative course
525     * request will be given instead)
526     */
527    public boolean isWaitlist() {
528        return iWaitlist;
529    }
530    
531    /**
532     * True if the student can be put on a wait-list (no alternative course
533     * request will be given instead)
534     */
535    public void setWaitlist(boolean waitlist) {
536        iWaitlist = waitlist;
537    }
538    
539    /**
540     * Time stamp of the request
541     */
542    public Long getTimeStamp() {
543        return iTimeStamp;
544    }
545
546    @Override
547    public String toString() {
548        return getName() + (getWeight() != 1.0 ? " (W:" + sDF.format(getWeight()) + ")" : "");
549    }
550
551    /** Return course of the requested courses with the given id */
552    public Course getCourse(long courseId) {
553        for (Course course : iCourses) {
554            if (course.getId() == courseId)
555                return course;
556        }
557        return null;
558    }
559
560    /** Return configuration of the requested courses with the given id */
561    public Config getConfig(long configId) {
562        for (Course course : iCourses) {
563            for (Config config : course.getOffering().getConfigs()) {
564                if (config.getId() == configId)
565                    return config;
566            }
567        }
568        return null;
569    }
570
571    /** Return subpart of the requested courses with the given id */
572    public Subpart getSubpart(long subpartId) {
573        for (Course course : iCourses) {
574            for (Config config : course.getOffering().getConfigs()) {
575                for (Subpart subpart : config.getSubparts()) {
576                    if (subpart.getId() == subpartId)
577                        return subpart;
578                }
579            }
580        }
581        return null;
582    }
583
584    /** Return section of the requested courses with the given id */
585    public Section getSection(long sectionId) {
586        for (Course course : iCourses) {
587            for (Config config : course.getOffering().getConfigs()) {
588                for (Subpart subpart : config.getSubparts()) {
589                    for (Section section : subpart.getSections()) {
590                        if (section.getId() == sectionId)
591                            return section;
592                    }
593                }
594            }
595        }
596        return null;
597    }
598
599    /**
600     * Minimal penalty (minimum of {@link Offering#getMinPenalty()} among
601     * requested courses)
602     */
603    public double getMinPenalty() {
604        if (iCachedMinPenalty == null) {
605            double min = Double.MAX_VALUE;
606            for (Course course : iCourses) {
607                min = Math.min(min, course.getOffering().getMinPenalty());
608            }
609            iCachedMinPenalty = new Double(min);
610        }
611        return iCachedMinPenalty.doubleValue();
612    }
613
614    /**
615     * Maximal penalty (maximum of {@link Offering#getMaxPenalty()} among
616     * requested courses)
617     */
618    public double getMaxPenalty() {
619        if (iCachedMaxPenalty == null) {
620            double max = Double.MIN_VALUE;
621            for (Course course : iCourses) {
622                max = Math.max(max, course.getOffering().getMaxPenalty());
623            }
624            iCachedMaxPenalty = new Double(max);
625        }
626        return iCachedMaxPenalty.doubleValue();
627    }
628
629    /** Clear cached min/max penalties and cached bound */
630    public void clearCache() {
631        iCachedMaxPenalty = null;
632        iCachedMinPenalty = null;
633    }
634
635    /**
636     * Estimated bound for this request -- it estimates the smallest value among
637     * all possible enrollments
638     */
639    @Override
640    public double getBound() {
641        return - getWeight() * ((StudentSectioningModel)getModel()).getStudentWeights().getBound(this);
642        /*
643        if (iCachedBound == null) {
644            iCachedBound = new Double(-Math.pow(Enrollment.sPriorityWeight, getPriority())
645                    * (isAlternative() ? Enrollment.sAlterativeWeight : 1.0)
646                    * Math.pow(Enrollment.sInitialWeight, (getInitialAssignment() == null ? 0 : 1))
647                    * Math.pow(Enrollment.sSelectedWeight, (iSelectedChoices.isEmpty() ? 0 : 1))
648                    * Math.pow(Enrollment.sWaitlistedWeight, (iWaitlistedChoices.isEmpty() ? 0 : 1))
649                    *
650                    // Math.max(Enrollment.sMinWeight,getWeight()) *
651                    (getStudent().isDummy() ? Student.sDummyStudentWeight : 1.0)
652                    * Enrollment.normalizePenalty(getMinPenalty()));
653        }
654        return iCachedBound.doubleValue();
655        */
656    }
657
658    /** Return true if request is assigned. */
659    @Override
660    public boolean isAssigned() {
661        return getAssignment() != null && !(getAssignment()).getAssignments().isEmpty();
662    }
663
664    @Override
665    public boolean equals(Object o) {
666        return super.equals(o) && (o instanceof CourseRequest);
667    }
668    
669    /**
670     * Get reservations for this course requests
671     */
672    public List<Reservation> getReservations(Course course) {
673        if (iReservations == null)
674            iReservations = new HashMap<Course, List<Reservation>>();
675        List<Reservation> reservations = iReservations.get(course);
676        if (reservations == null) {
677            reservations = new ArrayList<Reservation>();
678            boolean mustBeUsed = false;
679            for (Reservation r: course.getOffering().getReservations()) {
680                if (!r.isApplicable(getStudent())) continue;
681                if (!mustBeUsed && r.mustBeUsed()) { reservations.clear(); mustBeUsed = true; }
682                if (mustBeUsed && !r.mustBeUsed()) continue;
683                reservations.add(r);
684            }
685            iReservations.put(course, reservations);
686        }
687        return reservations;
688    }
689    private Map<Course, List<Reservation>> iReservations = null;
690    
691    /**
692     * Return true if there is a reservation for a course of this request
693     */
694    public boolean hasReservations() {
695        for (Course course: getCourses())
696            if (!getReservations(course).isEmpty())
697                return true;
698        return false;
699    }
700    
701    /**
702     * Clear reservation information that was cached on this section
703     */
704    public void clearReservationCache() {
705        if (iReservations != null) iReservations.clear();
706    }
707
708}