001package org.cpsolver.studentsct.model; 002 003import java.text.DecimalFormat; 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.Collections; 007import java.util.HashMap; 008import java.util.HashSet; 009import java.util.List; 010import java.util.Map; 011import java.util.Set; 012import java.util.TreeSet; 013 014import org.cpsolver.coursett.model.TimeLocation; 015import org.cpsolver.ifs.assignment.Assignment; 016import org.cpsolver.ifs.assignment.AssignmentComparator; 017import org.cpsolver.ifs.util.ToolBox; 018import org.cpsolver.studentsct.StudentSectioningModel; 019import org.cpsolver.studentsct.constraint.ConfigLimit; 020import org.cpsolver.studentsct.constraint.CourseLimit; 021import org.cpsolver.studentsct.constraint.SectionLimit; 022import org.cpsolver.studentsct.reservation.Reservation; 023 024 025/** 026 * Representation of a request of a student for one or more course. A student 027 * requests one of the given courses, preferably the first one. <br> 028 * <br> 029 * 030 * @version StudentSct 1.3 (Student Sectioning)<br> 031 * Copyright (C) 2007 - 2014 Tomas Muller<br> 032 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 033 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 034 * <br> 035 * This library is free software; you can redistribute it and/or modify 036 * it under the terms of the GNU Lesser General Public License as 037 * published by the Free Software Foundation; either version 3 of the 038 * License, or (at your option) any later version. <br> 039 * <br> 040 * This library is distributed in the hope that it will be useful, but 041 * WITHOUT ANY WARRANTY; without even the implied warranty of 042 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 043 * Lesser General Public License for more details. <br> 044 * <br> 045 * You should have received a copy of the GNU Lesser General Public 046 * License along with this library; if not see 047 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 048 */ 049public class CourseRequest extends Request { 050 private static DecimalFormat sDF = new DecimalFormat("0.000"); 051 private List<Course> iCourses = null; 052 private Set<Choice> iWaitlistedChoices = new HashSet<Choice>(); 053 private Set<Choice> iSelectedChoices = new HashSet<Choice>(); 054 private boolean iWaitlist = false; 055 private Long iTimeStamp = null; 056 private Double iCachedMinPenalty = null, iCachedMaxPenalty = null; 057 public static boolean sSameTimePrecise = false; 058 059 /** 060 * Constructor 061 * 062 * @param id 063 * request unique id 064 * @param priority 065 * request priority 066 * @param alternative 067 * true if the request is alternative (alternative request can be 068 * assigned instead of a non-alternative course requests, if it 069 * is left unassigned) 070 * @param student 071 * appropriate student 072 * @param courses 073 * list of requested courses (in the correct order -- first is 074 * the requested course, second is the first alternative, etc.) 075 * @param waitlist 076 * time stamp of the request if the student can be put on a wait-list (no alternative 077 * course request will be given instead) 078 * @param timeStamp request time stamp 079 */ 080 public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses, 081 boolean waitlist, Long timeStamp) { 082 super(id, priority, alternative, student); 083 iCourses = new ArrayList<Course>(courses); 084 for (Course course: iCourses) 085 course.getRequests().add(this); 086 iWaitlist = waitlist; 087 iTimeStamp = timeStamp; 088 } 089 090 /** 091 * List of requested courses (in the correct order -- first is the requested 092 * course, second is the first alternative, etc.) 093 * @return requested course offerings 094 */ 095 public List<Course> getCourses() { 096 return iCourses; 097 } 098 099 /** 100 * Create enrollment for the given list of sections. The list of sections 101 * needs to be correct, i.e., a section for each subpart of a configuration 102 * of one of the requested courses. 103 * @param sections selected sections 104 * @param reservation selected reservation 105 * @return enrollment 106 */ 107 public Enrollment createEnrollment(Set<? extends SctAssignment> sections, Reservation reservation) { 108 if (sections == null || sections.isEmpty()) 109 return null; 110 Config config = ((Section) sections.iterator().next()).getSubpart().getConfig(); 111 Course course = null; 112 for (Course c: iCourses) { 113 if (c.getOffering().getConfigs().contains(config)) { 114 course = c; 115 break; 116 } 117 } 118 return new Enrollment(this, iCourses.indexOf(course), course, config, sections, reservation); 119 } 120 121 /** 122 * Create enrollment for the given list of sections. The list of sections 123 * needs to be correct, i.e., a section for each subpart of a configuration 124 * of one of the requested courses. 125 * @param assignment current assignment (to guess the reservation) 126 * @param sections selected sections 127 * @return enrollment 128 */ 129 public Enrollment createEnrollment(Assignment<Request, Enrollment> assignment, Set<? extends SctAssignment> sections) { 130 Enrollment ret = createEnrollment(sections, null); 131 ret.guessReservation(assignment, true); 132 return ret; 133 134 } 135 136 /** 137 * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit. 138 * @return maximal domain size, -1 if unlimited 139 */ 140 protected int getMaxDomainSize() { 141 StudentSectioningModel model = (StudentSectioningModel) getModel(); 142 return model == null ? -1 : model.getMaxDomainSize(); 143 } 144 145 /** 146 * Return all possible enrollments. 147 */ 148 @Override 149 public List<Enrollment> computeEnrollments(Assignment<Request, Enrollment> assignment) { 150 List<Enrollment> ret = new ArrayList<Enrollment>(); 151 int idx = 0; 152 for (Course course : iCourses) { 153 for (Config config : course.getOffering().getConfigs()) { 154 computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false, 155 false, false, getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize()); 156 } 157 idx++; 158 } 159 return ret; 160 } 161 162 /** 163 * Return a subset of all enrollments -- randomly select only up to 164 * limitEachConfig enrollments of each config. 165 * @param assignment current assignment 166 * @param limitEachConfig maximal number of enrollments in each configuration 167 * @return computed enrollments 168 */ 169 public List<Enrollment> computeRandomEnrollments(Assignment<Request, Enrollment> assignment, int limitEachConfig) { 170 List<Enrollment> ret = new ArrayList<Enrollment>(); 171 int idx = 0; 172 for (Course course : iCourses) { 173 for (Config config : course.getOffering().getConfigs()) { 174 computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false, 175 false, true, (limitEachConfig <= 0 ? limitEachConfig : ret.size() + limitEachConfig)); 176 } 177 idx++; 178 } 179 return ret; 180 } 181 182 /** 183 * Return true if the both sets of sections contain sections of the same 184 * subparts, and each pair of sections of the same subpart is offered at the 185 * same time. 186 */ 187 private boolean sameTimes(Set<Section> sections1, Set<Section> sections2) { 188 for (Section s1 : sections1) { 189 Section s2 = null; 190 for (Section s : sections2) { 191 if (s.getSubpart().equals(s1.getSubpart())) { 192 s2 = s; 193 break; 194 } 195 } 196 if (s2 == null) 197 return false; 198 if (!ToolBox.equals(s1.getTime(), s2.getTime())) 199 return false; 200 } 201 return true; 202 } 203 204 /** 205 * Recursive computation of enrollments 206 * 207 * @param enrollments 208 * list of enrollments to be returned 209 * @param priority 210 * zero for the course, one for the first alternative, two for the second alternative 211 * @param penalty 212 * penalty of the selected sections 213 * @param course 214 * selected course 215 * @param config 216 * selected configuration 217 * @param sections 218 * sections selected so far 219 * @param idx 220 * index of the subparts (a section of 0..idx-1 subparts has been 221 * already selected) 222 * @param availableOnly 223 * only use available sections 224 * @param skipSameTime 225 * for each possible times, pick only one section 226 * @param selectedOnly 227 * select only sections that are selected ( 228 * {@link CourseRequest#isSelected(Section)} is true) 229 * @param random 230 * pick sections in a random order (useful when limit is used) 231 * @param limit 232 * when above zero, limit the number of selected enrollments to 233 * this limit 234 * @param reservations 235 * list of applicable reservations 236 */ 237 private void computeEnrollments(Assignment<Request, Enrollment> assignment, Collection<Enrollment> enrollments, int priority, double penalty, Course course, Config config, 238 HashSet<Section> sections, int idx, boolean availableOnly, boolean skipSameTime, boolean selectedOnly, 239 boolean random, int limit) { 240 if (limit > 0 && enrollments.size() >= limit) 241 return; 242 if (idx == 0) { // run only once for each configuration 243 boolean canOverLimit = false; 244 if (availableOnly && (getModel() == null || ((StudentSectioningModel)getModel()).getReservationCanAssignOverTheLimit())) { 245 for (Reservation r: getReservations(course)) { 246 if (!r.canAssignOverLimit()) continue; 247 if (!r.getConfigs().isEmpty() && !r.getConfigs().contains(config)) continue; 248 if (r.getReservedAvailableSpace(assignment, this) < getWeight()) continue; 249 canOverLimit = true; break; 250 } 251 } 252 if (!canOverLimit) { 253 if (availableOnly && config.getLimit() >= 0 && ConfigLimit.getEnrollmentWeight(assignment, config, this) > config.getLimit()) 254 return; 255 if (availableOnly && course.getLimit() >= 0 && CourseLimit.getEnrollmentWeight(assignment, course, this) > course.getLimit()) 256 return; 257 if (config.getOffering().hasReservations()) { 258 boolean hasReservation = false, hasOtherReservation = false, hasConfigReservation = false, reservationMustBeUsed = false; 259 for (Reservation r: getReservations(course)) { 260 if (r.mustBeUsed()) reservationMustBeUsed = true; 261 if (availableOnly && r.getReservedAvailableSpace(assignment, this) < getWeight()) continue; 262 if (r.getConfigs().isEmpty()) { 263 hasReservation = true; 264 } else if (r.getConfigs().contains(config)) { 265 hasReservation = true; 266 hasConfigReservation = true; 267 } else { 268 hasOtherReservation = true; 269 } 270 } 271 if (!hasConfigReservation && config.getTotalUnreservedSpace() < getWeight()) 272 return; 273 if (!hasReservation && config.getOffering().getTotalUnreservedSpace() < getWeight()) 274 return; 275 if (hasOtherReservation && !hasReservation) 276 return; 277 if (availableOnly && !hasReservation && config.getOffering().getUnreservedSpace(assignment, this) < getWeight()) 278 return; 279 if (availableOnly && !hasConfigReservation && config.getUnreservedSpace(assignment, this) < getWeight()) 280 return; 281 if (!hasReservation && reservationMustBeUsed) 282 return; 283 } 284 } 285 } 286 if (config.getSubparts().size() == idx) { 287 if (skipSameTime && sSameTimePrecise) { 288 boolean waitListedOrSelected = false; 289 if (!getSelectedChoices().isEmpty() || !getWaitlistedChoices().isEmpty()) { 290 for (Section section : sections) { 291 if (isWaitlisted(section) || isSelected(section)) { 292 waitListedOrSelected = true; 293 break; 294 } 295 } 296 } 297 if (!waitListedOrSelected) { 298 for (Enrollment enrollment : enrollments) { 299 if (sameTimes(enrollment.getSections(), sections)) 300 return; 301 } 302 } 303 } 304 if (!config.getOffering().hasReservations()) { 305 enrollments.add(new Enrollment(this, priority, null, config, new HashSet<SctAssignment>(sections), null)); 306 } else { 307 Enrollment e = new Enrollment(this, priority, null, config, new HashSet<SctAssignment>(sections), null); 308 boolean mustHaveReservation = config.getOffering().getTotalUnreservedSpace() < getWeight(); 309 boolean mustHaveConfigReservation = config.getTotalUnreservedSpace() < getWeight(); 310 boolean mustHaveSectionReservation = false; 311 for (Section s: sections) { 312 if (s.getTotalUnreservedSpace() < getWeight()) { 313 mustHaveSectionReservation = true; 314 break; 315 } 316 } 317 boolean canOverLimit = false; 318 if (availableOnly && (getModel() == null || ((StudentSectioningModel)getModel()).getReservationCanAssignOverTheLimit())) { 319 for (Reservation r: getReservations(course)) { 320 if (!r.canAssignOverLimit() || !r.isIncluded(e)) continue; 321 if (r.getReservedAvailableSpace(assignment, this) < getWeight()) continue; 322 enrollments.add(new Enrollment(this, priority, null, config, new HashSet<SctAssignment>(sections), r)); 323 canOverLimit = true; 324 } 325 } 326 if (!canOverLimit) { 327 boolean reservationMustBeUsed = false; 328 reservations: for (Reservation r: (availableOnly ? getSortedReservations(assignment, course) : getReservations(course))) { 329 if (r.mustBeUsed()) reservationMustBeUsed = true; 330 if (!r.isIncluded(e)) continue; 331 if (availableOnly && r.getReservedAvailableSpace(assignment, this) < getWeight()) continue; 332 if (mustHaveConfigReservation && r.getConfigs().isEmpty()) continue; 333 if (mustHaveSectionReservation) 334 for (Section s: sections) 335 if (r.getSections(s.getSubpart()) == null && s.getTotalUnreservedSpace() < getWeight()) continue reservations; 336 enrollments.add(new Enrollment(this, priority, null, config, new HashSet<SctAssignment>(sections), r)); 337 if (availableOnly) return; // only one available reservation suffice (the best matching one) 338 } 339 // a case w/o reservation 340 if (!(mustHaveReservation || mustHaveConfigReservation || mustHaveSectionReservation) && 341 !(availableOnly && config.getOffering().getUnreservedSpace(assignment, this) < getWeight()) && 342 !reservationMustBeUsed) { 343 enrollments.add(new Enrollment(this, (getReservations(course).isEmpty() ? 0 : 1) + priority, null, config, new HashSet<SctAssignment>(sections), null)); 344 } 345 } 346 } 347 } else { 348 Subpart subpart = config.getSubparts().get(idx); 349 HashSet<TimeLocation> times = (skipSameTime ? new HashSet<TimeLocation>() : null); 350 List<Section> sectionsThisSubpart = subpart.getSections(); 351 if (skipSameTime) { 352 sectionsThisSubpart = new ArrayList<Section>(subpart.getSections()); 353 Collections.sort(sectionsThisSubpart, new AssignmentComparator<Section, Request, Enrollment>(assignment)); 354 } 355 List<Section> matchingSectionsThisSubpart = new ArrayList<Section>(subpart.getSections().size()); 356 boolean hasChildren = !subpart.getChildren().isEmpty(); 357 for (Section section : sectionsThisSubpart) { 358 if (section.getParent() != null && !sections.contains(section.getParent())) 359 continue; 360 if (section.isOverlapping(sections)) 361 continue; 362 if (selectedOnly && !isSelected(section)) 363 continue; 364 boolean canOverLimit = false; 365 if (availableOnly && (getModel() == null || ((StudentSectioningModel)getModel()).getReservationCanAssignOverTheLimit())) { 366 for (Reservation r: getReservations(course)) { 367 if (!r.canAssignOverLimit()) continue; 368 if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue; 369 if (r.getReservedAvailableSpace(assignment, this) < getWeight()) continue; 370 canOverLimit = true; break; 371 } 372 } 373 if (!canOverLimit) { 374 if (availableOnly && section.getLimit() >= 0 375 && SectionLimit.getEnrollmentWeight(assignment, section, this) > section.getLimit()) 376 continue; 377 if (config.getOffering().hasReservations()) { 378 boolean hasReservation = false, hasSectionReservation = false, hasOtherReservation = false, reservationMustBeUsed = false; 379 for (Reservation r: getReservations(course)) { 380 if (r.mustBeUsed()) reservationMustBeUsed = true; 381 if (availableOnly && r.getReservedAvailableSpace(assignment, this) < getWeight()) continue; 382 if (r.getSections(subpart) == null) { 383 hasReservation = true; 384 } else if (r.getSections(subpart).contains(section)) { 385 hasReservation = true; 386 hasSectionReservation = true; 387 } else { 388 hasOtherReservation = true; 389 } 390 } 391 if (!hasSectionReservation && section.getTotalUnreservedSpace() < getWeight()) 392 continue; 393 if (hasOtherReservation && !hasReservation) 394 continue; 395 if (availableOnly && !hasSectionReservation && section.getUnreservedSpace(assignment, this) < getWeight()) 396 continue; 397 if (!hasReservation && reservationMustBeUsed) 398 continue; 399 } 400 } 401 if (skipSameTime && section.getTime() != null && !hasChildren && !times.add(section.getTime()) && !isSelected(section) && !isWaitlisted(section)) 402 continue; 403 matchingSectionsThisSubpart.add(section); 404 } 405 if (random || limit > 0) { 406 sectionsThisSubpart = new ArrayList<Section>(sectionsThisSubpart); 407 Collections.shuffle(sectionsThisSubpart); 408 } 409 int i = 0; 410 for (Section section: matchingSectionsThisSubpart) { 411 sections.add(section); 412 computeEnrollments(assignment, enrollments, priority, penalty + section.getPenalty(), course, config, sections, idx + 1, 413 availableOnly, skipSameTime, selectedOnly, random, 414 limit < 0 ? limit : Math.max(1, limit * (1 + i) / matchingSectionsThisSubpart.size()) 415 ); 416 sections.remove(section); 417 i++; 418 } 419 } 420 } 421 422 /** Return all enrollments that are available 423 * @param assignment current assignment 424 * @return all available enrollments 425 **/ 426 public List<Enrollment> getAvaiableEnrollments(Assignment<Request, Enrollment> assignment) { 427 List<Enrollment> ret = new ArrayList<Enrollment>(); 428 int idx = 0; 429 for (Course course : iCourses) { 430 for (Config config : course.getOffering().getConfigs()) { 431 computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, true, false, false, false, 432 getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize()); 433 } 434 idx++; 435 } 436 return ret; 437 } 438 439 /** 440 * Return all enrollments that are selected ( 441 * {@link CourseRequest#isSelected(Section)} is true) 442 * 443 * @param assignment current assignment 444 * @param availableOnly 445 * pick only available sections 446 * @return selected enrollments 447 */ 448 public List<Enrollment> getSelectedEnrollments(Assignment<Request, Enrollment> assignment, boolean availableOnly) { 449 if (getSelectedChoices().isEmpty()) 450 return null; 451 Choice firstChoice = getSelectedChoices().iterator().next(); 452 List<Enrollment> enrollments = new ArrayList<Enrollment>(); 453 for (Course course : iCourses) { 454 if (!course.getOffering().equals(firstChoice.getOffering())) 455 continue; 456 for (Config config : course.getOffering().getConfigs()) { 457 computeEnrollments(assignment, enrollments, 0, 0, course, config, new HashSet<Section>(), 0, availableOnly, false, true, false, -1); 458 } 459 } 460 return enrollments; 461 } 462 463 /** 464 * Return all enrollments that are available, pick only the first section of 465 * the sections with the same time (of each subpart, {@link Section} 466 * comparator is used) 467 * @param assignment current assignment 468 * @return available enrollments 469 */ 470 public List<Enrollment> getAvaiableEnrollmentsSkipSameTime(Assignment<Request, Enrollment> assignment) { 471 List<Enrollment> ret = new ArrayList<Enrollment>(); 472 if (getInitialAssignment() != null) 473 ret.add(getInitialAssignment()); 474 int idx = 0; 475 for (Course course : iCourses) { 476 for (Config config : course.getOffering().getConfigs()) { 477 computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, true, true, false, false, 478 getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize()); 479 } 480 idx++; 481 } 482 return ret; 483 } 484 485 /** 486 * Return all possible enrollments, but pick only the first section of 487 * the sections with the same time (of each subpart, {@link Section} 488 * comparator is used). 489 * @param assignment current assignment 490 * @return computed enrollments 491 */ 492 public List<Enrollment> getEnrollmentsSkipSameTime(Assignment<Request, Enrollment> assignment) { 493 List<Enrollment> ret = new ArrayList<Enrollment>(); 494 int idx = 0; 495 for (Course course : iCourses) { 496 for (Config config : course.getOffering().getConfigs()) { 497 computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, true, false, false, 498 getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize()); 499 } 500 idx++; 501 } 502 return ret; 503 } 504 505 /** Wait-listed choices 506 * @return wait-listed choices 507 **/ 508 public Set<Choice> getWaitlistedChoices() { 509 return iWaitlistedChoices; 510 } 511 512 /** 513 * Return true when the given section is wait-listed (i.e., its choice is 514 * among wait-listed choices) 515 * @param section given section 516 * @return true if the given section matches the wait-listed choices 517 */ 518 public boolean isWaitlisted(Section section) { 519 return iWaitlistedChoices.contains(section.getChoice()); 520 } 521 522 /** Selected choices 523 * @return selected choices 524 **/ 525 public Set<Choice> getSelectedChoices() { 526 return iSelectedChoices; 527 } 528 529 /** 530 * Return true when the given section is selected (i.e., its choice is among 531 * selected choices) 532 * @param section given section 533 * @return true if the given section matches the selected choices 534 */ 535 public boolean isSelected(Section section) { 536 return iSelectedChoices.contains(section.getChoice()); 537 } 538 539 /** 540 * Request name: A for alternative, 1 + priority, (w) when wait-list, list of 541 * course names 542 */ 543 @Override 544 public String getName() { 545 String ret = (isAlternative() ? "A" : "") 546 + (1 + getPriority() + (isAlternative() ? -getStudent().nrRequests() : 0)) + ". " 547 + (isWaitlist() ? "(w) " : ""); 548 int idx = 0; 549 for (Course course : iCourses) { 550 if (idx == 0) 551 ret += course.getName(); 552 else 553 ret += ", " + idx + ". alt " + course.getName(); 554 idx++; 555 } 556 return ret; 557 } 558 559 /** 560 * True if the student can be put on a wait-list (no alternative course 561 * request will be given instead) 562 * @return true if the request can be wait-listed 563 */ 564 public boolean isWaitlist() { 565 return iWaitlist; 566 } 567 568 /** 569 * True if the student can be put on a wait-list (no alternative course 570 * request will be given instead) 571 * @param waitlist true if the request can be wait-listed 572 */ 573 public void setWaitlist(boolean waitlist) { 574 iWaitlist = waitlist; 575 } 576 577 /** 578 * Time stamp of the request 579 * @return request time stamp 580 */ 581 public Long getTimeStamp() { 582 return iTimeStamp; 583 } 584 585 @Override 586 public String toString() { 587 return getName() + (getWeight() != 1.0 ? " (W:" + sDF.format(getWeight()) + ")" : ""); 588 } 589 590 /** Return course of the requested courses with the given id 591 * @param courseId course offering id 592 * @return course of the given id 593 **/ 594 public Course getCourse(long courseId) { 595 for (Course course : iCourses) { 596 if (course.getId() == courseId) 597 return course; 598 } 599 return null; 600 } 601 602 /** Return configuration of the requested courses with the given id 603 * @param configId instructional offering configuration unique id 604 * @return config of the given id 605 **/ 606 public Config getConfig(long configId) { 607 for (Course course : iCourses) { 608 for (Config config : course.getOffering().getConfigs()) { 609 if (config.getId() == configId) 610 return config; 611 } 612 } 613 return null; 614 } 615 616 /** Return subpart of the requested courses with the given id 617 * @param subpartId scheduling subpart unique id 618 * @return subpart of the given id 619 **/ 620 public Subpart getSubpart(long subpartId) { 621 for (Course course : iCourses) { 622 for (Config config : course.getOffering().getConfigs()) { 623 for (Subpart subpart : config.getSubparts()) { 624 if (subpart.getId() == subpartId) 625 return subpart; 626 } 627 } 628 } 629 return null; 630 } 631 632 /** Return section of the requested courses with the given id 633 * @param sectionId class unique id 634 * @return section of the given id 635 **/ 636 public Section getSection(long sectionId) { 637 for (Course course : iCourses) { 638 for (Config config : course.getOffering().getConfigs()) { 639 for (Subpart subpart : config.getSubparts()) { 640 for (Section section : subpart.getSections()) { 641 if (section.getId() == sectionId) 642 return section; 643 } 644 } 645 } 646 } 647 return null; 648 } 649 650 /** 651 * Minimal penalty (minimum of {@link Offering#getMinPenalty()} among 652 * requested courses) 653 * @return minimal penalty 654 */ 655 public double getMinPenalty() { 656 if (iCachedMinPenalty == null) { 657 double min = Double.MAX_VALUE; 658 for (Course course : iCourses) { 659 min = Math.min(min, course.getOffering().getMinPenalty()); 660 } 661 iCachedMinPenalty = new Double(min); 662 } 663 return iCachedMinPenalty.doubleValue(); 664 } 665 666 /** 667 * Maximal penalty (maximum of {@link Offering#getMaxPenalty()} among 668 * requested courses) 669 * @return maximal penalty 670 */ 671 public double getMaxPenalty() { 672 if (iCachedMaxPenalty == null) { 673 double max = Double.MIN_VALUE; 674 for (Course course : iCourses) { 675 max = Math.max(max, course.getOffering().getMaxPenalty()); 676 } 677 iCachedMaxPenalty = new Double(max); 678 } 679 return iCachedMaxPenalty.doubleValue(); 680 } 681 682 /** Clear cached min/max penalties and cached bound */ 683 public void clearCache() { 684 iCachedMaxPenalty = null; 685 iCachedMinPenalty = null; 686 } 687 688 /** 689 * Estimated bound for this request -- it estimates the smallest value among 690 * all possible enrollments 691 */ 692 @Override 693 public double getBound() { 694 return - getWeight() * ((StudentSectioningModel)getModel()).getStudentWeights().getBound(this); 695 /* 696 if (iCachedBound == null) { 697 iCachedBound = new Double(-Math.pow(Enrollment.sPriorityWeight, getPriority()) 698 * (isAlternative() ? Enrollment.sAlterativeWeight : 1.0) 699 * Math.pow(Enrollment.sInitialWeight, (getInitialAssignment() == null ? 0 : 1)) 700 * Math.pow(Enrollment.sSelectedWeight, (iSelectedChoices.isEmpty() ? 0 : 1)) 701 * Math.pow(Enrollment.sWaitlistedWeight, (iWaitlistedChoices.isEmpty() ? 0 : 1)) 702 * 703 // Math.max(Enrollment.sMinWeight,getWeight()) * 704 (getStudent().isDummy() ? Student.sDummyStudentWeight : 1.0) 705 * Enrollment.normalizePenalty(getMinPenalty())); 706 } 707 return iCachedBound.doubleValue(); 708 */ 709 } 710 711 /** Return true if request is assigned. */ 712 @Override 713 public boolean isAssigned(Assignment<Request, Enrollment> assignment) { 714 Enrollment e = assignment.getValue(this); 715 return e != null && !e.getAssignments().isEmpty(); 716 } 717 718 @Override 719 public boolean equals(Object o) { 720 return super.equals(o) && (o instanceof CourseRequest); 721 } 722 723 /** 724 * Get reservations for this course requests 725 * @param course given course 726 * @return reservations for this course requests and the given course 727 */ 728 public List<Reservation> getReservations(Course course) { 729 if (iReservations == null) 730 iReservations = new HashMap<Course, List<Reservation>>(); 731 List<Reservation> reservations = iReservations.get(course); 732 if (reservations == null) { 733 reservations = new ArrayList<Reservation>(); 734 boolean mustBeUsed = false; 735 for (Reservation r: course.getOffering().getReservations()) { 736 if (!r.isApplicable(getStudent())) continue; 737 if (!mustBeUsed && r.mustBeUsed()) { reservations.clear(); mustBeUsed = true; } 738 if (mustBeUsed && !r.mustBeUsed()) continue; 739 reservations.add(r); 740 } 741 iReservations.put(course, reservations); 742 } 743 return reservations; 744 } 745 private Map<Course, List<Reservation>> iReservations = null; 746 747 /** 748 * Get reservations for this course requests ordered using {@link Reservation#compareTo(Assignment, Reservation)} 749 * @param course given course 750 * @return reservations for this course requests and the given course 751 */ 752 public TreeSet<Reservation> getSortedReservations(Assignment<Request, Enrollment> assignment, Course course) { 753 TreeSet<Reservation> reservations = new TreeSet<Reservation>(new AssignmentComparator<Reservation, Request, Enrollment>(assignment)); 754 reservations.addAll(getReservations(course)); 755 return reservations; 756 } 757 758 /** 759 * Return true if there is a reservation for a course of this request 760 * @return true if there is a reservation for a course of this request 761 */ 762 public boolean hasReservations() { 763 for (Course course: getCourses()) 764 if (!getReservations(course).isEmpty()) 765 return true; 766 return false; 767 } 768 769 /** 770 * Clear reservation information that was cached on this section 771 */ 772 public void clearReservationCache() { 773 if (iReservations != null) iReservations.clear(); 774 } 775}