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; 024import org.cpsolver.studentsct.reservation.Restriction; 025 026 027/** 028 * Representation of a request of a student for one or more course. A student 029 * requests one of the given courses, preferably the first one. <br> 030 * <br> 031 * 032 * @author Tomáš Müller 033 * @version StudentSct 1.3 (Student Sectioning)<br> 034 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 035 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 036 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 037 * <br> 038 * This library is free software; you can redistribute it and/or modify 039 * it under the terms of the GNU Lesser General Public License as 040 * published by the Free Software Foundation; either version 3 of the 041 * License, or (at your option) any later version. <br> 042 * <br> 043 * This library is distributed in the hope that it will be useful, but 044 * WITHOUT ANY WARRANTY; without even the implied warranty of 045 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 046 * Lesser General Public License for more details. <br> 047 * <br> 048 * You should have received a copy of the GNU Lesser General Public 049 * License along with this library; if not see 050 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 051 */ 052public class CourseRequest extends Request { 053 private static DecimalFormat sDF = new DecimalFormat("0.000"); 054 private List<Course> iCourses = null; 055 private Set<Choice> iWaitlistedChoices = new HashSet<Choice>(); 056 private Set<Choice> iSelectedChoices = new HashSet<Choice>(); 057 private Set<Choice> iRequiredChoices = new HashSet<Choice>(); 058 private boolean iWaitlist = false; 059 private Long iTimeStamp = null; 060 private Double iCachedMinPenalty = null, iCachedMaxPenalty = null; 061 public static boolean sSameTimePrecise = false; 062 private Set<RequestGroup> iRequestGroups = new HashSet<RequestGroup>(); 063 private Enrollment iFixed = null; 064 065 /** 066 * Constructor 067 * 068 * @param id 069 * request unique id 070 * @param priority 071 * request priority 072 * @param alternative 073 * true if the request is alternative (alternative request can be 074 * assigned instead of a non-alternative course requests, if it 075 * is left unassigned) 076 * @param student 077 * appropriate student 078 * @param courses 079 * list of requested courses (in the correct order -- first is 080 * the requested course, second is the first alternative, etc.) 081 * @param waitlist 082 * time stamp of the request if the student can be put on a wait-list (no alternative 083 * course request will be given instead) 084 * @param critical 085 * is the course request is critical for the student in order to move forward in their degree 086 * @param timeStamp request time stamp 087 */ 088 public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses, boolean waitlist, boolean critical, Long timeStamp) { 089 super(id, priority, alternative, student); 090 iCourses = new ArrayList<Course>(courses); 091 for (Course course: iCourses) 092 course.getRequests().add(this); 093 iWaitlist = waitlist; 094 setRequestPriority(critical ? RequestPriority.Critical : RequestPriority.Normal); 095 iTimeStamp = timeStamp; 096 } 097 098 /** 099 * Constructor 100 * 101 * @param id 102 * request unique id 103 * @param priority 104 * request priority 105 * @param alternative 106 * true if the request is alternative (alternative request can be 107 * assigned instead of a non-alternative course requests, if it 108 * is left unassigned) 109 * @param student 110 * appropriate student 111 * @param courses 112 * list of requested courses (in the correct order -- first is 113 * the requested course, second is the first alternative, etc.) 114 * @param waitlist 115 * time stamp of the request if the student can be put on a wait-list (no alternative 116 * course request will be given instead) 117 * @param importance 118 * request priority 119 * @param timeStamp request time stamp 120 */ 121 public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses, boolean waitlist, RequestPriority importance, Long timeStamp) { 122 super(id, priority, alternative, student); 123 iCourses = new ArrayList<Course>(courses); 124 for (Course course: iCourses) 125 course.getRequests().add(this); 126 iWaitlist = waitlist; 127 setRequestPriority(importance); 128 iTimeStamp = timeStamp; 129 } 130 131 /** 132 * Constructor 133 * 134 * @param id 135 * request unique id 136 * @param priority 137 * request priority 138 * @param alternative 139 * true if the request is alternative (alternative request can be 140 * assigned instead of a non-alternative course requests, if it 141 * is left unassigned) 142 * @param student 143 * appropriate student 144 * @param courses 145 * list of requested courses (in the correct order -- first is 146 * the requested course, second is the first alternative, etc.) 147 * @param waitlist 148 * time stamp of the request if the student can be put on a wait-list (no alternative 149 * course request will be given instead) 150 * @param timeStamp request time stamp 151 */ 152 public CourseRequest(long id, int priority, boolean alternative, Student student, java.util.List<Course> courses, boolean waitlist, Long timeStamp) { 153 this(id, priority, alternative, student, courses, waitlist, false, timeStamp); 154 } 155 156 /** 157 * List of requested courses (in the correct order -- first is the requested 158 * course, second is the first alternative, etc.) 159 * @return requested course offerings 160 */ 161 public List<Course> getCourses() { 162 return iCourses; 163 } 164 165 /** 166 * Create enrollment for the given list of sections. The list of sections 167 * needs to be correct, i.e., a section for each subpart of a configuration 168 * of one of the requested courses. 169 * @param sections selected sections 170 * @param reservation selected reservation 171 * @return enrollment 172 */ 173 public Enrollment createEnrollment(Set<? extends SctAssignment> sections, Reservation reservation) { 174 if (sections == null || sections.isEmpty()) 175 return null; 176 Config config = ((Section) sections.iterator().next()).getSubpart().getConfig(); 177 Course course = null; 178 for (Course c: iCourses) { 179 if (c.getOffering().getConfigs().contains(config)) { 180 course = c; 181 break; 182 } 183 } 184 return new Enrollment(this, iCourses.indexOf(course), course, config, sections, reservation); 185 } 186 187 /** 188 * Create enrollment for the given list of sections. The list of sections 189 * needs to be correct, i.e., a section for each subpart of a configuration 190 * of one of the requested courses. 191 * @param course selected course 192 * @param sections selected sections 193 * @param reservation selected reservation 194 * @return enrollment 195 */ 196 public Enrollment createEnrollment(Course course, Set<? extends SctAssignment> sections, Reservation reservation) { 197 if (sections == null || sections.isEmpty()) 198 return null; 199 Config config = ((Section) sections.iterator().next()).getSubpart().getConfig(); 200 return new Enrollment(this, iCourses.indexOf(course), course, config, sections, reservation); 201 } 202 203 /** 204 * Create enrollment for the given list of sections. The list of sections 205 * needs to be correct, i.e., a section for each subpart of a configuration 206 * of one of the requested courses. 207 * @param assignment current assignment (to guess the reservation) 208 * @param sections selected sections 209 * @return enrollment 210 */ 211 public Enrollment createEnrollment(Assignment<Request, Enrollment> assignment, Set<? extends SctAssignment> sections) { 212 Enrollment ret = createEnrollment(sections, null); 213 ret.guessReservation(assignment, true); 214 return ret; 215 216 } 217 218 /** 219 * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit. 220 * @return maximal domain size, -1 if unlimited 221 */ 222 protected int getMaxDomainSize() { 223 StudentSectioningModel model = (StudentSectioningModel) getModel(); 224 return model == null ? -1 : model.getMaxDomainSize(); 225 } 226 227 /** 228 * Return all possible enrollments. 229 */ 230 @Override 231 public List<Enrollment> computeEnrollments(Assignment<Request, Enrollment> assignment) { 232 List<Enrollment> ret = new ArrayList<Enrollment>(); 233 if (isFixed()) { 234 ret.add(getFixedValue()); 235 return ret; 236 } 237 if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) { 238 ret.add(getInitialAssignment()); 239 return ret; 240 } 241 int idx = 0; 242 for (Course course : iCourses) { 243 for (Config config : course.getOffering().getConfigs()) { 244 computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false, 245 false, false, getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), false); 246 } 247 idx++; 248 } 249 return ret; 250 } 251 252 /** 253 * Return a subset of all enrollments -- randomly select only up to 254 * limitEachConfig enrollments of each config. 255 * @param assignment current assignment 256 * @param limitEachConfig maximal number of enrollments in each configuration 257 * @return computed enrollments 258 */ 259 public List<Enrollment> computeRandomEnrollments(Assignment<Request, Enrollment> assignment, int limitEachConfig) { 260 List<Enrollment> ret = new ArrayList<Enrollment>(); 261 if (isFixed()) { 262 ret.add(getFixedValue()); 263 return ret; 264 } 265 if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) { 266 ret.add(getInitialAssignment()); 267 return ret; 268 } 269 int idx = 0; 270 for (Course course : iCourses) { 271 for (Config config : course.getOffering().getConfigs()) { 272 computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, false, 273 false, true, (limitEachConfig <= 0 ? limitEachConfig : ret.size() + limitEachConfig), false); 274 } 275 idx++; 276 } 277 return ret; 278 } 279 280 /** 281 * Return true if the both sets of sections contain sections of the same 282 * subparts, and each pair of sections of the same subpart is offered at the 283 * same time. 284 */ 285 private boolean sameTimes(Set<Section> sections1, Set<Section> sections2) { 286 for (Section s1 : sections1) { 287 Section s2 = null; 288 for (Section s : sections2) { 289 if (s.getSubpart().equals(s1.getSubpart())) { 290 s2 = s; 291 break; 292 } 293 } 294 if (s2 == null) 295 return false; 296 if (!ToolBox.equals(s1.getTime(), s2.getTime())) 297 return false; 298 } 299 return true; 300 } 301 302 /** 303 * Recursive computation of enrollments 304 * 305 * @param enrollments 306 * list of enrollments to be returned 307 * @param priority 308 * zero for the course, one for the first alternative, two for the second alternative 309 * @param penalty 310 * penalty of the selected sections 311 * @param course 312 * selected course 313 * @param config 314 * selected configuration 315 * @param sections 316 * sections selected so far 317 * @param idx 318 * index of the subparts (a section of 0..idx-1 subparts has been 319 * already selected) 320 * @param availableOnly 321 * only use available sections 322 * @param skipSameTime 323 * for each possible times, pick only one section 324 * @param selectedOnly 325 * select only sections that are selected ( 326 * {@link CourseRequest#isSelected(Section)} is true) 327 * @param random 328 * pick sections in a random order (useful when limit is used) 329 * @param limit 330 * when above zero, limit the number of selected enrollments to 331 * this limit 332 * @param ignoreDisabled 333 * are sections that are disabled for student scheduling allowed to be used 334 * @param reservations 335 * list of applicable reservations 336 */ 337 private void computeEnrollments(Assignment<Request, Enrollment> assignment, Collection<Enrollment> enrollments, int priority, double penalty, Course course, Config config, 338 HashSet<Section> sections, int idx, boolean availableOnly, boolean skipSameTime, boolean selectedOnly, 339 boolean random, int limit, boolean checkParent) { 340 if (limit > 0 && enrollments.size() >= limit) 341 return; 342 if (checkParent && course.hasParent()) { 343 Course parent = course.getParent(); 344 for (Request r: getStudent().getRequests()) { 345 if (r.hasCourse(parent)) { 346 Enrollment e = assignment.getValue(r); 347 if (e == null || !parent.equals(e.getCourse())) return; 348 } 349 } 350 } 351 if (idx == 0) { // run only once for each configuration 352 if (isNotAllowed(course, config)) return; 353 boolean canOverLimit = false; 354 if (availableOnly) { 355 for (Reservation r: getReservations(course)) { 356 if (!r.canBatchAssignOverLimit()) continue; 357 if (r.neverIncluded()) continue; 358 if (!r.getConfigs().isEmpty() && !r.getConfigs().contains(config)) continue; 359 if (r.getReservedAvailableSpace(assignment, this) < getWeight()) continue; 360 if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue; 361 canOverLimit = true; break; 362 } 363 } 364 if (!canOverLimit) { 365 if (availableOnly && config.getLimit() >= 0 && ConfigLimit.getEnrollmentWeight(assignment, config, this) > config.getLimit()) 366 return; 367 if (availableOnly && course.getLimit() >= 0 && CourseLimit.getEnrollmentWeight(assignment, course, this) > course.getLimit()) 368 return; 369 if (config.getOffering().hasReservations()) { 370 boolean hasReservation = false, hasConfigReservation = false, reservationMustBeUsed = false; 371 for (Reservation r: getReservations(course)) { 372 if (r.mustBeUsed()) reservationMustBeUsed = true; 373 if (availableOnly && r.getReservedAvailableSpace(assignment, this) < getWeight()) continue; 374 if (availableOnly && r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue; 375 if (r.neverIncluded()) { 376 } else if (r.getConfigs().isEmpty()) { 377 hasReservation = true; 378 } else if (r.getConfigs().contains(config)) { 379 hasReservation = true; 380 hasConfigReservation = true; 381 } else if (!r.areRestrictionsInclusive()) { 382 hasReservation = true; 383 } 384 } 385 if (!hasConfigReservation && config.getTotalUnreservedSpace() < getWeight()) 386 return; 387 if (!hasReservation && config.getOffering().getTotalUnreservedSpace() < getWeight()) 388 return; 389 if (availableOnly && !hasReservation && config.getOffering().getUnreservedSpace(assignment, this) < getWeight()) 390 return; 391 if (availableOnly && !hasConfigReservation && config.getUnreservedSpace(assignment, this) < getWeight()) 392 return; 393 if (!hasReservation && reservationMustBeUsed) 394 return; 395 } 396 } 397 } 398 if (config.getSubparts().size() == idx) { 399 if (skipSameTime && sSameTimePrecise) { 400 boolean waitListedOrSelected = false; 401 if (!getSelectedChoices().isEmpty() || !getWaitlistedChoices().isEmpty()) { 402 for (Section section : sections) { 403 if (isWaitlisted(section) || isSelected(section)) { 404 waitListedOrSelected = true; 405 break; 406 } 407 } 408 } 409 if (!waitListedOrSelected) { 410 for (Enrollment enrollment : enrollments) { 411 if (sameTimes(enrollment.getSections(), sections)) 412 return; 413 } 414 } 415 } 416 Enrollment e = new Enrollment(this, priority, course, config, new HashSet<SctAssignment>(sections), null); 417 if (isNotAllowed(e)) { 418 } else if (!config.getOffering().hasReservations()) { 419 enrollments.add(e); 420 } else { 421 boolean mustHaveReservation = config.getOffering().getTotalUnreservedSpace() < getWeight(); 422 boolean mustHaveConfigReservation = config.getTotalUnreservedSpace() < getWeight(); 423 boolean mustHaveSectionReservation = false; 424 boolean containDisabledSection = false; 425 for (Section s: sections) { 426 if (s.getTotalUnreservedSpace() < getWeight()) { 427 mustHaveSectionReservation = true; 428 } 429 if (!getStudent().isAllowDisabled() && !s.isEnabled(getStudent())) { 430 containDisabledSection = true; 431 } 432 } 433 boolean canOverLimit = false; 434 if (availableOnly) { 435 for (Reservation r: getReservations(course)) { 436 if (!r.canBatchAssignOverLimit() || !r.isIncluded(e)) continue; 437 if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue; 438 if (containDisabledSection && !r.isAllowDisabled()) continue; 439 enrollments.add(new Enrollment(this, priority, null, config, new HashSet<SctAssignment>(sections), r)); 440 canOverLimit = true; 441 } 442 } 443 if (!canOverLimit) { 444 boolean reservationMustBeUsed = false; 445 reservations: for (Reservation r: (availableOnly ? getSortedReservations(assignment, course) : getReservations(course))) { 446 if (r.mustBeUsed()) reservationMustBeUsed = true; 447 if (!r.isIncluded(e)) continue; 448 if (availableOnly && r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue; 449 if (mustHaveConfigReservation && r.getConfigs().isEmpty()) continue; 450 if (mustHaveSectionReservation) 451 for (Section s: sections) 452 if (r.getSections(s.getSubpart()) == null && s.getTotalUnreservedSpace() < getWeight()) continue reservations; 453 if (containDisabledSection && !r.isAllowDisabled()) continue; 454 enrollments.add(new Enrollment(this, priority, null, config, new HashSet<SctAssignment>(sections), r)); 455 if (availableOnly) return; // only one available reservation suffice (the best matching one) 456 } 457 // a case w/o reservation 458 if (!(mustHaveReservation || mustHaveConfigReservation || mustHaveSectionReservation) && 459 !(availableOnly && config.getOffering().getUnreservedSpace(assignment, this) < getWeight()) && 460 !reservationMustBeUsed && !containDisabledSection) { 461 enrollments.add(new Enrollment(this, priority, !getReservations(course).isEmpty(), null, config, new HashSet<SctAssignment>(sections), null)); 462 } 463 } 464 } 465 } else { 466 Subpart subpart = config.getSubparts().get(idx); 467 HashSet<TimeLocation> times = (skipSameTime ? new HashSet<TimeLocation>() : null); 468 List<Section> sectionsThisSubpart = subpart.getSections(); 469 if (skipSameTime) { 470 sectionsThisSubpart = new ArrayList<Section>(subpart.getSections()); 471 Collections.sort(sectionsThisSubpart, new AssignmentComparator<Section, Request, Enrollment>(assignment)); 472 } 473 List<Section> matchingSectionsThisSubpart = new ArrayList<Section>(subpart.getSections().size()); 474 boolean hasChildren = !subpart.getChildren().isEmpty(); 475 for (Section section : sectionsThisSubpart) { 476 if (section.isCancelled()) 477 continue; 478 if (!isRequired(section)) 479 continue; 480 if (getInitialAssignment() != null && (getModel() != null && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) && 481 !getInitialAssignment().getAssignments().contains(section)) 482 continue; 483 if (isFixed() && !getFixedValue().getAssignments().contains(section)) 484 continue; 485 if (section.getParent() != null && !sections.contains(section.getParent())) 486 continue; 487 if (section.isOverlapping(sections)) 488 continue; 489 if (selectedOnly && hasSelection(section) && !isSelected(section)) 490 continue; 491 if (isNotAllowed(course, section)) 492 continue; 493 if (!getStudent().isAvailable(section)) { 494 boolean canOverlap = false; 495 for (Reservation r: getReservations(course)) { 496 if (!r.isAllowOverlap()) continue; 497 if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue; 498 if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue; 499 canOverlap = true; break; 500 } 501 if (!canOverlap) continue; 502 } 503 boolean canOverLimit = false; 504 if (availableOnly) { 505 for (Reservation r: getReservations(course)) { 506 if (!r.canBatchAssignOverLimit()) continue; 507 if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue; 508 if (r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue; 509 canOverLimit = true; break; 510 } 511 } 512 if (!canOverLimit) { 513 if (availableOnly && section.getLimit() >= 0 514 && SectionLimit.getEnrollmentWeight(assignment, section, this) > section.getLimit()) 515 continue; 516 if (config.getOffering().hasReservations()) { 517 boolean hasReservation = false, hasSectionReservation = false, reservationMustBeUsed = false; 518 for (Reservation r: getReservations(course)) { 519 if (r.mustBeUsed()) reservationMustBeUsed = true; 520 if (availableOnly && r.getReservedAvailableSpace(assignment, config, this) < getWeight()) continue; 521 if (r.getSections(subpart) == null) { 522 hasReservation = true; 523 } else if (r.getSections(subpart).contains(section)) { 524 hasReservation = true; 525 hasSectionReservation = true; 526 } 527 } 528 if (!hasSectionReservation && section.getTotalUnreservedSpace() < getWeight()) 529 continue; 530 if (availableOnly && !hasSectionReservation && section.getUnreservedSpace(assignment, this) < getWeight()) 531 continue; 532 if (!hasReservation && reservationMustBeUsed) 533 continue; 534 } 535 } 536 if (!getStudent().isAllowDisabled() && !section.isEnabled(getStudent())) { 537 boolean allowDisabled = false; 538 for (Reservation r: getReservations(course)) { 539 if (!r.isAllowDisabled()) continue; 540 if (r.getSections(subpart) != null && !r.getSections(subpart).contains(section)) continue; 541 if (!r.getConfigs().isEmpty() && !r.getConfigs().contains(config)) continue; 542 allowDisabled = true; break; 543 } 544 if (!allowDisabled) continue; 545 } 546 if (skipSameTime && section.getTime() != null && !hasChildren && !times.add(section.getTime()) && !isSelected(section) && !isWaitlisted(section) && 547 (section.getIgnoreConflictWithSectionIds() == null || section.getIgnoreConflictWithSectionIds().isEmpty())) 548 continue; 549 matchingSectionsThisSubpart.add(section); 550 } 551 if (random || limit > 0) { 552 sectionsThisSubpart = new ArrayList<Section>(sectionsThisSubpart); 553 Collections.shuffle(sectionsThisSubpart); 554 } 555 int i = 0; 556 for (Section section: matchingSectionsThisSubpart) { 557 sections.add(section); 558 computeEnrollments(assignment, enrollments, priority, penalty + section.getPenalty(), course, config, sections, idx + 1, 559 availableOnly, skipSameTime, selectedOnly, random, 560 limit < 0 ? limit : Math.max(1, limit * (1 + i) / matchingSectionsThisSubpart.size()), 561 checkParent); 562 sections.remove(section); 563 i++; 564 } 565 } 566 } 567 568 /** Return all enrollments that are available 569 * @param assignment current assignment 570 * @return all available enrollments 571 **/ 572 public List<Enrollment> getAvaiableEnrollments(Assignment<Request, Enrollment> assignment) { 573 return getAvaiableEnrollments(assignment, true); 574 } 575 576 /** Return all enrollments that are available 577 * @param assignment current assignment 578 * @return all available enrollments 579 **/ 580 public List<Enrollment> getAvaiableEnrollments(Assignment<Request, Enrollment> assignment, boolean checkParent) { 581 List<Enrollment> ret = new ArrayList<Enrollment>(); 582 if (isFixed()) { 583 ret.add(getFixedValue()); 584 return ret; 585 } 586 if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) { 587 ret.add(getInitialAssignment()); 588 return ret; 589 } 590 int idx = 0; 591 for (Course course : iCourses) { 592 for (Config config : course.getOffering().getConfigs()) { 593 computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, true, false, false, false, 594 getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), checkParent); 595 } 596 idx++; 597 } 598 return ret; 599 } 600 601 /** 602 * Return all enrollments of the first course that are selected ( 603 * {@link CourseRequest#isSelected(Section)} is true) 604 * 605 * @param assignment current assignment 606 * @param availableOnly 607 * pick only available sections 608 * @return selected enrollments 609 */ 610 public List<Enrollment> getSelectedEnrollments(Assignment<Request, Enrollment> assignment, boolean availableOnly) { 611 if (getSelectedChoices().isEmpty()) 612 return null; 613 List<Enrollment> enrollments = new ArrayList<Enrollment>(); 614 if (isFixed()) return enrollments; 615 if (getInitialAssignment() != null && getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) 616 return enrollments; 617 for (Course course : iCourses) { 618 boolean hasChoice = false; 619 for (Choice choice: getSelectedChoices()) 620 if (course.getOffering().equals(choice.getOffering())) { hasChoice = true; break; } 621 if (hasChoice) 622 for (Config config : course.getOffering().getConfigs()) { 623 computeEnrollments(assignment, enrollments, 0, 0, course, config, new HashSet<Section>(), 0, availableOnly, false, true, false, -1, false); 624 } 625 break; 626 } 627 return enrollments; 628 } 629 630 /** 631 * Return all enrollments that are available, pick only the first section of 632 * the sections with the same time (of each subpart, {@link Section} 633 * comparator is used) 634 * @param assignment current assignment 635 * @return available enrollments 636 */ 637 public List<Enrollment> getAvaiableEnrollmentsSkipSameTime(Assignment<Request, Enrollment> assignment) { 638 return getAvaiableEnrollmentsSkipSameTime(assignment, true); 639 } 640 641 /** 642 * Return all enrollments that are available, pick only the first section of 643 * the sections with the same time (of each subpart, {@link Section} 644 * comparator is used) 645 * @param assignment current assignment 646 * @param checkParent check course dependency with other assigned courses 647 * @return available enrollments 648 */ 649 public List<Enrollment> getAvaiableEnrollmentsSkipSameTime(Assignment<Request, Enrollment> assignment, boolean checkParent) { 650 List<Enrollment> ret = new ArrayList<Enrollment>(); 651 if (isFixed()) { 652 ret.add(getFixedValue()); 653 return ret; 654 } 655 if (getInitialAssignment() != null) { 656 ret.add(getInitialAssignment()); 657 if (getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) 658 return ret; 659 } 660 int idx = 0; 661 for (Course course : iCourses) { 662 boolean skipSameTime = true; 663 for (LinkedSections link: getStudent().getLinkedSections()) 664 if (link.getOfferings().contains(course.getOffering())) { skipSameTime = false; break; } 665 for (Config config : course.getOffering().getConfigs()) { 666 computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, true, skipSameTime, false, false, 667 getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), checkParent); 668 } 669 idx++; 670 } 671 return ret; 672 } 673 674 /** 675 * Return all possible enrollments, but pick only the first section of 676 * the sections with the same time (of each subpart, {@link Section} 677 * comparator is used). 678 * @param assignment current assignment 679 * @return computed enrollments 680 */ 681 public List<Enrollment> getEnrollmentsSkipSameTime(Assignment<Request, Enrollment> assignment) { 682 List<Enrollment> ret = new ArrayList<Enrollment>(); 683 if (isFixed()) { 684 ret.add(getFixedValue()); 685 return ret; 686 } 687 if (getInitialAssignment() != null) { 688 ret.add(getInitialAssignment()); 689 if (getModel() != null && ((StudentSectioningModel)getModel()).isMPP() && ((StudentSectioningModel)getModel()).getKeepInitialAssignments()) 690 return ret; 691 } 692 int idx = 0; 693 for (Course course : iCourses) { 694 for (Config config : course.getOffering().getConfigs()) { 695 boolean skipSameTime = true; 696 for (LinkedSections link: getStudent().getLinkedSections()) 697 if (link.getOfferings().contains(course.getOffering())) { skipSameTime = false; break; } 698 computeEnrollments(assignment, ret, idx, 0, course, config, new HashSet<Section>(), 0, false, skipSameTime, false, false, 699 getMaxDomainSize() <= 0 ? -1 : ret.size() + getMaxDomainSize(), false); 700 } 701 idx++; 702 } 703 return ret; 704 } 705 706 /** Wait-listed choices 707 * @return wait-listed choices 708 **/ 709 public Set<Choice> getWaitlistedChoices() { 710 return iWaitlistedChoices; 711 } 712 713 /** 714 * Return true when the given section is wait-listed (i.e., its choice is 715 * among wait-listed choices) 716 * @param section given section 717 * @return true if the given section matches the wait-listed choices 718 */ 719 public boolean isWaitlisted(Section section) { 720 for (Choice choice: iWaitlistedChoices) 721 if (choice.sameChoice(section)) return true; 722 return false; 723 } 724 725 /** Selected choices 726 * @return selected choices 727 **/ 728 public Set<Choice> getSelectedChoices() { 729 return iSelectedChoices; 730 } 731 732 /** 733 * Return true when the given section is selected (i.e., its choice is among 734 * selected choices) 735 * @param section given section 736 * @return true if the given section matches the selected choices 737 */ 738 public boolean isSelected(Section section) { 739 for (Choice choice: iSelectedChoices) 740 if (choice.sameSection(section) || choice.sameConfiguration(section)) return true; 741 return false; 742 } 743 744 /** 745 * Return true when the given section has a preference (i.e., there is a matching selection), 746 * or when there is a section preference for a different configuration 747 * @param section given section 748 * @return true if the there is a matching choice for the given section that is of the same offering 749 */ 750 public boolean hasSelection(Section section) { 751 boolean hasSectionChoices = false, hasSectionChoicesThisConfig = false; 752 for (Choice choice: iSelectedChoices) { 753 if (choice.sameOffering(section)) { 754 if (choice.isMatching(section)) return true; 755 if (choice.getSubpartId() != null) { 756 hasSectionChoices = true; 757 for (Subpart subpart: section.getSubpart().getConfig().getSubparts()) { 758 if (choice.getSubpartId().equals(subpart.getId())) { hasSectionChoicesThisConfig = true; } 759 } 760 } 761 } 762 } 763 return (hasSectionChoices && !hasSectionChoicesThisConfig); 764 } 765 766 /** 767 * Required choices 768 * @return required choices 769 */ 770 public Set<Choice> getRequiredChoices() { 771 return iRequiredChoices; 772 } 773 774 /** 775 * Return true when the given section is required (i.e., its choice is among required choices, or there are no requirements) 776 * @param section given section 777 * @return true if the given section matches the required choices 778 */ 779 public boolean isRequired(Section section) { 780 if (iRequiredChoices.isEmpty()) return true; 781 boolean hasConfig = false, hasMatchingConfig = false; 782 boolean hasSubpart = false, hasMatchingSection = false; 783 boolean hasSectionReq = false; 784 for (Choice choice: iRequiredChoices) { 785 // different offering -> skip 786 if (!choice.getOffering().equals(section.getSubpart().getConfig().getOffering())) continue; 787 // has config -> check config 788 if (choice.getConfigId() != null) { 789 hasConfig = true; 790 if (choice.sameConfiguration(section)) hasMatchingConfig = true; 791 } 792 // has section of the matching subpart -> check section 793 if (choice.getSubpartId() != null) { 794 hasSectionReq = true; 795 if (choice.getSubpartId().equals(section.getSubpart().getId())) { 796 hasSubpart = true; 797 if (choice.sameSection(section)) hasMatchingSection = true; 798 } else if (!hasMatchingConfig) { 799 for (Subpart subpart: section.getSubpart().getConfig().getSubparts()) { 800 if (choice.getSubpartId().equals(subpart.getId())) { 801 hasMatchingConfig = true; 802 break; 803 } 804 } 805 } 806 } 807 } 808 if (hasConfig && !hasMatchingConfig) return false; 809 if (hasSubpart && !hasMatchingSection) return false; 810 // no match, but there are section requirements for a different config -> not satisfied 811 if (!hasMatchingConfig && !hasMatchingSection && hasSectionReq) return false; 812 return true; 813 } 814 815 /** 816 * Request name: A for alternative, 1 + priority, (w) when wait-list, list of 817 * course names 818 */ 819 @Override 820 public String getName() { 821 String ret = (isAlternative() ? "A" : "") 822 + (1 + getPriority() + (isAlternative() ? -getStudent().nrRequests() : 0)) + ". " 823 + (getRequestPriority() != RequestPriority.Normal ? 824 (isWaitlist() ? "(" + getRequestPriority().getAbbreviation() + "w) " : "(" + getRequestPriority().getAbbreviation() + ") ") 825 : isWaitlist() ? "(w) " : ""); 826 int idx = 0; 827 for (Course course : iCourses) { 828 if (idx == 0) 829 ret += course.getName(); 830 else 831 ret += ", " + idx + ". alt " + course.getName(); 832 idx++; 833 } 834 if (getStudent().getExternalId() != null) 835 ret = "[" + getStudent().getExternalId() + "] " + ret; 836 return ret; 837 } 838 839 /** 840 * True if the student can be put on a wait-list (no alternative course 841 * request will be given instead) 842 * @return true if the request can be wait-listed 843 */ 844 public boolean isWaitlist() { 845 return iWaitlist; 846 } 847 848 /** 849 * True if the student can be put on a wait-list (no alternative course 850 * request will be given instead) 851 * @param waitlist true if the request can be wait-listed 852 */ 853 public void setWaitlist(boolean waitlist) { 854 iWaitlist = waitlist; 855 } 856 857 /** 858 * True if the course request is critical for the student in order to move forward in their degree 859 * @param critical true if the request is critical 860 */ 861 @Deprecated 862 public void setCritical(boolean critical) { 863 setRequestPriority(critical ? RequestPriority.Critical : RequestPriority.Normal); 864 } 865 866 /** 867 * Time stamp of the request 868 * @return request time stamp 869 */ 870 public Long getTimeStamp() { 871 return iTimeStamp; 872 } 873 874 @Override 875 public String toString() { 876 return getName() + (getWeight() != 1.0 ? " (W:" + sDF.format(getWeight()) + ")" : ""); 877 } 878 879 /** Return course of the requested courses with the given id 880 * @param courseId course offering id 881 * @return course of the given id 882 **/ 883 public Course getCourse(long courseId) { 884 for (Course course : iCourses) { 885 if (course.getId() == courseId) 886 return course; 887 } 888 return null; 889 } 890 891 /** Return configuration of the requested courses with the given id 892 * @param configId instructional offering configuration unique id 893 * @return config of the given id 894 **/ 895 public Config getConfig(long configId) { 896 for (Course course : iCourses) { 897 for (Config config : course.getOffering().getConfigs()) { 898 if (config.getId() == configId) 899 return config; 900 } 901 } 902 return null; 903 } 904 905 /** Return subpart of the requested courses with the given id 906 * @param subpartId scheduling subpart unique id 907 * @return subpart of the given id 908 **/ 909 public Subpart getSubpart(long subpartId) { 910 for (Course course : iCourses) { 911 for (Config config : course.getOffering().getConfigs()) { 912 for (Subpart subpart : config.getSubparts()) { 913 if (subpart.getId() == subpartId) 914 return subpart; 915 } 916 } 917 } 918 return null; 919 } 920 921 /** Return section of the requested courses with the given id 922 * @param sectionId class unique id 923 * @return section of the given id 924 **/ 925 public Section getSection(long sectionId) { 926 for (Course course : iCourses) { 927 for (Config config : course.getOffering().getConfigs()) { 928 for (Subpart subpart : config.getSubparts()) { 929 for (Section section : subpart.getSections()) { 930 if (section.getId() == sectionId) 931 return section; 932 } 933 } 934 } 935 } 936 return null; 937 } 938 939 /** 940 * Minimal penalty (minimum of {@link Offering#getMinPenalty()} among 941 * requested courses) 942 * @return minimal penalty 943 */ 944 public double getMinPenalty() { 945 if (iCachedMinPenalty == null) { 946 double min = Double.MAX_VALUE; 947 for (Course course : iCourses) { 948 min = Math.min(min, course.getOffering().getMinPenalty()); 949 } 950 iCachedMinPenalty = Double.valueOf(min); 951 } 952 return iCachedMinPenalty.doubleValue(); 953 } 954 955 /** 956 * Maximal penalty (maximum of {@link Offering#getMaxPenalty()} among 957 * requested courses) 958 * @return maximal penalty 959 */ 960 public double getMaxPenalty() { 961 if (iCachedMaxPenalty == null) { 962 double max = Double.MIN_VALUE; 963 for (Course course : iCourses) { 964 max = Math.max(max, course.getOffering().getMaxPenalty()); 965 } 966 iCachedMaxPenalty = Double.valueOf(max); 967 } 968 return iCachedMaxPenalty.doubleValue(); 969 } 970 971 /** Clear cached min/max penalties and cached bound */ 972 public void clearCache() { 973 iCachedMaxPenalty = null; 974 iCachedMinPenalty = null; 975 } 976 977 /** 978 * Estimated bound for this request -- it estimates the smallest value among 979 * all possible enrollments 980 */ 981 @Override 982 public double getBound() { 983 return - getWeight() * ((StudentSectioningModel)getModel()).getStudentWeights().getBound(this); 984 /* 985 if (iCachedBound == null) { 986 iCachedBound = Double.valueOf(-Math.pow(Enrollment.sPriorityWeight, getPriority()) 987 * (isAlternative() ? Enrollment.sAlterativeWeight : 1.0) 988 * Math.pow(Enrollment.sInitialWeight, (getInitialAssignment() == null ? 0 : 1)) 989 * Math.pow(Enrollment.sSelectedWeight, (iSelectedChoices.isEmpty() ? 0 : 1)) 990 * Math.pow(Enrollment.sWaitlistedWeight, (iWaitlistedChoices.isEmpty() ? 0 : 1)) 991 * 992 // Math.max(Enrollment.sMinWeight,getWeight()) * 993 (getStudent().isDummy() ? Student.sDummyStudentWeight : 1.0) 994 * Enrollment.normalizePenalty(getMinPenalty())); 995 } 996 return iCachedBound.doubleValue(); 997 */ 998 } 999 1000 /** Return true if request is assigned. */ 1001 @Override 1002 public boolean isAssigned(Assignment<Request, Enrollment> assignment) { 1003 Enrollment e = assignment.getValue(this); 1004 return e != null && !e.getAssignments().isEmpty(); 1005 } 1006 1007 @Override 1008 public boolean equals(Object o) { 1009 return super.equals(o) && (o instanceof CourseRequest); 1010 } 1011 1012 /** 1013 * Get reservations for this course requests 1014 * @param course given course 1015 * @return reservations for this course requests and the given course 1016 */ 1017 public synchronized List<Reservation> getReservations(Course course) { 1018 if (iReservations == null) 1019 iReservations = new HashMap<Course, List<Reservation>>(); 1020 List<Reservation> reservations = iReservations.get(course); 1021 if (reservations == null) { 1022 reservations = new ArrayList<Reservation>(); 1023 boolean mustBeUsed = false; 1024 for (Reservation r: course.getOffering().getReservations()) { 1025 if (!r.isApplicable(getStudent(), course)) continue; 1026 if (!mustBeUsed && r.mustBeUsed()) { reservations.clear(); mustBeUsed = true; } 1027 if (mustBeUsed && !r.mustBeUsed()) continue; 1028 reservations.add(r); 1029 } 1030 iReservations.put(course, reservations); 1031 } 1032 return reservations; 1033 } 1034 private Map<Course, List<Reservation>> iReservations = null; 1035 1036 /** 1037 * Get reservations for this course requests ordered using {@link Reservation#compareTo(Assignment, Reservation)} 1038 * @param course given course 1039 * @return reservations for this course requests and the given course 1040 */ 1041 public TreeSet<Reservation> getSortedReservations(Assignment<Request, Enrollment> assignment, Course course) { 1042 TreeSet<Reservation> reservations = new TreeSet<Reservation>(new AssignmentComparator<Reservation, Request, Enrollment>(assignment)); 1043 reservations.addAll(getReservations(course)); 1044 return reservations; 1045 } 1046 1047 /** 1048 * Return true if there is a reservation for a course of this request 1049 * @return true if there is a reservation for a course of this request 1050 */ 1051 public boolean hasReservations() { 1052 for (Course course: getCourses()) 1053 if (!getReservations(course).isEmpty()) 1054 return true; 1055 return false; 1056 } 1057 1058 /** 1059 * Clear reservation information that was cached on this section 1060 */ 1061 public synchronized void clearReservationCache() { 1062 if (iReservations != null) iReservations.clear(); 1063 } 1064 1065 /** 1066 * Get restrictions for this course requests 1067 * @param course given course 1068 * @return restrictions for this course requests and the given course 1069 */ 1070 public synchronized List<Restriction> getRestrictions(Course course) { 1071 if (iRestrictions == null) 1072 iRestrictions = new HashMap<Course, List<Restriction>>(); 1073 List<Restriction> restrictions = iRestrictions.get(course); 1074 if (restrictions == null) { 1075 restrictions = new ArrayList<Restriction>(); 1076 for (Restriction r: course.getOffering().getRestrictions()) { 1077 if (r.isApplicable(getStudent(), course)) 1078 restrictions.add(r); 1079 } 1080 iRestrictions.put(course, restrictions); 1081 } 1082 return restrictions; 1083 } 1084 private Map<Course, List<Restriction>> iRestrictions = null; 1085 1086 /** 1087 * Return true if there is a restriction for a course of this request 1088 * @return true if there is a restriction for a course of this request 1089 */ 1090 public boolean hasRestrictions(Course course) { 1091 return !getRestrictions(course).isEmpty(); 1092 } 1093 1094 /** 1095 * Return true when there are restrictions for a course of this course request and the given config does not meet any of them 1096 */ 1097 public boolean isNotAllowed(Course course, Config config) { 1098 List<Restriction> restrictions = getRestrictions(course); 1099 if (restrictions.isEmpty()) return false; 1100 for (Restriction r: restrictions) 1101 if (r.isIncluded(config)) return false; 1102 return true; 1103 } 1104 1105 /** 1106 * Return true when there are restrictions for a course of this course request and the given section does not meet any of them 1107 */ 1108 public boolean isNotAllowed(Course course, Section section) { 1109 List<Restriction> restrictions = getRestrictions(course); 1110 if (restrictions.isEmpty()) return false; 1111 for (Restriction r: restrictions) 1112 if (r.isIncluded(section)) return false; 1113 return true; 1114 } 1115 1116 /** 1117 * Return true when there are restrictions for a course of this course request and the given enrollment does not meet any of them 1118 */ 1119 public boolean isNotAllowed(Enrollment e) { 1120 List<Restriction> restrictions = getRestrictions(e.getCourse()); 1121 if (restrictions.isEmpty()) return false; 1122 for (Restriction r: restrictions) 1123 if (r.isIncluded(e)) return false; 1124 return true; 1125 } 1126 1127 /** 1128 * Clear restriction information that was cached on this request 1129 */ 1130 public synchronized void clearRestrictionCache() { 1131 if (iRestrictions != null) iRestrictions.clear(); 1132 } 1133 1134 /** 1135 * Return true if this request can track MPP 1136 * @return true if the request is course request and it either has an initial enrollment 1137 */ 1138 @Override 1139 public boolean isMPP() { 1140 StudentSectioningModel model = (StudentSectioningModel) getModel(); 1141 if (model == null || !model.isMPP()) return false; 1142 return !getStudent().isDummy() && getInitialAssignment() != null; 1143 } 1144 1145 /** 1146 * Return true if this request has any selection 1147 * @return true if the request is course request and has some selected choices. 1148 */ 1149 @Override 1150 public boolean hasSelection() { 1151 if (getStudent().isDummy() || getSelectedChoices().isEmpty()) return false; 1152 for (Choice choice: getSelectedChoices()) 1153 if (choice.getSectionId() != null || choice.getConfigId() != null) return true; 1154 return false; 1155 } 1156 1157 /** 1158 * Add request group to this request. 1159 * @param group request group to be added 1160 */ 1161 public void addRequestGroup(RequestGroup group) { 1162 iRequestGroups.add(group); 1163 group.addRequest(this); 1164 } 1165 1166 /** 1167 * Removed request group from this request. 1168 * @param group request group to be removed 1169 */ 1170 public void removeRequestGroup(RequestGroup group) { 1171 iRequestGroups.remove(group); 1172 group.removeRequest(this); 1173 } 1174 1175 /** 1176 * Lists request groups of this request 1177 * @return request groups of this course requests 1178 */ 1179 public Set<RequestGroup> getRequestGroups() { 1180 return iRequestGroups; 1181 } 1182 1183 @Override 1184 public void variableAssigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment enrollment) { 1185 super.variableAssigned(assignment, iteration, enrollment); 1186 for (RequestGroup g: getRequestGroups()) 1187 if (g.getCourse().equals(enrollment.getCourse())) 1188 g.assigned(assignment, enrollment); 1189 } 1190 1191 @Override 1192 public void variableUnassigned(Assignment<Request, Enrollment> assignment, long iteration, Enrollment enrollment) { 1193 super.variableUnassigned(assignment, iteration, enrollment); 1194 for (RequestGroup g: getRequestGroups()) 1195 if (g.getCourse().equals(enrollment.getCourse())) 1196 g.unassigned(assignment, enrollment); 1197 /* 1198 if (enrollment != null && enrollment.getCourse() != null && enrollment.getCourse().hasChildren()) { 1199 for (Request r: enrollment.getStudent().getRequests()) { 1200 if (r.equals(enrollment.getRequest())) continue; 1201 Enrollment e = assignment.getValue(r); 1202 if (e != null && e.getCourse() != null && enrollment.getCourse().equals(e.getCourse().getParent())) { 1203 assignment.unassign(iteration, r); 1204 } 1205 } 1206 }*/ 1207 } 1208 1209 @Override 1210 public float getMinCredit() { 1211 Float credit = null; 1212 for (Course course: getCourses()) { 1213 if (course.hasCreditValue() && (credit == null || credit > course.getCreditValue())) 1214 credit = course.getCreditValue(); 1215 for (Config config: course.getOffering().getConfigs()) { 1216 Float configCredit = config.getCreditValue(); 1217 if (configCredit != null && (credit == null || credit > configCredit)) 1218 credit = configCredit; 1219 } 1220 } 1221 return (credit == null ? 0 : credit.floatValue()); 1222 } 1223 1224 public boolean isFixed() { return iFixed != null; } 1225 public Enrollment getFixedValue() { return iFixed; } 1226 public void setFixedValue(Enrollment constant) { iFixed = constant; } 1227 1228 @Override 1229 public boolean hasCourse(Course course) { 1230 return iCourses.contains(course); 1231 } 1232 1233 @Override 1234 public boolean hasChildren() { 1235 for (Course course: iCourses) 1236 if (course.hasChildren()) return true; 1237 return false; 1238 } 1239}