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 Tomáš Müller<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, iOnline = false, iPast = false;
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    /** Check if the class has a time assignment (is not arranged hours) */
224    public boolean hasTime() {
225        return iPlacement != null && iPlacement.getTimeLocation() != null && iPlacement.getTimeLocation().getDayCode() != 0;
226    }
227    
228    /** True if the instructional type is the same */
229    public boolean sameInstructionalType(Section section) {
230        return getSubpart().getInstructionalType().equals(section.getSubpart().getInstructionalType());
231    }
232
233    /** True if the time assignment is the same */
234    public boolean sameTime(Section section) {
235        return getTime() == null ? section.getTime() == null : getTime().equals(section.getTime());
236    }
237    
238    /** True if the instructor(s) are the same */
239    public boolean sameInstructors(Section section) {
240        if (nrInstructors() != section.nrInstructors()) return false;
241        return !hasInstructors() || getInstructors().containsAll(section.getInstructors());
242    }
243    
244    /** True if the time assignment as well as the instructor(s) are the same */
245    public boolean sameChoice(Section section) {
246        return sameInstructionalType(section) && sameTime(section) && sameInstructors(section);
247    }
248
249    /** Number of rooms in which the section meet. */
250    @Override
251    public int getNrRooms() {
252        return (iPlacement == null ? 0 : iPlacement.getNrRooms());
253    }
254
255    /**
256     * Room placement -- list of
257     * {@link org.cpsolver.coursett.model.RoomLocation}
258     */
259    @Override
260    public List<RoomLocation> getRooms() {
261        if (iPlacement == null)
262            return null;
263        if (iPlacement.getRoomLocations() == null && iPlacement.getRoomLocation() != null) {
264            List<RoomLocation> ret = new ArrayList<RoomLocation>(1);
265            ret.add(iPlacement.getRoomLocation());
266            return ret;
267        }
268        return iPlacement.getRoomLocations();
269    }
270
271    /**
272     * True, if this section overlaps with the given assignment in time and
273     * space
274     */
275    @Override
276    public boolean isOverlapping(SctAssignment assignment) {
277        if (isAllowOverlap() || assignment.isAllowOverlap()) return false;
278        if (getTime() == null || assignment.getTime() == null) return false;
279        if (assignment instanceof Section && isToIgnoreStudentConflictsWith(assignment.getId())) return false;
280        return getTime().hasIntersection(assignment.getTime());
281    }
282
283    /**
284     * True, if this section overlaps with one of the given set of assignments
285     * in time and space
286     */
287    @Override
288    public boolean isOverlapping(Set<? extends SctAssignment> assignments) {
289        if (isAllowOverlap()) return false;
290        if (getTime() == null || assignments == null)
291            return false;
292        for (SctAssignment assignment : assignments) {
293            if (assignment.isAllowOverlap())
294                continue;
295            if (assignment.getTime() == null)
296                continue;
297            if (assignment instanceof Section && isToIgnoreStudentConflictsWith(assignment.getId()))
298                continue;
299            if (getTime().hasIntersection(assignment.getTime()))
300                return true;
301        }
302        return false;
303    }
304
305    /** Called when an enrollment with this section is assigned to a request */
306    @Override
307    public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
308        getContext(assignment).assigned(assignment, enrollment);
309    }
310
311    /** Called when an enrollment with this section is unassigned from a request */
312    @Override
313    public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
314        getContext(assignment).unassigned(assignment, enrollment);
315    }
316
317    /** Long name: subpart name + time long name + room names + instructor names
318     * @param useAmPm use 12-hour format 
319     * @return long name
320     **/
321    public String getLongName(boolean useAmPm) {
322        return getSubpart().getName() + " " + getName() + " " + (getTime() == null ? "" : " " + getTime().getLongName(useAmPm))
323                + (getNrRooms() == 0 ? "" : " " + getPlacement().getRoomName(","))
324                + (hasInstructors() ? " " + getInstructorNames(",") : "");
325    }
326    
327    @Deprecated
328    public String getLongName() {
329        return getLongName(true);
330    }
331
332    @Override
333    public String toString() {
334        return getSubpart().getConfig().getOffering().getName() + " " + getSubpart().getName() + " " + getName()
335                + (getTime() == null ? "" : " " + getTime().getLongName(true))
336                + (getNrRooms() == 0 ? "" : " " + getPlacement().getRoomName(","))
337                + (hasInstructors() ? " " + getInstructorNames(",") : "") + " (L:"
338                + (getLimit() < 0 ? "unlimited" : "" + getLimit())
339                + (getPenalty() == 0.0 ? "" : ",P:" + sDF.format(getPenalty())) + ")";
340    }
341
342    /** Instructors assigned to this section 
343     * @return list of instructors
344     **/
345    public List<Instructor> getInstructors() {
346        return iInstructors;
347    }
348    
349    /**
350     * Has any instructors assigned
351     * @return return true if there is at least one instructor assigned
352     */
353    public boolean hasInstructors() {
354        return iInstructors != null && !iInstructors.isEmpty();
355    }
356    
357    /**
358     * Return number of instructors of this section
359     * @return number of assigned instructors
360     */
361    public int nrInstructors() {
362        return iInstructors == null ? 0 : iInstructors.size();
363    }
364    
365    /**
366     * Instructor names
367     * @param delim delimiter
368     * @return instructor names
369     */
370    public String getInstructorNames(String delim) {
371        if (iInstructors == null || iInstructors.isEmpty()) return "";
372        StringBuffer sb = new StringBuffer();
373        for (Iterator<Instructor> i = iInstructors.iterator(); i.hasNext(); ) {
374            Instructor instructor = i.next();
375            sb.append(instructor.getName() != null ? instructor.getName() : instructor.getExternalId() != null ? instructor.getExternalId() : "I" + instructor.getId());
376            if (i.hasNext()) sb.append(delim);
377        }
378        return sb.toString();
379    }
380
381    /**
382     * Return penalty which is added to an enrollment that contains this
383     * section.
384     * @return online penalty
385     */
386    public double getPenalty() {
387        return iPenalty;
388    }
389
390    /** Set penalty which is added to an enrollment that contains this section. 
391     * @param penalty online penalty
392     **/
393    public void setPenalty(double penalty) {
394        iPenalty = penalty;
395    }
396
397    /**
398     * Compare two sections, prefer sections with lower penalty and more open
399     * space
400     */
401    @Override
402    public int compareTo(Assignment<Request, Enrollment> assignment, Section s) {
403        int cmp = Double.compare(getPenalty(), s.getPenalty());
404        if (cmp != 0)
405            return cmp;
406        cmp = Double.compare(
407                getLimit() < 0 ? getContext(assignment).getEnrollmentWeight(assignment, null) : getContext(assignment).getEnrollmentWeight(assignment, null) - getLimit(),
408                s.getLimit() < 0 ? s.getContext(assignment).getEnrollmentWeight(assignment, null) : s.getContext(assignment).getEnrollmentWeight(assignment, null) - s.getLimit());
409        if (cmp != 0)
410            return cmp;
411        return Double.compare(getId(), s.getId());
412    }
413    
414    /**
415     * Compare two sections, prefer sections with lower penalty
416     */
417    @Override
418    public int compareTo(Section s) {
419        int cmp = Double.compare(getPenalty(), s.getPenalty());
420        if (cmp != 0)
421            return cmp;
422        return Double.compare(getId(), s.getId());
423    }
424
425    /**
426     * Return the amount of space of this section that is held for incoming
427     * students. This attribute is computed during the batch sectioning (it is
428     * the overall weight of dummy students enrolled in this section) and it is
429     * being updated with each incomming student during the online sectioning.
430     * @return space held
431     */
432    public double getSpaceHeld() {
433        return iSpaceHeld;
434    }
435
436    /**
437     * Set the amount of space of this section that is held for incoming
438     * students. See {@link Section#getSpaceHeld()} for more info.
439     * @param spaceHeld space held
440     */
441    public void setSpaceHeld(double spaceHeld) {
442        iSpaceHeld = spaceHeld;
443    }
444
445    /**
446     * Return the amount of space of this section that is expected to be taken
447     * by incoming students. This attribute is computed during the batch
448     * sectioning (for each dummy student that can attend this section (without
449     * any conflict with other enrollments of that student), 1 / x where x is
450     * the number of such sections of this subpart is added to this value).
451     * Also, this value is being updated with each incoming student during the
452     * online sectioning.
453     * @return space expected
454     */
455    public double getSpaceExpected() {
456        return iSpaceExpected;
457    }
458
459    /**
460     * Set the amount of space of this section that is expected to be taken by
461     * incoming students. See {@link Section#getSpaceExpected()} for more info.
462     * @param spaceExpected space expected
463     */
464    public void setSpaceExpected(double spaceExpected) {
465        iSpaceExpected = spaceExpected;
466    }
467
468    /**
469     * Online sectioning penalty.
470     * @param assignment current assignment
471     * @return online sectioning penalty
472     */
473    public double getOnlineSectioningPenalty(Assignment<Request, Enrollment> assignment) {
474        if (getLimit() <= 0)
475            return 0.0;
476
477        double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, null);
478
479        double penalty = (getSpaceExpected() - available) / getLimit();
480
481        return Math.max(-1.0, Math.min(1.0, penalty));
482    }
483
484    /**
485     * Return true if overlaps are allowed, but the number of overlapping slots should be minimized.
486     * This can be changed on the subpart, using {@link Subpart#setAllowOverlap(boolean)}.
487     **/
488    @Override
489    public boolean isAllowOverlap() {
490        return iSubpart.isAllowOverlap();
491    }
492    
493    /** Sections first, then by {@link FreeTimeRequest#getId()} */
494    @Override
495    public int compareById(SctAssignment a) {
496        if (a instanceof Section) {
497            return new Long(getId()).compareTo(((Section)a).getId());
498        } else {
499            return -1;
500        }
501    }
502
503    /**
504     * Available space in the section that is not reserved by any section reservation
505     * @param assignment current assignment
506     * @param excludeRequest excluding given request (if not null)
507     * @return unreserved space in this class
508     **/
509    public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
510        // section is unlimited -> there is unreserved space unless there is an unlimited reservation too 
511        // (in which case there is no unreserved space)
512        if (getLimit() < 0) {
513            // exclude reservations that are not directly set on this section
514            for (Reservation r: getSectionReservations()) {
515                // ignore expired reservations
516                if (r.isExpired()) continue;
517                // there is an unlimited reservation -> no unreserved space
518                if (r.getLimit() < 0) return 0.0;
519            }
520            return Double.MAX_VALUE;
521        }
522        
523        double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
524        // exclude reservations that are not directly set on this section
525        for (Reservation r: getSectionReservations()) {
526            // ignore expired reservations
527            if (r.isExpired()) continue;
528            // unlimited reservation -> all the space is reserved
529            if (r.getLimit() < 0.0) return 0.0;
530            // compute space that can be potentially taken by this reservation
531            double reserved = r.getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest);
532            // deduct the space from available space
533            available -= Math.max(0.0, reserved);
534        }
535        
536        return available;
537    }
538    
539    /**
540     * Total space in the section that cannot be used by any section reservation
541     * @return total unreserved space in this class
542     **/
543    public synchronized double getTotalUnreservedSpace() {
544        if (iTotalUnreservedSpace == null)
545            iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
546        return iTotalUnreservedSpace;
547    }
548    private Double iTotalUnreservedSpace = null;
549    private double getTotalUnreservedSpaceNoCache() {
550        // section is unlimited -> there is unreserved space unless there is an unlimited reservation too 
551        // (in which case there is no unreserved space)
552        if (getLimit() < 0) {
553            // exclude reservations that are not directly set on this section
554            for (Reservation r: getSectionReservations()) {
555                // ignore expired reservations
556                if (r.isExpired()) continue;
557                // there is an unlimited reservation -> no unreserved space
558                if (r.getLimit() < 0) return 0.0;
559            }
560            return Double.MAX_VALUE;
561        }
562        
563        // we need to check all reservations linked with this section
564        double available = getLimit(), reserved = 0, exclusive = 0;
565        Set<Section> sections = new HashSet<Section>();
566        reservations: for (Reservation r: getSectionReservations()) {
567            // ignore expired reservations
568            if (r.isExpired()) continue;
569            // unlimited reservation -> no unreserved space
570            if (r.getLimit() < 0) return 0.0;
571            for (Section s: r.getSections(getSubpart())) {
572                if (s.equals(this)) continue;
573                if (s.getLimit() < 0) continue reservations;
574                if (sections.add(s))
575                    available += s.getLimit();
576            }
577            reserved += r.getLimit();
578            if (r.getSections(getSubpart()).size() == 1)
579                exclusive += r.getLimit();
580        }
581        
582        return Math.min(available - reserved, getLimit() - exclusive);
583    }
584    
585    
586    /**
587     * Get reservations for this section
588     * @return reservations that can use this class
589     */
590    public synchronized List<Reservation> getReservations() {
591        if (iReservations == null) {
592            iReservations = new ArrayList<Reservation>();
593            for (Reservation r: getSubpart().getConfig().getOffering().getReservations()) {
594                if (r.getSections(getSubpart()) == null || r.getSections(getSubpart()).contains(this))
595                    iReservations.add(r);
596            }
597        }
598        return iReservations;
599    }
600    private List<Reservation> iReservations = null;
601    
602    /**
603     * Get reservations that require this section
604     * @return reservations that must use this class
605     */
606    public synchronized List<Reservation> getSectionReservations() {
607        if (iSectionReservations == null) {
608            iSectionReservations = new ArrayList<Reservation>();
609            for (Reservation r: getSubpart().getSectionReservations()) {
610                if (r.getSections(getSubpart()).contains(this))
611                    iSectionReservations.add(r);
612            }
613        }
614        return iSectionReservations;
615    }
616    private List<Reservation> iSectionReservations = null;
617
618    /**
619     * Clear reservation information that was cached on this section
620     */
621    public synchronized void clearReservationCache() {
622        iReservations = null;
623        iSectionReservations = null;
624        iTotalUnreservedSpace = null;
625    }
626    
627    /**
628     * Return course-dependent section name
629     * @param courseId course offering unique id
630     * @return course dependent class name
631     */
632    public String getName(long courseId) {
633        if (iNameByCourse == null) return getName();
634        String name = iNameByCourse.get(courseId);
635        return (name == null ? getName() : name);
636    }
637    
638    /**
639     * Set course-dependent section name
640     * @param courseId course offering unique id
641     * @param name course dependent class name
642     */
643    public void setName(long courseId, String name) {
644        if (iNameByCourse == null) iNameByCourse = new HashMap<Long, String>();
645        iNameByCourse.put(courseId, name);
646    }
647
648    /**
649     * Return course-dependent section names
650     * @return map of course-dependent class names
651     */
652    public Map<Long, String> getNameByCourse() { return iNameByCourse; }
653    
654    @Override
655    public boolean equals(Object o) {
656        if (o == null || !(o instanceof Section)) return false;
657        return getId() == ((Section)o).getId();
658    }
659    
660    @Override
661    public int hashCode() {
662        return (int) (iId ^ (iId >>> 32));
663    }
664    
665    /**
666     * Section note
667     * @return scheduling note
668     */
669    public String getNote() { return iNote; }
670    
671    /**
672     * Section note
673     * @param note scheduling note
674     */
675    public void setNote(String note) { iNote = note; }
676    
677    /**
678     * Add section id of a section that student conflicts are to be ignored with
679     * @param sectionId class unique id
680     */
681    public void addIgnoreConflictWith(long sectionId) {
682        if (iIgnoreConflictsWith == null) iIgnoreConflictsWith = new HashSet<Long>();
683        iIgnoreConflictsWith.add(sectionId);
684    }
685    
686    /**
687     * Returns true if student conflicts between this section and the given one are to be ignored
688     * @param sectionId class unique id
689     * @return true if student conflicts between these two sections are to be ignored
690     */
691    public boolean isToIgnoreStudentConflictsWith(long sectionId) {
692        return iIgnoreConflictsWith != null && iIgnoreConflictsWith.contains(sectionId);
693    }
694    
695    /**
696     * Returns a set of ids of sections that student conflicts are to be ignored with (between this section and the others)
697     * @return set of class unique ids of the sections that student conflicts are to be ignored with 
698     */
699    public Set<Long> getIgnoreConflictWithSectionIds() {
700        return iIgnoreConflictsWith;
701    }
702    
703    /** Set of assigned enrollments */
704    @Override
705    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
706        return getContext(assignment).getEnrollments();
707    }
708    
709    /**
710     * Enrollment weight -- weight of all requests which have an enrollment that
711     * contains this section, excluding the given one. See
712     * {@link Request#getWeight()}.
713     * @param assignment current assignment
714     * @param excludeRequest course request to ignore, if any
715     * @return enrollment weight
716     */
717    public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
718        return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
719    }
720    
721    /**
722     * Enrollment weight including over the limit enrollments.
723     * That is enrollments that have reservation with {@link Reservation#canBatchAssignOverLimit()} set to true.
724     * @param assignment current assignment
725     * @param excludeRequest course request to ignore, if any
726     * @return enrollment weight
727     */
728    public double getEnrollmentTotalWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
729        return getContext(assignment).getEnrollmentTotalWeight(assignment, excludeRequest);
730    }
731    
732    /**
733     * Maximal weight of a single enrollment in the section
734     * @param assignment current assignment
735     * @return maximal enrollment weight
736     */
737    public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
738        return getContext(assignment).getMaxEnrollmentWeight();
739    }
740
741    /**
742     * Minimal weight of a single enrollment in the section
743     * @param assignment current assignment
744     * @return minimal enrollment weight
745     */
746    public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
747        return getContext(assignment).getMinEnrollmentWeight();
748    }
749    
750    /**
751     * Return cancelled flag of the class.
752     * @return true if the class is cancelled
753     */
754    public boolean isCancelled() { return iCancelled; }
755    
756    /**
757     * Set cancelled flag of the class.
758     * @param cancelled true if the class is cancelled
759     */
760    public void setCancelled(boolean cancelled) { iCancelled = cancelled; }
761
762    /**
763     * Return past flag of the class.
764     * @return true if the class is in the past and should be avoided when possible
765     */
766    public boolean isPast() { return iPast; }
767    
768    /**
769     * Set past flag of the class.
770     * @param past true if the class is in the past and should be avoided when possible
771     */
772    public void setPast(boolean past) { iPast = past; }
773
774    /**
775     * Return enabled flag of the class.
776     * @return true if the class is enabled for student scheduling
777     */
778    public boolean isEnabled() { return iEnabled; }
779    
780    /**
781     * Set enabled flag of the class.
782     * @param enabled true if the class is enabled for student scheduling
783     */
784    public void setEnabled(boolean enabled) { iEnabled = enabled; }
785    
786    /**
787     * Return whether the class is online.
788     * @return true if the class is online
789     */
790    public boolean isOnline() { return iOnline; }
791    
792    /**
793     * Set whether the class is online.
794     * @param online true if the class is online
795     */
796    public void setOnline(boolean online) { iOnline = online; }
797    
798    @Override
799    public Model<Request, Enrollment> getModel() {
800        return getSubpart().getConfig().getOffering().getModel();
801    }
802
803    @Override
804    public SectionContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
805        return new SectionContext(assignment);
806    }
807    
808    @Override
809    public SectionContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, SectionContext parentContext) {
810        return new SectionContext(parentContext);
811    }
812    
813    public class SectionContext implements AssignmentConstraintContext<Request, Enrollment> {
814        private Set<Enrollment> iEnrollments = null;
815        private double iEnrollmentWeight = 0.0;
816        private double iEnrollmentTotalWeight = 0.0;
817        private double iMaxEnrollmentWeight = 0.0;
818        private double iMinEnrollmentWeight = 0.0;
819        private boolean iReadOnly = false;
820
821        public SectionContext(Assignment<Request, Enrollment> assignment) {
822            iEnrollments = new HashSet<Enrollment>();
823            for (Course course: getSubpart().getConfig().getOffering().getCourses()) {
824                for (CourseRequest request: course.getRequests()) {
825                    Enrollment enrollment = assignment.getValue(request);
826                    if (enrollment != null && enrollment.getSections().contains(Section.this))
827                        assigned(assignment, enrollment);
828                }
829            }
830        }
831        
832        public SectionContext(SectionContext parent) {
833            iEnrollmentWeight = parent.iEnrollmentWeight;
834            iEnrollmentTotalWeight = parent.iEnrollmentTotalWeight;
835            iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight;
836            iMinEnrollmentWeight = parent.iMinEnrollmentWeight;
837            iEnrollments = parent.iEnrollments;
838            iReadOnly = true;
839        }
840
841        /** Called when an enrollment with this section is assigned to a request */
842        @Override
843        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
844            if (iReadOnly) {
845                iEnrollments = new HashSet<Enrollment>(iEnrollments);
846                iReadOnly = false;
847            }
848            if (iEnrollments.isEmpty()) {
849                iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
850            } else {
851                iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
852                iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
853            }
854            if (iEnrollments.add(enrollment)) {
855                iEnrollmentTotalWeight += enrollment.getRequest().getWeight();
856                if (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())
857                    iEnrollmentWeight += enrollment.getRequest().getWeight();
858            }
859        }
860
861        /** Called when an enrollment with this section is unassigned from a request */
862        @Override
863        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
864            if (iReadOnly) {
865                iEnrollments = new HashSet<Enrollment>(iEnrollments);
866                iReadOnly = false;
867            }
868            if (iEnrollments.remove(enrollment)) {
869                iEnrollmentTotalWeight -= enrollment.getRequest().getWeight();
870                if (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())
871                    iEnrollmentWeight -= enrollment.getRequest().getWeight();
872            }
873            if (iEnrollments.isEmpty()) {
874                iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
875            } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) {
876                if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) {
877                    double newMinEnrollmentWeight = Double.MAX_VALUE;
878                    for (Enrollment e : iEnrollments) {
879                        if (e.getRequest().getWeight() == iMinEnrollmentWeight) {
880                            newMinEnrollmentWeight = iMinEnrollmentWeight;
881                            break;
882                        } else {
883                            newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
884                        }
885                    }
886                    iMinEnrollmentWeight = newMinEnrollmentWeight;
887                }
888                if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) {
889                    double newMaxEnrollmentWeight = Double.MIN_VALUE;
890                    for (Enrollment e : iEnrollments) {
891                        if (e.getRequest().getWeight() == iMaxEnrollmentWeight) {
892                            newMaxEnrollmentWeight = iMaxEnrollmentWeight;
893                            break;
894                        } else {
895                            newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
896                        }
897                    }
898                    iMaxEnrollmentWeight = newMaxEnrollmentWeight;
899                }
900            }
901        }
902        
903        /** Set of assigned enrollments 
904         * @return assigned enrollments of this section
905         **/
906        public Set<Enrollment> getEnrollments() {
907            return iEnrollments;
908        }
909        
910        /**
911         * Enrollment weight -- weight of all requests which have an enrollment that
912         * contains this section, excluding the given one. See
913         * {@link Request#getWeight()}.
914         * @param assignment current assignment
915         * @param excludeRequest course request to ignore, if any
916         * @return enrollment weight
917         */
918        public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
919            double weight = iEnrollmentWeight;
920            if (excludeRequest != null) {
921                Enrollment enrollment = assignment.getValue(excludeRequest);
922                if (enrollment!= null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
923                    weight -= excludeRequest.getWeight();
924            }
925            return weight;
926        }
927        
928        /**
929         * Enrollment weight including over the limit enrollments.
930         *  That is enrollments that have reservation with {@link Reservation#canBatchAssignOverLimit()} set to true.
931         * @param assignment current assignment
932         * @param excludeRequest course request to ignore, if any
933         * @return enrollment weight
934         */
935        public double getEnrollmentTotalWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
936            double weight = iEnrollmentTotalWeight;
937            if (excludeRequest != null) {
938                Enrollment enrollment = assignment.getValue(excludeRequest);
939                if (enrollment!= null && iEnrollments.contains(enrollment))
940                    weight -= excludeRequest.getWeight();
941            }
942            return weight;
943        }
944        
945        /**
946         * Maximal weight of a single enrollment in the section
947         * @return maximal enrollment weight
948         */
949        public double getMaxEnrollmentWeight() {
950            return iMaxEnrollmentWeight;
951        }
952
953        /**
954         * Minimal weight of a single enrollment in the section
955         * @return minimal enrollment weight
956         */
957        public double getMinEnrollmentWeight() {
958            return iMinEnrollmentWeight;
959        }
960    }
961    
962    /**
963     * Choice matching this section
964     * @return choice matching this section
965     */
966    public Choice getChoice() {
967        return new Choice(this);
968    }
969    
970    /**
971     * List of student unavailabilities
972     * @return student unavailabilities
973     */
974    public List<Unavailability> getUnavailabilities() { return iUnavailabilities; }
975}