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 sameGroup += g.getEnrollmentSpread(a, this); 405 groupCount ++; 406 } 407 if (groupCount > 0) 408 ret += "/g:" + sDF.format(sameGroup / groupCount); 409 } 410 if (getRequest() instanceof CourseRequest) { 411 ret += " "; 412 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 413 SctAssignment assignment = i.next(); 414 ret += assignment + (i.hasNext() ? ", " : ""); 415 } 416 } 417 if (getReservation() != null) ret = "(r) " + ret; 418 return ret; 419 } 420 421 @Override 422 public String toString() { 423 if (getAssignments().isEmpty()) return "not assigned"; 424 String ret = sDF.format(getRequest().getBound()) + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty())); 425 if (getRequest() instanceof CourseRequest) { 426 ret += " "; 427 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 428 SctAssignment assignment = i.next(); 429 ret += assignment + (i.hasNext() ? ", " : ""); 430 } 431 } 432 if (getReservation() != null) ret = "(r) " + ret; 433 if (getRequest().isMPP()) ret += " [i" + sDF.format(100.0 * percentInitial()) + "/t" + sDF.format(100.0 * percentSameTime()) + "]"; 434 return ret; 435 } 436 437 @Override 438 public boolean equals(Object o) { 439 if (o == null || !(o instanceof Enrollment)) 440 return false; 441 Enrollment e = (Enrollment) o; 442 if (!ToolBox.equals(getConfig(), e.getConfig())) 443 return false; 444 if (!ToolBox.equals(getRequest(), e.getRequest())) 445 return false; 446 if (!ToolBox.equals(getAssignments(), e.getAssignments())) 447 return false; 448 return true; 449 } 450 451 /** Distance conflicts, in which this enrollment is involved. 452 * @param assignment current assignment 453 * @return distance conflicts 454 **/ 455 public Set<DistanceConflict.Conflict> distanceConflicts(Assignment<Request, Enrollment> assignment) { 456 if (!isCourseRequest()) 457 return null; 458 if (getRequest().getModel() instanceof StudentSectioningModel) { 459 DistanceConflict dc = ((StudentSectioningModel) getRequest().getModel()).getDistanceConflict(); 460 if (dc == null) return null; 461 return dc.allConflicts(assignment, this); 462 } else 463 return null; 464 } 465 466 /** Time overlapping conflicts, in which this enrollment is involved. 467 * @param assignment current assignment 468 * @return time overlapping conflicts 469 **/ 470 public Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts(Assignment<Request, Enrollment> assignment) { 471 if (getRequest().getModel() instanceof StudentSectioningModel) { 472 TimeOverlapsCounter toc = ((StudentSectioningModel) getRequest().getModel()).getTimeOverlaps(); 473 if (toc == null) 474 return null; 475 return toc.allConflicts(assignment, this); 476 } else 477 return null; 478 } 479 480 /** 481 * Return enrollment priority 482 * @return zero for the course, one for the first alternative, two for the second alternative 483 */ 484 public int getPriority() { 485 return iPriority; 486 } 487 488 /** 489 * Return total number of slots of all sections in the enrollment. 490 * @return number of slots used 491 */ 492 public int getNrSlots() { 493 int ret = 0; 494 for (SctAssignment a: getAssignments()) { 495 if (a.getTime() != null) ret += a.getTime().getLength() * a.getTime().getNrMeetings(); 496 } 497 return ret; 498 } 499 500 /** 501 * Return reservation used for this enrollment 502 * @return used reservation 503 */ 504 public Reservation getReservation() { return iReservation; } 505 506 /** 507 * Set reservation for this enrollment 508 * @param reservation used reservation 509 */ 510 public void setReservation(Reservation reservation) { iReservation = reservation; } 511 512 /** 513 * Time stamp of the enrollment 514 * @return enrollment time stamp 515 */ 516 public Long getTimeStamp() { 517 return iTimeStamp; 518 } 519 520 /** 521 * Time stamp of the enrollment 522 * @param timeStamp enrollment time stamp 523 */ 524 public void setTimeStamp(Long timeStamp) { 525 iTimeStamp = timeStamp; 526 } 527 528 /** 529 * Approval of the enrollment (only used by the online student sectioning) 530 * @return consent approval 531 */ 532 public String getApproval() { 533 return iApproval; 534 } 535 536 /** 537 * Approval of the enrollment (only used by the online student sectioning) 538 * @param approval consent approval 539 */ 540 public void setApproval(String approval) { 541 iApproval = approval; 542 } 543 544 /** 545 * True if this enrollment can overlap with other enrollments of the student. 546 * @return can overlap with other enrollments of the student 547 */ 548 public boolean isAllowOverlap() { 549 return (getReservation() != null && getReservation().isAllowOverlap()); 550 } 551 552 /** 553 * 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) 554 * @return enrollment limit 555 */ 556 public int getLimit() { 557 if (!isCourseRequest()) return -1; // free time requests have no limit 558 Integer limit = null; 559 for (Section section: getSections()) 560 if (section.getLimit() >= 0) { 561 if (limit == null) 562 limit = section.getLimit(); 563 else 564 limit = Math.min(limit, section.getLimit()); 565 } 566 return (limit == null ? -1 : limit); 567 } 568 569}