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