001package org.cpsolver.studentsct.model; 002 003import java.util.Collections; 004import java.util.HashSet; 005import java.util.Set; 006 007import org.cpsolver.ifs.assignment.Assignment; 008import org.cpsolver.ifs.assignment.context.AbstractClassWithContext; 009import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext; 010import org.cpsolver.ifs.assignment.context.CanInheritContext; 011import org.cpsolver.ifs.model.Model; 012 013 014/** 015 * Representation of a course offering. A course offering contains id, subject 016 * area, course number and an instructional offering. <br> 017 * <br> 018 * Each instructional offering (see {@link Offering}) is offered under one or 019 * more course offerings. 020 * 021 * <br> 022 * <br> 023 * 024 * @version StudentSct 1.3 (Student Sectioning)<br> 025 * Copyright (C) 2007 - 2014 Tomas Muller<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 Course extends AbstractClassWithContext<Request, Enrollment, Course.CourseContext> implements CanInheritContext<Request, Enrollment, Course.CourseContext> { 044 private long iId = -1; 045 private String iSubjectArea = null; 046 private String iCourseNumber = null; 047 private Offering iOffering = null; 048 private int iLimit = 0, iProjected = 0; 049 private Set<CourseRequest> iRequests = Collections.synchronizedSet(new HashSet<CourseRequest>()); 050 private String iNote = null; 051 private Set<RequestGroup> iRequestGroups = new HashSet<RequestGroup>(); 052 053 /** 054 * Constructor 055 * 056 * @param id 057 * course offering unique id 058 * @param subjectArea 059 * subject area (e.g., MA, CS, ENGL) 060 * @param courseNumber 061 * course number under the given subject area 062 * @param offering 063 * instructional offering which is offered under this course 064 * offering 065 */ 066 public Course(long id, String subjectArea, String courseNumber, Offering offering) { 067 iId = id; 068 iSubjectArea = subjectArea; 069 iCourseNumber = courseNumber; 070 iOffering = offering; 071 iOffering.getCourses().add(this); 072 } 073 074 /** 075 * Constructor 076 * 077 * @param id 078 * course offering unique id 079 * @param subjectArea 080 * subject area (e.g., MA, CS, ENGL) 081 * @param courseNumber 082 * course number under the given subject area 083 * @param offering 084 * instructional offering which is offered under this course 085 * offering 086 * @param limit 087 * course offering limit (-1 for unlimited) 088 * @param projected 089 * projected demand 090 */ 091 public Course(long id, String subjectArea, String courseNumber, Offering offering, int limit, int projected) { 092 iId = id; 093 iSubjectArea = subjectArea; 094 iCourseNumber = courseNumber; 095 iOffering = offering; 096 iOffering.getCourses().add(this); 097 iLimit = limit; 098 iProjected = projected; 099 } 100 101 /** Course offering unique id 102 * @return coure offering unqiue id 103 **/ 104 public long getId() { 105 return iId; 106 } 107 108 /** Subject area 109 * @return subject area abbreviation 110 **/ 111 public String getSubjectArea() { 112 return iSubjectArea; 113 } 114 115 /** Course number 116 * @return course number 117 **/ 118 public String getCourseNumber() { 119 return iCourseNumber; 120 } 121 122 /** Course offering name: subject area + course number 123 * @return course name 124 **/ 125 public String getName() { 126 return iSubjectArea + " " + iCourseNumber; 127 } 128 129 @Override 130 public String toString() { 131 return getName(); 132 } 133 134 /** Instructional offering which is offered under this course offering. 135 * @return instructional offering 136 **/ 137 public Offering getOffering() { 138 return iOffering; 139 } 140 141 /** Course offering limit 142 * @return course offering limit, -1 if unlimited 143 **/ 144 public int getLimit() { 145 return iLimit; 146 } 147 148 /** Set course offering limit 149 * @param limit course offering limit, -1 if unlimited 150 **/ 151 public void setLimit(int limit) { 152 iLimit = limit; 153 } 154 155 /** Course offering projected number of students 156 * @return course projection 157 **/ 158 public int getProjected() { 159 return iProjected; 160 } 161 162 /** Called when an enrollment with this course is assigned to a request 163 * @param assignment current assignment 164 * @param enrollment assigned enrollment 165 **/ 166 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 167 getContext(assignment).assigned(assignment, enrollment); 168 } 169 170 /** Called when an enrollment with this course is unassigned from a request 171 * @param assignment current assignment 172 * @param enrollment unassigned enrollment 173 */ 174 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 175 getContext(assignment).unassigned(assignment, enrollment); 176 } 177 178 /** Set of course requests requesting this course 179 * @return request for this course 180 **/ 181 public Set<CourseRequest> getRequests() { 182 return iRequests; 183 } 184 185 /** 186 * Course note 187 * @return course note 188 */ 189 public String getNote() { return iNote; } 190 191 /** 192 * Course note 193 * @param note course note 194 */ 195 public void setNote(String note) { iNote = note; } 196 197 @Override 198 public boolean equals(Object o) { 199 if (o == null || !(o instanceof Course)) return false; 200 return getId() == ((Course)o).getId(); 201 } 202 203 @Override 204 public int hashCode() { 205 return (int) (iId ^ (iId >>> 32)); 206 } 207 208 @Override 209 public Model<Request, Enrollment> getModel() { 210 return getOffering().getModel(); 211 } 212 213 /** 214 * Enrollment weight -- weight of all requests that are enrolled into this course, 215 * excluding the given one. See 216 * {@link Request#getWeight()}. 217 * @param assignment current assignment 218 * @param excludeRequest request to exclude 219 * @return enrollment weight 220 */ 221 public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 222 return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest); 223 } 224 225 /** Set of assigned enrollments 226 * @param assignment current assignment 227 * @return assigned enrollments for this course offering 228 **/ 229 public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) { 230 return getContext(assignment).getEnrollments(); 231 } 232 233 /** 234 * Maximal weight of a single enrollment in the course 235 * @param assignment current assignment 236 * @return maximal enrollment weight 237 */ 238 public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) { 239 return getContext(assignment).getMaxEnrollmentWeight(); 240 } 241 242 /** 243 * Minimal weight of a single enrollment in the course 244 * @param assignment current assignment 245 * @return minimal enrollment weight 246 */ 247 public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) { 248 return getContext(assignment).getMinEnrollmentWeight(); 249 } 250 251 /** 252 * Add request group of this course. This is automatically called 253 * by the constructor of the {@link RequestGroup}. 254 * @param group request group to be added 255 */ 256 public void addRequestGroup(RequestGroup group) { 257 iRequestGroups.add(group); 258 } 259 260 /** 261 * Remove request group from this course. 262 * @param group request group to be removed 263 */ 264 public void removeRequestGroup(RequestGroup group) { 265 iRequestGroups.remove(group); 266 } 267 268 /** 269 * Lists all the request groups of this course 270 * @return all request groups of this course 271 */ 272 public Set<RequestGroup> getRequestGroups() { 273 return iRequestGroups; 274 } 275 276 @Override 277 public CourseContext createAssignmentContext(Assignment<Request, Enrollment> assignment) { 278 return new CourseContext(assignment); 279 } 280 281 282 @Override 283 public CourseContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, CourseContext parentContext) { 284 return new CourseContext(parentContext); 285 } 286 287 public class CourseContext implements AssignmentConstraintContext<Request, Enrollment> { 288 private double iEnrollmentWeight = 0.0; 289 private Set<Enrollment> iEnrollments = null; 290 private double iMaxEnrollmentWeight = 0.0; 291 private double iMinEnrollmentWeight = 0.0; 292 private boolean iReadOnly = false; 293 294 public CourseContext(Assignment<Request, Enrollment> assignment) { 295 iEnrollments = new HashSet<Enrollment>(); 296 for (CourseRequest request: getRequests()) { 297 Enrollment enrollment = assignment.getValue(request); 298 if (enrollment != null && Course.this.equals(enrollment.getCourse())) 299 assigned(assignment, enrollment); 300 } 301 } 302 303 public CourseContext(CourseContext parent) { 304 iEnrollmentWeight = parent.iEnrollmentWeight; 305 iMinEnrollmentWeight = parent.iMinEnrollmentWeight; 306 iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight; 307 iEnrollments = parent.iEnrollments; 308 iReadOnly = true; 309 } 310 311 @Override 312 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 313 if (iReadOnly) { 314 iEnrollments = new HashSet<Enrollment>(iEnrollments); 315 iReadOnly = false; 316 } 317 if (iEnrollments.isEmpty()) { 318 iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight(); 319 } else { 320 iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight()); 321 iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight()); 322 } 323 if (iEnrollments.add(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 324 iEnrollmentWeight += enrollment.getRequest().getWeight(); 325 } 326 327 @Override 328 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 329 if (iReadOnly) { 330 iEnrollments = new HashSet<Enrollment>(iEnrollments); 331 iReadOnly = false; 332 } 333 if (iEnrollments.remove(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 334 iEnrollmentWeight -= enrollment.getRequest().getWeight(); 335 if (iEnrollments.isEmpty()) { 336 iMinEnrollmentWeight = iMaxEnrollmentWeight = 0; 337 } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) { 338 if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) { 339 double newMinEnrollmentWeight = Double.MAX_VALUE; 340 for (Enrollment e : iEnrollments) { 341 if (e.getRequest().getWeight() == iMinEnrollmentWeight) { 342 newMinEnrollmentWeight = iMinEnrollmentWeight; 343 break; 344 } else { 345 newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight()); 346 } 347 } 348 iMinEnrollmentWeight = newMinEnrollmentWeight; 349 } 350 if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) { 351 double newMaxEnrollmentWeight = Double.MIN_VALUE; 352 for (Enrollment e : iEnrollments) { 353 if (e.getRequest().getWeight() == iMaxEnrollmentWeight) { 354 newMaxEnrollmentWeight = iMaxEnrollmentWeight; 355 break; 356 } else { 357 newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight()); 358 } 359 } 360 iMaxEnrollmentWeight = newMaxEnrollmentWeight; 361 } 362 } 363 } 364 365 /** 366 * Enrollment weight -- weight of all requests that are enrolled into this course, 367 * excluding the given one. See 368 * {@link Request#getWeight()}. 369 * @param assignment current assignment 370 * @param excludeRequest request to exclude 371 * @return enrollment weight 372 */ 373 public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 374 double weight = iEnrollmentWeight; 375 if (excludeRequest != null) { 376 Enrollment enrollment = assignment.getValue(excludeRequest); 377 if (enrollment!= null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 378 weight -= excludeRequest.getWeight(); 379 } 380 return weight; 381 } 382 383 /** Set of assigned enrollments 384 * @return assigned enrollments for this course offering 385 **/ 386 public Set<Enrollment> getEnrollments() { 387 return iEnrollments; 388 } 389 390 /** 391 * Maximal weight of a single enrollment in the course 392 * @return maximal enrollment weight 393 */ 394 public double getMaxEnrollmentWeight() { 395 return iMaxEnrollmentWeight; 396 } 397 398 /** 399 * Minimal weight of a single enrollment in the course 400 * @return minimal enrollment weight 401 */ 402 public double getMinEnrollmentWeight() { 403 return iMinEnrollmentWeight; 404 } 405 } 406}