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