001package org.cpsolver.studentsct.model; 002 003import java.text.DecimalFormat; 004import java.util.HashSet; 005import java.util.Iterator; 006import java.util.Set; 007 008import org.cpsolver.ifs.assignment.Assignment; 009import org.cpsolver.ifs.model.Value; 010import org.cpsolver.ifs.util.ToolBox; 011import org.cpsolver.studentsct.StudentSectioningModel; 012import org.cpsolver.studentsct.extension.DistanceConflict; 013import org.cpsolver.studentsct.extension.TimeOverlapsCounter; 014import org.cpsolver.studentsct.reservation.Reservation; 015 016 017/** 018 * Representation of an enrollment of a student into a course. A student needs 019 * to be enrolled in a section of each subpart of a selected configuration. When 020 * parent-child relation is defined among sections, if a student is enrolled in 021 * a section that has a parent section defined, he/she has be enrolled in the 022 * parent section as well. Also, the selected sections cannot overlap in time. <br> 023 * <br> 024 * 025 * @version StudentSct 1.3 (Student Sectioning)<br> 026 * Copyright (C) 2007 - 2014 Tomas Muller<br> 027 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 028 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 029 * <br> 030 * This library is free software; you can redistribute it and/or modify 031 * it under the terms of the GNU Lesser General Public License as 032 * published by the Free Software Foundation; either version 3 of the 033 * License, or (at your option) any later version. <br> 034 * <br> 035 * This library is distributed in the hope that it will be useful, but 036 * WITHOUT ANY WARRANTY; without even the implied warranty of 037 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 038 * Lesser General Public License for more details. <br> 039 * <br> 040 * You should have received a copy of the GNU Lesser General Public 041 * License along with this library; if not see 042 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 043 */ 044 045public class Enrollment extends Value<Request, Enrollment> { 046 private static DecimalFormat sDF = new DecimalFormat("0.000"); 047 private Request iRequest = null; 048 private Config iConfig = null; 049 private Course iCourse = null; 050 private Set<? extends SctAssignment> iAssignments = null; 051 private Double iCachedPenalty = null; 052 private int iPriority = 0; 053 private Reservation iReservation = null; 054 private Long iTimeStamp = null; 055 private String iApproval = null; 056 057 /** 058 * Constructor 059 * 060 * @param request 061 * course / free time request 062 * @param priority 063 * zero for the course, one for the first alternative, two for the second alternative 064 * @param course 065 * selected course 066 * @param config 067 * selected configuration 068 * @param assignments 069 * valid list of sections 070 * @param reservation used reservation 071 */ 072 public Enrollment(Request request, int priority, Course course, Config config, Set<? extends SctAssignment> assignments, Reservation reservation) { 073 super(request); 074 iRequest = request; 075 iConfig = config; 076 iAssignments = assignments; 077 iPriority = priority; 078 iCourse = course; 079 if (iConfig != null && iCourse == null) 080 for (Course c: ((CourseRequest)iRequest).getCourses()) { 081 if (c.getOffering().getConfigs().contains(iConfig)) { 082 iCourse = c; 083 break; 084 } 085 } 086 iReservation = reservation; 087 } 088 089 /** 090 * Constructor 091 * 092 * @param request 093 * course / free time request 094 * @param priority 095 * zero for the course, one for the first alternative, two for the second alternative 096 * @param config 097 * selected configuration 098 * @param assignments 099 * valid list of sections 100 * @param assignment current assignment (to guess the reservation) 101 */ 102 public Enrollment(Request request, int priority, Config config, Set<? extends SctAssignment> assignments, Assignment<Request, Enrollment> assignment) { 103 this(request, priority, null, config, assignments, null); 104 if (assignments != null && assignment != null) 105 guessReservation(assignment, true); 106 } 107 108 /** 109 * Guess the reservation based on the enrollment 110 * @param assignment current assignment 111 * @param onlyAvailable use only reservation that have some space left in them 112 */ 113 public void guessReservation(Assignment<Request, Enrollment> assignment, boolean onlyAvailable) { 114 if (iCourse != null) { 115 Reservation best = null; 116 for (Reservation reservation: ((CourseRequest)iRequest).getReservations(iCourse)) { 117 if (reservation.isIncluded(this)) { 118 if (onlyAvailable && reservation.getContext(assignment).getReservedAvailableSpace(assignment, iRequest) < iRequest.getWeight() && !reservation.canBatchAssignOverLimit()) 119 continue; 120 if (best == null || best.getPriority() > reservation.getPriority()) { 121 best = reservation; 122 } else if (best.getPriority() == reservation.getPriority() && 123 best.getContext(assignment).getReservedAvailableSpace(assignment, iRequest) < reservation.getContext(assignment).getReservedAvailableSpace(assignment, iRequest)) { 124 best = reservation; 125 } 126 } 127 } 128 iReservation = best; 129 } 130 } 131 132 /** Student 133 * @return student 134 **/ 135 public Student getStudent() { 136 return iRequest.getStudent(); 137 } 138 139 /** Request 140 * @return request 141 **/ 142 public Request getRequest() { 143 return iRequest; 144 } 145 146 /** True if the request is course request 147 * @return true if the request if course request 148 **/ 149 public boolean isCourseRequest() { 150 return iConfig != null; 151 } 152 153 /** Offering of the course request 154 * @return offering of the course request 155 **/ 156 public Offering getOffering() { 157 return (iConfig == null ? null : iConfig.getOffering()); 158 } 159 160 /** Config of the course request 161 * @return config of the course request 162 **/ 163 public Config getConfig() { 164 return iConfig; 165 } 166 167 /** Course of the course request 168 * @return course of the course request 169 **/ 170 public Course getCourse() { 171 return iCourse; 172 } 173 174 /** List of assignments (selected sections) 175 * @return assignments (selected sections) 176 **/ 177 @SuppressWarnings("unchecked") 178 public Set<SctAssignment> getAssignments() { 179 return (Set<SctAssignment>) iAssignments; 180 } 181 182 /** List of sections (only for course request) 183 * @return selected sections 184 **/ 185 @SuppressWarnings("unchecked") 186 public Set<Section> getSections() { 187 if (isCourseRequest()) 188 return (Set<Section>) iAssignments; 189 return new HashSet<Section>(); 190 } 191 192 /** True when this enrollment is overlapping with the given enrollment 193 * @param enrl other enrollment 194 * @return true if there is an overlap 195 **/ 196 public boolean isOverlapping(Enrollment enrl) { 197 if (enrl == null || isAllowOverlap() || enrl.isAllowOverlap()) 198 return false; 199 for (SctAssignment a : getAssignments()) { 200 if (a.isOverlapping(enrl.getAssignments())) 201 return true; 202 } 203 return false; 204 } 205 206 /** Percent of sections that are wait-listed 207 * @return percent of sections that are wait-listed 208 **/ 209 public double percentWaitlisted() { 210 if (!isCourseRequest()) 211 return 0.0; 212 CourseRequest courseRequest = (CourseRequest) getRequest(); 213 int nrWaitlisted = 0; 214 for (Section section : getSections()) { 215 if (courseRequest.isWaitlisted(section)) 216 nrWaitlisted++; 217 } 218 return ((double) nrWaitlisted) / getAssignments().size(); 219 } 220 221 /** Percent of sections that are selected 222 * @return percent of sections that are selected 223 **/ 224 public double percentSelected() { 225 if (!isCourseRequest()) 226 return 0.0; 227 CourseRequest courseRequest = (CourseRequest) getRequest(); 228 int nrSelected = 0; 229 for (Section section : getSections()) { 230 if (courseRequest.isSelected(section)) 231 nrSelected++; 232 } 233 return ((double) nrSelected) / getAssignments().size(); 234 } 235 236 /** Percent of sections that are initial 237 * @return percent of sections that of the initial enrollment 238 **/ 239 public double percentInitial() { 240 if (!isCourseRequest()) 241 return 0.0; 242 if (getRequest().getInitialAssignment() == null) 243 return 0.0; 244 Enrollment inital = getRequest().getInitialAssignment(); 245 int nrInitial = 0; 246 for (Section section : getSections()) { 247 if (inital.getAssignments().contains(section)) 248 nrInitial++; 249 } 250 return ((double) nrInitial) / getAssignments().size(); 251 } 252 253 /** Percent of sections that have same time as the initial assignment 254 * @return percent of sections that have same time as the initial assignment 255 **/ 256 public double percentSameTime() { 257 if (!isCourseRequest()) 258 return 0.0; 259 Enrollment ie = getRequest().getInitialAssignment(); 260 if (ie != null) { 261 int nrInitial = 0; 262 sections: for (Section section : getSections()) { 263 for (Section initial: ie.getSections()) { 264 if (section.getSubpart().getInstructionalType().equals(initial.getSubpart().getInstructionalType()) && section.sameTime(initial)) { 265 nrInitial ++; 266 continue sections; 267 } 268 } 269 } 270 return ((double) nrInitial) / getAssignments().size(); 271 } 272 Set<Choice> selected = ((CourseRequest)getRequest()).getSelectedChoices(); 273 if (!selected.isEmpty()) { 274 int nrInitial = 0; 275 sections: for (Section section : getSections()) { 276 for (Choice choice: selected) { 277 if (section.getChoice().sameTime(choice)) { 278 nrInitial ++; 279 continue sections; 280 } 281 282 } 283 } 284 return ((double) nrInitial) / getAssignments().size(); 285 } 286 return 0.0; 287 } 288 289 /** True if all the sections are wait-listed 290 * @return all the sections are wait-listed 291 **/ 292 public boolean isWaitlisted() { 293 if (!isCourseRequest()) 294 return false; 295 CourseRequest courseRequest = (CourseRequest) getRequest(); 296 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 297 Section section = (Section) i.next(); 298 if (!courseRequest.isWaitlisted(section)) 299 return false; 300 } 301 return true; 302 } 303 304 /** True if all the sections are selected 305 * @return all the sections are selected 306 **/ 307 public boolean isSelected() { 308 if (!isCourseRequest()) 309 return false; 310 CourseRequest courseRequest = (CourseRequest) getRequest(); 311 for (Section section : getSections()) { 312 if (!courseRequest.isSelected(section)) 313 return false; 314 } 315 return true; 316 } 317 318 /** 319 * Enrollment penalty -- sum of section penalties (see 320 * {@link Section#getPenalty()}) 321 * @return online penalty 322 */ 323 public double getPenalty() { 324 if (iCachedPenalty == null) { 325 double penalty = 0.0; 326 if (isCourseRequest()) { 327 for (Section section : getSections()) { 328 penalty += section.getPenalty(); 329 } 330 } 331 iCachedPenalty = new Double(penalty / getAssignments().size()); 332 } 333 return iCachedPenalty.doubleValue(); 334 } 335 336 /** Enrollment value */ 337 @Override 338 public double toDouble(Assignment<Request, Enrollment> assignment) { 339 return toDouble(assignment, true); 340 } 341 342 /** Enrollment value 343 * @param assignment current assignment 344 * @param precise if false, distance conflicts and time overlaps are ignored (i.e., much faster, but less precise computation) 345 * @return enrollment penalty 346 **/ 347 public double toDouble(Assignment<Request, Enrollment> assignment, boolean precise) { 348 if (precise) 349 return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(assignment, this, distanceConflicts(assignment), timeOverlappingConflicts(assignment)); 350 else { 351 Double value = (assignment == null ? null : variable().getContext(assignment).getLastWeight()); 352 if (value != null) return - value; 353 return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(assignment, this); 354 } 355 } 356 357 /** Enrollment name */ 358 @Override 359 public String getName() { 360 if (getRequest() instanceof CourseRequest) { 361 Course course = null; 362 CourseRequest courseRequest = (CourseRequest) getRequest(); 363 for (Course c : courseRequest.getCourses()) { 364 if (c.getOffering().getConfigs().contains(getConfig())) { 365 course = c; 366 break; 367 } 368 } 369 String ret = (course == null ? getConfig() == null ? "" : getConfig().getName() : course.getName()); 370 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 371 Section assignment = (Section) i.next(); 372 ret += "\n " + assignment.getLongName(true) + (i.hasNext() ? "," : ""); 373 } 374 return ret; 375 } else if (getRequest() instanceof FreeTimeRequest) { 376 return "Free Time " + ((FreeTimeRequest) getRequest()).getTime().getLongName(true); 377 } else { 378 String ret = ""; 379 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 380 SctAssignment assignment = i.next(); 381 ret += assignment.toString() + (i.hasNext() ? "," : ""); 382 if (i.hasNext()) 383 ret += "\n "; 384 } 385 return ret; 386 } 387 } 388 389 public String toString(Assignment<Request, Enrollment> a) { 390 if (getAssignments().isEmpty()) return "not assigned"; 391 Set<DistanceConflict.Conflict> dc = distanceConflicts(a); 392 Set<TimeOverlapsCounter.Conflict> toc = timeOverlappingConflicts(a); 393 int share = 0; 394 if (toc != null) 395 for (TimeOverlapsCounter.Conflict c: toc) 396 share += c.getShare(); 397 String ret = toDouble(a) + "/" + sDF.format(getRequest().getBound()) 398 + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty())) 399 + (dc == null || dc.isEmpty() ? "" : "/dc:" + dc.size()) 400 + (share <= 0 ? "" : "/toc:" + share); 401 if (getRequest() instanceof CourseRequest) { 402 double sameGroup = 0.0; int groupCount = 0; 403 for (RequestGroup g: ((CourseRequest)getRequest()).getRequestGroups()) { 404 if (g.getCourse().equals(getCourse())) { 405 sameGroup += g.getEnrollmentSpread(a, this, 1.0, 0.0); 406 groupCount ++; 407 } 408 } 409 if (groupCount > 0) 410 ret += "/g:" + sDF.format(sameGroup / groupCount); 411 } 412 if (getRequest() instanceof CourseRequest) { 413 ret += " "; 414 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 415 SctAssignment assignment = i.next(); 416 ret += assignment + (i.hasNext() ? ", " : ""); 417 } 418 } 419 if (getReservation() != null) ret = "(r) " + ret; 420 return ret; 421 } 422 423 @Override 424 public String toString() { 425 if (getAssignments().isEmpty()) return "not assigned"; 426 String ret = sDF.format(getRequest().getBound()) + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty())); 427 if (getRequest() instanceof CourseRequest) { 428 ret += " "; 429 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 430 SctAssignment assignment = i.next(); 431 ret += assignment + (i.hasNext() ? ", " : ""); 432 } 433 } 434 if (getReservation() != null) ret = "(r) " + ret; 435 if (getRequest().isMPP()) ret += " [i" + sDF.format(100.0 * percentInitial()) + "/t" + sDF.format(100.0 * percentSameTime()) + "]"; 436 return ret; 437 } 438 439 @Override 440 public boolean equals(Object o) { 441 if (o == null || !(o instanceof Enrollment)) 442 return false; 443 Enrollment e = (Enrollment) o; 444 if (!ToolBox.equals(getConfig(), e.getConfig())) 445 return false; 446 if (!ToolBox.equals(getRequest(), e.getRequest())) 447 return false; 448 if (!ToolBox.equals(getAssignments(), e.getAssignments())) 449 return false; 450 return true; 451 } 452 453 /** Distance conflicts, in which this enrollment is involved. 454 * @param assignment current assignment 455 * @return distance conflicts 456 **/ 457 public Set<DistanceConflict.Conflict> distanceConflicts(Assignment<Request, Enrollment> assignment) { 458 if (!isCourseRequest()) 459 return null; 460 if (getRequest().getModel() instanceof StudentSectioningModel) { 461 DistanceConflict dc = ((StudentSectioningModel) getRequest().getModel()).getDistanceConflict(); 462 if (dc == null) return null; 463 return dc.allConflicts(assignment, this); 464 } else 465 return null; 466 } 467 468 /** Time overlapping conflicts, in which this enrollment is involved. 469 * @param assignment current assignment 470 * @return time overlapping conflicts 471 **/ 472 public Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts(Assignment<Request, Enrollment> assignment) { 473 if (getRequest().getModel() instanceof StudentSectioningModel) { 474 TimeOverlapsCounter toc = ((StudentSectioningModel) getRequest().getModel()).getTimeOverlaps(); 475 if (toc == null) 476 return null; 477 return toc.allConflicts(assignment, this); 478 } else 479 return null; 480 } 481 482 /** 483 * Return enrollment priority 484 * @return zero for the course, one for the first alternative, two for the second alternative 485 */ 486 public int getPriority() { 487 return iPriority; 488 } 489 490 /** 491 * Return total number of slots of all sections in the enrollment. 492 * @return number of slots used 493 */ 494 public int getNrSlots() { 495 int ret = 0; 496 for (SctAssignment a: getAssignments()) { 497 if (a.getTime() != null) ret += a.getTime().getLength() * a.getTime().getNrMeetings(); 498 } 499 return ret; 500 } 501 502 /** 503 * Return reservation used for this enrollment 504 * @return used reservation 505 */ 506 public Reservation getReservation() { return iReservation; } 507 508 /** 509 * Set reservation for this enrollment 510 * @param reservation used reservation 511 */ 512 public void setReservation(Reservation reservation) { iReservation = reservation; } 513 514 /** 515 * Time stamp of the enrollment 516 * @return enrollment time stamp 517 */ 518 public Long getTimeStamp() { 519 return iTimeStamp; 520 } 521 522 /** 523 * Time stamp of the enrollment 524 * @param timeStamp enrollment time stamp 525 */ 526 public void setTimeStamp(Long timeStamp) { 527 iTimeStamp = timeStamp; 528 } 529 530 /** 531 * Approval of the enrollment (only used by the online student sectioning) 532 * @return consent approval 533 */ 534 public String getApproval() { 535 return iApproval; 536 } 537 538 /** 539 * Approval of the enrollment (only used by the online student sectioning) 540 * @param approval consent approval 541 */ 542 public void setApproval(String approval) { 543 iApproval = approval; 544 } 545 546 /** 547 * True if this enrollment can overlap with other enrollments of the student. 548 * @return can overlap with other enrollments of the student 549 */ 550 public boolean isAllowOverlap() { 551 return (getReservation() != null && getReservation().isAllowOverlap()); 552 } 553 554 /** 555 * Enrollment limit, i.e., the number of students that would be able to get into the offering using this enrollment (if all the sections are empty) 556 * @return enrollment limit 557 */ 558 public int getLimit() { 559 if (!isCourseRequest()) return -1; // free time requests have no limit 560 Integer limit = null; 561 for (Section section: getSections()) 562 if (section.getLimit() >= 0) { 563 if (limit == null) 564 limit = section.getLimit(); 565 else 566 limit = Math.min(limit, section.getLimit()); 567 } 568 return (limit == null ? -1 : limit); 569 } 570 571}