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