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.assignment.context.AbstractClassWithContext; 010import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext; 011import org.cpsolver.ifs.assignment.context.CanInheritContext; 012import org.cpsolver.ifs.model.Model; 013import org.cpsolver.studentsct.reservation.Reservation; 014 015 016 017 018/** 019 * Representation of a configuration of an offering. A configuration contains 020 * id, name, an offering and a list of subparts. <br> 021 * <br> 022 * Each instructional offering (see {@link Offering}) contains one or more 023 * configurations. Each configuration contain one or more subparts. Each student 024 * has to take a class of each subpart of one of the possible configurations. 025 * 026 * <br> 027 * <br> 028 * 029 * @version StudentSct 1.3 (Student Sectioning)<br> 030 * Copyright (C) 2007 - 2014 Tomas Muller<br> 031 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 032 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 033 * <br> 034 * This library is free software; you can redistribute it and/or modify 035 * it under the terms of the GNU Lesser General Public License as 036 * published by the Free Software Foundation; either version 3 of the 037 * License, or (at your option) any later version. <br> 038 * <br> 039 * This library is distributed in the hope that it will be useful, but 040 * WITHOUT ANY WARRANTY; without even the implied warranty of 041 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 042 * Lesser General Public License for more details. <br> 043 * <br> 044 * You should have received a copy of the GNU Lesser General Public 045 * License along with this library; if not see 046 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 047 */ 048public class Config extends AbstractClassWithContext<Request, Enrollment, Config.ConfigContext> implements CanInheritContext<Request, Enrollment, Config.ConfigContext> { 049 private long iId = -1; 050 private String iName = null; 051 private Offering iOffering = null; 052 private int iLimit = -1; 053 private List<Subpart> iSubparts = new ArrayList<Subpart>(); 054 private Long iInstrMethodId; 055 private String iInstrMethodName; 056 057 /** 058 * Constructor 059 * 060 * @param id 061 * instructional offering configuration unique id 062 * @param limit 063 * configuration limit (-1 for unlimited) 064 * @param name 065 * configuration name 066 * @param offering 067 * instructional offering to which this configuration belongs 068 */ 069 public Config(long id, int limit, String name, Offering offering) { 070 iId = id; 071 iLimit = limit; 072 iName = name; 073 iOffering = offering; 074 iOffering.getConfigs().add(this); 075 } 076 077 /** Configuration id 078 * @return instructional offering configuration unique id 079 **/ 080 public long getId() { 081 return iId; 082 } 083 084 /** 085 * Configuration limit. This is defines the maximal number of students that can be 086 * enrolled into this configuration at the same time. It is -1 in the case of an 087 * unlimited configuration 088 * @return configuration limit 089 */ 090 public int getLimit() { 091 return iLimit; 092 } 093 094 /** Set configuration limit 095 * @param limit configuration limit, -1 if unlimited 096 **/ 097 public void setLimit(int limit) { 098 iLimit = limit; 099 } 100 101 102 103 /** Configuration name 104 * @return configuration name 105 **/ 106 public String getName() { 107 return iName; 108 } 109 110 /** Instructional offering to which this configuration belongs. 111 * @return instructional offering 112 **/ 113 public Offering getOffering() { 114 return iOffering; 115 } 116 117 /** List of subparts 118 * @return scheduling subparts 119 **/ 120 public List<Subpart> getSubparts() { 121 return iSubparts; 122 } 123 124 /** 125 * Return instructional method id 126 * @return instructional method id 127 */ 128 public Long getInstructionalMethodId() { return iInstrMethodId; } 129 130 /** 131 * Set instructional method id 132 * @param instrMethodId instructional method id 133 */ 134 public void setInstructionalMethodId(Long instrMethodId) { iInstrMethodId = instrMethodId; } 135 136 /** 137 * Return instructional method name 138 * @return instructional method name 139 */ 140 public String getInstructionalMethodName() { return iInstrMethodName; } 141 142 /** 143 * Set instructional method name 144 * @param instrMethodName instructional method name 145 */ 146 public void setInstructionalMethodName(String instrMethodName) { iInstrMethodName = instrMethodName; } 147 148 @Override 149 public String toString() { 150 return getName(); 151 } 152 153 /** Average minimal penalty from {@link Subpart#getMinPenalty()} 154 * @return minimal penalty 155 **/ 156 public double getMinPenalty() { 157 double min = 0.0; 158 for (Subpart subpart : getSubparts()) { 159 min += subpart.getMinPenalty(); 160 } 161 return min / getSubparts().size(); 162 } 163 164 /** Average maximal penalty from {@link Subpart#getMaxPenalty()} 165 * @return maximal penalty 166 **/ 167 public double getMaxPenalty() { 168 double max = 0.0; 169 for (Subpart subpart : getSubparts()) { 170 max += subpart.getMinPenalty(); 171 } 172 return max / getSubparts().size(); 173 } 174 175 /** 176 * Available space in the configuration that is not reserved by any config reservation 177 * @param assignment current assignment 178 * @param excludeRequest excluding given request (if not null) 179 * @return available space 180 **/ 181 public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 182 // configuration is unlimited -> there is unreserved space unless there is an unlimited reservation too 183 // (in which case there is no unreserved space) 184 if (getLimit() < 0) { 185 // exclude reservations that are not directly set on this section 186 for (Reservation r: getConfigReservations()) { 187 // ignore expired reservations 188 if (r.isExpired()) continue; 189 // there is an unlimited reservation -> no unreserved space 190 if (r.getLimit() < 0) return 0.0; 191 } 192 return Double.MAX_VALUE; 193 } 194 195 double available = getLimit() - getContext(assignment).getEnrollmentWeight(assignment, excludeRequest); 196 // exclude reservations that are not directly set on this section 197 for (Reservation r: getConfigReservations()) { 198 // ignore expired reservations 199 if (r.isExpired()) continue; 200 // unlimited reservation -> all the space is reserved 201 if (r.getLimit() < 0.0) return 0.0; 202 // compute space that can be potentially taken by this reservation 203 double reserved = r.getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest); 204 // deduct the space from available space 205 available -= Math.max(0.0, reserved); 206 } 207 208 return available; 209 } 210 211 /** 212 * Total space in the configuration that cannot be reserved by any config reservation 213 * @return total unreserved space 214 **/ 215 public synchronized double getTotalUnreservedSpace() { 216 if (iTotalUnreservedSpace == null) 217 iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache(); 218 return iTotalUnreservedSpace; 219 } 220 private Double iTotalUnreservedSpace = null; 221 private double getTotalUnreservedSpaceNoCache() { 222 // configuration is unlimited -> there is unreserved space unless there is an unlimited reservation too 223 // (in which case there is no unreserved space) 224 if (getLimit() < 0) { 225 // exclude reservations that are not directly set on this section 226 for (Reservation r: getConfigReservations()) { 227 // ignore expired reservations 228 if (r.isExpired()) continue; 229 // there is an unlimited reservation -> no unreserved space 230 if (r.getLimit() < 0) return 0.0; 231 } 232 return Double.MAX_VALUE; 233 } 234 235 // we need to check all reservations linked with this section 236 double available = getLimit(), reserved = 0, exclusive = 0; 237 Set<Config> configs = new HashSet<Config>(); 238 reservations: for (Reservation r: getConfigReservations()) { 239 // ignore expired reservations 240 if (r.isExpired()) continue; 241 // unlimited reservation -> no unreserved space 242 if (r.getLimit() < 0) return 0.0; 243 for (Config s: r.getConfigs()) { 244 if (s.equals(this)) continue; 245 if (s.getLimit() < 0) continue reservations; 246 if (configs.add(s)) 247 available += s.getLimit(); 248 } 249 reserved += r.getLimit(); 250 if (r.getConfigs().size() == 1) 251 exclusive += r.getLimit(); 252 } 253 254 return Math.min(available - reserved, getLimit() - exclusive); 255 } 256 257 /** 258 * Get reservations for this configuration 259 * @return related reservations 260 */ 261 public synchronized List<Reservation> getReservations() { 262 if (iReservations == null) { 263 iReservations = new ArrayList<Reservation>(); 264 for (Reservation r: getOffering().getReservations()) { 265 if (r.getConfigs().isEmpty() || r.getConfigs().contains(this)) 266 iReservations.add(r); 267 } 268 } 269 return iReservations; 270 } 271 List<Reservation> iReservations = null; 272 273 /** 274 * Get reservations that require this configuration 275 * @return related reservations 276 */ 277 public synchronized List<Reservation> getConfigReservations() { 278 if (iConfigReservations == null) { 279 iConfigReservations = new ArrayList<Reservation>(); 280 for (Reservation r: getOffering().getReservations()) { 281 if (!r.getConfigs().isEmpty() && r.getConfigs().contains(this)) 282 iConfigReservations.add(r); 283 } 284 } 285 return iConfigReservations; 286 } 287 List<Reservation> iConfigReservations = null; 288 289 /** 290 * Clear reservation information that was cached on this configuration or below 291 */ 292 public synchronized void clearReservationCache() { 293 for (Subpart s: getSubparts()) 294 s.clearReservationCache(); 295 iReservations = null; 296 iConfigReservations = null; 297 iTotalUnreservedSpace = null; 298 } 299 300 @Override 301 public boolean equals(Object o) { 302 if (o == null || !(o instanceof Config)) return false; 303 return getId() == ((Config)o).getId(); 304 } 305 306 @Override 307 public int hashCode() { 308 return new Long(getId()).hashCode(); 309 } 310 311 @Override 312 public Model<Request, Enrollment> getModel() { 313 return getOffering().getModel(); 314 } 315 316 /** Set of assigned enrollments 317 * @param assignment current assignment 318 * @return enrollments in this configuration 319 **/ 320 public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) { 321 return getContext(assignment).getEnrollments(); 322 } 323 324 /** 325 * Enrollment weight -- weight of all requests which have an enrollment that 326 * contains this config, excluding the given one. See 327 * {@link Request#getWeight()}. 328 * @param assignment current assignment 329 * @param excludeRequest request to exclude, null if all requests are to be included 330 * @return enrollment weight 331 */ 332 public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 333 return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest); 334 } 335 336 /** 337 * Maximal weight of a single enrollment in the config 338 * @param assignment current assignment 339 * @return maximal enrollment weight 340 */ 341 public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) { 342 return getContext(assignment).getMaxEnrollmentWeight(); 343 } 344 345 /** 346 * Minimal weight of a single enrollment in the config 347 * @param assignment current assignment 348 * @return minimal enrollment weight 349 */ 350 public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) { 351 return getContext(assignment).getMinEnrollmentWeight(); 352 } 353 354 @Override 355 public ConfigContext createAssignmentContext(Assignment<Request, Enrollment> assignment) { 356 return new ConfigContext(assignment); 357 } 358 359 360 @Override 361 public ConfigContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, ConfigContext parentContext) { 362 return new ConfigContext(parentContext); 363 } 364 365 public class ConfigContext implements AssignmentConstraintContext<Request, Enrollment> { 366 private double iEnrollmentWeight = 0.0; 367 private double iMaxEnrollmentWeight = 0.0; 368 private double iMinEnrollmentWeight = 0.0; 369 private Set<Enrollment> iEnrollments = null; 370 private boolean iReadOnly = false; 371 372 public ConfigContext(Assignment<Request, Enrollment> assignment) { 373 iEnrollments = new HashSet<Enrollment>(); 374 for (Course course: getOffering().getCourses()) { 375 for (CourseRequest request: course.getRequests()) { 376 Enrollment enrollment = assignment.getValue(request); 377 if (enrollment != null && Config.this.equals(enrollment.getConfig())) 378 assigned(assignment, enrollment); 379 } 380 } 381 } 382 383 public ConfigContext(ConfigContext parent) { 384 iEnrollmentWeight = parent.iEnrollmentWeight; 385 iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight; 386 iMinEnrollmentWeight = parent.iMinEnrollmentWeight; 387 iEnrollments = parent.iEnrollments; 388 iReadOnly = true; 389 } 390 391 /** Called when an enrollment with this config is assigned to a request */ 392 @Override 393 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 394 if (iReadOnly) { 395 iEnrollments = new HashSet<Enrollment>(iEnrollments); 396 iReadOnly = false; 397 } 398 if (iEnrollments.isEmpty()) { 399 iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight(); 400 } else { 401 iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight()); 402 iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight()); 403 } 404 if (iEnrollments.add(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 405 iEnrollmentWeight += enrollment.getRequest().getWeight(); 406 } 407 408 /** Called when an enrollment with this config is unassigned from a request */ 409 @Override 410 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 411 if (iReadOnly) { 412 iEnrollments = new HashSet<Enrollment>(iEnrollments); 413 iReadOnly = false; 414 } 415 if (iEnrollments.remove(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 416 iEnrollmentWeight -= enrollment.getRequest().getWeight(); 417 if (iEnrollments.isEmpty()) { 418 iMinEnrollmentWeight = iMaxEnrollmentWeight = 0; 419 } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) { 420 if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) { 421 double newMinEnrollmentWeight = Double.MAX_VALUE; 422 for (Enrollment e : iEnrollments) { 423 if (e.getRequest().getWeight() == iMinEnrollmentWeight) { 424 newMinEnrollmentWeight = iMinEnrollmentWeight; 425 break; 426 } else { 427 newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight()); 428 } 429 } 430 iMinEnrollmentWeight = newMinEnrollmentWeight; 431 } 432 if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) { 433 double newMaxEnrollmentWeight = Double.MIN_VALUE; 434 for (Enrollment e : iEnrollments) { 435 if (e.getRequest().getWeight() == iMaxEnrollmentWeight) { 436 newMaxEnrollmentWeight = iMaxEnrollmentWeight; 437 break; 438 } else { 439 newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight()); 440 } 441 } 442 iMaxEnrollmentWeight = newMaxEnrollmentWeight; 443 } 444 } 445 } 446 447 /** 448 * Enrollment weight -- weight of all requests which have an enrollment that 449 * contains this config, excluding the given one. See 450 * {@link Request#getWeight()}. 451 * @param assignment current assignment 452 * @param excludeRequest request to exclude 453 * @return enrollment weight 454 */ 455 public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 456 double weight = iEnrollmentWeight; 457 if (excludeRequest != null) { 458 Enrollment enrollment = assignment.getValue(excludeRequest); 459 if (enrollment != null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 460 weight -= excludeRequest.getWeight(); 461 } 462 return weight; 463 } 464 465 /** Set of assigned enrollments 466 * @return assigned enrollments (using this configuration) 467 **/ 468 public Set<Enrollment> getEnrollments() { 469 return iEnrollments; 470 } 471 472 /** 473 * Maximal weight of a single enrollment in the config 474 * @return maximal enrollment weight 475 */ 476 public double getMaxEnrollmentWeight() { 477 return iMaxEnrollmentWeight; 478 } 479 480 /** 481 * Minimal weight of a single enrollment in the config 482 * @return minimal enrollment weight 483 */ 484 public double getMinEnrollmentWeight() { 485 return iMinEnrollmentWeight; 486 } 487 } 488}