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    private String iCredit = null;
053
054    /**
055     * Constructor
056     * 
057     * @param id
058     *            course offering unique id
059     * @param subjectArea
060     *            subject area (e.g., MA, CS, ENGL)
061     * @param courseNumber
062     *            course number under the given subject area
063     * @param offering
064     *            instructional offering which is offered under this course
065     *            offering
066     */
067    public Course(long id, String subjectArea, String courseNumber, Offering offering) {
068        iId = id;
069        iSubjectArea = subjectArea;
070        iCourseNumber = courseNumber;
071        iOffering = offering;
072        iOffering.getCourses().add(this);
073    }
074
075    /**
076     * Constructor
077     * 
078     * @param id
079     *            course offering unique id
080     * @param subjectArea
081     *            subject area (e.g., MA, CS, ENGL)
082     * @param courseNumber
083     *            course number under the given subject area
084     * @param offering
085     *            instructional offering which is offered under this course
086     *            offering
087     * @param limit
088     *            course offering limit (-1 for unlimited)
089     * @param projected
090     *            projected demand
091     */
092    public Course(long id, String subjectArea, String courseNumber, Offering offering, int limit, int projected) {
093        iId = id;
094        iSubjectArea = subjectArea;
095        iCourseNumber = courseNumber;
096        iOffering = offering;
097        iOffering.getCourses().add(this);
098        iLimit = limit;
099        iProjected = projected;
100    }
101
102    /** Course offering unique id 
103     * @return coure offering unqiue id
104     **/
105    public long getId() {
106        return iId;
107    }
108
109    /** Subject area 
110     * @return subject area abbreviation
111     **/
112    public String getSubjectArea() {
113        return iSubjectArea;
114    }
115
116    /** Course number 
117     * @return course number
118     **/
119    public String getCourseNumber() {
120        return iCourseNumber;
121    }
122
123    /** Course offering name: subject area + course number 
124     * @return course name
125     **/
126    public String getName() {
127        return iSubjectArea + " " + iCourseNumber;
128    }
129
130    @Override
131    public String toString() {
132        return getName();
133    }
134
135    /** Instructional offering which is offered under this course offering. 
136     * @return instructional offering
137     **/
138    public Offering getOffering() {
139        return iOffering;
140    }
141
142    /** Course offering limit 
143     * @return course offering limit, -1 if unlimited
144     **/
145    public int getLimit() {
146        return iLimit;
147    }
148
149    /** Set course offering limit 
150     * @param limit course offering limit, -1 if unlimited
151     **/
152    public void setLimit(int limit) {
153        iLimit = limit;
154    }
155
156    /** Course offering projected number of students 
157     * @return course projection
158     **/
159    public int getProjected() {
160        return iProjected;
161    }
162    
163    /** Called when an enrollment with this course is assigned to a request 
164     * @param assignment current assignment
165     * @param enrollment assigned enrollment
166     **/
167    public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
168        getContext(assignment).assigned(assignment, enrollment);
169    }
170
171    /** Called when an enrollment with this course is unassigned from a request
172     * @param assignment current assignment
173     * @param enrollment unassigned enrollment
174     */
175    public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
176        getContext(assignment).unassigned(assignment, enrollment);
177    }
178    
179    /** Set of course requests requesting this course 
180     * @return request for this course
181     **/
182    public Set<CourseRequest> getRequests() {
183        return iRequests;
184    }
185    
186    /**
187     * Course note
188     * @return course note
189     */
190    public String getNote() { return iNote; }
191    
192    /**
193     * Course note
194     * @param note course note
195     */
196    public void setNote(String note) { iNote = note; }
197
198    @Override
199    public boolean equals(Object o) {
200        if (o == null || !(o instanceof Course)) return false;
201        return getId() == ((Course)o).getId();
202    }
203    
204    @Override
205    public int hashCode() {
206        return (int) (iId ^ (iId >>> 32));
207    }
208    
209    @Override
210    public Model<Request, Enrollment> getModel() {
211        return getOffering().getModel();
212    }
213    
214    /**
215     * Enrollment weight -- weight of all requests that are enrolled into this course,
216     * excluding the given one. See
217     * {@link Request#getWeight()}.
218     * @param assignment current assignment
219     * @param excludeRequest request to exclude
220     * @return enrollment weight
221     */
222    public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
223        return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
224    }
225    
226    /** Set of assigned enrollments 
227     * @param assignment current assignment
228     * @return assigned enrollments for this course offering
229     **/
230    public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) {
231        return getContext(assignment).getEnrollments();
232    }
233    
234    /**
235     * Maximal weight of a single enrollment in the course
236     * @param assignment current assignment
237     * @return maximal enrollment weight
238     */
239    public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
240        return getContext(assignment).getMaxEnrollmentWeight();
241    }
242
243    /**
244     * Minimal weight of a single enrollment in the course
245     * @param assignment current assignment
246     * @return minimal enrollment weight
247     */
248    public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) {
249        return getContext(assignment).getMinEnrollmentWeight();
250    }
251    
252    /**
253     * Add request group of this course. This is automatically called 
254     * by the constructor of the {@link RequestGroup}.
255     * @param group request group to be added
256     */
257    public void addRequestGroup(RequestGroup group) {
258        iRequestGroups.add(group);
259    }
260    
261    /**
262     * Remove request group from this course.
263     * @param group request group to be removed
264     */
265    public void removeRequestGroup(RequestGroup group) {
266        iRequestGroups.remove(group);
267    }
268    
269    /**
270     * Lists all the request groups of this course
271     * @return all request groups of this course
272     */
273    public Set<RequestGroup> getRequestGroups() {
274        return iRequestGroups;
275    }
276    
277    /**
278     * Set credit (Online Student Scheduling only)
279     * @param credit scheduling subpart credit
280     */
281    public void setCredit(String credit) { iCredit = credit; }
282    
283    /**
284     * Get credit (Online Student Scheduling only)
285     * @return scheduling subpart credit
286     */
287    public String getCredit() { return iCredit; }
288
289    @Override
290    public CourseContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
291        return new CourseContext(assignment);
292    }
293    
294
295    @Override
296    public CourseContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, CourseContext parentContext) {
297        return new CourseContext(parentContext);
298    }
299    
300    public class CourseContext implements AssignmentConstraintContext<Request, Enrollment> {
301        private double iEnrollmentWeight = 0.0;
302        private Set<Enrollment> iEnrollments = null;
303        private double iMaxEnrollmentWeight = 0.0;
304        private double iMinEnrollmentWeight = 0.0;
305        private boolean iReadOnly = false;
306
307        public CourseContext(Assignment<Request, Enrollment> assignment) {
308            iEnrollments = new HashSet<Enrollment>();
309            for (CourseRequest request: getRequests()) {
310                Enrollment enrollment = assignment.getValue(request);
311                if (enrollment != null && Course.this.equals(enrollment.getCourse()))
312                    assigned(assignment, enrollment);
313            }
314        }
315        
316        public CourseContext(CourseContext parent) {
317            iEnrollmentWeight = parent.iEnrollmentWeight;
318            iMinEnrollmentWeight = parent.iMinEnrollmentWeight;
319            iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight;
320            iEnrollments = parent.iEnrollments;
321            iReadOnly = true;
322        }
323
324        @Override
325        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
326            if (iReadOnly) {
327                iEnrollments = new HashSet<Enrollment>(iEnrollments);
328                iReadOnly = false;
329            }
330            if (iEnrollments.isEmpty()) {
331                iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
332            } else {
333                iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
334                iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
335            }
336            if (iEnrollments.add(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
337                iEnrollmentWeight += enrollment.getRequest().getWeight();
338        }
339
340        @Override
341        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
342            if (iReadOnly) {
343                iEnrollments = new HashSet<Enrollment>(iEnrollments);
344                iReadOnly = false;
345            }
346            if (iEnrollments.remove(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
347                iEnrollmentWeight -= enrollment.getRequest().getWeight();
348            if (iEnrollments.isEmpty()) {
349                iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
350            } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) {
351                if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) {
352                    double newMinEnrollmentWeight = Double.MAX_VALUE;
353                    for (Enrollment e : iEnrollments) {
354                        if (e.getRequest().getWeight() == iMinEnrollmentWeight) {
355                            newMinEnrollmentWeight = iMinEnrollmentWeight;
356                            break;
357                        } else {
358                            newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
359                        }
360                    }
361                    iMinEnrollmentWeight = newMinEnrollmentWeight;
362                }
363                if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) {
364                    double newMaxEnrollmentWeight = Double.MIN_VALUE;
365                    for (Enrollment e : iEnrollments) {
366                        if (e.getRequest().getWeight() == iMaxEnrollmentWeight) {
367                            newMaxEnrollmentWeight = iMaxEnrollmentWeight;
368                            break;
369                        } else {
370                            newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
371                        }
372                    }
373                    iMaxEnrollmentWeight = newMaxEnrollmentWeight;
374                }
375            }
376        }
377        
378        /**
379         * Enrollment weight -- weight of all requests that are enrolled into this course,
380         * excluding the given one. See
381         * {@link Request#getWeight()}.
382         * @param assignment current assignment
383         * @param excludeRequest request to exclude
384         * @return enrollment weight
385         */
386        public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
387            double weight = iEnrollmentWeight;
388            if (excludeRequest != null) {
389                Enrollment enrollment = assignment.getValue(excludeRequest);
390                if (enrollment!= null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()))
391                    weight -= excludeRequest.getWeight();
392            }
393            return weight;
394        }
395        
396        /** Set of assigned enrollments 
397         * @return assigned enrollments for this course offering
398         **/
399        public Set<Enrollment> getEnrollments() {
400            return iEnrollments;
401        }
402        
403        /**
404         * Maximal weight of a single enrollment in the course
405         * @return maximal enrollment weight
406         */
407        public double getMaxEnrollmentWeight() {
408            return iMaxEnrollmentWeight;
409        }
410
411        /**
412         * Minimal weight of a single enrollment in the course
413         * @return minimal enrollment weight
414         */
415        public double getMinEnrollmentWeight() {
416            return iMinEnrollmentWeight;
417        }
418    }
419}