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