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