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