001package org.cpsolver.studentsct.model;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.List;
006import java.util.Set;
007
008import org.cpsolver.ifs.assignment.Assignment;
009import org.cpsolver.ifs.model.Model;
010import org.cpsolver.studentsct.reservation.Reservation;
011import org.cpsolver.studentsct.reservation.Restriction;
012
013
014
015/**
016 * Representation of an instructional offering. An offering contains id, name,
017 * the list of course offerings, and the list of possible configurations. See
018 * {@link Config} and {@link Course}.
019 * 
020 * <br>
021 * <br>
022 * 
023 * @version StudentSct 1.3 (Student Sectioning)<br>
024 *          Copyright (C) 2007 - 2014 Tomáš Müller<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 Offering {
043    private long iId = -1;
044    private String iName = null;
045    private Model<Request, Enrollment> iModel = null;
046    private List<Config> iConfigs = new ArrayList<Config>();
047    private List<Course> iCourses = new ArrayList<Course>();
048    private List<Reservation> iReservations = new ArrayList<Reservation>();
049    private List<Restriction> iRestrictions = new ArrayList<Restriction>();
050    private boolean iDummy = false;
051
052    /**
053     * Constructor
054     * 
055     * @param id
056     *            instructional offering unique id
057     * @param name
058     *            instructional offering name (this is usually the name of the
059     *            controlling course)
060     */
061    public Offering(long id, String name) {
062        iId = id;
063        iName = name;
064    }
065
066    /** Offering id 
067     * @return instructional offering unique id
068     **/
069    public long getId() {
070        return iId;
071    }
072
073    /** Offering name 
074     * @return instructional offering name
075     **/
076    public String getName() {
077        return iName;
078    }
079
080    /** Possible configurations 
081     * @return instructional offering configurations
082     **/
083    public List<Config> getConfigs() {
084        return iConfigs;
085    }
086
087    /**
088     * List of courses. One instructional offering can contain multiple courses
089     * (names under which it is offered)
090     * @return list of course offerings
091     */
092    public List<Course> getCourses() {
093        return iCourses;
094    }
095
096    /**
097     * Return section of the given id, if it is part of one of this offering
098     * configurations.
099     * @param sectionId class unique id
100     * @return section of the given id
101     */
102    public Section getSection(long sectionId) {
103        for (Config config : getConfigs()) {
104            for (Subpart subpart : config.getSubparts()) {
105                for (Section section : subpart.getSections()) {
106                    if (section.getId() == sectionId)
107                        return section;
108                }
109            }
110        }
111        return null;
112    }
113
114    /** Return course, under which the given student enrolls into this offering. 
115     * @param student given student
116     * @return course of this offering requested by the student
117     **/
118    public Course getCourse(Student student) {
119        if (getCourses().isEmpty())
120            return null;
121        if (getCourses().size() == 1)
122            return getCourses().get(0);
123        for (Request request : student.getRequests()) {
124            if (request instanceof CourseRequest) {
125                for (Course course : ((CourseRequest) request).getCourses()) {
126                    if (getCourses().contains(course))
127                        return course;
128                }
129            }
130        }
131        return getCourses().get(0);
132    }
133
134    /** Return set of instructional types, union over all configurations. 
135     * @return set of instructional types
136     **/
137    public Set<String> getInstructionalTypes() {
138        Set<String> instructionalTypes = new HashSet<String>();
139        for (Config config : getConfigs()) {
140            for (Subpart subpart : config.getSubparts()) {
141                instructionalTypes.add(subpart.getInstructionalType());
142            }
143        }
144        return instructionalTypes;
145    }
146
147    /**
148     * Return the list of all possible choices of the given instructional type
149     * for this offering.
150     * @param instructionalType instructional type
151     * @return set of choices of the given instructional type
152     */
153    public Set<Choice> getChoices(String instructionalType) {
154        Set<Choice> choices = new HashSet<Choice>();
155        for (Config config : getConfigs()) {
156            for (Subpart subpart : config.getSubparts()) {
157                if (!instructionalType.equals(subpart.getInstructionalType()))
158                    continue;
159                choices.addAll(subpart.getChoices());
160            }
161        }
162        return choices;
163    }
164
165    /**
166     * Return list of all subparts of the given isntructional type for this
167     * offering.
168     * @param instructionalType instructional type
169     * @return subpart of the given instructional type
170     */
171    public Set<Subpart> getSubparts(String instructionalType) {
172        Set<Subpart> subparts = new HashSet<Subpart>();
173        for (Config config : getConfigs()) {
174            for (Subpart subpart : config.getSubparts()) {
175                if (instructionalType.equals(subpart.getInstructionalType()))
176                    subparts.add(subpart);
177            }
178        }
179        return subparts;
180    }
181
182    /** Minimal penalty from {@link Config#getMinPenalty()} 
183     * @return minimal penalty
184     **/
185    public double getMinPenalty() {
186        double min = Double.MAX_VALUE;
187        for (Config config : getConfigs()) {
188            min = Math.min(min, config.getMinPenalty());
189        }
190        return (min == Double.MAX_VALUE ? 0.0 : min);
191    }
192
193    /** Maximal penalty from {@link Config#getMaxPenalty()} 
194     * @return maximal penalty
195     **/
196    public double getMaxPenalty() {
197        double max = Double.MIN_VALUE;
198        for (Config config : getConfigs()) {
199            max = Math.max(max, config.getMaxPenalty());
200        }
201        return (max == Double.MIN_VALUE ? 0.0 : max);
202    }
203
204    @Override
205    public String toString() {
206        return iName;
207    }
208    
209    /** Reservations associated with this offering 
210     * @return reservations for this offering
211     **/
212    public List<Reservation> getReservations() { return iReservations; }
213    
214    /** True if there are reservations for this offering 
215     * @return true if there is at least one reservation
216     **/
217    public boolean hasReservations() { return !iReservations.isEmpty(); }
218    
219    /** Restrictions associated with this offering 
220     * @return restrictions for this offering
221     **/
222    public List<Restriction> getRestrictions() { return iRestrictions; }
223    
224    /** True if there are restrictions for this offering 
225     * @return true if there is at least one restriction
226     **/
227    public boolean hasRestrictions() { return !iRestrictions.isEmpty(); }
228    
229    /**
230     * Total space in the offering that is not reserved by any reservation 
231     * @return total unreserved space in the offering
232     **/
233    public synchronized double getTotalUnreservedSpace() {
234        if (iTotalUnreservedSpace == null)
235            iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
236        return iTotalUnreservedSpace;
237    }
238    Double iTotalUnreservedSpace = null;
239    private double getTotalUnreservedSpaceNoCache() {
240        // compute overall available space
241        double available = 0.0;
242        for (Config config: getConfigs()) {
243            available += config.getLimit();
244            // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too 
245            // (in which case there is no unreserved space)
246            if (config.getLimit() < 0) {
247                for (Reservation r: getReservations()) {
248                    // ignore expired reservations
249                    if (r.isExpired()) continue;
250                    // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
251                    if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
252                    // there is an unlimited reservation -> no unreserved space
253                    if (r.getLimit(config) < 0) return 0.0;
254                }
255                return Double.MAX_VALUE;
256            }
257        }
258        
259        // compute maximal reserved space (out of the available space)
260        double reserved = 0;
261        for (Reservation r: getReservations()) {
262            // ignore expired reservations
263            if (r.isExpired()) continue;
264            // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
265            if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
266            // unlimited reservation -> no unreserved space
267            if (r.getLimit() < 0) return 0.0;
268            reserved += r.getLimit();
269        }
270        
271        return Math.max(0.0, available - reserved);
272    }
273
274    /**
275     * Available space in the offering that is not reserved by any reservation 
276     * @param assignment current request
277     * @param excludeRequest excluding given request (if not null)
278     * @return remaining unreserved space in the offering
279     **/
280    public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) {
281        // compute available space
282        double available = 0.0;
283        for (Config config: getConfigs()) {
284            available += config.getLimit() - config.getContext(assignment).getEnrollmentWeight(assignment, excludeRequest);
285            // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too 
286            // (in which case there is no unreserved space)
287            if (config.getLimit() < 0) {
288                for (Reservation r: getReservations()) {
289                    // ignore expired reservations
290                    if (r.isExpired()) continue;
291                    // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
292                    if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
293                    // there is an unlimited reservation -> no unreserved space
294                    if (r.getLimit(config) < 0) return 0.0;
295                }
296                return Double.MAX_VALUE;
297            }
298        }
299        
300        // compute reserved space (out of the available space)
301        double reserved = 0;
302        for (Reservation r: getReservations()) {
303            // ignore expired reservations
304            if (r.isExpired()) continue;
305            // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs)
306            if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue;
307            // unlimited reservation -> no unreserved space
308            if (r.getLimit() < 0) return 0.0;
309            reserved += Math.max(0.0, r.getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest));
310        }
311        
312        return available - reserved;
313    }
314
315    
316    /**
317     * Clear reservation information that was cached on this offering or below
318     */
319    public synchronized void clearReservationCache() {
320        for (Config c: getConfigs())
321            c.clearReservationCache();
322        for (Course c: getCourses())
323            for (CourseRequest r: c.getRequests())
324                r.clearReservationCache();
325        iTotalUnreservedSpace = null;
326    }
327    
328    /**
329     * Clear restriction information that was cached on this offering or below
330     */
331    public synchronized void clearRestrictionCache() {
332        for (Course c: getCourses())
333            for (CourseRequest r: c.getRequests())
334                r.clearRestrictionCache();
335    }
336
337    @Override
338    public boolean equals(Object o) {
339        if (o == null || !(o instanceof Offering)) return false;
340        return getId() == ((Offering)o).getId();
341    }
342    
343    @Override
344    public int hashCode() {
345        return (int) (iId ^ (iId >>> 32));
346    }
347    
348    public Model<Request, Enrollment> getModel() { return iModel; }
349    public void setModel(Model<Request, Enrollment> model) { iModel = model; }
350    
351    /**
352     * Dummy courses that should show on the solver dashboard
353     * (e.g., because they are loaded due to an other session unavailability)
354     * @return true if the course is "dummy"
355     */
356    public boolean isDummy() { return iDummy; }
357    
358    /**
359     * Dummy courses that should show on the solver dashboard
360     * (e.g., because they are loaded due to an other session unavailability)
361    */
362    public void setDummy(boolean dummy) { iDummy = dummy; }
363}