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