001package net.sf.cpsolver.studentsct.model;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.List;
006import java.util.Set;
007
008import net.sf.cpsolver.studentsct.reservation.Reservation;
009
010
011
012/**
013 * Representation of a configuration of an offering. A configuration contains
014 * id, name, an offering and a list of subparts. <br>
015 * <br>
016 * Each instructional offering (see {@link Offering}) contains one or more
017 * configurations. Each configuration contain one or more subparts. Each student
018 * has to take a class of each subpart of one of the possible configurations.
019 * 
020 * <br>
021 * <br>
022 * 
023 * @version StudentSct 1.2 (Student Sectioning)<br>
024 *          Copyright (C) 2007 - 2010 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 Config {
043    private long iId = -1;
044    private String iName = null;
045    private Offering iOffering = null;
046    private int iLimit = -1;
047    private List<Subpart> iSubparts = new ArrayList<Subpart>();
048    private double iEnrollmentWeight = 0.0;
049    private double iMaxEnrollmentWeight = 0.0;
050    private double iMinEnrollmentWeight = 0.0;
051    private Set<Enrollment> iEnrollments = new HashSet<Enrollment>();
052
053    /**
054     * Constructor
055     * 
056     * @param id
057     *            instructional offering configuration unique id
058     * @param limit
059     *            configuration limit (-1 for unlimited)
060     * @param name
061     *            configuration name
062     * @param offering
063     *            instructional offering to which this configuration belongs
064     */
065    public Config(long id, int limit, String name, Offering offering) {
066        iId = id;
067        iLimit = limit;
068        iName = name;
069        iOffering = offering;
070        iOffering.getConfigs().add(this);
071    }
072
073    /** Configuration id */
074    public long getId() {
075        return iId;
076    }
077    
078    /**
079     * Configuration limit. This is defines the maximal number of students that can be
080     * enrolled into this configuration at the same time. It is -1 in the case of an
081     * unlimited configuration
082     */
083    public int getLimit() {
084        return iLimit;
085    }
086
087    /** Set configuration limit */
088    public void setLimit(int limit) {
089        iLimit = limit;
090    }
091
092
093
094    /** Configuration name */
095    public String getName() {
096        return iName;
097    }
098
099    /** Instructional offering to which this configuration belongs. */
100    public Offering getOffering() {
101        return iOffering;
102    }
103
104    /** List of subparts */
105    public List<Subpart> getSubparts() {
106        return iSubparts;
107    }
108
109    @Override
110    public String toString() {
111        return getName();
112    }
113
114    /** Average minimal penalty from {@link Subpart#getMinPenalty()} */
115    public double getMinPenalty() {
116        double min = 0.0;
117        for (Subpart subpart : getSubparts()) {
118            min += subpart.getMinPenalty();
119        }
120        return min / getSubparts().size();
121    }
122
123    /** Average maximal penalty from {@link Subpart#getMaxPenalty()} */
124    public double getMaxPenalty() {
125        double max = 0.0;
126        for (Subpart subpart : getSubparts()) {
127            max += subpart.getMinPenalty();
128        }
129        return max / getSubparts().size();
130    }
131    
132    /** Called when an enrollment with this config is assigned to a request */
133    public void assigned(Enrollment enrollment) {
134        if (iEnrollments.isEmpty()) {
135            iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight();
136        } else {
137            iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight());
138            iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight());
139        }
140        iEnrollments.add(enrollment);
141        iEnrollmentWeight += enrollment.getRequest().getWeight();
142    }
143
144    /** Called when an enrollment with this config is unassigned from a request */
145    public void unassigned(Enrollment enrollment) {
146        iEnrollments.remove(enrollment);
147        iEnrollmentWeight -= enrollment.getRequest().getWeight();
148        if (iEnrollments.isEmpty()) {
149            iMinEnrollmentWeight = iMaxEnrollmentWeight = 0;
150        } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) {
151            if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) {
152                double newMinEnrollmentWeight = Double.MAX_VALUE;
153                for (Enrollment e : iEnrollments) {
154                    if (e.getRequest().getWeight() == iMinEnrollmentWeight) {
155                        newMinEnrollmentWeight = iMinEnrollmentWeight;
156                        break;
157                    } else {
158                        newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight());
159                    }
160                }
161                iMinEnrollmentWeight = newMinEnrollmentWeight;
162            }
163            if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) {
164                double newMaxEnrollmentWeight = Double.MIN_VALUE;
165                for (Enrollment e : iEnrollments) {
166                    if (e.getRequest().getWeight() == iMaxEnrollmentWeight) {
167                        newMaxEnrollmentWeight = iMaxEnrollmentWeight;
168                        break;
169                    } else {
170                        newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight());
171                    }
172                }
173                iMaxEnrollmentWeight = newMaxEnrollmentWeight;
174            }
175        }
176    }    
177    /**
178     * Enrollment weight -- weight of all requests which have an enrollment that
179     * contains this config, excluding the given one. See
180     * {@link Request#getWeight()}.
181     */
182    public double getEnrollmentWeight(Request excludeRequest) {
183        double weight = iEnrollmentWeight;
184        if (excludeRequest != null && excludeRequest.getAssignment() != null
185                && iEnrollments.contains(excludeRequest.getAssignment()))
186            weight -= excludeRequest.getWeight();
187        return weight;
188    }
189    
190    /** Set of assigned enrollments */
191    public Set<Enrollment> getEnrollments() {
192        return iEnrollments;
193    }
194
195    /**
196     * Maximal weight of a single enrollment in the config
197     */
198    public double getMaxEnrollmentWeight() {
199        return iMaxEnrollmentWeight;
200    }
201
202    /**
203     * Minimal weight of a single enrollment in the config
204     */
205    public double getMinEnrollmentWeight() {
206        return iMinEnrollmentWeight;
207    }
208    
209    /**
210     * Available space in the configuration that is not reserved by any config reservation
211     * @param excludeRequest excluding given request (if not null)
212     **/
213    public double getUnreservedSpace(Request excludeRequest) {
214        // configuration is unlimited -> there is unreserved space unless there is an unlimited reservation too 
215        // (in which case there is no unreserved space)
216        if (getLimit() < 0) {
217            // exclude reservations that are not directly set on this section
218            for (Reservation r: getConfigReservations()) {
219                // ignore expired reservations
220                if (r.isExpired()) continue;
221                // there is an unlimited reservation -> no unreserved space
222                if (r.getLimit() < 0) return 0.0;
223            }
224            return Double.MAX_VALUE;
225        }
226        
227        double available = getLimit() - getEnrollmentWeight(excludeRequest);
228        // exclude reservations that are not directly set on this section
229        for (Reservation r: getConfigReservations()) {
230            // ignore expired reservations
231            if (r.isExpired()) continue;
232            // unlimited reservation -> all the space is reserved
233            if (r.getLimit() < 0.0) return 0.0;
234            // compute space that can be potentially taken by this reservation
235            double reserved = r.getReservedAvailableSpace(excludeRequest);
236            // deduct the space from available space
237            available -= Math.max(0.0, reserved);
238        }
239        
240        return available;
241    }
242    
243    /**
244     * Total space in the configuration that cannot be reserved by any config reservation
245     **/
246    public double getTotalUnreservedSpace() {
247        if (iTotalUnreservedSpace == null)
248            iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache();
249        return iTotalUnreservedSpace;
250    }
251    private Double iTotalUnreservedSpace = null;
252    private double getTotalUnreservedSpaceNoCache() {
253        // configuration is unlimited -> there is unreserved space unless there is an unlimited reservation too 
254        // (in which case there is no unreserved space)
255        if (getLimit() < 0) {
256            // exclude reservations that are not directly set on this section
257            for (Reservation r: getConfigReservations()) {
258                // ignore expired reservations
259                if (r.isExpired()) continue;
260                // there is an unlimited reservation -> no unreserved space
261                if (r.getLimit() < 0) return 0.0;
262            }
263            return Double.MAX_VALUE;
264        }
265        
266        // we need to check all reservations linked with this section
267        double available = getLimit(), reserved = 0, exclusive = 0;
268        Set<Config> configs = new HashSet<Config>();
269        reservations: for (Reservation r: getConfigReservations()) {
270            // ignore expired reservations
271            if (r.isExpired()) continue;
272            // unlimited reservation -> no unreserved space
273            if (r.getLimit() < 0) return 0.0;
274            for (Config s: r.getConfigs()) {
275                if (s.equals(this)) continue;
276                if (s.getLimit() < 0) continue reservations;
277                if (configs.add(s))
278                    available += s.getLimit();
279            }
280            reserved += r.getLimit();
281            if (r.getConfigs().size() == 1)
282                exclusive += r.getLimit();
283        }
284        
285        return Math.min(available - reserved, getLimit() - exclusive);
286    }
287    
288    /**
289     * Get reservations for this configuration
290     */
291    public List<Reservation> getReservations() {
292        if (iReservations == null) {
293            iReservations = new ArrayList<Reservation>();
294            for (Reservation r: getOffering().getReservations()) {
295                if (r.getConfigs().isEmpty() || r.getConfigs().contains(this))
296                    iReservations.add(r);
297            }
298        }
299        return iReservations;
300    }
301    List<Reservation> iReservations = null;
302    
303    /**
304     * Get reservations that require this configuration
305     */
306    public List<Reservation> getConfigReservations() {
307        if (iConfigReservations == null) {
308            iConfigReservations = new ArrayList<Reservation>();
309            for (Reservation r: getOffering().getReservations()) {
310                if (!r.getConfigs().isEmpty() && r.getConfigs().contains(this))
311                    iConfigReservations.add(r);
312            }
313        }
314        return iConfigReservations;
315    }
316    List<Reservation> iConfigReservations = null;
317    
318    /**
319     * Clear reservation information that was cached on this configuration or below
320     */
321    public void clearReservationCache() {
322        for (Subpart s: getSubparts())
323            s.clearReservationCache();
324        iReservations = null;
325        iConfigReservations = null;
326        iTotalUnreservedSpace = null;
327    }
328    
329    @Override
330    public boolean equals(Object o) {
331        if (o == null || !(o instanceof Config)) return false;
332        return getId() == ((Config)o).getId();
333    }
334    
335    @Override
336    public int hashCode() {
337        return new Long(getId()).hashCode();
338    }
339}