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