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