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.model.Model; 010import org.cpsolver.studentsct.reservation.Reservation; 011import org.cpsolver.studentsct.reservation.Restriction; 012 013 014 015/** 016 * Representation of an instructional offering. An offering contains id, name, 017 * the list of course offerings, and the list of possible configurations. See 018 * {@link Config} and {@link Course}. 019 * 020 * <br> 021 * <br> 022 * 023 * @version StudentSct 1.3 (Student Sectioning)<br> 024 * Copyright (C) 2007 - 2014 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 Offering { 043 private long iId = -1; 044 private String iName = null; 045 private Model<Request, Enrollment> iModel = null; 046 private List<Config> iConfigs = new ArrayList<Config>(); 047 private List<Course> iCourses = new ArrayList<Course>(); 048 private List<Reservation> iReservations = new ArrayList<Reservation>(); 049 private List<Restriction> iRestrictions = new ArrayList<Restriction>(); 050 051 /** 052 * Constructor 053 * 054 * @param id 055 * instructional offering unique id 056 * @param name 057 * instructional offering name (this is usually the name of the 058 * controlling course) 059 */ 060 public Offering(long id, String name) { 061 iId = id; 062 iName = name; 063 } 064 065 /** Offering id 066 * @return instructional offering unique id 067 **/ 068 public long getId() { 069 return iId; 070 } 071 072 /** Offering name 073 * @return instructional offering name 074 **/ 075 public String getName() { 076 return iName; 077 } 078 079 /** Possible configurations 080 * @return instructional offering configurations 081 **/ 082 public List<Config> getConfigs() { 083 return iConfigs; 084 } 085 086 /** 087 * List of courses. One instructional offering can contain multiple courses 088 * (names under which it is offered) 089 * @return list of course offerings 090 */ 091 public List<Course> getCourses() { 092 return iCourses; 093 } 094 095 /** 096 * Return section of the given id, if it is part of one of this offering 097 * configurations. 098 * @param sectionId class unique id 099 * @return section of the given id 100 */ 101 public Section getSection(long sectionId) { 102 for (Config config : getConfigs()) { 103 for (Subpart subpart : config.getSubparts()) { 104 for (Section section : subpart.getSections()) { 105 if (section.getId() == sectionId) 106 return section; 107 } 108 } 109 } 110 return null; 111 } 112 113 /** Return course, under which the given student enrolls into this offering. 114 * @param student given student 115 * @return course of this offering requested by the student 116 **/ 117 public Course getCourse(Student student) { 118 if (getCourses().isEmpty()) 119 return null; 120 if (getCourses().size() == 1) 121 return getCourses().get(0); 122 for (Request request : student.getRequests()) { 123 if (request instanceof CourseRequest) { 124 for (Course course : ((CourseRequest) request).getCourses()) { 125 if (getCourses().contains(course)) 126 return course; 127 } 128 } 129 } 130 return getCourses().get(0); 131 } 132 133 /** Return set of instructional types, union over all configurations. 134 * @return set of instructional types 135 **/ 136 public Set<String> getInstructionalTypes() { 137 Set<String> instructionalTypes = new HashSet<String>(); 138 for (Config config : getConfigs()) { 139 for (Subpart subpart : config.getSubparts()) { 140 instructionalTypes.add(subpart.getInstructionalType()); 141 } 142 } 143 return instructionalTypes; 144 } 145 146 /** 147 * Return the list of all possible choices of the given instructional type 148 * for this offering. 149 * @param instructionalType instructional type 150 * @return set of choices of the given instructional type 151 */ 152 public Set<Choice> getChoices(String instructionalType) { 153 Set<Choice> choices = new HashSet<Choice>(); 154 for (Config config : getConfigs()) { 155 for (Subpart subpart : config.getSubparts()) { 156 if (!instructionalType.equals(subpart.getInstructionalType())) 157 continue; 158 choices.addAll(subpart.getChoices()); 159 } 160 } 161 return choices; 162 } 163 164 /** 165 * Return list of all subparts of the given isntructional type for this 166 * offering. 167 * @param instructionalType instructional type 168 * @return subpart of the given instructional type 169 */ 170 public Set<Subpart> getSubparts(String instructionalType) { 171 Set<Subpart> subparts = new HashSet<Subpart>(); 172 for (Config config : getConfigs()) { 173 for (Subpart subpart : config.getSubparts()) { 174 if (instructionalType.equals(subpart.getInstructionalType())) 175 subparts.add(subpart); 176 } 177 } 178 return subparts; 179 } 180 181 /** Minimal penalty from {@link Config#getMinPenalty()} 182 * @return minimal penalty 183 **/ 184 public double getMinPenalty() { 185 double min = Double.MAX_VALUE; 186 for (Config config : getConfigs()) { 187 min = Math.min(min, config.getMinPenalty()); 188 } 189 return (min == Double.MAX_VALUE ? 0.0 : min); 190 } 191 192 /** Maximal penalty from {@link Config#getMaxPenalty()} 193 * @return maximal penalty 194 **/ 195 public double getMaxPenalty() { 196 double max = Double.MIN_VALUE; 197 for (Config config : getConfigs()) { 198 max = Math.max(max, config.getMaxPenalty()); 199 } 200 return (max == Double.MIN_VALUE ? 0.0 : max); 201 } 202 203 @Override 204 public String toString() { 205 return iName; 206 } 207 208 /** Reservations associated with this offering 209 * @return reservations for this offering 210 **/ 211 public List<Reservation> getReservations() { return iReservations; } 212 213 /** True if there are reservations for this offering 214 * @return true if there is at least one reservation 215 **/ 216 public boolean hasReservations() { return !iReservations.isEmpty(); } 217 218 /** Restrictions associated with this offering 219 * @return restrictions for this offering 220 **/ 221 public List<Restriction> getRestrictions() { return iRestrictions; } 222 223 /** True if there are restrictions for this offering 224 * @return true if there is at least one restriction 225 **/ 226 public boolean hasRestrictions() { return !iRestrictions.isEmpty(); } 227 228 /** 229 * Total space in the offering that is not reserved by any reservation 230 * @return total unreserved space in the offering 231 **/ 232 public synchronized double getTotalUnreservedSpace() { 233 if (iTotalUnreservedSpace == null) 234 iTotalUnreservedSpace = getTotalUnreservedSpaceNoCache(); 235 return iTotalUnreservedSpace; 236 } 237 Double iTotalUnreservedSpace = null; 238 private double getTotalUnreservedSpaceNoCache() { 239 // compute overall available space 240 double available = 0.0; 241 for (Config config: getConfigs()) { 242 available += config.getLimit(); 243 // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too 244 // (in which case there is no unreserved space) 245 if (config.getLimit() < 0) { 246 for (Reservation r: getReservations()) { 247 // ignore expired reservations 248 if (r.isExpired()) continue; 249 // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs) 250 if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue; 251 // there is an unlimited reservation -> no unreserved space 252 if (r.getLimit() < 0) return 0.0; 253 } 254 return Double.MAX_VALUE; 255 } 256 } 257 258 // compute maximal reserved space (out of the available space) 259 double reserved = 0; 260 for (Reservation r: getReservations()) { 261 // ignore expired reservations 262 if (r.isExpired()) continue; 263 // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs) 264 if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue; 265 // unlimited reservation -> no unreserved space 266 if (r.getLimit() < 0) return 0.0; 267 reserved += r.getLimit(); 268 } 269 270 return Math.max(0.0, available - reserved); 271 } 272 273 /** 274 * Available space in the offering that is not reserved by any reservation 275 * @param assignment current request 276 * @param excludeRequest excluding given request (if not null) 277 * @return remaining unreserved space in the offering 278 **/ 279 public double getUnreservedSpace(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 280 // compute available space 281 double available = 0.0; 282 for (Config config: getConfigs()) { 283 available += config.getLimit() - config.getContext(assignment).getEnrollmentWeight(assignment, excludeRequest); 284 // offering is unlimited -> there is unreserved space unless there is an unlimited reservation too 285 // (in which case there is no unreserved space) 286 if (config.getLimit() < 0) { 287 for (Reservation r: getReservations()) { 288 // ignore expired reservations 289 if (r.isExpired()) continue; 290 // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs) 291 if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue; 292 // there is an unlimited reservation -> no unreserved space 293 if (r.getLimit() < 0) return 0.0; 294 } 295 return Double.MAX_VALUE; 296 } 297 } 298 299 // compute reserved space (out of the available space) 300 double reserved = 0; 301 for (Reservation r: getReservations()) { 302 // ignore expired reservations 303 if (r.isExpired()) continue; 304 // skip reservations that have restrictions that are not inclusive (these are only checked on the restricted sections/configs) 305 if (!r.areRestrictionsInclusive() && (!r.getConfigs().isEmpty() || !r.getSections().isEmpty())) continue; 306 // unlimited reservation -> no unreserved space 307 if (r.getLimit() < 0) return 0.0; 308 reserved += Math.max(0.0, r.getContext(assignment).getReservedAvailableSpace(assignment, excludeRequest)); 309 } 310 311 return available - reserved; 312 } 313 314 315 /** 316 * Clear reservation information that was cached on this offering or below 317 */ 318 public synchronized void clearReservationCache() { 319 for (Config c: getConfigs()) 320 c.clearReservationCache(); 321 for (Course c: getCourses()) 322 for (CourseRequest r: c.getRequests()) 323 r.clearReservationCache(); 324 iTotalUnreservedSpace = null; 325 } 326 327 /** 328 * Clear restriction information that was cached on this offering or below 329 */ 330 public synchronized void clearRestrictionCache() { 331 for (Course c: getCourses()) 332 for (CourseRequest r: c.getRequests()) 333 r.clearRestrictionCache(); 334 } 335 336 @Override 337 public boolean equals(Object o) { 338 if (o == null || !(o instanceof Offering)) return false; 339 return getId() == ((Offering)o).getId(); 340 } 341 342 @Override 343 public int hashCode() { 344 return (int) (iId ^ (iId >>> 32)); 345 } 346 347 public Model<Request, Enrollment> getModel() { return iModel; } 348 public void setModel(Model<Request, Enrollment> model) { iModel = model; } 349}