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