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 boolean canAssignOverTheLimit = (variable().getModel() == null || ((StudentSectioningModel)variable().getModel()).getReservationCanAssignOverTheLimit()); 117 for (Reservation reservation: ((CourseRequest)iRequest).getReservations(iCourse)) { 118 if (reservation.isIncluded(this)) { 119 if (onlyAvailable && reservation.getContext(assignment).getReservedAvailableSpace(assignment, iRequest) < iRequest.getWeight() && 120 (!reservation.canAssignOverLimit() || !canAssignOverTheLimit)) 121 continue; 122 if (best == null || best.getPriority() > reservation.getPriority()) { 123 best = reservation; 124 } else if (best.getPriority() == reservation.getPriority() && 125 best.getContext(assignment).getReservedAvailableSpace(assignment, iRequest) < reservation.getContext(assignment).getReservedAvailableSpace(assignment, iRequest)) { 126 best = reservation; 127 } 128 } 129 } 130 iReservation = best; 131 } 132 } 133 134 /** Student 135 * @return student 136 **/ 137 public Student getStudent() { 138 return iRequest.getStudent(); 139 } 140 141 /** Request 142 * @return request 143 **/ 144 public Request getRequest() { 145 return iRequest; 146 } 147 148 /** True if the request is course request 149 * @return true if the request if course request 150 **/ 151 public boolean isCourseRequest() { 152 return iConfig != null; 153 } 154 155 /** Offering of the course request 156 * @return offering of the course request 157 **/ 158 public Offering getOffering() { 159 return (iConfig == null ? null : iConfig.getOffering()); 160 } 161 162 /** Config of the course request 163 * @return config of the course request 164 **/ 165 public Config getConfig() { 166 return iConfig; 167 } 168 169 /** Course of the course request 170 * @return course of the course request 171 **/ 172 public Course getCourse() { 173 return iCourse; 174 } 175 176 /** List of assignments (selected sections) 177 * @return assignments (selected sections) 178 **/ 179 @SuppressWarnings("unchecked") 180 public Set<SctAssignment> getAssignments() { 181 return (Set<SctAssignment>) iAssignments; 182 } 183 184 /** List of sections (only for course request) 185 * @return selected sections 186 **/ 187 @SuppressWarnings("unchecked") 188 public Set<Section> getSections() { 189 if (isCourseRequest()) 190 return (Set<Section>) iAssignments; 191 return new HashSet<Section>(); 192 } 193 194 /** True when this enrollment is overlapping with the given enrollment 195 * @param enrl other enrollment 196 * @return true if there is an overlap 197 **/ 198 public boolean isOverlapping(Enrollment enrl) { 199 if (enrl == null || isAllowOverlap() || enrl.isAllowOverlap()) 200 return false; 201 for (SctAssignment a : getAssignments()) { 202 if (a.isOverlapping(enrl.getAssignments())) 203 return true; 204 } 205 return false; 206 } 207 208 /** Percent of sections that are wait-listed 209 * @return percent of sections that are wait-listed 210 **/ 211 public double percentWaitlisted() { 212 if (!isCourseRequest()) 213 return 0.0; 214 CourseRequest courseRequest = (CourseRequest) getRequest(); 215 int nrWaitlisted = 0; 216 for (Section section : getSections()) { 217 if (courseRequest.isWaitlisted(section)) 218 nrWaitlisted++; 219 } 220 return ((double) nrWaitlisted) / getAssignments().size(); 221 } 222 223 /** Percent of sections that are selected 224 * @return percent of sections that are selected 225 **/ 226 public double percentSelected() { 227 if (!isCourseRequest()) 228 return 0.0; 229 CourseRequest courseRequest = (CourseRequest) getRequest(); 230 int nrSelected = 0; 231 for (Section section : getSections()) { 232 if (courseRequest.isSelected(section)) 233 nrSelected++; 234 } 235 return ((double) nrSelected) / getAssignments().size(); 236 } 237 238 /** Percent of sections that are initial 239 * @return percent of sections that of the initial enrollment 240 **/ 241 public double percentInitial() { 242 if (!isCourseRequest()) 243 return 0.0; 244 if (getRequest().getInitialAssignment() == null) 245 return 0.0; 246 Enrollment inital = getRequest().getInitialAssignment(); 247 int nrInitial = 0; 248 for (Section section : getSections()) { 249 if (inital.getAssignments().contains(section)) 250 nrInitial++; 251 } 252 return ((double) nrInitial) / getAssignments().size(); 253 } 254 255 /** True if all the sections are wait-listed 256 * @return all the sections are wait-listed 257 **/ 258 public boolean isWaitlisted() { 259 if (!isCourseRequest()) 260 return false; 261 CourseRequest courseRequest = (CourseRequest) getRequest(); 262 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 263 Section section = (Section) i.next(); 264 if (!courseRequest.isWaitlisted(section)) 265 return false; 266 } 267 return true; 268 } 269 270 /** True if all the sections are selected 271 * @return all the sections are selected 272 **/ 273 public boolean isSelected() { 274 if (!isCourseRequest()) 275 return false; 276 CourseRequest courseRequest = (CourseRequest) getRequest(); 277 for (Section section : getSections()) { 278 if (!courseRequest.isSelected(section)) 279 return false; 280 } 281 return true; 282 } 283 284 /** 285 * Enrollment penalty -- sum of section penalties (see 286 * {@link Section#getPenalty()}) 287 * @return online penalty 288 */ 289 public double getPenalty() { 290 if (iCachedPenalty == null) { 291 double penalty = 0.0; 292 if (isCourseRequest()) { 293 for (Section section : getSections()) { 294 penalty += section.getPenalty(); 295 } 296 } 297 iCachedPenalty = new Double(penalty / getAssignments().size()); 298 } 299 return iCachedPenalty.doubleValue(); 300 } 301 302 /** Enrollment value */ 303 @Override 304 public double toDouble(Assignment<Request, Enrollment> assignment) { 305 return toDouble(assignment, true); 306 } 307 308 /** Enrollment value 309 * @param assignment current assignment 310 * @param precise if false, distance conflicts and time overlaps are ignored (i.e., much faster, but less precise computation) 311 * @return enrollment penalty 312 **/ 313 public double toDouble(Assignment<Request, Enrollment> assignment, boolean precise) { 314 if (precise) 315 return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(assignment, this, distanceConflicts(assignment), timeOverlappingConflicts(assignment)); 316 else { 317 Double value = (assignment == null ? null : variable().getContext(assignment).getLastWeight()); 318 if (value != null) return - value; 319 return - getRequest().getWeight() * ((StudentSectioningModel)variable().getModel()).getStudentWeights().getWeight(assignment, this); 320 } 321 } 322 323 /** Enrollment name */ 324 @Override 325 public String getName() { 326 if (getRequest() instanceof CourseRequest) { 327 Course course = null; 328 CourseRequest courseRequest = (CourseRequest) getRequest(); 329 for (Course c : courseRequest.getCourses()) { 330 if (c.getOffering().getConfigs().contains(getConfig())) { 331 course = c; 332 break; 333 } 334 } 335 String ret = (course == null ? getConfig() == null ? "" : getConfig().getName() : course.getName()); 336 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 337 Section assignment = (Section) i.next(); 338 ret += "\n " + assignment.getLongName(true) + (i.hasNext() ? "," : ""); 339 } 340 return ret; 341 } else if (getRequest() instanceof FreeTimeRequest) { 342 return "Free Time " + ((FreeTimeRequest) getRequest()).getTime().getLongName(true); 343 } else { 344 String ret = ""; 345 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 346 SctAssignment assignment = i.next(); 347 ret += assignment.toString() + (i.hasNext() ? "," : ""); 348 if (i.hasNext()) 349 ret += "\n "; 350 } 351 return ret; 352 } 353 } 354 355 public String toString(Assignment<Request, Enrollment> a) { 356 if (getAssignments().isEmpty()) return "not assigned"; 357 Set<DistanceConflict.Conflict> dc = distanceConflicts(a); 358 Set<TimeOverlapsCounter.Conflict> toc = timeOverlappingConflicts(a); 359 int share = 0; 360 if (toc != null) 361 for (TimeOverlapsCounter.Conflict c: toc) 362 share += c.getShare(); 363 String ret = sDF.format(toDouble(a)) + "/" + sDF.format(getRequest().getBound()) 364 + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty())) 365 + (dc == null || dc.isEmpty() ? "" : "/dc:" + dc.size()) 366 + (share <= 0 ? "" : "/toc:" + share); 367 if (getRequest() instanceof CourseRequest) { 368 ret += " "; 369 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 370 SctAssignment assignment = i.next(); 371 ret += assignment + (i.hasNext() ? ", " : ""); 372 } 373 } 374 if (getReservation() != null) ret = "(r) " + ret; 375 return ret; 376 } 377 378 @Override 379 public String toString() { 380 if (getAssignments().isEmpty()) return "not assigned"; 381 String ret = sDF.format(getRequest().getBound()) + (getPenalty() == 0.0 ? "" : "/" + sDF.format(getPenalty())); 382 if (getRequest() instanceof CourseRequest) { 383 ret += " "; 384 for (Iterator<? extends SctAssignment> i = getAssignments().iterator(); i.hasNext();) { 385 SctAssignment assignment = i.next(); 386 ret += assignment + (i.hasNext() ? ", " : ""); 387 } 388 } 389 if (getReservation() != null) ret = "(r) " + ret; 390 return ret; 391 } 392 393 @Override 394 public boolean equals(Object o) { 395 if (o == null || !(o instanceof Enrollment)) 396 return false; 397 Enrollment e = (Enrollment) o; 398 if (!ToolBox.equals(getConfig(), e.getConfig())) 399 return false; 400 if (!ToolBox.equals(getRequest(), e.getRequest())) 401 return false; 402 if (!ToolBox.equals(getAssignments(), e.getAssignments())) 403 return false; 404 return true; 405 } 406 407 /** Distance conflicts, in which this enrollment is involved. 408 * @param assignment current assignment 409 * @return distance conflicts 410 **/ 411 public Set<DistanceConflict.Conflict> distanceConflicts(Assignment<Request, Enrollment> assignment) { 412 if (!isCourseRequest()) 413 return null; 414 if (getRequest().getModel() instanceof StudentSectioningModel) { 415 DistanceConflict dc = ((StudentSectioningModel) getRequest().getModel()).getDistanceConflict(); 416 if (dc == null) return null; 417 return dc.allConflicts(assignment, this); 418 } else 419 return null; 420 } 421 422 /** Time overlapping conflicts, in which this enrollment is involved. 423 * @param assignment current assignment 424 * @return time overlapping conflicts 425 **/ 426 public Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts(Assignment<Request, Enrollment> assignment) { 427 if (getRequest().getModel() instanceof StudentSectioningModel) { 428 TimeOverlapsCounter toc = ((StudentSectioningModel) getRequest().getModel()).getTimeOverlaps(); 429 if (toc == null) 430 return null; 431 return toc.allConflicts(assignment, this); 432 } else 433 return null; 434 } 435 436 /** 437 * Return enrollment priority 438 * @return zero for the course, one for the first alternative, two for the second alternative 439 */ 440 public int getPriority() { 441 return iPriority; 442 } 443 444 /** 445 * Return total number of slots of all sections in the enrollment. 446 * @return number of slots used 447 */ 448 public int getNrSlots() { 449 int ret = 0; 450 for (SctAssignment a: getAssignments()) { 451 if (a.getTime() != null) ret += a.getTime().getLength() * a.getTime().getNrMeetings(); 452 } 453 return ret; 454 } 455 456 /** 457 * Return reservation used for this enrollment 458 * @return used reservation 459 */ 460 public Reservation getReservation() { return iReservation; } 461 462 /** 463 * Set reservation for this enrollment 464 * @param reservation used reservation 465 */ 466 public void setReservation(Reservation reservation) { iReservation = reservation; } 467 468 /** 469 * Time stamp of the enrollment 470 * @return enrollment time stamp 471 */ 472 public Long getTimeStamp() { 473 return iTimeStamp; 474 } 475 476 /** 477 * Time stamp of the enrollment 478 * @param timeStamp enrollment time stamp 479 */ 480 public void setTimeStamp(Long timeStamp) { 481 iTimeStamp = timeStamp; 482 } 483 484 /** 485 * Approval of the enrollment (only used by the online student sectioning) 486 * @return consent approval 487 */ 488 public String getApproval() { 489 return iApproval; 490 } 491 492 /** 493 * Approval of the enrollment (only used by the online student sectioning) 494 * @param approval consent approval 495 */ 496 public void setApproval(String approval) { 497 iApproval = approval; 498 } 499 500 /** 501 * True if this enrollment can overlap with other enrollments of the student. 502 * @return can overlap with other enrollments of the student 503 */ 504 public boolean isAllowOverlap() { 505 return (getReservation() != null && getReservation().isAllowOverlap()); 506 } 507 508 /** 509 * 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) 510 * @return enrollment limit 511 */ 512 public int getLimit() { 513 if (!isCourseRequest()) return -1; // free time requests have no limit 514 Integer limit = null; 515 for (Section section: getSections()) 516 if (section.getLimit() >= 0) { 517 if (limit == null) 518 limit = section.getLimit(); 519 else 520 limit = Math.min(limit, section.getLimit()); 521 } 522 return (limit == null ? -1 : limit); 523 } 524 525}