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