001package org.cpsolver.studentsct.model;
002
003import java.util.Collections;
004import java.util.HashSet;
005import java.util.Set;
006import java.util.regex.Matcher;
007import java.util.regex.Pattern;
008
009import org.cpsolver.ifs.assignment.Assignment;
010import org.cpsolver.ifs.assignment.context.AbstractClassWithContext;
011import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
012import org.cpsolver.ifs.assignment.context.CanInheritContext;
013import org.cpsolver.ifs.model.Model;
014
015
016/**
017 * Representation of a course offering. A course offering contains id, subject
018 * area, course number and an instructional offering. <br>
019 * <br>
020 * Each instructional offering (see {@link Offering}) is offered under one or
021 * more course offerings.
022 * 
023 * <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 */
045public class Course extends AbstractClassWithContext<Request, Enrollment, Course.CourseContext> implements CanInheritContext<Request, Enrollment, Course.CourseContext> {
046    private long iId = -1;
047    private String iSubjectArea = null;
048    private String iCourseNumber = null;
049    private Offering iOffering = null;
050    private int iLimit = 0, iProjected = 0;
051    private Set<CourseRequest> iRequests = Collections.synchronizedSet(new HashSet<CourseRequest>());
052    private String iNote = null;
053    private Set<RequestGroup> iRequestGroups = new HashSet<RequestGroup>();
054    private String iCredit = null;
055    private Float iCreditValue = null;
056    private String iTitle = null;
057    private String iType = null;
058
059    /**
060     * Constructor
061     * 
062     * @param id
063     *            course offering unique id
064     * @param subjectArea
065     *            subject area (e.g., MA, CS, ENGL)
066     * @param courseNumber
067     *            course number under the given subject area
068     * @param offering
069     *            instructional offering which is offered under this course
070     *            offering
071     */
072    public Course(long id, String subjectArea, String courseNumber, Offering offering) {
073        iId = id;
074        iSubjectArea = subjectArea;
075        iCourseNumber = courseNumber;
076        iOffering = offering;
077        iOffering.getCourses().add(this);
078    }
079
080    /**
081     * Constructor
082     * 
083     * @param id
084     *            course offering unique id
085     * @param subjectArea
086     *            subject area (e.g., MA, CS, ENGL)
087     * @param courseNumber
088     *            course number under the given subject area
089     * @param offering
090     *            instructional offering which is offered under this course
091     *            offering
092     * @param limit
093     *            course offering limit (-1 for unlimited)
094     * @param projected
095     *            projected demand
096     */
097    public Course(long id, String subjectArea, String courseNumber, Offering offering, int limit, int projected) {
098        iId = id;
099        iSubjectArea = subjectArea;
100        iCourseNumber = courseNumber;
101        iOffering = offering;
102        iOffering.getCourses().add(this);
103        iLimit = limit;
104        iProjected = projected;
105    }
106
107    /** Course offering unique id 
108     * @return coure offering unqiue id
109     **/
110    public long getId() {
111        return iId;
112    }
113
114    /** Subject area 
115     * @return subject area abbreviation
116     **/
117    public String getSubjectArea() {
118        return iSubjectArea;
119    }
120
121    /** Course number 
122     * @return course number
123     **/
124    public String getCourseNumber() {
125        return iCourseNumber;
126    }
127
128    /** Course offering name: subject area + course number 
129     * @return course name
130     **/
131    public String getName() {
132        return iSubjectArea + " " + iCourseNumber;
133    }
134
135    @Override
136    public String toString() {
137        return getName();
138    }
139
140    /** Instructional offering which is offered under this course offering. 
141     * @return instructional offering
142     **/
143    public Offering getOffering() {
144        return iOffering;
145    }
146
147    /** Course offering limit 
148     * @return course offering limit, -1 if unlimited
149     **/
150    public int getLimit() {
151        return iLimit;
152    }
153
154    /** Set course offering limit 
155     * @param limit course offering limit, -1 if unlimited
156     **/
157    public void setLimit(int limit) {
158        iLimit = limit;
159    }
160
161    /** Course offering projected number of students 
162     * @return course projection
163     **/
164    public int getProjected() {
165        return iProjected;
166    }
167    
168    /** Called when an enrollment with this course is assigned to a request 
169     * @param assignment current assignment
170     * @param enrollment assigned enrollment
171     **/
172    public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
173        getContext(assignment).assigned(assignment, enrollment);
174    }
175
176    /** Called when an enrollment with this course is unassigned from a request
177     * @param assignment current assignment
178     * @param enrollment unassigned enrollment
179     */
180    public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
181        getContext(assignment).unassigned(assignment, enrollment);
182    }
183    
184    /** Set of course requests requesting this course 
185     * @return request for this course
186     **/
187    public Set<CourseRequest> getRequests() {
188        return iRequests;
189    }
190    
191    /**
192     * Course note
193     * @return course note
194     */
195    public String getNote() { return iNote; }
196    
197    /**
198     * Course note
199     * @param note course note
200     */
201    public void setNote(String note) { iNote = note; }
202
203    @Override
204    public boolean equals(Object o) {
205        if (o == null || !(o instanceof Course)) return false;
206        return getId() == ((Course)o).getId();
207    }
208    
209    @Override
210    public int hashCode() {
211        return (int) (iId ^ (iId >>> 32));
212    }
213    
214    @Override
215    public Model<Request, Enrollment> getModel() {
216        return getOffering().getModel();
217    }
218    
219    /**
220     * Enrollment weight -- weight of all requests that are enrolled into this course,
221     * excluding the given one. See
222     * {@link Request#getWeight()}.
223     * @param assignment current assignment
224     * @param excludeRequest request to exclude
225     * @return enrollment weight
226     */
227    public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
228        return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
229    }
230    
231    /** Set of assigned enrollments 
232     * @param assignment current assignment
233     * @return assigned enrollments for this course offering
234     **/
235    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
236        return getContext(assignment).getEnrollments();
237    }
238    
239    /**
240     * Maximal weight of a single enrollment in the course
241     * @param assignment current assignment
242     * @return maximal enrollment weight
243     */
244    public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
245        return getContext(assignment).getMaxEnrollmentWeight();
246    }
247
248    /**
249     * Minimal weight of a single enrollment in the course
250     * @param assignment current assignment
251     * @return minimal enrollment weight
252     */
253    public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
254        return getContext(assignment).getMinEnrollmentWeight();
255    }
256    
257    /**
258     * Add request group of this course. This is automatically called 
259     * by the constructor of the {@link RequestGroup}.
260     * @param group request group to be added
261     */
262    public void addRequestGroup(RequestGroup group) {
263        iRequestGroups.add(group);
264    }
265    
266    /**
267     * Remove request group from this course.
268     * @param group request group to be removed
269     */
270    public void removeRequestGroup(RequestGroup group) {
271        iRequestGroups.remove(group);
272    }
273    
274    /**
275     * Lists all the request groups of this course
276     * @return all request groups of this course
277     */
278    public Set<RequestGroup> getRequestGroups() {
279        return iRequestGroups;
280    }
281    
282    /**
283     * Set credit (Online Student Scheduling only)
284     * @param credit scheduling course credit
285     */
286    public void setCredit(String credit) {
287        iCredit = credit;
288        if (iCreditValue == null && credit != null) {
289            int split = credit.indexOf('|');
290            String abbv = null;
291            if (split >= 0) {
292                abbv = credit.substring(0, split);
293            } else {
294                abbv = credit;
295            }
296            Matcher m = Pattern.compile("(^| )(\\d+\\.?\\d*)([,-]?(\\d+\\.?\\d*))?($| )").matcher(abbv);
297            if (m.find())
298                iCreditValue = Float.parseFloat(m.group(2));
299        }
300    }
301    
302    /**
303     * Get credit (Online Student Scheduling only)
304     * @return scheduling course credit
305     */
306    public String getCredit() { return iCredit; }
307    
308    /**
309     * True if this course has a credit value defined
310     * @return true if a credit value is set
311     */
312    public boolean hasCreditValue() { return iCreditValue != null; }
313    
314    /**
315     * Set course credit value (null if not set)
316     * @param creditValue course credit value
317     */
318    public void setCreditValue(Float creditValue) { iCreditValue = creditValue; }
319    
320    /**
321     * Get course credit value (null if not set)
322     * return course credit value
323     */
324    public Float getCreditValue() { return iCreditValue; }
325
326    @Override
327    public CourseContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
328        return new CourseContext(assignment);
329    }
330    
331
332    @Override
333    public CourseContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, CourseContext parentContext) {
334        return new CourseContext(parentContext);
335    }
336    
337    public class CourseContext implements AssignmentConstraintContext<Request, Enrollment> {
338        private double iEnrollmentWeight = 0.0;
339        private Set<Enrollment> iEnrollments = null;
340        private double iMaxEnrollmentWeight = 0.0;
341        private double iMinEnrollmentWeight = 0.0;
342        private boolean iReadOnly = false;
343
344        public CourseContext(Assignment<Request, Enrollment> assignment) {
345            iEnrollments = new HashSet<Enrollment>();
346            for (CourseRequest request: getRequests()) {
347                Enrollment enrollment = assignment.getValue(request);
348                if (enrollment != null && Course.this.equals(enrollment.getCourse()))
349                    assigned(assignment, enrollment);
350            }
351        }
352        
353        public CourseContext(CourseContext parent) {
354            iEnrollmentWeight = parent.iEnrollmentWeight;
355            iMinEnrollmentWeight = parent.iMinEnrollmentWeight;
356            iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight;
357            iEnrollments = parent.iEnrollments;
358            iReadOnly = true;
359        }
360
361        @Override
362        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
363            if (iReadOnly) {
364                iEnrollments = new HashSet<Enrollment>(iEnrollments);
365                iReadOnly = false;
366            }
367            if (iEnrollments.isEmpty()) {
368                iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
369            } else {
370                iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
371                iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
372            }
373            if (iEnrollments.add(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
374                iEnrollmentWeight += enrollment.getRequest().getWeight();
375        }
376
377        @Override
378        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
379            if (iReadOnly) {
380                iEnrollments = new HashSet<Enrollment>(iEnrollments);
381                iReadOnly = false;
382            }
383            if (iEnrollments.remove(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
384                iEnrollmentWeight -= enrollment.getRequest().getWeight();
385            if (iEnrollments.isEmpty()) {
386                iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
387            } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) {
388                if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) {
389                    double newMinEnrollmentWeight = Double.MAX_VALUE;
390                    for (Enrollment e : iEnrollments) {
391                        if (e.getRequest().getWeight() == iMinEnrollmentWeight) {
392                            newMinEnrollmentWeight = iMinEnrollmentWeight;
393                            break;
394                        } else {
395                            newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
396                        }
397                    }
398                    iMinEnrollmentWeight = newMinEnrollmentWeight;
399                }
400                if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) {
401                    double newMaxEnrollmentWeight = Double.MIN_VALUE;
402                    for (Enrollment e : iEnrollments) {
403                        if (e.getRequest().getWeight() == iMaxEnrollmentWeight) {
404                            newMaxEnrollmentWeight = iMaxEnrollmentWeight;
405                            break;
406                        } else {
407                            newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
408                        }
409                    }
410                    iMaxEnrollmentWeight = newMaxEnrollmentWeight;
411                }
412            }
413        }
414        
415        /**
416         * Enrollment weight -- weight of all requests that are enrolled into this course,
417         * excluding the given one. See
418         * {@link Request#getWeight()}.
419         * @param assignment current assignment
420         * @param excludeRequest request to exclude
421         * @return enrollment weight
422         */
423        public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
424            double weight = iEnrollmentWeight;
425            if (excludeRequest != null) {
426                Enrollment enrollment = assignment.getValue(excludeRequest);
427                if (enrollment!= null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
428                    weight -= excludeRequest.getWeight();
429            }
430            return weight;
431        }
432        
433        /** Set of assigned enrollments 
434         * @return assigned enrollments for this course offering
435         **/
436        public Set<Enrollment> getEnrollments() {
437            return iEnrollments;
438        }
439        
440        /**
441         * Maximal weight of a single enrollment in the course
442         * @return maximal enrollment weight
443         */
444        public double getMaxEnrollmentWeight() {
445            return iMaxEnrollmentWeight;
446        }
447
448        /**
449         * Minimal weight of a single enrollment in the course
450         * @return minimal enrollment weight
451         */
452        public double getMinEnrollmentWeight() {
453            return iMinEnrollmentWeight;
454        }
455    }
456    
457    public double getOnlineBound() {
458        double bound = 1.0; 
459        for (Config config: getOffering().getConfigs()) {
460            int online = config.getNrOnline();
461            if (online == 0) return 0.0;
462            double factor = ((double)online) / config.getSubparts().size();
463            if (factor < bound) bound = factor;
464        }
465        return bound;
466    }
467    
468    public double getArrHrsBound() {
469        double bound = 1.0; 
470        for (Config config: getOffering().getConfigs()) {
471            int arrHrs = config.getNrArrHours();
472            if (arrHrs == 0) return 0.0;
473            double factor = ((double)arrHrs) / config.getSubparts().size();
474            if (factor < bound) bound = factor;
475        }
476        return bound;
477    }
478    
479    public double getPastBound() {
480        double bound = 1.0; 
481        for (Config config: getOffering().getConfigs()) {
482            int past = config.getNrPast();
483            if (past == 0) return 0.0;
484            double factor = ((double)past) / config.getSubparts().size();
485            if (factor < bound) bound = factor;
486        }
487        return bound;
488    }
489    
490    /**
491     * Course title
492     */
493    public String getTitle() {
494        return iTitle;
495    }
496
497    /**
498     * Course title
499     */
500    public void setTitle(String title) {
501        iTitle = title;
502    }
503    
504    /**
505     * Course type
506     */
507    public String getType() {
508        return iType;
509    }
510
511    /**
512     * Course type
513     */
514    public void setType(String type) {
515        iType = type;
516    }
517}