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