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
008 import net.sf.cpsolver.ifs.model.Value;
009 import net.sf.cpsolver.ifs.util.ToolBox;
010 import net.sf.cpsolver.studentsct.StudentSectioningModel;
011 import net.sf.cpsolver.studentsct.extension.DistanceConflict;
012 import net.sf.cpsolver.studentsct.extension.TimeOverlapsCounter;
013 import net.sf.cpsolver.studentsct.reservation.Reservation;
014
015 /**
016 * Representation of an enrollment of a student into a course. A student needs
017 * to be enrolled in a section of each subpart of a selected configuration. When
018 * parent-child relation is defined among sections, if a student is enrolled in
019 * a section that has a parent section defined, he/she has be enrolled in the
020 * parent section as well. Also, the selected sections cannot overlap in time. <br>
021 * <br>
022 *
023 * @version StudentSct 1.2 (Student Sectioning)<br>
024 * Copyright (C) 2007 - 2010 Tomas Muller<br>
025 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
026 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
027 * <br>
028 * This library is free software; you can redistribute it and/or modify
029 * it under the terms of the GNU Lesser General Public License as
030 * published by the Free Software Foundation; either version 3 of the
031 * License, or (at your option) any later version. <br>
032 * <br>
033 * This library is distributed in the hope that it will be useful, but
034 * WITHOUT ANY WARRANTY; without even the implied warranty of
035 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
036 * Lesser General Public License for more details. <br>
037 * <br>
038 * You should have received a copy of the GNU Lesser General Public
039 * License along with this library; if not see
040 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
041 */
042
043 public class Enrollment extends Value<Request, Enrollment> {
044 private static DecimalFormat sDF = new DecimalFormat("0.000");
045 private Request iRequest = null;
046 private Config iConfig = null;
047 private Course iCourse = null;
048 private Set<? extends Assignment> iAssignments = null;
049 private Double iCachedPenalty = null;
050 private int iPriority = 0;
051 private Reservation iReservation = null;
052 private Long iTimeStamp = null;
053 private String iApproval = null;
054
055 /**
056 * Constructor
057 *
058 * @param request
059 * course / free time request
060 * @param priority
061 * zero for the course, one for the first alternative, two for the second alternative
062 * @param course
063 * selected course
064 * @param config
065 * selected configuration
066 * @param assignments
067 * valid list of sections
068 */
069 public Enrollment(Request request, int priority, Course course, Config config, Set<? extends Assignment> assignments, Reservation reservation) {
070 super(request);
071 iRequest = request;
072 iConfig = config;
073 iAssignments = assignments;
074 iPriority = priority;
075 iCourse = course;
076 if (iConfig != null && iCourse == null)
077 for (Course c: ((CourseRequest)iRequest).getCourses()) {
078 if (c.getOffering().getConfigs().contains(iConfig)) {
079 iCourse = c;
080 break;
081 }
082 }
083 iReservation = reservation;
084 }
085
086 /**
087 * Constructor
088 *
089 * @param request
090 * course / free time request
091 * @param priority
092 * zero for the course, one for the first alternative, two for the second alternative
093 * @param config
094 * selected configuration
095 * @param assignments
096 * valid list of sections
097 */
098 public Enrollment(Request request, int priority, Config config, Set<? extends Assignment> assignments) {
099 this(request, priority, null, config, assignments, null);
100 if (assignments != null)
101 guessReservation(true);
102 }
103
104 /**
105 * Guess the reservation based on the enrollment
106 */
107 public void guessReservation(boolean onlyAvailable) {
108 if (iCourse != null) {
109 Reservation best = null;
110 boolean canAssignOverTheLimit = (variable().getModel() == null || ((StudentSectioningModel)variable().getModel()).getReservationCanAssignOverTheLimit());
111 for (Reservation reservation: ((CourseRequest)iRequest).getReservations(iCourse)) {
112 if (reservation.isIncluded(this)) {
113 if (onlyAvailable && reservation.getReservedAvailableSpace(iRequest) < iRequest.getWeight() &&
114 (!reservation.canAssignOverLimit() || !canAssignOverTheLimit))
115 continue;
116 if (best == null || best.getPriority() > reservation.getPriority()) {
117 best = reservation;
118 } else if (best.getPriority() == reservation.getPriority() &&
119 best.getReservedAvailableSpace(iRequest) < reservation.getReservedAvailableSpace(iRequest)) {
120 best = reservation;
121 }
122 }
123 }
124 iReservation = best;
125 }
126 }
127
128 /** Student */
129 public Student getStudent() {
130 return iRequest.getStudent();
131 }
132
133 /** Request */
134 public Request getRequest() {
135 return iRequest;
136 }
137
138 /** True if the request is course request */
139 public boolean isCourseRequest() {
140 return iConfig != null;
141 }
142
143 /** Offering of the course request */
144 public Offering getOffering() {
145 return (iConfig == null ? null : iConfig.getOffering());
146 }
147
148 /** Config of the course request */
149 public Config getConfig() {
150 return iConfig;
151 }
152
153 /** Course of the course request */
154 public Course getCourse() {
155 return iCourse;
156 }
157
158 /** List of assignments (selected sections) */
159 @SuppressWarnings("unchecked")
160 public Set<Assignment> getAssignments() {
161 return (Set<Assignment>) iAssignments;
162 }
163
164 /** List of sections (only for course request) */
165 @SuppressWarnings("unchecked")
166 public Set<Section> getSections() {
167 if (isCourseRequest())
168 return (Set<Section>) iAssignments;
169 return new HashSet<Section>();
170 }
171
172 /** True when this enrollment is overlapping with the given enrollment */
173 public boolean isOverlapping(Enrollment enrl) {
174 if (enrl == null || isAllowOverlap() || enrl.isAllowOverlap())
175 return false;
176 for (Assignment a : getAssignments()) {
177 if (a.isOverlapping(enrl.getAssignments()))
178 return true;
179 }
180 return false;
181 }
182
183 /** Percent of sections that are wait-listed */
184 public double percentWaitlisted() {
185 if (!isCourseRequest())
186 return 0.0;
187 CourseRequest courseRequest = (CourseRequest) getRequest();
188 int nrWaitlisted = 0;
189 for (Section section : getSections()) {
190 if (courseRequest.isWaitlisted(section))
191 nrWaitlisted++;
192 }
193 return ((double) nrWaitlisted) / getAssignments().size();
194 }
195
196 /** Percent of sections that are selected */
197 public double percentSelected() {
198 if (!isCourseRequest())
199 return 0.0;
200 CourseRequest courseRequest = (CourseRequest) getRequest();
201 int nrSelected = 0;
202 for (Section section : getSections()) {
203 if (courseRequest.isSelected(section))
204 nrSelected++;
205 }
206 return ((double) nrSelected) / getAssignments().size();
207 }
208
209 /** Percent of sections that are initial */
210 public double percentInitial() {
211 if (!isCourseRequest())
212 return 0.0;
213 if (getRequest().getInitialAssignment() == null)
214 return 0.0;
215 Enrollment inital = getRequest().getInitialAssignment();
216 int nrInitial = 0;
217 for (Section section : getSections()) {
218 if (inital.getAssignments().contains(section))
219 nrInitial++;
220 }
221 return ((double) nrInitial) / getAssignments().size();
222 }
223
224 /** True if all the sections are wait-listed */
225 public boolean isWaitlisted() {
226 if (!isCourseRequest())
227 return false;
228 CourseRequest courseRequest = (CourseRequest) getRequest();
229 for (Iterator<? extends Assignment> i = getAssignments().iterator(); i.hasNext();) {
230 Section section = (Section) i.next();
231 if (!courseRequest.isWaitlisted(section))
232 return false;
233 }
234 return true;
235 }
236
237 /** True if all the sections are selected */
238 public boolean isSelected() {
239 if (!isCourseRequest())
240 return false;
241 CourseRequest courseRequest = (CourseRequest) getRequest();
242 for (Section section : getSections()) {
243 if (!courseRequest.isSelected(section))
244 return false;
245 }
246 return true;
247 }
248
249 /**
250 * Enrollment penalty -- sum of section penalties (see
251 * {@link Section#getPenalty()})
252 */
253 public double getPenalty() {
254 if (iCachedPenalty == null) {
255 double penalty = 0.0;
256 if (isCourseRequest()) {
257 for (Section section : getSections()) {
258 penalty += section.getPenalty();
259 }
260 }
261 iCachedPenalty = new Double(penalty / getAssignments().size());
262 }
263 return iCachedPenalty.doubleValue();
264 }
265
266 /** Enrollment value */
267 @Override
268 public double toDouble() {
269 return toDouble(true);
270 }
271
272 /** Enrollment value
273 * @param precise if false, distance conflicts and time overlaps are ignored (i.e., much faster, but less precise computation)
274 **/
275 public double toDouble(boolean precise) {
276 if (precise)
277 return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(this, distanceConflicts(), timeOverlappingConflicts());
278 else {
279 if (getExtra() != null) return - (Double) getExtra();
280 return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(this);
281 }
282 }
283
284 /** Enrollment name */
285 @Override
286 public String getName() {
287 if (getRequest() instanceof CourseRequest) {
288 Course course = null;
289 CourseRequest courseRequest = (CourseRequest) getRequest();
290 for (Course c : courseRequest.getCourses()) {
291 if (c.getOffering().getConfigs().contains(getConfig())) {
292 course = c;
293 break;
294 }
295 }
296 String ret = (course == null ? getConfig() == null ? "" : getConfig().getName() : course.getName());
297 for (Iterator<? extends Assignment> i = getAssignments().iterator(); i.hasNext();) {
298 Section assignment = (Section) i.next();
299 ret += "\n " + assignment.getLongName() + (i.hasNext() ? "," : "");
300 }
301 return ret;
302 } else if (getRequest() instanceof FreeTimeRequest) {
303 return "Free Time " + ((FreeTimeRequest) getRequest()).getTime().getLongName();
304 } else {
305 String ret = "";
306 for (Iterator<? extends Assignment> i = getAssignments().iterator(); i.hasNext();) {
307 Assignment assignment = i.next();
308 ret += assignment.toString() + (i.hasNext() ? "," : "");
309 if (i.hasNext())
310 ret += "\n ";
311 }
312 return ret;
313 }
314 }
315
316 @Override
317 public String toString() {
318 if (getAssignments().isEmpty()) return "not assigned";
319 Set<DistanceConflict.Conflict> dc = distanceConflicts();
320 Set<TimeOverlapsCounter.Conflict> toc = timeOverlappingConflicts();
321 int share = 0;
322 if (toc != null)
323 for (TimeOverlapsCounter.Conflict c: toc)
324 share += c.getShare();
325 String ret = sDF.format(toDouble()) + "/" + sDF.format(getRequest().getBound())
326 + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty()))
327 + (dc == null || dc.isEmpty() ? "" : "/dc:" + dc.size())
328 + (share <= 0 ? "" : "/toc:" + share);
329 if (getRequest() instanceof CourseRequest) {
330 ret += " ";
331 for (Iterator<? extends Assignment> i = getAssignments().iterator(); i.hasNext();) {
332 Assignment assignment = i.next();
333 ret += assignment + (i.hasNext() ? ", " : "");
334 }
335 }
336 if (getReservation() != null) ret = "(r) " + ret;
337 return ret;
338 }
339
340 @Override
341 public boolean equals(Object o) {
342 if (o == null || !(o instanceof Enrollment))
343 return false;
344 Enrollment e = (Enrollment) o;
345 if (!ToolBox.equals(getConfig(), e.getConfig()))
346 return false;
347 if (!ToolBox.equals(getRequest(), e.getRequest()))
348 return false;
349 if (!ToolBox.equals(getAssignments(), e.getAssignments()))
350 return false;
351 return true;
352 }
353
354 /** Distance conflicts, in which this enrollment is involved. */
355 public Set<DistanceConflict.Conflict> distanceConflicts() {
356 if (!isCourseRequest())
357 return null;
358 if (getRequest().getModel() instanceof StudentSectioningModel) {
359 DistanceConflict dc = ((StudentSectioningModel) getRequest().getModel()).getDistanceConflict();
360 if (dc == null) return null;
361 return dc.allConflicts(this);
362 } else
363 return null;
364 }
365
366 /** Time overlapping conflicts, in which this enrollment is involved. */
367 public Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts() {
368 if (getRequest().getModel() instanceof StudentSectioningModel) {
369 TimeOverlapsCounter toc = ((StudentSectioningModel) getRequest().getModel()).getTimeOverlaps();
370 if (toc == null)
371 return null;
372 return toc.allConflicts(this);
373 } else
374 return null;
375 }
376
377 /**
378 * Return enrollment priority
379 * @return zero for the course, one for the first alternative, two for the second alternative
380 */
381 public int getPriority() {
382 return iPriority;
383 }
384
385 /**
386 * Return total number of slots of all sections in the enrollment.
387 */
388 public int getNrSlots() {
389 int ret = 0;
390 for (Assignment a: getAssignments()) {
391 if (a.getTime() != null) ret += a.getTime().getLength() * a.getTime().getNrMeetings();
392 }
393 return ret;
394 }
395
396 /**
397 * Return reservation used for this enrollment
398 */
399 public Reservation getReservation() { return iReservation; }
400
401 /**
402 * Set reservation for this enrollment
403 */
404 public void setReservation(Reservation reservation) { iReservation = reservation; }
405
406 /**
407 * Time stamp of the enrollment
408 */
409 public Long getTimeStamp() {
410 return iTimeStamp;
411 }
412
413 /**
414 * Time stamp of the enrollment
415 */
416 public void setTimeStamp(Long timeStamp) {
417 iTimeStamp = timeStamp;
418 }
419
420 /**
421 * Approval of the enrollment (only used by the online student sectioning)
422 */
423 public String getApproval() {
424 return iApproval;
425 }
426
427 /**
428 * Approval of the enrollment (only used by the online student sectioning)
429 */
430 public void setApproval(String approval) {
431 iApproval = approval;
432 }
433
434 /**
435 * True if this enrollment can overlap with other enrollments of the student.
436 */
437 public boolean isAllowOverlap() {
438 return (getReservation() != null && getReservation().isAllowOverlap());
439 }
440
441 /**
442 * Enrollment limit, i.e., the number of students that would be able to get into the offering using this enrollment (if all the sections are empty)
443 */
444 public int getLimit() {
445 if (!isCourseRequest()) return -1; // free time requests have no limit
446 Integer limit = null;
447 for (Section section: getSections())
448 if (section.getLimit() >= 0) {
449 if (limit == null)
450 limit = section.getLimit();
451 else
452 limit = Math.min(limit, section.getLimit());
453 }
454 return (limit == null ? -1 : limit);
455 }
456
457 }