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