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 }