001    package net.sf.cpsolver.studentsct.model;
002    
003    import java.text.DecimalFormat;
004    import java.util.HashSet;
005    import java.util.Iterator;
006    import java.util.Set;
007    import java.util.Vector;
008    
009    import net.sf.cpsolver.coursett.model.Placement;
010    import net.sf.cpsolver.coursett.model.TimeLocation;
011    
012    /**
013     * Representation of a class. Each section contains id, name, scheduling subpart, time/room placement, and a limit. 
014     * Optionally, parent-child relation between sections can be defined.
015     * <br><br>
016     * Each student requesting a course needs to be enrolled in a class of each subpart of a selected configuration. 
017     * In the case of parent-child relation between classes, if a student is enrolled in a section that has a parent 
018     * section defined, he/she has to be enrolled in the parent section as well. If there is a parent-child relation between
019     * two sections, the same relation is defined on their subparts as well, i.e., if section A is a parent section B, subpart 
020     * of section A isa parent of subpart of section B.  
021     * <br><br>
022     * 
023     * @version
024     * StudentSct 1.1 (Student Sectioning)<br>
025     * Copyright (C) 2007 Tomáš Müller<br>
026     * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
027     * Lazenska 391, 76314 Zlin, Czech Republic<br>
028     * <br>
029     * This library is free software; you can redistribute it and/or
030     * modify it under the terms of the GNU Lesser General Public
031     * License as published by the Free Software Foundation; either
032     * version 2.1 of the License, or (at your option) any later version.
033     * <br><br>
034     * This library is distributed in the hope that it will be useful,
035     * but WITHOUT ANY WARRANTY; without even the implied warranty of
036     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
037     * Lesser General Public License for more details.
038     * <br><br>
039     * You should have received a copy of the GNU Lesser General Public
040     * License along with this library; if not, write to the Free Software
041     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
042     */
043    public class Section implements Assignment, Comparable {
044        private static DecimalFormat sDF = new DecimalFormat("0.000");
045        private long iId = -1;
046        private String iName = null;
047        private Subpart iSubpart = null;
048        private Section iParent = null;
049        private Placement iPlacement = null;
050        private int iLimit = 0;
051        private HashSet iEnrollments = new HashSet();
052        private Choice iChoice = null;
053        private double iPenalty = 0.0;
054        private double iEnrollmentWeight = 0.0;
055        private double iMaxEnrollmentWeight = 0.0;
056        private double iMinEnrollmentWeight = 0.0;
057        private double iSpaceExpected = 0.0;
058        private double iSpaceHeld = 0.0;
059        
060        /** Constructor
061         * @param id section unique id
062         * @param limit section limit, i.e., the maximal number of students that can be enrolled in this section at the same time
063         * @param name section name
064         * @param subpart subpart of this section
065         * @param placement time/room placement
066         * @param instructorIds instructor(s) id -- needed for {@link Section#getChoice()}
067         * @param instructorNames instructor(s) name -- needed for {@link Section#getChoice()}
068         * @param parent parent section -- if there is a parent section defined, a student that is enrolled in this
069         * section has to be enrolled in the parent section as well. Also, the same relation needs to be defined between subpart of 
070         * this section and the subpart of the parent section
071         */
072        public Section(long id, int limit, String name, Subpart subpart, Placement placement, String instructorIds, String instructorNames, Section parent) {
073            iId = id;
074            iLimit = limit;
075            iName = name;
076            iSubpart = subpart; 
077            iSubpart.getSections().add(this);
078            iPlacement = placement;
079            iParent = parent;
080            iChoice = new Choice(getSubpart().getConfig().getOffering(),getSubpart().getInstructionalType(), getTime(), instructorIds, instructorNames);
081        }
082        
083        /** Section id */
084        public long getId() {
085            return iId;
086        }
087        
088        /** Section limit. This is defines the maximal number of students that can be enrolled into this section at 
089         * the same time. It is -1 in the case of an unlimited section */
090        public int getLimit() {
091            return iLimit;
092        }
093    
094        /** Set section limit */
095        public void setLimit(int limit) {
096            iLimit = limit;
097        }
098        
099        /** Section name */
100        public String getName() {
101            return iName;
102        }
103        
104        /** Scheduling subpart to which this section belongs */
105        public Subpart getSubpart() {
106            return iSubpart;
107        }
108    
109        /** Parent section of this section (can be null). If there is a parent section defined, a student that is enrolled in this
110         * section has to be enrolled in the parent section as well. Also, the same relation needs to be defined between subpart of 
111         * this section and the subpart of the parent section. 
112         */
113        public Section getParent() {
114            return iParent;
115        }
116        
117        /** Time/room placement of the section. This can be null, for arranged sections. */
118        public Placement getPlacement() {
119            return iPlacement;
120        }
121        
122        /** Time placement of the section. */
123        public TimeLocation getTime() {
124            return (iPlacement==null?null:iPlacement.getTimeLocation());
125        }
126        
127        /** Number of rooms in which the section meet. */
128        public int getNrRooms() {
129            return (iPlacement==null?0:iPlacement.getNrRooms());
130        }
131        
132        /** Room placement -- list of {@link net.sf.cpsolver.coursett.model.RoomLocation} */ 
133        public Vector getRooms() {
134            if (iPlacement==null) return null;
135            if (iPlacement.getRoomLocations()==null && iPlacement.getRoomLocation()!=null) {
136                Vector ret = new Vector(1);
137                ret.add(iPlacement.getRoomLocation());
138                return ret;
139            }
140            return iPlacement.getRoomLocations();
141        }
142        
143        /** True, if this section overlaps with the given assignment in time and space */
144        public boolean isOverlapping(Assignment assignment) {
145            if (getTime()==null || assignment.getTime()==null) return false;
146            return getTime().hasIntersection(assignment.getTime());
147        }
148    
149        /** True, if this section overlaps with one of the given set of assignments in time and space */
150        public boolean isOverlapping(Set assignments) {
151            if (getTime()==null) return false;
152            for (Iterator i=assignments.iterator();i.hasNext();) {
153                Assignment assignment = (Assignment)i.next();
154                if (assignment.getTime()==null) continue;
155                if (getTime().hasIntersection(assignment.getTime())) return true;
156            }
157            return false;
158        }
159        
160        /** Called when an enrollment with this section is assigned to a request */
161        public void assigned(Enrollment enrollment) {
162            if (iEnrollments.isEmpty()) {
163                iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
164            } else {
165                iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
166                iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
167            }
168            iEnrollments.add(enrollment);
169            iEnrollmentWeight += enrollment.getRequest().getWeight();
170        }
171        
172        /** Called when an enrollment with this section is unassigned from a request */
173        public void unassigned(Enrollment enrollment) {
174            iEnrollments.remove(enrollment);
175            iEnrollmentWeight -= enrollment.getRequest().getWeight();
176            if (iEnrollments.isEmpty()) {
177                iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
178            } else if (iMinEnrollmentWeight!=iMaxEnrollmentWeight) {
179                if (iMinEnrollmentWeight==enrollment.getRequest().getWeight()) {
180                    double newMinEnrollmentWeight = Double.MAX_VALUE;
181                    for (Iterator i=iEnrollments.iterator();i.hasNext();) {
182                        Enrollment e = (Enrollment)i.next();
183                        if (e.getRequest().getWeight()==iMinEnrollmentWeight) {
184                            newMinEnrollmentWeight = iMinEnrollmentWeight; break;
185                        } else {
186                            newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
187                        }
188                    }
189                    iMinEnrollmentWeight = newMinEnrollmentWeight;
190                }
191                if (iMaxEnrollmentWeight==enrollment.getRequest().getWeight()) {
192                    double newMaxEnrollmentWeight = Double.MIN_VALUE;
193                    for (Iterator i=iEnrollments.iterator();i.hasNext();) {
194                        Enrollment e = (Enrollment)i.next();
195                        if (e.getRequest().getWeight()==iMaxEnrollmentWeight) {
196                            newMaxEnrollmentWeight = iMaxEnrollmentWeight; break;
197                        } else {
198                            newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
199                        }
200                    }
201                    iMaxEnrollmentWeight = newMaxEnrollmentWeight;
202                }
203            }
204        }
205        
206        /** Set of assigned enrollments */
207        public Set getEnrollments() {
208            return iEnrollments;
209        }
210        
211        /** Enrollment weight -- weight of all requests which have an enrollment that contains this
212         * section, excluding the given one. See {@link Request#getWeight()}.*/
213        public double getEnrollmentWeight(Request excludeRequest) {
214            double weight = iEnrollmentWeight;
215            if (excludeRequest!=null && excludeRequest.getAssignment()!=null && iEnrollments.contains(excludeRequest.getAssignment()))
216                weight -= excludeRequest.getWeight();
217            return weight;
218        }
219        
220        /**
221         * Maximal weight of a single enrollment in the section
222         */
223        public double getMaxEnrollmentWeight() {
224            return iMaxEnrollmentWeight;
225        }
226        
227        /**
228         * Minimal weight of a single enrollment in the section
229         */
230        public double getMinEnrollmentWeight() {
231            return iMinEnrollmentWeight;
232        }
233    
234        /** Long name: subpart name + time long name + room names + instructor names */
235        public String getLongName() {
236            return getSubpart().getName()+
237            (getTime()==null?"":" "+getTime().getLongName())+
238            (getNrRooms()==0?"":" "+getPlacement().getRoomName(","))+
239            (getChoice().getInstructorNames()==null?"":" "+getChoice().getInstructorNames());
240        }
241        
242        public String toString() {
243            return getName()+
244            (getTime()==null?"":" "+getTime().getLongName())+
245            (getNrRooms()==0?"":" "+getPlacement().getRoomName(","))+
246            (getChoice().getInstructorNames()==null?"":" "+getChoice().getInstructorNames())+
247            " (L:"+(getLimit()<0?"unlimited":""+getLimit())+(getPenalty()==0.0?"":",P:"+sDF.format(getPenalty()))+")";
248        }
249        
250        /** A (student) choice representing this section. */
251        public Choice getChoice() {
252            return iChoice;
253        }
254        
255        /** Return penalty which is added to an enrollment that contains this section. */
256        public double getPenalty() {
257            return iPenalty;
258        }
259        
260        /** Set penalty which is added to an enrollment that contains this section. */
261        public void setPenalty(double penalty) {
262            iPenalty = penalty;
263        }
264        
265        /** Compare two sections, prefer sections with lower penalty and more open space*/
266        public int compareTo(Object o) {
267            if (o==null || !(o instanceof Section)) return -1;
268            Section s = (Section)o;
269            int cmp = Double.compare(getPenalty(),s.getPenalty());
270            if (cmp!=0) return cmp;
271            cmp = Double.compare(getLimit()-getEnrollmentWeight(null),s.getLimit()-s.getEnrollmentWeight(null));
272            if (cmp!=0) return cmp;
273            return Double.compare(getId(),s.getId());
274        }
275        
276        /** 
277         * Return the amount of space of this section that is held for incoming students. 
278         * This attribute is computed during the batch sectioning (it is the overall weight of 
279         * dummy students enrolled in this section) and it is being updated with each incomming student during the 
280         * online sectioning.  
281         */ 
282        public double getSpaceHeld() {
283            return iSpaceHeld;
284        }
285        
286        /** 
287         * Set the amount of space of this section that is held for incoming students. 
288         * See {@link Section#getSpaceHeld()} for more info. 
289         */ 
290        public void setSpaceHeld(double spaceHeld) {
291            iSpaceHeld = spaceHeld;
292        }
293        
294        /** 
295         * Return the amount of space of this section that is expected to be taken by incoming students.
296         * This attribute is computed during the batch sectioning (for each dummy student that can attend 
297         * this section (without any conflict with other enrollments of that student), 1 / x where x is the 
298         * number of such sections of this subpart is added to this value). Also, this value is being updated with 
299         * each incomming student during the online sectioning.  
300         */
301        public double getSpaceExpected() {
302            return iSpaceExpected;
303        }
304    
305        /** 
306         * Set the amount of space of this section that is expected to be taken by incoming students. 
307         * See {@link Section#getSpaceExpected()} for more info. 
308         */ 
309        public void setSpaceExpected(double spaceExpected) {
310            iSpaceExpected = spaceExpected;
311        }
312        
313        /**
314         * Online sectioning penalty. 
315         */
316        public double getOnlineSectioningPenalty() {
317            if (getLimit()<=0)return 0.0;
318            
319            double available = getLimit() - getEnrollmentWeight(null);
320            
321            double penalty = (getSpaceExpected() - available) / getLimit();
322            
323            return Math.max(-1.0,Math.min(1.0,penalty));
324        }
325        
326    }