001package net.sf.cpsolver.studentsct.model;
002
003import java.text.DecimalFormat;
004import java.util.HashSet;
005import java.util.Iterator;
006import java.util.Set;
007
008import net.sf.cpsolver.ifs.model.Value;
009import net.sf.cpsolver.ifs.util.ToolBox;
010import net.sf.cpsolver.studentsct.StudentSectioningModel;
011import net.sf.cpsolver.studentsct.extension.DistanceConflict;
012import net.sf.cpsolver.studentsct.extension.TimeOverlapsCounter;
013import 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 Tomáš Müller<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
043public 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}