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(getLimit() - getContext(assignment).getEnrollmentWeight(assignment, null), s.getLimit() - s.getContext(assignment).getEnrollmentWeight(assignment, null));
322        if (cmp != 0)
323            return cmp;
324        return Double.compare(getId(), s.getId());
325    }
326    
327    /**
328     * Compare two sections, prefer sections with lower penalty
329     */
330    @Override
331    public int compareTo(Section s) {
332        int cmp = Double.compare(getPenalty(), s.getPenalty());
333        if (cmp != 0)
334            return cmp;
335        return Double.compare(getId(), s.getId());
336    }
337
338    /**
339     * Return the amount of space of this section that is held for incoming
340     * students. This attribute is computed during the batch sectioning (it is
341     * the overall weight of dummy students enrolled in this section) and it is
342     * being updated with each incomming student during the online sectioning.
343     * @return space held
344     */
345    public double getSpaceHeld() {
346        return iSpaceHeld;
347    }
348
349    /**
350     * Set the amount of space of this section that is held for incoming
351     * students. See {@link Section#getSpaceHeld()} for more info.
352     * @param spaceHeld space held
353     */
354    public void setSpaceHeld(double spaceHeld) {
355        iSpaceHeld = spaceHeld;
356    }
357
358    /**
359     * Return the amount of space of this section that is expected to be taken
360     * by incoming students. This attribute is computed during the batch
361     * sectioning (for each dummy student that can attend this section (without
362     * any conflict with other enrollments of that student), 1 / x where x is
363     * the number of such sections of this subpart is added to this value).
364     * Also, this value is being updated with each incoming student during the
365     * online sectioning.
366     * @return space expected
367     */
368    public double getSpaceExpected() {
369        return iSpaceExpected;
370    }
371
372    /**
373     * Set the amount of space of this section that is expected to be taken by
374     * incoming students. See {@link Section#getSpaceExpected()} for more info.
375     * @param spaceExpected space expected
376     */
377    public void setSpaceExpected(double spaceExpected) {
378        iSpaceExpected = spaceExpected;
379    }
380
381    /**
382     * Online sectioning penalty.
383     * @param assignment current assignment
384     * @return online sectioning penalty
385     */
386    public double getOnlineSectioningPenalty(Assignment<Request, Enrollment> assignment) {
387        if (getLimit() <= 0)
388            return 0.0;
389
390        double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, null);
391
392        double penalty = (getSpaceExpected() - available) / getLimit();
393
394        return Math.max(-1.0, Math.min(1.0, penalty));
395    }
396
397    /**
398     * Return true if overlaps are allowed, but the number of overlapping slots should be minimized.
399     * This can be changed on the subpart, using {@link Subpart#setAllowOverlap(boolean)}.
400     **/
401    @Override
402    public boolean isAllowOverlap() {
403        return iSubpart.isAllowOverlap();
404    }
405    
406    /** Sections first, then by {@link FreeTimeRequest#getId()} */
407    @Override
408    public int compareById(SctAssignment a) {
409        if (a instanceof Section) {
410            return new Long(getId()).compareTo(((Section)a).getId());
411        } else {
412            return -1;
413        }
414    }
415
416    /**
417     * Available space in the section that is not reserved by any section reservation
418     * @param assignment current assignment
419     * @param excludeRequest excluding given request (if not null)
420     * @return unreserved space in this class
421     **/
422    public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
423        // section is unlimited -> there is unreserved space unless there is an unlimited reservation too 
424        // (in which case there is no unreserved space)
425        if (getLimit() < 0) {
426            // exclude reservations that are not directly set on this section
427            for (Reservation r: getSectionReservations()) {
428                // ignore expired reservations
429                if (r.isExpired()) continue;
430                // there is an unlimited reservation -> no unreserved space
431                if (r.getLimit() < 0) return 0.0;
432            }
433            return Double.MAX_VALUE;
434        }
435        
436        double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
437        // exclude reservations that are not directly set on this section
438        for (Reservation r: getSectionReservations()) {
439            // ignore expired reservations
440            if (r.isExpired()) continue;
441            // unlimited reservation -> all the space is reserved
442            if (r.getLimit() < 0.0) return 0.0;
443            // compute space that can be potentially taken by this reservation
444            double reserved = r.getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest);
445            // deduct the space from available space
446            available -= Math.max(0.0, reserved);
447        }
448        
449        return available;
450    }
451    
452    /**
453     * Total space in the section that cannot be used by any section reservation
454     * @return total unreserved space in this class
455     **/
456    public synchronized double getTotalUnreservedSpace() {
457        if (iTotalUnreservedSpace == null)
458            iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
459        return iTotalUnreservedSpace;
460    }
461    private Double iTotalUnreservedSpace = null;
462    private double getTotalUnreservedSpaceNoCache() {
463        // section is unlimited -> there is unreserved space unless there is an unlimited reservation too 
464        // (in which case there is no unreserved space)
465        if (getLimit() < 0) {
466            // exclude reservations that are not directly set on this section
467            for (Reservation r: getSectionReservations()) {
468                // ignore expired reservations
469                if (r.isExpired()) continue;
470                // there is an unlimited reservation -> no unreserved space
471                if (r.getLimit() < 0) return 0.0;
472            }
473            return Double.MAX_VALUE;
474        }
475        
476        // we need to check all reservations linked with this section
477        double available = getLimit(), reserved = 0, exclusive = 0;
478        Set<Section> sections = new HashSet<Section>();
479        reservations: for (Reservation r: getSectionReservations()) {
480            // ignore expired reservations
481            if (r.isExpired()) continue;
482            // unlimited reservation -> no unreserved space
483            if (r.getLimit() < 0) return 0.0;
484            for (Section s: r.getSections(getSubpart())) {
485                if (s.equals(this)) continue;
486                if (s.getLimit() < 0) continue reservations;
487                if (sections.add(s))
488                    available += s.getLimit();
489            }
490            reserved += r.getLimit();
491            if (r.getSections(getSubpart()).size() == 1)
492                exclusive += r.getLimit();
493        }
494        
495        return Math.min(available - reserved, getLimit() - exclusive);
496    }
497    
498    
499    /**
500     * Get reservations for this section
501     * @return reservations that can use this class
502     */
503    public synchronized List<Reservation> getReservations() {
504        if (iReservations == null) {
505            iReservations = new ArrayList<Reservation>();
506            for (Reservation r: getSubpart().getConfig().getOffering().getReservations()) {
507                if (r.getSections(getSubpart()) == null || r.getSections(getSubpart()).contains(this))
508                    iReservations.add(r);
509            }
510        }
511        return iReservations;
512    }
513    private List<Reservation> iReservations = null;
514    
515    /**
516     * Get reservations that require this section
517     * @return reservations that must use this class
518     */
519    public synchronized List<Reservation> getSectionReservations() {
520        if (iSectionReservations == null) {
521            iSectionReservations = new ArrayList<Reservation>();
522            for (Reservation r: getSubpart().getSectionReservations()) {
523                if (r.getSections(getSubpart()).contains(this))
524                    iSectionReservations.add(r);
525            }
526        }
527        return iSectionReservations;
528    }
529    private List<Reservation> iSectionReservations = null;
530
531    /**
532     * Clear reservation information that was cached on this section
533     */
534    public synchronized void clearReservationCache() {
535        iReservations = null;
536        iSectionReservations = null;
537        iTotalUnreservedSpace = null;
538    }
539    
540    /**
541     * Return course-dependent section name
542     * @param courseId course offering unique id
543     * @return course dependent class name
544     */
545    public String getName(long courseId) {
546        if (iNameByCourse == null) return getName();
547        String name = iNameByCourse.get(courseId);
548        return (name == null ? getName() : name);
549    }
550    
551    /**
552     * Set course-dependent section name
553     * @param courseId course offering unique id
554     * @param name course dependent class name
555     */
556    public void setName(long courseId, String name) {
557        if (iNameByCourse == null) iNameByCourse = new HashMap<Long, String>();
558        iNameByCourse.put(courseId, name);
559    }
560
561    /**
562     * Return course-dependent section names
563     * @return map of course-dependent class names
564     */
565    public Map<Long, String> getNameByCourse() { return iNameByCourse; }
566    
567    @Override
568    public boolean equals(Object o) {
569        if (o == null || !(o instanceof Section)) return false;
570        return getId() == ((Section)o).getId();
571    }
572    
573    @Override
574    public int hashCode() {
575        return (int) (iId ^ (iId >>> 32));
576    }
577    
578    /**
579     * Section note
580     * @return scheduling note
581     */
582    public String getNote() { return iNote; }
583    
584    /**
585     * Section note
586     * @param note scheduling note
587     */
588    public void setNote(String note) { iNote = note; }
589    
590    /**
591     * Add section id of a section that student conflicts are to be ignored with
592     * @param sectionId class unique id
593     */
594    public void addIgnoreConflictWith(long sectionId) {
595        if (iIgnoreConflictsWith == null) iIgnoreConflictsWith = new HashSet<Long>();
596        iIgnoreConflictsWith.add(sectionId);
597    }
598    
599    /**
600     * Returns true if student conflicts between this section and the given one are to be ignored
601     * @param sectionId class unique id
602     * @return true if student conflicts between these two sections are to be ignored
603     */
604    public boolean isToIgnoreStudentConflictsWith(long sectionId) {
605        return iIgnoreConflictsWith != null && iIgnoreConflictsWith.contains(sectionId);
606    }
607    
608    /**
609     * Returns a set of ids of sections that student conflicts are to be ignored with (between this section and the others)
610     * @return set of class unique ids of the sections that student conflicts are to be ignored with 
611     */
612    public Set<Long> getIgnoreConflictWithSectionIds() {
613        return iIgnoreConflictsWith;
614    }
615    
616    /** Set of assigned enrollments */
617    @Override
618    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
619        return getContext(assignment).getEnrollments();
620    }
621    
622    /**
623     * Enrollment weight -- weight of all requests which have an enrollment that
624     * contains this section, excluding the given one. See
625     * {@link Request#getWeight()}.
626     * @param assignment current assignment
627     * @param excludeRequest course request to ignore, if any
628     * @return enrollment weight
629     */
630    public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
631        return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
632    }
633    
634    /**
635     * Maximal weight of a single enrollment in the section
636     * @param assignment current assignment
637     * @return maximal enrollment weight
638     */
639    public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
640        return getContext(assignment).getMaxEnrollmentWeight();
641    }
642
643    /**
644     * Minimal weight of a single enrollment in the section
645     * @param assignment current assignment
646     * @return minimal enrollment weight
647     */
648    public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
649        return getContext(assignment).getMinEnrollmentWeight();
650    }
651    
652    /**
653     * Return cancelled flag of the class.
654     * @return true if the class is cancelled
655     */
656    public boolean isCancelled() { return iCancelled; }
657    
658    /**
659     * Set cancelled flag of the class.
660     * @param cancelled true if the class is cancelled
661     */
662    public void setCancelled(boolean cancelled) { iCancelled = cancelled; }
663    
664    @Override
665    public Model<Request, Enrollment> getModel() {
666        return getSubpart().getConfig().getOffering().getModel();
667    }
668
669    @Override
670    public SectionContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
671        return new SectionContext(assignment);
672    }
673    
674    @Override
675    public SectionContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, SectionContext parentContext) {
676        return new SectionContext(parentContext);
677    }
678    
679    public class SectionContext implements AssignmentConstraintContext<Request, Enrollment> {
680        private Set<Enrollment> iEnrollments = null;
681        private double iEnrollmentWeight = 0.0;
682        private double iMaxEnrollmentWeight = 0.0;
683        private double iMinEnrollmentWeight = 0.0;
684        private boolean iReadOnly = false;
685
686        public SectionContext(Assignment<Request, Enrollment> assignment) {
687            iEnrollments = new HashSet<Enrollment>();
688            for (Course course: getSubpart().getConfig().getOffering().getCourses()) {
689                for (CourseRequest request: course.getRequests()) {
690                    Enrollment enrollment = assignment.getValue(request);
691                    if (enrollment != null && enrollment.getSections().contains(Section.this))
692                        assigned(assignment, enrollment);
693                }
694            }
695        }
696        
697        public SectionContext(SectionContext parent) {
698            iEnrollmentWeight = parent.iEnrollmentWeight;
699            iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight;
700            iMinEnrollmentWeight = parent.iMinEnrollmentWeight;
701            iEnrollments = parent.iEnrollments;
702            iReadOnly = true;
703        }
704
705        /** Called when an enrollment with this section is assigned to a request */
706        @Override
707        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
708            if (iReadOnly) {
709                iEnrollments = new HashSet<Enrollment>(iEnrollments);
710                iReadOnly = false;
711            }
712            if (iEnrollments.isEmpty()) {
713                iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
714            } else {
715                iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
716                iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
717            }
718            if (iEnrollments.add(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
719                iEnrollmentWeight += enrollment.getRequest().getWeight();
720        }
721
722        /** Called when an enrollment with this section is unassigned from a request */
723        @Override
724        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
725            if (iReadOnly) {
726                iEnrollments = new HashSet<Enrollment>(iEnrollments);
727                iReadOnly = false;
728            }
729            if (iEnrollments.remove(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
730                iEnrollmentWeight -= enrollment.getRequest().getWeight();
731            if (iEnrollments.isEmpty()) {
732                iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
733            } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) {
734                if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) {
735                    double newMinEnrollmentWeight = Double.MAX_VALUE;
736                    for (Enrollment e : iEnrollments) {
737                        if (e.getRequest().getWeight() == iMinEnrollmentWeight) {
738                            newMinEnrollmentWeight = iMinEnrollmentWeight;
739                            break;
740                        } else {
741                            newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
742                        }
743                    }
744                    iMinEnrollmentWeight = newMinEnrollmentWeight;
745                }
746                if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) {
747                    double newMaxEnrollmentWeight = Double.MIN_VALUE;
748                    for (Enrollment e : iEnrollments) {
749                        if (e.getRequest().getWeight() == iMaxEnrollmentWeight) {
750                            newMaxEnrollmentWeight = iMaxEnrollmentWeight;
751                            break;
752                        } else {
753                            newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
754                        }
755                    }
756                    iMaxEnrollmentWeight = newMaxEnrollmentWeight;
757                }
758            }
759        }
760        
761        /** Set of assigned enrollments 
762         * @return assigned enrollments of this section
763         **/
764        public Set<Enrollment> getEnrollments() {
765            return iEnrollments;
766        }
767        
768        /**
769         * Enrollment weight -- weight of all requests which have an enrollment that
770         * contains this section, excluding the given one. See
771         * {@link Request#getWeight()}.
772         * @param assignment current assignment
773         * @param excludeRequest course request to ignore, if any
774         * @return enrollment weight
775         */
776        public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
777            double weight = iEnrollmentWeight;
778            if (excludeRequest != null) {
779                Enrollment enrollment = assignment.getValue(excludeRequest);
780                if (enrollment!= null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
781                    weight -= excludeRequest.getWeight();
782            }
783            return weight;
784        }
785        
786        /**
787         * Maximal weight of a single enrollment in the section
788         * @return maximal enrollment weight
789         */
790        public double getMaxEnrollmentWeight() {
791            return iMaxEnrollmentWeight;
792        }
793
794        /**
795         * Minimal weight of a single enrollment in the section
796         * @return minimal enrollment weight
797         */
798        public double getMinEnrollmentWeight() {
799            return iMinEnrollmentWeight;
800        }
801    }
802}