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