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