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 || assignments==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 }