001package org.cpsolver.studentsct.model;
002
003import java.text.DecimalFormat;
004import java.util.ArrayList;
005import java.util.HashMap;
006import java.util.HashSet;
007import java.util.List;
008import java.util.Map;
009import java.util.Set;
010
011import org.cpsolver.coursett.model.Placement;
012import org.cpsolver.coursett.model.RoomLocation;
013import org.cpsolver.coursett.model.TimeLocation;
014import org.cpsolver.ifs.assignment.Assignment;
015import org.cpsolver.ifs.assignment.AssignmentComparable;
016import org.cpsolver.ifs.assignment.context.AbstractClassWithContext;
017import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
018import org.cpsolver.ifs.model.Model;
019import org.cpsolver.studentsct.reservation.Reservation;
020
021
022/**
023 * Representation of a class. Each section contains id, name, scheduling
024 * subpart, time/room placement, and a limit. Optionally, parent-child relation
025 * between sections can be defined. <br>
026 * <br>
027 * Each student requesting a course needs to be enrolled in a class of each
028 * subpart of a selected configuration. In the case of parent-child relation
029 * between classes, if a student is enrolled in a section that has a parent
030 * section defined, he/she has to be enrolled in the parent section as well. If
031 * there is a parent-child relation between two sections, the same relation is
032 * defined on their subparts as well, i.e., if section A is a parent section B,
033 * subpart of section A isa parent of subpart of section B. <br>
034 * <br>
035 * 
036 * @version StudentSct 1.3 (Student Sectioning)<br>
037 *          Copyright (C) 2007 - 2014 Tomas Muller<br>
038 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
039 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
040 * <br>
041 *          This library is free software; you can redistribute it and/or modify
042 *          it under the terms of the GNU Lesser General Public License as
043 *          published by the Free Software Foundation; either version 3 of the
044 *          License, or (at your option) any later version. <br>
045 * <br>
046 *          This library is distributed in the hope that it will be useful, but
047 *          WITHOUT ANY WARRANTY; without even the implied warranty of
048 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
049 *          Lesser General Public License for more details. <br>
050 * <br>
051 *          You should have received a copy of the GNU Lesser General Public
052 *          License along with this library; if not see
053 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
054 */
055public class Section extends AbstractClassWithContext<Request, Enrollment, Section.SectionContext> implements SctAssignment, AssignmentComparable<Section, Request, Enrollment> {
056    private static DecimalFormat sDF = new DecimalFormat("0.000");
057    private long iId = -1;
058    private String iName = null;
059    private Map<Long, String> iNameByCourse = null;
060    private Subpart iSubpart = null;
061    private Section iParent = null;
062    private Placement iPlacement = null;
063    private int iLimit = 0;
064    private Choice iChoice = null;
065    private double iPenalty = 0.0;
066    private double iSpaceExpected = 0.0;
067    private double iSpaceHeld = 0.0;
068    private String iNote = null;
069    private Set<Long> iIgnoreConflictsWith = null;
070
071    /**
072     * Constructor
073     * 
074     * @param id
075     *            section unique id
076     * @param limit
077     *            section limit, i.e., the maximal number of students that can
078     *            be enrolled in this section at the same time
079     * @param name
080     *            section name
081     * @param subpart
082     *            subpart of this section
083     * @param placement
084     *            time/room placement
085     * @param instructorIds
086     *            instructor(s) id -- needed for {@link Section#getChoice()}
087     * @param instructorNames
088     *            instructor(s) name -- needed for {@link Section#getChoice()}
089     * @param parent
090     *            parent section -- if there is a parent section defined, a
091     *            student that is enrolled in this section has to be enrolled in
092     *            the parent section as well. Also, the same relation needs to
093     *            be defined between subpart of this section and the subpart of
094     *            the parent section
095     */
096    public Section(long id, int limit, String name, Subpart subpart, Placement placement, String instructorIds,
097            String instructorNames, Section parent) {
098        iId = id;
099        iLimit = limit;
100        iName = name;
101        iSubpart = subpart;
102        iSubpart.getSections().add(this);
103        iPlacement = placement;
104        iParent = parent;
105        iChoice = new Choice(getSubpart().getConfig().getOffering(), getSubpart().getInstructionalType(), getTime(),
106                instructorIds, instructorNames);
107    }
108
109    /** Section id */
110    @Override
111    public long getId() {
112        return iId;
113    }
114
115    /**
116     * Section limit. This is defines the maximal number of students that can be
117     * enrolled into this section at the same time. It is -1 in the case of an
118     * unlimited section
119     * @return class limit
120     */
121    public int getLimit() {
122        return iLimit;
123    }
124
125    /** Set section limit 
126     * @param limit class limit
127     **/
128    public void setLimit(int limit) {
129        iLimit = limit;
130    }
131
132    /** Section name 
133     * @return class name
134     **/
135    public String getName() {
136        return iName;
137    }
138    
139    /** Set section name 
140     * @param name class name
141     **/
142    public void setName(String name) {
143        iName = name;
144    }
145
146    /** Scheduling subpart to which this section belongs 
147     * @return scheduling subpart
148     **/
149    public Subpart getSubpart() {
150        return iSubpart;
151    }
152
153    /**
154     * Parent section of this section (can be null). If there is a parent
155     * section defined, a student that is enrolled in this section has to be
156     * enrolled in the parent section as well. Also, the same relation needs to
157     * be defined between subpart of this section and the subpart of the parent
158     * section.
159     * @return parent class
160     */
161    public Section getParent() {
162        return iParent;
163    }
164
165    /**
166     * Time/room placement of the section. This can be null, for arranged
167     * sections.
168     * @return time and room assignment of this class
169     */
170    public Placement getPlacement() {
171        return iPlacement;
172    }
173    
174    /**
175     * Set time/room placement of the section. This can be null, for arranged
176     * sections.
177     * @param placement time and room assignment of this class
178     */
179    public void setPlacement(Placement placement) {
180        iPlacement = placement;
181    }
182
183    /** Time placement of the section. */
184    @Override
185    public TimeLocation getTime() {
186        return (iPlacement == null ? null : iPlacement.getTimeLocation());
187    }
188
189    /** Number of rooms in which the section meet. */
190    @Override
191    public int getNrRooms() {
192        return (iPlacement == null ? 0 : iPlacement.getNrRooms());
193    }
194
195    /**
196     * Room placement -- list of
197     * {@link org.cpsolver.coursett.model.RoomLocation}
198     */
199    @Override
200    public List<RoomLocation> getRooms() {
201        if (iPlacement == null)
202            return null;
203        if (iPlacement.getRoomLocations() == null && iPlacement.getRoomLocation() != null) {
204            List<RoomLocation> ret = new ArrayList<RoomLocation>(1);
205            ret.add(iPlacement.getRoomLocation());
206            return ret;
207        }
208        return iPlacement.getRoomLocations();
209    }
210
211    /**
212     * True, if this section overlaps with the given assignment in time and
213     * space
214     */
215    @Override
216    public boolean isOverlapping(SctAssignment assignment) {
217        if (isAllowOverlap() || assignment.isAllowOverlap()) return false;
218        if (getTime() == null || assignment.getTime() == null) return false;
219        if (assignment instanceof Section && isToIgnoreStudentConflictsWith(assignment.getId())) return false;
220        return getTime().hasIntersection(assignment.getTime());
221    }
222
223    /**
224     * True, if this section overlaps with one of the given set of assignments
225     * in time and space
226     */
227    @Override
228    public boolean isOverlapping(Set<? extends SctAssignment> assignments) {
229        if (isAllowOverlap()) return false;
230        if (getTime() == null || assignments == null)
231            return false;
232        for (SctAssignment assignment : assignments) {
233            if (assignment.isAllowOverlap())
234                continue;
235            if (assignment.getTime() == null)
236                continue;
237            if (assignment instanceof Section && isToIgnoreStudentConflictsWith(assignment.getId()))
238                continue;
239            if (getTime().hasIntersection(assignment.getTime()))
240                return true;
241        }
242        return false;
243    }
244
245    /** Called when an enrollment with this section is assigned to a request */
246    @Override
247    public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
248        getContext(assignment).assigned(assignment, enrollment);
249    }
250
251    /** Called when an enrollment with this section is unassigned from a request */
252    @Override
253    public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
254        getContext(assignment).unassigned(assignment, enrollment);
255    }
256
257    /** Long name: subpart name + time long name + room names + instructor names
258     * @param useAmPm use 12-hour format 
259     * @return long name
260     **/
261    public String getLongName(boolean useAmPm) {
262        return getSubpart().getName() + " " + getName() + " " + (getTime() == null ? "" : " " + getTime().getLongName(useAmPm))
263                + (getNrRooms() == 0 ? "" : " " + getPlacement().getRoomName(","))
264                + (getChoice().getInstructorNames() == null ? "" : " " + getChoice().getInstructorNames());
265    }
266    
267    @Deprecated
268    public String getLongName() {
269        return getLongName(true);
270    }
271
272    @Override
273    public String toString() {
274        return getSubpart().getConfig().getOffering().getName() + " " + getSubpart().getName() + " " + getName()
275                + (getTime() == null ? "" : " " + getTime().getLongName(true))
276                + (getNrRooms() == 0 ? "" : " " + getPlacement().getRoomName(","))
277                + (getChoice().getInstructorNames() == null ? "" : " " + getChoice().getInstructorNames()) + " (L:"
278                + (getLimit() < 0 ? "unlimited" : "" + getLimit())
279                + (getPenalty() == 0.0 ? "" : ",P:" + sDF.format(getPenalty())) + ")";
280    }
281
282    /** A (student) choice representing this section. 
283     * @return choice for this class
284     **/
285    public Choice getChoice() {
286        return iChoice;
287    }
288
289    /**
290     * Return penalty which is added to an enrollment that contains this
291     * section.
292     * @return online penalty
293     */
294    public double getPenalty() {
295        return iPenalty;
296    }
297
298    /** Set penalty which is added to an enrollment that contains this section. 
299     * @param penalty online penalty
300     **/
301    public void setPenalty(double penalty) {
302        iPenalty = penalty;
303    }
304
305    /**
306     * Compare two sections, prefer sections with lower penalty and more open
307     * space
308     */
309    @Override
310    public int compareTo(Assignment<Request, Enrollment> assignment, Section s) {
311        int cmp = Double.compare(getPenalty(), s.getPenalty());
312        if (cmp != 0)
313            return cmp;
314        cmp = Double.compare(getLimit() - getContext(assignment).getEnrollmentWeight(assignment, null), s.getLimit() - s.getContext(assignment).getEnrollmentWeight(assignment, null));
315        if (cmp != 0)
316            return cmp;
317        return Double.compare(getId(), s.getId());
318    }
319    
320    /**
321     * Compare two sections, prefer sections with lower penalty
322     */
323    @Override
324    public int compareTo(Section s) {
325        int cmp = Double.compare(getPenalty(), s.getPenalty());
326        if (cmp != 0)
327            return cmp;
328        return Double.compare(getId(), s.getId());
329    }
330
331    /**
332     * Return the amount of space of this section that is held for incoming
333     * students. This attribute is computed during the batch sectioning (it is
334     * the overall weight of dummy students enrolled in this section) and it is
335     * being updated with each incomming student during the online sectioning.
336     * @return space held
337     */
338    public double getSpaceHeld() {
339        return iSpaceHeld;
340    }
341
342    /**
343     * Set the amount of space of this section that is held for incoming
344     * students. See {@link Section#getSpaceHeld()} for more info.
345     * @param spaceHeld space held
346     */
347    public void setSpaceHeld(double spaceHeld) {
348        iSpaceHeld = spaceHeld;
349    }
350
351    /**
352     * Return the amount of space of this section that is expected to be taken
353     * by incoming students. This attribute is computed during the batch
354     * sectioning (for each dummy student that can attend this section (without
355     * any conflict with other enrollments of that student), 1 / x where x is
356     * the number of such sections of this subpart is added to this value).
357     * Also, this value is being updated with each incoming student during the
358     * online sectioning.
359     * @return space expected
360     */
361    public double getSpaceExpected() {
362        return iSpaceExpected;
363    }
364
365    /**
366     * Set the amount of space of this section that is expected to be taken by
367     * incoming students. See {@link Section#getSpaceExpected()} for more info.
368     * @param spaceExpected space expected
369     */
370    public void setSpaceExpected(double spaceExpected) {
371        iSpaceExpected = spaceExpected;
372    }
373
374    /**
375     * Online sectioning penalty.
376     * @param assignment current assignment
377     * @return online sectioning penalty
378     */
379    public double getOnlineSectioningPenalty(Assignment<Request, Enrollment> assignment) {
380        if (getLimit() <= 0)
381            return 0.0;
382
383        double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, null);
384
385        double penalty = (getSpaceExpected() - available) / getLimit();
386
387        return Math.max(-1.0, Math.min(1.0, penalty));
388    }
389
390    /**
391     * Return true if overlaps are allowed, but the number of overlapping slots should be minimized.
392     * This can be changed on the subpart, using {@link Subpart#setAllowOverlap(boolean)}.
393     **/
394    @Override
395    public boolean isAllowOverlap() {
396        return iSubpart.isAllowOverlap();
397    }
398    
399    /** Sections first, then by {@link FreeTimeRequest#getId()} */
400    @Override
401    public int compareById(SctAssignment a) {
402        if (a instanceof Section) {
403            return new Long(getId()).compareTo(((Section)a).getId());
404        } else {
405            return -1;
406        }
407    }
408
409    /**
410     * Available space in the section that is not reserved by any section reservation
411     * @param assignment current assignment
412     * @param excludeRequest excluding given request (if not null)
413     * @return unreserved space in this class
414     **/
415    public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
416        // section is unlimited -> there is unreserved space unless there is an unlimited reservation too 
417        // (in which case there is no unreserved space)
418        if (getLimit() < 0) {
419            // exclude reservations that are not directly set on this section
420            for (Reservation r: getSectionReservations()) {
421                // ignore expired reservations
422                if (r.isExpired()) continue;
423                // there is an unlimited reservation -> no unreserved space
424                if (r.getLimit() < 0) return 0.0;
425            }
426            return Double.MAX_VALUE;
427        }
428        
429        double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
430        // exclude reservations that are not directly set on this section
431        for (Reservation r: getSectionReservations()) {
432            // ignore expired reservations
433            if (r.isExpired()) continue;
434            // unlimited reservation -> all the space is reserved
435            if (r.getLimit() < 0.0) return 0.0;
436            // compute space that can be potentially taken by this reservation
437            double reserved = r.getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest);
438            // deduct the space from available space
439            available -= Math.max(0.0, reserved);
440        }
441        
442        return available;
443    }
444    
445    /**
446     * Total space in the section that cannot be used by any section reservation
447     * @return total unreserved space in this class
448     **/
449    public double getTotalUnreservedSpace() {
450        if (iTotalUnreservedSpace == null)
451            iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
452        return iTotalUnreservedSpace;
453    }
454    private Double iTotalUnreservedSpace = null;
455    private double getTotalUnreservedSpaceNoCache() {
456        // section is unlimited -> there is unreserved space unless there is an unlimited reservation too 
457        // (in which case there is no unreserved space)
458        if (getLimit() < 0) {
459            // exclude reservations that are not directly set on this section
460            for (Reservation r: getSectionReservations()) {
461                // ignore expired reservations
462                if (r.isExpired()) continue;
463                // there is an unlimited reservation -> no unreserved space
464                if (r.getLimit() < 0) return 0.0;
465            }
466            return Double.MAX_VALUE;
467        }
468        
469        // we need to check all reservations linked with this section
470        double available = getLimit(), reserved = 0, exclusive = 0;
471        Set<Section> sections = new HashSet<Section>();
472        reservations: for (Reservation r: getSectionReservations()) {
473            // ignore expired reservations
474            if (r.isExpired()) continue;
475            // unlimited reservation -> no unreserved space
476            if (r.getLimit() < 0) return 0.0;
477            for (Section s: r.getSections(getSubpart())) {
478                if (s.equals(this)) continue;
479                if (s.getLimit() < 0) continue reservations;
480                if (sections.add(s))
481                    available += s.getLimit();
482            }
483            reserved += r.getLimit();
484            if (r.getSections(getSubpart()).size() == 1)
485                exclusive += r.getLimit();
486        }
487        
488        return Math.min(available - reserved, getLimit() - exclusive);
489    }
490    
491    
492    /**
493     * Get reservations for this section
494     * @return reservations that can use this class
495     */
496    public List<Reservation> getReservations() {
497        if (iReservations == null) {
498            iReservations = new ArrayList<Reservation>();
499            for (Reservation r: getSubpart().getConfig().getOffering().getReservations()) {
500                if (r.getSections(getSubpart()) == null || r.getSections(getSubpart()).contains(this))
501                    iReservations.add(r);
502            }
503        }
504        return iReservations;
505    }
506    private List<Reservation> iReservations = null;
507    
508    /**
509     * Get reservations that require this section
510     * @return reservations that must use this class
511     */
512    public List<Reservation> getSectionReservations() {
513        if (iSectionReservations == null) {
514            iSectionReservations = new ArrayList<Reservation>();
515            for (Reservation r: getSubpart().getSectionReservations()) {
516                if (r.getSections(getSubpart()).contains(this))
517                    iSectionReservations.add(r);
518            }
519        }
520        return iSectionReservations;
521    }
522    private List<Reservation> iSectionReservations = null;
523
524    /**
525     * Clear reservation information that was cached on this section
526     */
527    public void clearReservationCache() {
528        iReservations = null;
529        iSectionReservations = null;
530        iTotalUnreservedSpace = null;
531    }
532    
533    /**
534     * Return course-dependent section name
535     * @param courseId course offering unique id
536     * @return course dependent class name
537     */
538    public String getName(long courseId) {
539        if (iNameByCourse == null) return getName();
540        String name = iNameByCourse.get(courseId);
541        return (name == null ? getName() : name);
542    }
543    
544    /**
545     * Set course-dependent section name
546     * @param courseId course offering unique id
547     * @param name course dependent class name
548     */
549    public void setName(long courseId, String name) {
550        if (iNameByCourse == null) iNameByCourse = new HashMap<Long, String>();
551        iNameByCourse.put(courseId, name);
552    }
553
554    /**
555     * Return course-dependent section names
556     * @return map of course-dependent class names
557     */
558    public Map<Long, String> getNameByCourse() { return iNameByCourse; }
559    
560    @Override
561    public boolean equals(Object o) {
562        if (o == null || !(o instanceof Section)) return false;
563        return getId() == ((Section)o).getId();
564    }
565    
566    @Override
567    public int hashCode() {
568        return (int) (iId ^ (iId >>> 32));
569    }
570    
571    /**
572     * Section note
573     * @return scheduling note
574     */
575    public String getNote() { return iNote; }
576    
577    /**
578     * Section note
579     * @param note scheduling note
580     */
581    public void setNote(String note) { iNote = note; }
582    
583    /**
584     * Add section id of a section that student conflicts are to be ignored with
585     * @param sectionId class unique id
586     */
587    public void addIgnoreConflictWith(long sectionId) {
588        if (iIgnoreConflictsWith == null) iIgnoreConflictsWith = new HashSet<Long>();
589        iIgnoreConflictsWith.add(sectionId);
590    }
591    
592    /**
593     * Returns true if student conflicts between this section and the given one are to be ignored
594     * @param sectionId class unique id
595     * @return true if student conflicts between these two sections are to be ignored
596     */
597    public boolean isToIgnoreStudentConflictsWith(long sectionId) {
598        return iIgnoreConflictsWith != null && iIgnoreConflictsWith.contains(sectionId);
599    }
600    
601    /**
602     * Returns a set of ids of sections that student conflicts are to be ignored with (between this section and the others)
603     * @return set of class unique ids of the sections that student conflicts are to be ignored with 
604     */
605    public Set<Long> getIgnoreConflictWithSectionIds() {
606        return iIgnoreConflictsWith;
607    }
608    
609    /** Set of assigned enrollments */
610    @Override
611    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
612        return getContext(assignment).getEnrollments();
613    }
614    
615    /**
616     * Enrollment weight -- weight of all requests which have an enrollment that
617     * contains this section, excluding the given one. See
618     * {@link Request#getWeight()}.
619     * @param assignment current assignment
620     * @param excludeRequest course request to ignore, if any
621     * @return enrollment weight
622     */
623    public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
624        return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
625    }
626    
627    /**
628     * Maximal weight of a single enrollment in the section
629     * @param assignment current assignment
630     * @return maximal enrollment weight
631     */
632    public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
633        return getContext(assignment).getMaxEnrollmentWeight();
634    }
635
636    /**
637     * Minimal weight of a single enrollment in the section
638     * @param assignment current assignment
639     * @return minimal enrollment weight
640     */
641    public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
642        return getContext(assignment).getMinEnrollmentWeight();
643    }
644    
645    @Override
646    public Model<Request, Enrollment> getModel() {
647        return getSubpart().getConfig().getOffering().getModel();
648    }
649
650    @Override
651    public SectionContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
652        return new SectionContext(assignment);
653    }
654    
655    public class SectionContext implements AssignmentConstraintContext<Request, Enrollment> {
656        private Set<Enrollment> iEnrollments = new HashSet<Enrollment>();
657        private double iEnrollmentWeight = 0.0;
658        private double iMaxEnrollmentWeight = 0.0;
659        private double iMinEnrollmentWeight = 0.0;
660
661        public SectionContext(Assignment<Request, Enrollment> assignment) {
662            for (Course course: getSubpart().getConfig().getOffering().getCourses()) {
663                for (CourseRequest request: course.getRequests()) {
664                    Enrollment enrollment = assignment.getValue(request);
665                    if (enrollment != null && enrollment.getSections().contains(Section.this))
666                        assigned(assignment, enrollment);
667                }
668            }
669        }
670
671        /** Called when an enrollment with this section is assigned to a request */
672        @Override
673        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
674            if (iEnrollments.isEmpty()) {
675                iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
676            } else {
677                iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
678                iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
679            }
680            if (iEnrollments.add(enrollment))
681                iEnrollmentWeight += enrollment.getRequest().getWeight();
682        }
683
684        /** Called when an enrollment with this section is unassigned from a request */
685        @Override
686        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
687            if (iEnrollments.remove(enrollment))
688                iEnrollmentWeight -= enrollment.getRequest().getWeight();
689            if (iEnrollments.isEmpty()) {
690                iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
691            } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) {
692                if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) {
693                    double newMinEnrollmentWeight = Double.MAX_VALUE;
694                    for (Enrollment e : iEnrollments) {
695                        if (e.getRequest().getWeight() == iMinEnrollmentWeight) {
696                            newMinEnrollmentWeight = iMinEnrollmentWeight;
697                            break;
698                        } else {
699                            newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
700                        }
701                    }
702                    iMinEnrollmentWeight = newMinEnrollmentWeight;
703                }
704                if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) {
705                    double newMaxEnrollmentWeight = Double.MIN_VALUE;
706                    for (Enrollment e : iEnrollments) {
707                        if (e.getRequest().getWeight() == iMaxEnrollmentWeight) {
708                            newMaxEnrollmentWeight = iMaxEnrollmentWeight;
709                            break;
710                        } else {
711                            newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
712                        }
713                    }
714                    iMaxEnrollmentWeight = newMaxEnrollmentWeight;
715                }
716            }
717        }
718        
719        /** Set of assigned enrollments 
720         * @return assigned enrollments of this section
721         **/
722        public Set<Enrollment> getEnrollments() {
723            return iEnrollments;
724        }
725        
726        /**
727         * Enrollment weight -- weight of all requests which have an enrollment that
728         * contains this section, excluding the given one. See
729         * {@link Request#getWeight()}.
730         * @param assignment current assignment
731         * @param excludeRequest course request to ignore, if any
732         * @return enrollment weight
733         */
734        public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
735            double weight = iEnrollmentWeight;
736            if (excludeRequest != null && assignment.getValue(excludeRequest) != null && iEnrollments.contains(assignment.getValue(excludeRequest)))
737                weight -= excludeRequest.getWeight();
738            return weight;
739        }
740        
741        /**
742         * Maximal weight of a single enrollment in the section
743         * @return maximal enrollment weight
744         */
745        public double getMaxEnrollmentWeight() {
746            return iMaxEnrollmentWeight;
747        }
748
749        /**
750         * Minimal weight of a single enrollment in the section
751         * @return minimal enrollment weight
752         */
753        public double getMinEnrollmentWeight() {
754            return iMinEnrollmentWeight;
755        }
756    }
757}