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