001    package net.sf.cpsolver.studentsct.model;
002    
003    import java.util.ArrayList;
004    import java.util.HashSet;
005    import java.util.List;
006    import java.util.Set;
007    
008    import 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 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     */
042    public 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    }