001package org.cpsolver.studentsct.model;
002
003import java.util.Collections;
004import java.util.HashSet;
005import java.util.Set;
006
007import org.cpsolver.ifs.assignment.Assignment;
008import org.cpsolver.ifs.assignment.context.AbstractClassWithContext;
009import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
010import org.cpsolver.ifs.assignment.context.CanInheritContext;
011import org.cpsolver.ifs.model.Model;
012
013
014/**
015 * Representation of a course offering. A course offering contains id, subject
016 * area, course number and an instructional offering. <br>
017 * <br>
018 * Each instructional offering (see {@link Offering}) is offered under one or
019 * more course offerings.
020 * 
021 * <br>
022 * <br>
023 * 
024 * @version StudentSct 1.3 (Student Sectioning)<br>
025 *          Copyright (C) 2007 - 2014 Tomas Muller<br>
026 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
027 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
028 * <br>
029 *          This library is free software; you can redistribute it and/or modify
030 *          it under the terms of the GNU Lesser General Public License as
031 *          published by the Free Software Foundation; either version 3 of the
032 *          License, or (at your option) any later version. <br>
033 * <br>
034 *          This library is distributed in the hope that it will be useful, but
035 *          WITHOUT ANY WARRANTY; without even the implied warranty of
036 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
037 *          Lesser General Public License for more details. <br>
038 * <br>
039 *          You should have received a copy of the GNU Lesser General Public
040 *          License along with this library; if not see
041 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
042 */
043public class Course extends AbstractClassWithContext<Request, Enrollment, Course.CourseContext> implements CanInheritContext<Request, Enrollment, Course.CourseContext> {
044    private long iId = -1;
045    private String iSubjectArea = null;
046    private String iCourseNumber = null;
047    private Offering iOffering = null;
048    private int iLimit = 0, iProjected = 0;
049    private Set<CourseRequest> iRequests = Collections.synchronizedSet(new HashSet<CourseRequest>());
050    private String iNote = null;
051    private Set<RequestGroup> iRequestGroups = new HashSet<RequestGroup>();
052
053    /**
054     * Constructor
055     * 
056     * @param id
057     *            course offering unique id
058     * @param subjectArea
059     *            subject area (e.g., MA, CS, ENGL)
060     * @param courseNumber
061     *            course number under the given subject area
062     * @param offering
063     *            instructional offering which is offered under this course
064     *            offering
065     */
066    public Course(long id, String subjectArea, String courseNumber, Offering offering) {
067        iId = id;
068        iSubjectArea = subjectArea;
069        iCourseNumber = courseNumber;
070        iOffering = offering;
071        iOffering.getCourses().add(this);
072    }
073
074    /**
075     * Constructor
076     * 
077     * @param id
078     *            course offering unique id
079     * @param subjectArea
080     *            subject area (e.g., MA, CS, ENGL)
081     * @param courseNumber
082     *            course number under the given subject area
083     * @param offering
084     *            instructional offering which is offered under this course
085     *            offering
086     * @param limit
087     *            course offering limit (-1 for unlimited)
088     * @param projected
089     *            projected demand
090     */
091    public Course(long id, String subjectArea, String courseNumber, Offering offering, int limit, int projected) {
092        iId = id;
093        iSubjectArea = subjectArea;
094        iCourseNumber = courseNumber;
095        iOffering = offering;
096        iOffering.getCourses().add(this);
097        iLimit = limit;
098        iProjected = projected;
099    }
100
101    /** Course offering unique id 
102     * @return coure offering unqiue id
103     **/
104    public long getId() {
105        return iId;
106    }
107
108    /** Subject area 
109     * @return subject area abbreviation
110     **/
111    public String getSubjectArea() {
112        return iSubjectArea;
113    }
114
115    /** Course number 
116     * @return course number
117     **/
118    public String getCourseNumber() {
119        return iCourseNumber;
120    }
121
122    /** Course offering name: subject area + course number 
123     * @return course name
124     **/
125    public String getName() {
126        return iSubjectArea + " " + iCourseNumber;
127    }
128
129    @Override
130    public String toString() {
131        return getName();
132    }
133
134    /** Instructional offering which is offered under this course offering. 
135     * @return instructional offering
136     **/
137    public Offering getOffering() {
138        return iOffering;
139    }
140
141    /** Course offering limit 
142     * @return course offering limit, -1 if unlimited
143     **/
144    public int getLimit() {
145        return iLimit;
146    }
147
148    /** Set course offering limit 
149     * @param limit course offering limit, -1 if unlimited
150     **/
151    public void setLimit(int limit) {
152        iLimit = limit;
153    }
154
155    /** Course offering projected number of students 
156     * @return course projection
157     **/
158    public int getProjected() {
159        return iProjected;
160    }
161    
162    /** Called when an enrollment with this course is assigned to a request 
163     * @param assignment current assignment
164     * @param enrollment assigned enrollment
165     **/
166    public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
167        getContext(assignment).assigned(assignment, enrollment);
168    }
169
170    /** Called when an enrollment with this course is unassigned from a request
171     * @param assignment current assignment
172     * @param enrollment unassigned enrollment
173     */
174    public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
175        getContext(assignment).unassigned(assignment, enrollment);
176    }
177    
178    /** Set of course requests requesting this course 
179     * @return request for this course
180     **/
181    public Set<CourseRequest> getRequests() {
182        return iRequests;
183    }
184    
185    /**
186     * Course note
187     * @return course note
188     */
189    public String getNote() { return iNote; }
190    
191    /**
192     * Course note
193     * @param note course note
194     */
195    public void setNote(String note) { iNote = note; }
196
197    @Override
198    public boolean equals(Object o) {
199        if (o == null || !(o instanceof Course)) return false;
200        return getId() == ((Course)o).getId();
201    }
202    
203    @Override
204    public int hashCode() {
205        return (int) (iId ^ (iId >>> 32));
206    }
207    
208    @Override
209    public Model<Request, Enrollment> getModel() {
210        return getOffering().getModel();
211    }
212    
213    /**
214     * Enrollment weight -- weight of all requests that are enrolled into this course,
215     * excluding the given one. See
216     * {@link Request#getWeight()}.
217     * @param assignment current assignment
218     * @param excludeRequest request to exclude
219     * @return enrollment weight
220     */
221    public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
222        return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
223    }
224    
225    /** Set of assigned enrollments 
226     * @param assignment current assignment
227     * @return assigned enrollments for this course offering
228     **/
229    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
230        return getContext(assignment).getEnrollments();
231    }
232    
233    /**
234     * Maximal weight of a single enrollment in the course
235     * @param assignment current assignment
236     * @return maximal enrollment weight
237     */
238    public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
239        return getContext(assignment).getMaxEnrollmentWeight();
240    }
241
242    /**
243     * Minimal weight of a single enrollment in the course
244     * @param assignment current assignment
245     * @return minimal enrollment weight
246     */
247    public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
248        return getContext(assignment).getMinEnrollmentWeight();
249    }
250    
251    /**
252     * Add request group of this course. This is automatically called 
253     * by the constructor of the {@link RequestGroup}.
254     * @param group request group to be added
255     */
256    public void addRequestGroup(RequestGroup group) {
257        iRequestGroups.add(group);
258    }
259    
260    /**
261     * Remove request group from this course.
262     * @param group request group to be removed
263     */
264    public void removeRequestGroup(RequestGroup group) {
265        iRequestGroups.remove(group);
266    }
267    
268    /**
269     * Lists all the request groups of this course
270     * @return all request groups of this course
271     */
272    public Set<RequestGroup> getRequestGroups() {
273        return iRequestGroups;
274    }
275
276    @Override
277    public CourseContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
278        return new CourseContext(assignment);
279    }
280    
281
282    @Override
283    public CourseContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, CourseContext parentContext) {
284        return new CourseContext(parentContext);
285    }
286    
287    public class CourseContext implements AssignmentConstraintContext<Request, Enrollment> {
288        private double iEnrollmentWeight = 0.0;
289        private Set<Enrollment> iEnrollments = null;
290        private double iMaxEnrollmentWeight = 0.0;
291        private double iMinEnrollmentWeight = 0.0;
292        private boolean iReadOnly = false;
293
294        public CourseContext(Assignment<Request, Enrollment> assignment) {
295            iEnrollments = new HashSet<Enrollment>();
296            for (CourseRequest request: getRequests()) {
297                Enrollment enrollment = assignment.getValue(request);
298                if (enrollment != null && Course.this.equals(enrollment.getCourse()))
299                    assigned(assignment, enrollment);
300            }
301        }
302        
303        public CourseContext(CourseContext parent) {
304            iEnrollmentWeight = parent.iEnrollmentWeight;
305            iMinEnrollmentWeight = parent.iMinEnrollmentWeight;
306            iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight;
307            iEnrollments = parent.iEnrollments;
308            iReadOnly = true;
309        }
310
311        @Override
312        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
313            if (iReadOnly) {
314                iEnrollments = new HashSet<Enrollment>(iEnrollments);
315                iReadOnly = false;
316            }
317            if (iEnrollments.isEmpty()) {
318                iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
319            } else {
320                iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
321                iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
322            }
323            if (iEnrollments.add(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
324                iEnrollmentWeight += enrollment.getRequest().getWeight();
325        }
326
327        @Override
328        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
329            if (iReadOnly) {
330                iEnrollments = new HashSet<Enrollment>(iEnrollments);
331                iReadOnly = false;
332            }
333            if (iEnrollments.remove(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
334                iEnrollmentWeight -= enrollment.getRequest().getWeight();
335            if (iEnrollments.isEmpty()) {
336                iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
337            } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) {
338                if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) {
339                    double newMinEnrollmentWeight = Double.MAX_VALUE;
340                    for (Enrollment e : iEnrollments) {
341                        if (e.getRequest().getWeight() == iMinEnrollmentWeight) {
342                            newMinEnrollmentWeight = iMinEnrollmentWeight;
343                            break;
344                        } else {
345                            newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
346                        }
347                    }
348                    iMinEnrollmentWeight = newMinEnrollmentWeight;
349                }
350                if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) {
351                    double newMaxEnrollmentWeight = Double.MIN_VALUE;
352                    for (Enrollment e : iEnrollments) {
353                        if (e.getRequest().getWeight() == iMaxEnrollmentWeight) {
354                            newMaxEnrollmentWeight = iMaxEnrollmentWeight;
355                            break;
356                        } else {
357                            newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
358                        }
359                    }
360                    iMaxEnrollmentWeight = newMaxEnrollmentWeight;
361                }
362            }
363        }
364        
365        /**
366         * Enrollment weight -- weight of all requests that are enrolled into this course,
367         * excluding the given one. See
368         * {@link Request#getWeight()}.
369         * @param assignment current assignment
370         * @param excludeRequest request to exclude
371         * @return enrollment weight
372         */
373        public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
374            double weight = iEnrollmentWeight;
375            if (excludeRequest != null) {
376                Enrollment enrollment = assignment.getValue(excludeRequest);
377                if (enrollment!= null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
378                    weight -= excludeRequest.getWeight();
379            }
380            return weight;
381        }
382        
383        /** Set of assigned enrollments 
384         * @return assigned enrollments for this course offering
385         **/
386        public Set<Enrollment> getEnrollments() {
387            return iEnrollments;
388        }
389        
390        /**
391         * Maximal weight of a single enrollment in the course
392         * @return maximal enrollment weight
393         */
394        public double getMaxEnrollmentWeight() {
395            return iMaxEnrollmentWeight;
396        }
397
398        /**
399         * Minimal weight of a single enrollment in the course
400         * @return minimal enrollment weight
401         */
402        public double getMinEnrollmentWeight() {
403            return iMinEnrollmentWeight;
404        }
405    }
406}