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