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