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 * Enrollment weight including over the limit enrollments. 338 * That is enrollments that have reservation with {@link Reservation#canBatchAssignOverLimit()} set to true. 339 * {@link Request#getWeight()}. 340 * @param assignment current assignment 341 * @param excludeRequest request to exclude, null if all requests are to be included 342 * @return enrollment weight 343 */ 344 public double getEnrollmentTotalWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 345 return getContext(assignment).getEnrollmentTotalWeight(assignment, excludeRequest); 346 } 347 348 /** 349 * Maximal weight of a single enrollment in the config 350 * @param assignment current assignment 351 * @return maximal enrollment weight 352 */ 353 public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) { 354 return getContext(assignment).getMaxEnrollmentWeight(); 355 } 356 357 /** 358 * Minimal weight of a single enrollment in the config 359 * @param assignment current assignment 360 * @return minimal enrollment weight 361 */ 362 public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) { 363 return getContext(assignment).getMinEnrollmentWeight(); 364 } 365 366 @Override 367 public ConfigContext createAssignmentContext(Assignment<Request, Enrollment> assignment) { 368 return new ConfigContext(assignment); 369 } 370 371 372 @Override 373 public ConfigContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, ConfigContext parentContext) { 374 return new ConfigContext(parentContext); 375 } 376 377 public class ConfigContext implements AssignmentConstraintContext<Request, Enrollment> { 378 private double iEnrollmentWeight = 0.0; 379 private double iEnrollmentTotalWeight = 0.0; 380 private double iMaxEnrollmentWeight = 0.0; 381 private double iMinEnrollmentWeight = 0.0; 382 private Set<Enrollment> iEnrollments = null; 383 private boolean iReadOnly = false; 384 385 public ConfigContext(Assignment<Request, Enrollment> assignment) { 386 iEnrollments = new HashSet<Enrollment>(); 387 for (Course course: getOffering().getCourses()) { 388 for (CourseRequest request: course.getRequests()) { 389 Enrollment enrollment = assignment.getValue(request); 390 if (enrollment != null && Config.this.equals(enrollment.getConfig())) 391 assigned(assignment, enrollment); 392 } 393 } 394 } 395 396 public ConfigContext(ConfigContext parent) { 397 iEnrollmentWeight = parent.iEnrollmentWeight; 398 iEnrollmentTotalWeight = parent.iEnrollmentTotalWeight; 399 iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight; 400 iMinEnrollmentWeight = parent.iMinEnrollmentWeight; 401 iEnrollments = parent.iEnrollments; 402 iReadOnly = true; 403 } 404 405 /** Called when an enrollment with this config is assigned to a request */ 406 @Override 407 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 408 if (iReadOnly) { 409 iEnrollments = new HashSet<Enrollment>(iEnrollments); 410 iReadOnly = false; 411 } 412 if (iEnrollments.isEmpty()) { 413 iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight(); 414 } else { 415 iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight()); 416 iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight()); 417 } 418 if (iEnrollments.add(enrollment)) { 419 iEnrollmentTotalWeight += enrollment.getRequest().getWeight(); 420 if (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()) 421 iEnrollmentWeight += enrollment.getRequest().getWeight(); 422 } 423 } 424 425 /** Called when an enrollment with this config is unassigned from a request */ 426 @Override 427 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 428 if (iReadOnly) { 429 iEnrollments = new HashSet<Enrollment>(iEnrollments); 430 iReadOnly = false; 431 } 432 if (iEnrollments.remove(enrollment)) { 433 iEnrollmentTotalWeight -= enrollment.getRequest().getWeight(); 434 if (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit()) 435 iEnrollmentWeight -= enrollment.getRequest().getWeight(); 436 } 437 if (iEnrollments.isEmpty()) { 438 iMinEnrollmentWeight = iMaxEnrollmentWeight = 0; 439 } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) { 440 if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) { 441 double newMinEnrollmentWeight = Double.MAX_VALUE; 442 for (Enrollment e : iEnrollments) { 443 if (e.getRequest().getWeight() == iMinEnrollmentWeight) { 444 newMinEnrollmentWeight = iMinEnrollmentWeight; 445 break; 446 } else { 447 newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight()); 448 } 449 } 450 iMinEnrollmentWeight = newMinEnrollmentWeight; 451 } 452 if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) { 453 double newMaxEnrollmentWeight = Double.MIN_VALUE; 454 for (Enrollment e : iEnrollments) { 455 if (e.getRequest().getWeight() == iMaxEnrollmentWeight) { 456 newMaxEnrollmentWeight = iMaxEnrollmentWeight; 457 break; 458 } else { 459 newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight()); 460 } 461 } 462 iMaxEnrollmentWeight = newMaxEnrollmentWeight; 463 } 464 } 465 } 466 467 /** 468 * Enrollment weight -- weight of all requests which have an enrollment that 469 * contains this config, excluding the given one. See 470 * {@link Request#getWeight()}. 471 * @param assignment current assignment 472 * @param excludeRequest request to exclude 473 * @return enrollment weight 474 */ 475 public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 476 double weight = iEnrollmentWeight; 477 if (excludeRequest != null) { 478 Enrollment enrollment = assignment.getValue(excludeRequest); 479 if (enrollment != null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 480 weight -= excludeRequest.getWeight(); 481 } 482 return weight; 483 } 484 485 /** 486 * Enrollment weight including over the limit enrollments. 487 * That is enrollments that have reservation with {@link Reservation#canBatchAssignOverLimit()} set to true. 488 * @param assignment current assignment 489 * @param excludeRequest request to exclude 490 * @return enrollment weight 491 */ 492 public double getEnrollmentTotalWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 493 double weight = iEnrollmentTotalWeight; 494 if (excludeRequest != null) { 495 Enrollment enrollment = assignment.getValue(excludeRequest); 496 if (enrollment != null && iEnrollments.contains(enrollment)) 497 weight -= excludeRequest.getWeight(); 498 } 499 return weight; 500 } 501 502 /** Set of assigned enrollments 503 * @return assigned enrollments (using this configuration) 504 **/ 505 public Set<Enrollment> getEnrollments() { 506 return iEnrollments; 507 } 508 509 /** 510 * Maximal weight of a single enrollment in the config 511 * @return maximal enrollment weight 512 */ 513 public double getMaxEnrollmentWeight() { 514 return iMaxEnrollmentWeight; 515 } 516 517 /** 518 * Minimal weight of a single enrollment in the config 519 * @return minimal enrollment weight 520 */ 521 public double getMinEnrollmentWeight() { 522 return iMinEnrollmentWeight; 523 } 524 } 525}