001package org.cpsolver.studentsct.model; 002 003import java.util.Collections; 004import java.util.HashSet; 005import java.util.Set; 006import java.util.regex.Matcher; 007import java.util.regex.Pattern; 008 009import org.cpsolver.ifs.assignment.Assignment; 010import org.cpsolver.ifs.assignment.context.AbstractClassWithContext; 011import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext; 012import org.cpsolver.ifs.assignment.context.CanInheritContext; 013import org.cpsolver.ifs.model.Model; 014 015 016/** 017 * Representation of a course offering. A course offering contains id, subject 018 * area, course number and an instructional offering. <br> 019 * <br> 020 * Each instructional offering (see {@link Offering}) is offered under one or 021 * more course offerings. 022 * 023 * <br> 024 * <br> 025 * 026 * @version StudentSct 1.3 (Student Sectioning)<br> 027 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 028 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 029 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 030 * <br> 031 * This library is free software; you can redistribute it and/or modify 032 * it under the terms of the GNU Lesser General Public License as 033 * published by the Free Software Foundation; either version 3 of the 034 * License, or (at your option) any later version. <br> 035 * <br> 036 * This library is distributed in the hope that it will be useful, but 037 * WITHOUT ANY WARRANTY; without even the implied warranty of 038 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 039 * Lesser General Public License for more details. <br> 040 * <br> 041 * You should have received a copy of the GNU Lesser General Public 042 * License along with this library; if not see 043 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 044 */ 045public class Course extends AbstractClassWithContext<Request, Enrollment, Course.CourseContext> implements CanInheritContext<Request, Enrollment, Course.CourseContext> { 046 private long iId = -1; 047 private String iSubjectArea = null; 048 private String iCourseNumber = null; 049 private Offering iOffering = null; 050 private int iLimit = 0, iProjected = 0; 051 private Set<CourseRequest> iRequests = Collections.synchronizedSet(new HashSet<CourseRequest>()); 052 private String iNote = null; 053 private Set<RequestGroup> iRequestGroups = new HashSet<RequestGroup>(); 054 private String iCredit = null; 055 private Float iCreditValue = null; 056 private String iTitle = null; 057 private String iType = null; 058 059 /** 060 * Constructor 061 * 062 * @param id 063 * course offering unique id 064 * @param subjectArea 065 * subject area (e.g., MA, CS, ENGL) 066 * @param courseNumber 067 * course number under the given subject area 068 * @param offering 069 * instructional offering which is offered under this course 070 * offering 071 */ 072 public Course(long id, String subjectArea, String courseNumber, Offering offering) { 073 iId = id; 074 iSubjectArea = subjectArea; 075 iCourseNumber = courseNumber; 076 iOffering = offering; 077 iOffering.getCourses().add(this); 078 } 079 080 /** 081 * Constructor 082 * 083 * @param id 084 * course offering unique id 085 * @param subjectArea 086 * subject area (e.g., MA, CS, ENGL) 087 * @param courseNumber 088 * course number under the given subject area 089 * @param offering 090 * instructional offering which is offered under this course 091 * offering 092 * @param limit 093 * course offering limit (-1 for unlimited) 094 * @param projected 095 * projected demand 096 */ 097 public Course(long id, String subjectArea, String courseNumber, Offering offering, int limit, int projected) { 098 iId = id; 099 iSubjectArea = subjectArea; 100 iCourseNumber = courseNumber; 101 iOffering = offering; 102 iOffering.getCourses().add(this); 103 iLimit = limit; 104 iProjected = projected; 105 } 106 107 /** Course offering unique id 108 * @return coure offering unqiue id 109 **/ 110 public long getId() { 111 return iId; 112 } 113 114 /** Subject area 115 * @return subject area abbreviation 116 **/ 117 public String getSubjectArea() { 118 return iSubjectArea; 119 } 120 121 /** Course number 122 * @return course number 123 **/ 124 public String getCourseNumber() { 125 return iCourseNumber; 126 } 127 128 /** Course offering name: subject area + course number 129 * @return course name 130 **/ 131 public String getName() { 132 return iSubjectArea + " " + iCourseNumber; 133 } 134 135 @Override 136 public String toString() { 137 return getName(); 138 } 139 140 /** Instructional offering which is offered under this course offering. 141 * @return instructional offering 142 **/ 143 public Offering getOffering() { 144 return iOffering; 145 } 146 147 /** Course offering limit 148 * @return course offering limit, -1 if unlimited 149 **/ 150 public int getLimit() { 151 return iLimit; 152 } 153 154 /** Set course offering limit 155 * @param limit course offering limit, -1 if unlimited 156 **/ 157 public void setLimit(int limit) { 158 iLimit = limit; 159 } 160 161 /** Course offering projected number of students 162 * @return course projection 163 **/ 164 public int getProjected() { 165 return iProjected; 166 } 167 168 /** Called when an enrollment with this course is assigned to a request 169 * @param assignment current assignment 170 * @param enrollment assigned enrollment 171 **/ 172 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 173 getContext(assignment).assigned(assignment, enrollment); 174 } 175 176 /** Called when an enrollment with this course is unassigned from a request 177 * @param assignment current assignment 178 * @param enrollment unassigned enrollment 179 */ 180 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 181 getContext(assignment).unassigned(assignment, enrollment); 182 } 183 184 /** Set of course requests requesting this course 185 * @return request for this course 186 **/ 187 public Set<CourseRequest> getRequests() { 188 return iRequests; 189 } 190 191 /** 192 * Course note 193 * @return course note 194 */ 195 public String getNote() { return iNote; } 196 197 /** 198 * Course note 199 * @param note course note 200 */ 201 public void setNote(String note) { iNote = note; } 202 203 @Override 204 public boolean equals(Object o) { 205 if (o == null || !(o instanceof Course)) return false; 206 return getId() == ((Course)o).getId(); 207 } 208 209 @Override 210 public int hashCode() { 211 return (int) (iId ^ (iId >>> 32)); 212 } 213 214 @Override 215 public Model<Request, Enrollment> getModel() { 216 return getOffering().getModel(); 217 } 218 219 /** 220 * Enrollment weight -- weight of all requests that are enrolled into this course, 221 * excluding the given one. See 222 * {@link Request#getWeight()}. 223 * @param assignment current assignment 224 * @param excludeRequest request to exclude 225 * @return enrollment weight 226 */ 227 public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 228 return getContext(assignment).getEnrollmentWeight(assignment, excludeRequest); 229 } 230 231 /** Set of assigned enrollments 232 * @param assignment current assignment 233 * @return assigned enrollments for this course offering 234 **/ 235 public Set<Enrollment> getEnrollments(Assignment<Request, Enrollment> assignment) { 236 return getContext(assignment).getEnrollments(); 237 } 238 239 /** 240 * Maximal weight of a single enrollment in the course 241 * @param assignment current assignment 242 * @return maximal enrollment weight 243 */ 244 public double getMaxEnrollmentWeight(Assignment<Request, Enrollment> assignment) { 245 return getContext(assignment).getMaxEnrollmentWeight(); 246 } 247 248 /** 249 * Minimal weight of a single enrollment in the course 250 * @param assignment current assignment 251 * @return minimal enrollment weight 252 */ 253 public double getMinEnrollmentWeight(Assignment<Request, Enrollment> assignment) { 254 return getContext(assignment).getMinEnrollmentWeight(); 255 } 256 257 /** 258 * Add request group of this course. This is automatically called 259 * by the constructor of the {@link RequestGroup}. 260 * @param group request group to be added 261 */ 262 public void addRequestGroup(RequestGroup group) { 263 iRequestGroups.add(group); 264 } 265 266 /** 267 * Remove request group from this course. 268 * @param group request group to be removed 269 */ 270 public void removeRequestGroup(RequestGroup group) { 271 iRequestGroups.remove(group); 272 } 273 274 /** 275 * Lists all the request groups of this course 276 * @return all request groups of this course 277 */ 278 public Set<RequestGroup> getRequestGroups() { 279 return iRequestGroups; 280 } 281 282 /** 283 * Set credit (Online Student Scheduling only) 284 * @param credit scheduling course credit 285 */ 286 public void setCredit(String credit) { 287 iCredit = credit; 288 if (iCreditValue == null && credit != null) { 289 int split = credit.indexOf('|'); 290 String abbv = null; 291 if (split >= 0) { 292 abbv = credit.substring(0, split); 293 } else { 294 abbv = credit; 295 } 296 Matcher m = Pattern.compile("(^| )(\\d+\\.?\\d*)([,-]?(\\d+\\.?\\d*))?($| )").matcher(abbv); 297 if (m.find()) 298 iCreditValue = Float.parseFloat(m.group(2)); 299 } 300 } 301 302 /** 303 * Get credit (Online Student Scheduling only) 304 * @return scheduling course credit 305 */ 306 public String getCredit() { return iCredit; } 307 308 /** 309 * True if this course has a credit value defined 310 * @return true if a credit value is set 311 */ 312 public boolean hasCreditValue() { return iCreditValue != null; } 313 314 /** 315 * Set course credit value (null if not set) 316 * @param creditValue course credit value 317 */ 318 public void setCreditValue(Float creditValue) { iCreditValue = creditValue; } 319 320 /** 321 * Get course credit value (null if not set) 322 * return course credit value 323 */ 324 public Float getCreditValue() { return iCreditValue; } 325 326 @Override 327 public CourseContext createAssignmentContext(Assignment<Request, Enrollment> assignment) { 328 return new CourseContext(assignment); 329 } 330 331 332 @Override 333 public CourseContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, CourseContext parentContext) { 334 return new CourseContext(parentContext); 335 } 336 337 public class CourseContext implements AssignmentConstraintContext<Request, Enrollment> { 338 private double iEnrollmentWeight = 0.0; 339 private Set<Enrollment> iEnrollments = null; 340 private double iMaxEnrollmentWeight = 0.0; 341 private double iMinEnrollmentWeight = 0.0; 342 private boolean iReadOnly = false; 343 344 public CourseContext(Assignment<Request, Enrollment> assignment) { 345 iEnrollments = new HashSet<Enrollment>(); 346 for (CourseRequest request: getRequests()) { 347 Enrollment enrollment = assignment.getValue(request); 348 if (enrollment != null && Course.this.equals(enrollment.getCourse())) 349 assigned(assignment, enrollment); 350 } 351 } 352 353 public CourseContext(CourseContext parent) { 354 iEnrollmentWeight = parent.iEnrollmentWeight; 355 iMinEnrollmentWeight = parent.iMinEnrollmentWeight; 356 iMaxEnrollmentWeight = parent.iMaxEnrollmentWeight; 357 iEnrollments = parent.iEnrollments; 358 iReadOnly = true; 359 } 360 361 @Override 362 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 363 if (iReadOnly) { 364 iEnrollments = new HashSet<Enrollment>(iEnrollments); 365 iReadOnly = false; 366 } 367 if (iEnrollments.isEmpty()) { 368 iMinEnrollmentWeight = iMaxEnrollmentWeight = enrollment.getRequest().getWeight(); 369 } else { 370 iMaxEnrollmentWeight = Math.max(iMaxEnrollmentWeight, enrollment.getRequest().getWeight()); 371 iMinEnrollmentWeight = Math.min(iMinEnrollmentWeight, enrollment.getRequest().getWeight()); 372 } 373 if (iEnrollments.add(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 374 iEnrollmentWeight += enrollment.getRequest().getWeight(); 375 } 376 377 @Override 378 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 379 if (iReadOnly) { 380 iEnrollments = new HashSet<Enrollment>(iEnrollments); 381 iReadOnly = false; 382 } 383 if (iEnrollments.remove(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 384 iEnrollmentWeight -= enrollment.getRequest().getWeight(); 385 if (iEnrollments.isEmpty()) { 386 iMinEnrollmentWeight = iMaxEnrollmentWeight = 0; 387 } else if (iMinEnrollmentWeight != iMaxEnrollmentWeight) { 388 if (iMinEnrollmentWeight == enrollment.getRequest().getWeight()) { 389 double newMinEnrollmentWeight = Double.MAX_VALUE; 390 for (Enrollment e : iEnrollments) { 391 if (e.getRequest().getWeight() == iMinEnrollmentWeight) { 392 newMinEnrollmentWeight = iMinEnrollmentWeight; 393 break; 394 } else { 395 newMinEnrollmentWeight = Math.min(newMinEnrollmentWeight, e.getRequest().getWeight()); 396 } 397 } 398 iMinEnrollmentWeight = newMinEnrollmentWeight; 399 } 400 if (iMaxEnrollmentWeight == enrollment.getRequest().getWeight()) { 401 double newMaxEnrollmentWeight = Double.MIN_VALUE; 402 for (Enrollment e : iEnrollments) { 403 if (e.getRequest().getWeight() == iMaxEnrollmentWeight) { 404 newMaxEnrollmentWeight = iMaxEnrollmentWeight; 405 break; 406 } else { 407 newMaxEnrollmentWeight = Math.max(newMaxEnrollmentWeight, e.getRequest().getWeight()); 408 } 409 } 410 iMaxEnrollmentWeight = newMaxEnrollmentWeight; 411 } 412 } 413 } 414 415 /** 416 * Enrollment weight -- weight of all requests that are enrolled into this course, 417 * excluding the given one. See 418 * {@link Request#getWeight()}. 419 * @param assignment current assignment 420 * @param excludeRequest request to exclude 421 * @return enrollment weight 422 */ 423 public double getEnrollmentWeight(Assignment<Request, Enrollment> assignment, Request excludeRequest) { 424 double weight = iEnrollmentWeight; 425 if (excludeRequest != null) { 426 Enrollment enrollment = assignment.getValue(excludeRequest); 427 if (enrollment!= null && iEnrollments.contains(enrollment) && (enrollment.getReservation() == null || !enrollment.getReservation().canBatchAssignOverLimit())) 428 weight -= excludeRequest.getWeight(); 429 } 430 return weight; 431 } 432 433 /** Set of assigned enrollments 434 * @return assigned enrollments for this course offering 435 **/ 436 public Set<Enrollment> getEnrollments() { 437 return iEnrollments; 438 } 439 440 /** 441 * Maximal weight of a single enrollment in the course 442 * @return maximal enrollment weight 443 */ 444 public double getMaxEnrollmentWeight() { 445 return iMaxEnrollmentWeight; 446 } 447 448 /** 449 * Minimal weight of a single enrollment in the course 450 * @return minimal enrollment weight 451 */ 452 public double getMinEnrollmentWeight() { 453 return iMinEnrollmentWeight; 454 } 455 } 456 457 public double getOnlineBound() { 458 double bound = 1.0; 459 for (Config config: getOffering().getConfigs()) { 460 int online = config.getNrOnline(); 461 if (online == 0) return 0.0; 462 double factor = ((double)online) / config.getSubparts().size(); 463 if (factor < bound) bound = factor; 464 } 465 return bound; 466 } 467 468 public double getArrHrsBound() { 469 double bound = 1.0; 470 for (Config config: getOffering().getConfigs()) { 471 int arrHrs = config.getNrArrHours(); 472 if (arrHrs == 0) return 0.0; 473 double factor = ((double)arrHrs) / config.getSubparts().size(); 474 if (factor < bound) bound = factor; 475 } 476 return bound; 477 } 478 479 public double getPastBound() { 480 double bound = 1.0; 481 for (Config config: getOffering().getConfigs()) { 482 int past = config.getNrPast(); 483 if (past == 0) return 0.0; 484 double factor = ((double)past) / config.getSubparts().size(); 485 if (factor < bound) bound = factor; 486 } 487 return bound; 488 } 489 490 /** 491 * Course title 492 */ 493 public String getTitle() { 494 return iTitle; 495 } 496 497 /** 498 * Course title 499 */ 500 public void setTitle(String title) { 501 iTitle = title; 502 } 503 504 /** 505 * Course type 506 */ 507 public String getType() { 508 return iType; 509 } 510 511 /** 512 * Course type 513 */ 514 public void setType(String type) { 515 iType = type; 516 } 517}