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    }