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