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