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                if (g.getCourse().equals(getCourse())) {
405                    sameGroup += g.getEnrollmentSpread(a, this, 1.0, 0.0);
406                    groupCount ++;
407                }
408            }
409            if (groupCount > 0)
410                ret += "/g:" + sDF.format(sameGroup / groupCount);
411        }
412        if (getRequest() instanceof CourseRequest) {
413            ret += " ";
414            for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
415                SctAssignment assignment = i.next();
416                ret += assignment + (i.hasNext() ? ", " : "");
417            }
418        }
419        if (getReservation() != null) ret = "(r) " + ret;
420        return ret;
421    }
422    
423    @Override
424    public String toString() {
425        if (getAssignments().isEmpty()) return "not assigned";
426        String ret = sDF.format(getRequest().getBound()) + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty()));
427        if (getRequest() instanceof CourseRequest) {
428            ret += " ";
429            for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) {
430                SctAssignment assignment = i.next();
431                ret += assignment + (i.hasNext() ? ", " : "");
432            }
433        }
434        if (getReservation() != null) ret = "(r) " + ret;
435        if (getRequest().isMPP()) ret += " [i" + sDF.format(100.0 * percentInitial()) + "/t" + sDF.format(100.0 * percentSameTime()) + "]";
436        return ret;
437    }
438
439    @Override
440    public boolean equals(Object o) {
441        if (o == null || !(o instanceof Enrollment))
442            return false;
443        Enrollment e = (Enrollment) o;
444        if (!ToolBox.equals(getConfig(), e.getConfig()))
445            return false;
446        if (!ToolBox.equals(getRequest(), e.getRequest()))
447            return false;
448        if (!ToolBox.equals(getAssignments(), e.getAssignments()))
449            return false;
450        return true;
451    }
452
453    /** Distance conflicts, in which this enrollment is involved. 
454     * @param assignment current assignment
455     * @return distance conflicts
456     **/
457    public Set<DistanceConflict.Conflict> distanceConflicts(Assignment<Request, Enrollment> assignment) {
458        if (!isCourseRequest())
459            return null;
460        if (getRequest().getModel() instanceof StudentSectioningModel) {
461            DistanceConflict dc = ((StudentSectioningModel) getRequest().getModel()).getDistanceConflict();
462            if (dc == null) return null;
463            return dc.allConflicts(assignment, this);
464        } else
465            return null;
466    }
467
468    /** Time overlapping conflicts, in which this enrollment is involved. 
469     * @param assignment current assignment
470     * @return time overlapping conflicts
471     **/
472    public Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts(Assignment<Request, Enrollment> assignment) {
473        if (getRequest().getModel() instanceof StudentSectioningModel) {
474            TimeOverlapsCounter toc = ((StudentSectioningModel) getRequest().getModel()).getTimeOverlaps();
475            if (toc == null)
476                return null;
477            return toc.allConflicts(assignment, this);
478        } else
479            return null;
480    }
481
482    /** 
483     * Return enrollment priority
484     * @return zero for the course, one for the first alternative, two for the second alternative
485     */
486    public int getPriority() {
487        return iPriority;
488    }
489    
490    /**
491     * Return total number of slots of all sections in the enrollment.
492     * @return number of slots used
493     */
494    public int getNrSlots() {
495        int ret = 0;
496        for (SctAssignment a: getAssignments()) {
497            if (a.getTime() != null) ret += a.getTime().getLength() * a.getTime().getNrMeetings();
498        }
499        return ret;
500    }
501    
502    /**
503     * Return reservation used for this enrollment
504     * @return used reservation
505     */
506    public Reservation getReservation() { return iReservation; }
507    
508    /**
509     * Set reservation for this enrollment
510     * @param reservation used reservation
511     */
512    public void setReservation(Reservation reservation) { iReservation = reservation; }
513    
514    /**
515     * Time stamp of the enrollment
516     * @return enrollment time stamp
517     */
518    public Long getTimeStamp() {
519        return iTimeStamp;
520    }
521
522    /**
523     * Time stamp of the enrollment
524     * @param timeStamp enrollment time stamp
525     */
526    public void setTimeStamp(Long timeStamp) {
527        iTimeStamp = timeStamp;
528    }
529
530    /**
531     * Approval of the enrollment (only used by the online student sectioning)
532     * @return consent approval
533     */
534    public String getApproval() {
535        return iApproval;
536    }
537
538    /**
539     * Approval of the enrollment (only used by the online student sectioning)
540     * @param approval consent approval
541     */
542    public void setApproval(String approval) {
543        iApproval = approval;
544    }
545    
546    /**
547     * True if this enrollment can overlap with other enrollments of the student.
548     * @return can overlap with other enrollments of the student
549     */
550    public boolean isAllowOverlap() {
551        return (getReservation() != null && getReservation().isAllowOverlap());
552    }
553    
554    /**
555     * 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)  
556     * @return enrollment limit
557     */
558    public int getLimit() {
559        if (!isCourseRequest()) return -1; // free time requests have no limit
560        Integer limit = null;
561        for (Section section: getSections())
562            if (section.getLimit() >= 0) {
563                if (limit == null)
564                    limit = section.getLimit();
565                else
566                    limit = Math.min(limit, section.getLimit());
567            }
568        return (limit == null ? -1 : limit);
569    }
570    
571}