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}