001package org.cpsolver.studentsct; 002 003import java.text.DecimalFormat; 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.Comparator; 007import java.util.HashSet; 008import java.util.List; 009import java.util.Map; 010import java.util.Set; 011import java.util.TreeSet; 012 013import org.apache.log4j.Logger; 014import org.cpsolver.coursett.Constants; 015import org.cpsolver.ifs.assignment.Assignment; 016import org.cpsolver.ifs.assignment.InheritedAssignment; 017import org.cpsolver.ifs.assignment.OptimisticInheritedAssignment; 018import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext; 019import org.cpsolver.ifs.assignment.context.CanInheritContext; 020import org.cpsolver.ifs.assignment.context.ModelWithContext; 021import org.cpsolver.ifs.model.Constraint; 022import org.cpsolver.ifs.model.ConstraintListener; 023import org.cpsolver.ifs.model.InfoProvider; 024import org.cpsolver.ifs.model.Model; 025import org.cpsolver.ifs.solution.Solution; 026import org.cpsolver.ifs.util.DataProperties; 027import org.cpsolver.ifs.util.DistanceMetric; 028import org.cpsolver.studentsct.constraint.CancelledSections; 029import org.cpsolver.studentsct.constraint.ConfigLimit; 030import org.cpsolver.studentsct.constraint.CourseLimit; 031import org.cpsolver.studentsct.constraint.DisabledSections; 032import org.cpsolver.studentsct.constraint.FixInitialAssignments; 033import org.cpsolver.studentsct.constraint.LinkedSections; 034import org.cpsolver.studentsct.constraint.RequiredReservation; 035import org.cpsolver.studentsct.constraint.RequiredRestrictions; 036import org.cpsolver.studentsct.constraint.RequiredSections; 037import org.cpsolver.studentsct.constraint.ReservationLimit; 038import org.cpsolver.studentsct.constraint.SectionLimit; 039import org.cpsolver.studentsct.constraint.StudentConflict; 040import org.cpsolver.studentsct.constraint.StudentNotAvailable; 041import org.cpsolver.studentsct.extension.DistanceConflict; 042import org.cpsolver.studentsct.extension.StudentQuality; 043import org.cpsolver.studentsct.extension.TimeOverlapsCounter; 044import org.cpsolver.studentsct.model.Config; 045import org.cpsolver.studentsct.model.Course; 046import org.cpsolver.studentsct.model.CourseRequest; 047import org.cpsolver.studentsct.model.Enrollment; 048import org.cpsolver.studentsct.model.Offering; 049import org.cpsolver.studentsct.model.Request; 050import org.cpsolver.studentsct.model.RequestGroup; 051import org.cpsolver.studentsct.model.Section; 052import org.cpsolver.studentsct.model.Student; 053import org.cpsolver.studentsct.model.Subpart; 054import org.cpsolver.studentsct.model.Unavailability; 055import org.cpsolver.studentsct.model.Request.RequestPriority; 056import org.cpsolver.studentsct.model.Student.StudentPriority; 057import org.cpsolver.studentsct.reservation.Reservation; 058import org.cpsolver.studentsct.weights.PriorityStudentWeights; 059import org.cpsolver.studentsct.weights.StudentWeights; 060 061/** 062 * Student sectioning model. 063 * 064 * <br> 065 * <br> 066 * 067 * @version StudentSct 1.3 (Student Sectioning)<br> 068 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 069 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 070 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 071 * <br> 072 * This library is free software; you can redistribute it and/or modify 073 * it under the terms of the GNU Lesser General Public License as 074 * published by the Free Software Foundation; either version 3 of the 075 * License, or (at your option) any later version. <br> 076 * <br> 077 * This library is distributed in the hope that it will be useful, but 078 * WITHOUT ANY WARRANTY; without even the implied warranty of 079 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 080 * Lesser General Public License for more details. <br> 081 * <br> 082 * You should have received a copy of the GNU Lesser General Public 083 * License along with this library; if not see 084 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 085 */ 086public class StudentSectioningModel extends ModelWithContext<Request, Enrollment, StudentSectioningModel.StudentSectioningModelContext> implements CanInheritContext<Request, Enrollment, StudentSectioningModel.StudentSectioningModelContext> { 087 private static Logger sLog = Logger.getLogger(StudentSectioningModel.class); 088 protected static DecimalFormat sDecimalFormat = new DecimalFormat("0.00"); 089 private List<Student> iStudents = new ArrayList<Student>(); 090 private List<Offering> iOfferings = new ArrayList<Offering>(); 091 private List<LinkedSections> iLinkedSections = new ArrayList<LinkedSections>(); 092 private DataProperties iProperties; 093 private DistanceConflict iDistanceConflict = null; 094 private TimeOverlapsCounter iTimeOverlaps = null; 095 private StudentQuality iStudentQuality = null; 096 private int iNrDummyStudents = 0, iNrDummyRequests = 0; 097 private int[] iNrPriorityStudents = null; 098 private double iTotalDummyWeight = 0.0; 099 private double iTotalCRWeight = 0.0, iTotalDummyCRWeight = 0.0; 100 private double[] iTotalPriorityCRWeight = null; 101 private double[] iTotalCriticalCRWeight; 102 private double[][] iTotalPriorityCriticalCRWeight; 103 private double iTotalMPPCRWeight = 0.0; 104 private double iTotalSelCRWeight = 0.0; 105 private double iBestAssignedCourseRequestWeight = 0.0; 106 private StudentWeights iStudentWeights = null; 107 private boolean iReservationCanAssignOverTheLimit; 108 private boolean iMPP; 109 private boolean iKeepInitials; 110 protected double iProjectedStudentWeight = 0.0100; 111 private int iMaxDomainSize = -1; 112 113 114 /** 115 * Constructor 116 * 117 * @param properties 118 * configuration 119 */ 120 @SuppressWarnings("unchecked") 121 public StudentSectioningModel(DataProperties properties) { 122 super(); 123 iTotalCriticalCRWeight = new double[RequestPriority.values().length]; 124 iTotalPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length]; 125 for (int i = 0; i < RequestPriority.values().length; i++) { 126 iTotalCriticalCRWeight[i] = 0.0; 127 for (int j = 0; j < StudentPriority.values().length; j++) { 128 iTotalPriorityCriticalCRWeight[i][j] = 0.0; 129 } 130 } 131 iNrPriorityStudents = new int[StudentPriority.values().length]; 132 iTotalPriorityCRWeight = new double[StudentPriority.values().length]; 133 for (int i = 0; i < StudentPriority.values().length; i++) { 134 iNrPriorityStudents[i] = 0; 135 iTotalPriorityCRWeight[i] = 0.0; 136 } 137 iReservationCanAssignOverTheLimit = properties.getPropertyBoolean("Reservation.CanAssignOverTheLimit", false); 138 iMPP = properties.getPropertyBoolean("General.MPP", false); 139 iKeepInitials = properties.getPropertyBoolean("Sectioning.KeepInitialAssignments", false); 140 iStudentWeights = new PriorityStudentWeights(properties); 141 iMaxDomainSize = properties.getPropertyInt("Sectioning.MaxDomainSize", iMaxDomainSize); 142 if (properties.getPropertyBoolean("Sectioning.SectionLimit", true)) { 143 SectionLimit sectionLimit = new SectionLimit(properties); 144 addGlobalConstraint(sectionLimit); 145 if (properties.getPropertyBoolean("Sectioning.SectionLimit.Debug", false)) { 146 sectionLimit.addConstraintListener(new ConstraintListener<Request, Enrollment>() { 147 @Override 148 public void constraintBeforeAssigned(Assignment<Request, Enrollment> assignment, long iteration, Constraint<Request, Enrollment> constraint, Enrollment enrollment, Set<Enrollment> unassigned) { 149 if (enrollment.getStudent().isDummy()) 150 for (Enrollment conflict : unassigned) { 151 if (!conflict.getStudent().isDummy()) { 152 sLog.warn("Enrolment of a real student " + conflict.getStudent() + " is unassigned " 153 + "\n -- " + conflict + "\ndue to an enrollment of a dummy student " 154 + enrollment.getStudent() + " " + "\n -- " + enrollment); 155 } 156 } 157 } 158 159 @Override 160 public void constraintAfterAssigned(Assignment<Request, Enrollment> assignment, long iteration, Constraint<Request, Enrollment> constraint, Enrollment assigned, Set<Enrollment> unassigned) { 161 } 162 }); 163 } 164 } 165 if (properties.getPropertyBoolean("Sectioning.ConfigLimit", true)) { 166 ConfigLimit configLimit = new ConfigLimit(properties); 167 addGlobalConstraint(configLimit); 168 } 169 if (properties.getPropertyBoolean("Sectioning.CourseLimit", true)) { 170 CourseLimit courseLimit = new CourseLimit(properties); 171 addGlobalConstraint(courseLimit); 172 } 173 if (properties.getPropertyBoolean("Sectioning.ReservationLimit", true)) { 174 ReservationLimit reservationLimit = new ReservationLimit(properties); 175 addGlobalConstraint(reservationLimit); 176 } 177 if (properties.getPropertyBoolean("Sectioning.RequiredReservations", true)) { 178 RequiredReservation requiredReservation = new RequiredReservation(); 179 addGlobalConstraint(requiredReservation); 180 } 181 if (properties.getPropertyBoolean("Sectioning.CancelledSections", true)) { 182 CancelledSections cancelledSections = new CancelledSections(); 183 addGlobalConstraint(cancelledSections); 184 } 185 if (properties.getPropertyBoolean("Sectioning.StudentNotAvailable", true)) { 186 StudentNotAvailable studentNotAvailable = new StudentNotAvailable(); 187 addGlobalConstraint(studentNotAvailable); 188 } 189 if (properties.getPropertyBoolean("Sectioning.DisabledSections", true)) { 190 DisabledSections disabledSections = new DisabledSections(); 191 addGlobalConstraint(disabledSections); 192 } 193 if (properties.getPropertyBoolean("Sectioning.RequiredSections", true)) { 194 RequiredSections requiredSections = new RequiredSections(); 195 addGlobalConstraint(requiredSections); 196 } 197 if (properties.getPropertyBoolean("Sectioning.RequiredRestrictions", true)) { 198 RequiredRestrictions requiredRestrictions = new RequiredRestrictions(); 199 addGlobalConstraint(requiredRestrictions); 200 } 201 if (iMPP && iKeepInitials) { 202 addGlobalConstraint(new FixInitialAssignments()); 203 } 204 try { 205 Class<StudentWeights> studentWeightsClass = (Class<StudentWeights>)Class.forName(properties.getProperty("StudentWeights.Class", PriorityStudentWeights.class.getName())); 206 iStudentWeights = studentWeightsClass.getConstructor(DataProperties.class).newInstance(properties); 207 } catch (Exception e) { 208 sLog.error("Unable to create custom student weighting model (" + e.getMessage() + "), using default.", e); 209 iStudentWeights = new PriorityStudentWeights(properties); 210 } 211 iProjectedStudentWeight = properties.getPropertyDouble("StudentWeights.ProjectedStudentWeight", iProjectedStudentWeight); 212 iProperties = properties; 213 } 214 215 /** 216 * Return true if reservation that has {@link Reservation#canAssignOverLimit()} can assign enrollments over the limit 217 * @return true if reservation that has {@link Reservation#canAssignOverLimit()} can assign enrollments over the limit 218 */ 219 public boolean getReservationCanAssignOverTheLimit() { 220 return iReservationCanAssignOverTheLimit; 221 } 222 223 /** 224 * Return true if the problem is minimal perturbation problem 225 * @return true if MPP is enabled 226 */ 227 public boolean isMPP() { 228 return iMPP; 229 } 230 231 /** 232 * Return true if the inital assignments are to be kept unchanged 233 * @return true if the initial assignments are to be kept at all cost 234 */ 235 public boolean getKeepInitialAssignments() { 236 return iKeepInitials; 237 } 238 239 /** 240 * Return student weighting model 241 * @return student weighting model 242 */ 243 public StudentWeights getStudentWeights() { 244 return iStudentWeights; 245 } 246 247 /** 248 * Set student weighting model 249 * @param weights student weighting model 250 */ 251 public void setStudentWeights(StudentWeights weights) { 252 iStudentWeights = weights; 253 } 254 255 /** 256 * Students 257 * @return all students in the problem 258 */ 259 public List<Student> getStudents() { 260 return iStudents; 261 } 262 263 /** 264 * Add a student into the model 265 * @param student a student to be added into the problem 266 */ 267 public void addStudent(Student student) { 268 iStudents.add(student); 269 if (student.isDummy()) 270 iNrDummyStudents++; 271 iNrPriorityStudents[student.getPriority().ordinal()]++; 272 for (Request request : student.getRequests()) 273 addVariable(request); 274 if (getProperties().getPropertyBoolean("Sectioning.StudentConflict", true)) { 275 addConstraint(new StudentConflict(student)); 276 } 277 } 278 279 public int getNbrStudents(StudentPriority priority) { 280 return iNrPriorityStudents[priority.ordinal()]; 281 } 282 283 @Override 284 public void addVariable(Request request) { 285 super.addVariable(request); 286 if (request instanceof CourseRequest && !request.isAlternative()) 287 iTotalCRWeight += request.getWeight(); 288 if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.getStudent().isDummy() && !request.isAlternative()) 289 iTotalCriticalCRWeight[request.getRequestPriority().ordinal()] += request.getWeight(); 290 if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.isAlternative()) 291 iTotalPriorityCriticalCRWeight[request.getRequestPriority().ordinal()][request.getStudent().getPriority().ordinal()] += request.getWeight(); 292 if (request.getStudent().isDummy()) { 293 iNrDummyRequests++; 294 iTotalDummyWeight += request.getWeight(); 295 if (request instanceof CourseRequest && !request.isAlternative()) 296 iTotalDummyCRWeight += request.getWeight(); 297 } 298 if (request instanceof CourseRequest && !request.isAlternative()) 299 iTotalPriorityCRWeight[request.getStudent().getPriority().ordinal()] += request.getWeight(); 300 if (request.isMPP()) 301 iTotalMPPCRWeight += request.getWeight(); 302 if (request.hasSelection()) 303 iTotalSelCRWeight += request.getWeight(); 304 } 305 306 /** 307 * Recompute cached request weights 308 * @param assignment current assignment 309 */ 310 public void requestWeightsChanged(Assignment<Request, Enrollment> assignment) { 311 getContext(assignment).requestWeightsChanged(assignment); 312 } 313 314 /** 315 * Remove a student from the model 316 * @param student a student to be removed from the problem 317 */ 318 public void removeStudent(Student student) { 319 iStudents.remove(student); 320 if (student.isDummy()) 321 iNrDummyStudents--; 322 iNrPriorityStudents[student.getPriority().ordinal()]--; 323 StudentConflict conflict = null; 324 for (Request request : student.getRequests()) { 325 for (Constraint<Request, Enrollment> c : request.constraints()) { 326 if (c instanceof StudentConflict) { 327 conflict = (StudentConflict) c; 328 break; 329 } 330 } 331 if (conflict != null) 332 conflict.removeVariable(request); 333 removeVariable(request); 334 } 335 if (conflict != null) 336 removeConstraint(conflict); 337 } 338 339 @Override 340 public void removeVariable(Request request) { 341 super.removeVariable(request); 342 if (request instanceof CourseRequest) { 343 CourseRequest cr = (CourseRequest)request; 344 for (Course course: cr.getCourses()) 345 course.getRequests().remove(request); 346 } 347 if (request.getStudent().isDummy()) { 348 iNrDummyRequests--; 349 iTotalDummyWeight -= request.getWeight(); 350 if (request instanceof CourseRequest && !request.isAlternative()) 351 iTotalDummyCRWeight -= request.getWeight(); 352 } 353 if (request instanceof CourseRequest && !request.isAlternative()) 354 iTotalPriorityCRWeight[request.getStudent().getPriority().ordinal()] -= request.getWeight(); 355 if (request.isMPP()) 356 iTotalMPPCRWeight -= request.getWeight(); 357 if (request.hasSelection()) 358 iTotalSelCRWeight -= request.getWeight(); 359 if (request instanceof CourseRequest && !request.isAlternative()) 360 iTotalCRWeight -= request.getWeight(); 361 if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.getStudent().isDummy() && !request.isAlternative()) 362 iTotalCriticalCRWeight[request.getRequestPriority().ordinal()] -= request.getWeight(); 363 if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.isAlternative()) 364 iTotalPriorityCriticalCRWeight[request.getRequestPriority().ordinal()][request.getStudent().getPriority().ordinal()] -= request.getWeight(); 365 } 366 367 368 /** 369 * List of offerings 370 * @return all instructional offerings of the problem 371 */ 372 public List<Offering> getOfferings() { 373 return iOfferings; 374 } 375 376 /** 377 * Add an offering into the model 378 * @param offering an instructional offering to be added into the problem 379 */ 380 public void addOffering(Offering offering) { 381 iOfferings.add(offering); 382 offering.setModel(this); 383 } 384 385 /** 386 * Link sections using {@link LinkedSections} 387 * @param mustBeUsed if true, a pair of linked sections must be used when a student requests both courses 388 * @param sections a linked section constraint to be added into the problem 389 */ 390 public void addLinkedSections(boolean mustBeUsed, Section... sections) { 391 LinkedSections constraint = new LinkedSections(sections); 392 constraint.setMustBeUsed(mustBeUsed); 393 iLinkedSections.add(constraint); 394 constraint.createConstraints(); 395 } 396 397 /** 398 * Link sections using {@link LinkedSections} 399 * @param sections a linked section constraint to be added into the problem 400 */ 401 @Deprecated 402 public void addLinkedSections(Section... sections) { 403 addLinkedSections(false, sections); 404 } 405 406 /** 407 * Link sections using {@link LinkedSections} 408 * @param mustBeUsed if true, a pair of linked sections must be used when a student requests both courses 409 * @param sections a linked section constraint to be added into the problem 410 */ 411 public void addLinkedSections(boolean mustBeUsed, Collection<Section> sections) { 412 LinkedSections constraint = new LinkedSections(sections); 413 constraint.setMustBeUsed(mustBeUsed); 414 iLinkedSections.add(constraint); 415 constraint.createConstraints(); 416 } 417 418 /** 419 * Link sections using {@link LinkedSections} 420 * @param sections a linked section constraint to be added into the problem 421 */ 422 @Deprecated 423 public void addLinkedSections(Collection<Section> sections) { 424 addLinkedSections(false, sections); 425 } 426 427 /** 428 * List of linked sections 429 * @return all linked section constraints of the problem 430 */ 431 public List<LinkedSections> getLinkedSections() { 432 return iLinkedSections; 433 } 434 435 /** 436 * Model info 437 */ 438 @Override 439 public Map<String, String> getInfo(Assignment<Request, Enrollment> assignment) { 440 Map<String, String> info = super.getInfo(assignment); 441 StudentSectioningModelContext context = getContext(assignment); 442 if (!getStudents().isEmpty()) 443 info.put("Students with complete schedule", sDoubleFormat.format(100.0 * context.nrComplete() / getStudents().size()) + "% (" + context.nrComplete() + "/" + getStudents().size() + ")"); 444 String priorityComplete = ""; 445 for (StudentPriority sp: StudentPriority.values()) { 446 if (sp != StudentPriority.Dummy && iNrPriorityStudents[sp.ordinal()] > 0) 447 priorityComplete += (priorityComplete.isEmpty() ? "" : "\n") + 448 sp.name() + ": " + sDoubleFormat.format(100.0 * context.iNrCompletePriorityStudents[sp.ordinal()] / iNrPriorityStudents[sp.ordinal()]) + "% (" + context.iNrCompletePriorityStudents[sp.ordinal()] + "/" + iNrPriorityStudents[sp.ordinal()] + ")"; 449 } 450 if (!priorityComplete.isEmpty()) 451 info.put("Students with complete schedule (priority students)", priorityComplete); 452 if (getStudentQuality() != null) { 453 int confs = getStudentQuality().getTotalPenalty(StudentQuality.Type.Distance, assignment); 454 int shortConfs = getStudentQuality().getTotalPenalty(StudentQuality.Type.ShortDistance, assignment); 455 if (confs > 0 || shortConfs > 0) { 456 info.put("Student distance conflicts", confs + (shortConfs == 0 ? "" : " (" + getDistanceMetric().getShortDistanceAccommodationReference() + ": " + shortConfs + ")")); 457 } 458 } else if (getDistanceConflict() != null) { 459 int confs = getDistanceConflict().getTotalNrConflicts(assignment); 460 if (confs > 0) { 461 int shortConfs = getDistanceConflict().getTotalNrShortConflicts(assignment); 462 info.put("Student distance conflicts", confs + (shortConfs == 0 ? "" : " (" + getDistanceConflict().getDistanceMetric().getShortDistanceAccommodationReference() + ": " + shortConfs + ")")); 463 } 464 } 465 if (getStudentQuality() != null) { 466 int shareCR = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.CourseTimeOverlap, assignment); 467 int shareFT = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.FreeTimeOverlap, assignment); 468 int shareUN = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.Unavailability, assignment); 469 if (shareCR + shareFT + shareUN > 0) 470 info.put("Time overlapping conflicts", sDoubleFormat.format((5.0 * (shareCR + shareFT + shareUN)) / iStudents.size()) + " mins per student\n" + 471 "(" + sDoubleFormat.format(5.0 * shareCR / iStudents.size()) + " between courses, " + sDoubleFormat.format(5.0 * shareFT / iStudents.size()) + " free time" + 472 (shareUN == 0 ? "" : ", " + sDoubleFormat.format(5.0 * shareUN / iStudents.size()) + " teaching assignments") + "; " + sDoubleFormat.format((shareCR + shareFT + shareUN) / 12.0) + " hours total)"); 473 } else if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) { 474 info.put("Time overlapping conflicts", sDoubleFormat.format(5.0 * getTimeOverlaps().getTotalNrConflicts(assignment) / iStudents.size()) + " mins per student (" + sDoubleFormat.format(getTimeOverlaps().getTotalNrConflicts(assignment) / 12.0) + " hours total)"); 475 } 476 if (getStudentQuality() != null) { 477 int confLunch = getStudentQuality().getTotalPenalty(StudentQuality.Type.LunchBreak, assignment); 478 if (confLunch > 0) 479 info.put("Schedule Quality: Lunch conflicts", sDoubleFormat.format(20.0 * confLunch / getNrRealStudents(false)) + "% (" + confLunch + ")"); 480 int confTravel = getStudentQuality().getTotalPenalty(StudentQuality.Type.TravelTime, assignment); 481 if (confTravel > 0) 482 info.put("Schedule Quality: Travel time", sDoubleFormat.format(((double)confTravel) / getNrRealStudents(false)) + " mins per student (" + sDecimalFormat.format(confTravel / 60.0) + " hours total)"); 483 int confBtB = getStudentQuality().getTotalPenalty(StudentQuality.Type.BackToBack, assignment); 484 if (confBtB > 0) 485 info.put("Schedule Quality: Back-to-back classes", sDoubleFormat.format(((double)confBtB) / getNrRealStudents(false)) + " per student (" + confBtB + ")"); 486 int confWorkDay = getStudentQuality().getTotalPenalty(StudentQuality.Type.WorkDay, assignment); 487 if (confWorkDay > 0) 488 info.put("Schedule Quality: Work day", sDoubleFormat.format(5.0 * confWorkDay / getNrRealStudents(false)) + " mins over " + 489 new DecimalFormat("0.#").format(getProperties().getPropertyInt("WorkDay.WorkDayLimit", 6*12) / 12.0) + " hours a day per student\n(from start to end, " + sDoubleFormat.format(confWorkDay / 12.0) + " hours total)"); 490 int early = getStudentQuality().getTotalPenalty(StudentQuality.Type.TooEarly, assignment); 491 if (early > 0) { 492 int min = getProperties().getPropertyInt("WorkDay.EarlySlot", 102) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN; 493 int h = min / 60; 494 int m = min % 60; 495 String time = (getProperties().getPropertyBoolean("General.UseAmPm", true) ? (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a") : h + ":" + (m < 10 ? "0" : "") + m); 496 info.put("Schedule Quality: Early classes", sDoubleFormat.format(5.0 * early / iStudents.size()) + " mins before " + time + " per student (" + sDoubleFormat.format(early / 12.0) + " hours total)"); 497 } 498 int late = getStudentQuality().getTotalPenalty(StudentQuality.Type.TooLate, assignment); 499 if (late > 0) { 500 int min = getProperties().getPropertyInt("WorkDay.LateSlot", 210) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN; 501 int h = min / 60; 502 int m = min % 60; 503 String time = (getProperties().getPropertyBoolean("General.UseAmPm", true) ? (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a") : h + ":" + (m < 10 ? "0" : "") + m); 504 info.put("Schedule Quality: Late classes", sDoubleFormat.format(5.0 * late / iStudents.size()) + " mins after " + time + " per student (" + sDoubleFormat.format(late / 12.0) + " hours total)"); 505 } 506 int accFT = getStudentQuality().getTotalPenalty(StudentQuality.Type.AccFreeTimeOverlap, assignment); 507 if (accFT > 0) { 508 info.put("Accommodations: Free time conflicts", sDoubleFormat.format(5.0 * accFT / getStudentsWithAccommodation(getStudentQuality().getStudentQualityContext().getFreeTimeAccommodation())) + " mins per student, " + sDoubleFormat.format(accFT / 12.0) + " hours total"); 509 } 510 int accBtB = getStudentQuality().getTotalPenalty(StudentQuality.Type.AccBackToBack, assignment); 511 if (accBtB > 0) { 512 info.put("Accommodations: Back-to-back classes", sDoubleFormat.format(((double)accBtB) / getStudentsWithAccommodation(getStudentQuality().getStudentQualityContext().getBackToBackAccommodation())) + " non-BTB classes per student, " + accBtB + " total"); 513 } 514 int accBbc = getStudentQuality().getTotalPenalty(StudentQuality.Type.AccBreaksBetweenClasses, assignment); 515 if (accBbc > 0) { 516 info.put("Accommodations: Break between classes", sDoubleFormat.format(((double)accBbc) / getStudentsWithAccommodation(getStudentQuality().getStudentQualityContext().getBreakBetweenClassesAccommodation())) + " BTB classes per student, " + accBbc + " total"); 517 } 518 int shortConfs = getStudentQuality().getTotalPenalty(StudentQuality.Type.ShortDistance, assignment); 519 if (shortConfs > 0) { 520 info.put("Accommodations: Distance conflicts", sDoubleFormat.format(((double)shortConfs) / getStudentsWithAccommodation(getStudentQuality().getDistanceMetric().getShortDistanceAccommodationReference())) + " short distance conflicts per student, " + shortConfs + " total"); 521 } 522 } 523 int nrLastLikeStudents = getNrLastLikeStudents(false); 524 if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) { 525 int nrRealStudents = getStudents().size() - nrLastLikeStudents; 526 int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(assignment, false); 527 int nrRealCompleteStudents = context.nrComplete() - nrLastLikeCompleteStudents; 528 if (nrLastLikeStudents > 0) 529 info.put("Projected students with complete schedule", sDecimalFormat.format(100.0 530 * nrLastLikeCompleteStudents / nrLastLikeStudents) 531 + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")"); 532 if (nrRealStudents > 0) 533 info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents 534 / nrRealStudents) 535 + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")"); 536 int nrLastLikeRequests = getNrLastLikeRequests(false); 537 int nrRealRequests = variables().size() - nrLastLikeRequests; 538 int nrLastLikeAssignedRequests = context.getNrAssignedLastLikeRequests(); 539 int nrRealAssignedRequests = assignment.nrAssignedVariables() - nrLastLikeAssignedRequests; 540 if (nrLastLikeRequests > 0) 541 info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests / nrLastLikeRequests) 542 + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")"); 543 if (nrRealRequests > 0) 544 info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests) 545 + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")"); 546 } 547 context.getInfo(assignment, info); 548 549 double groupSpread = 0.0; double groupCount = 0; 550 for (Offering offering: iOfferings) { 551 for (Course course: offering.getCourses()) { 552 for (RequestGroup group: course.getRequestGroups()) { 553 groupSpread += group.getAverageSpread(assignment) * group.getEnrollmentWeight(assignment, null); 554 groupCount += group.getEnrollmentWeight(assignment, null); 555 } 556 } 557 } 558 if (groupCount > 0) 559 info.put("Same group", sDecimalFormat.format(100.0 * groupSpread / groupCount) + "%"); 560 561 return info; 562 } 563 564 /** 565 * Overall solution value 566 * @param assignment current assignment 567 * @param precise true if should be computed 568 * @return solution value 569 */ 570 public double getTotalValue(Assignment<Request, Enrollment> assignment, boolean precise) { 571 if (precise) { 572 double total = 0; 573 for (Request r: assignment.assignedVariables()) 574 total += r.getWeight() * iStudentWeights.getWeight(assignment, assignment.getValue(r)); 575 if (iDistanceConflict != null) 576 for (DistanceConflict.Conflict c: iDistanceConflict.computeAllConflicts(assignment)) 577 total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 578 if (iTimeOverlaps != null) 579 for (TimeOverlapsCounter.Conflict c: iTimeOverlaps.getContext(assignment).computeAllConflicts(assignment)) { 580 if (c.getR1() != null) total -= c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c); 581 if (c.getR2() != null) total -= c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c); 582 } 583 if (iStudentQuality != null) 584 for (StudentQuality.Type t: StudentQuality.Type.values()) { 585 for (StudentQuality.Conflict c: iStudentQuality.getContext(assignment).computeAllConflicts(t, assignment)) { 586 switch (c.getType().getType()) { 587 case REQUEST: 588 if (c.getR1() instanceof CourseRequest) 589 total -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 590 else 591 total -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 592 break; 593 case BOTH: 594 total -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 595 total -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 596 break; 597 case LOWER: 598 total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 599 break; 600 case HIGHER: 601 total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 602 break; 603 } 604 } 605 } 606 return -total; 607 } 608 return getContext(assignment).getTotalValue(); 609 } 610 611 /** 612 * Overall solution value 613 */ 614 @Override 615 public double getTotalValue(Assignment<Request, Enrollment> assignment) { 616 return getContext(assignment).getTotalValue(); 617 } 618 619 /** 620 * Configuration 621 * @return solver configuration 622 */ 623 public DataProperties getProperties() { 624 return iProperties; 625 } 626 627 /** 628 * Empty online student sectioning infos for all sections (see 629 * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}). 630 */ 631 public void clearOnlineSectioningInfos() { 632 for (Offering offering : iOfferings) { 633 for (Config config : offering.getConfigs()) { 634 for (Subpart subpart : config.getSubparts()) { 635 for (Section section : subpart.getSections()) { 636 section.setSpaceExpected(0); 637 section.setSpaceHeld(0); 638 } 639 } 640 } 641 } 642 } 643 644 /** 645 * Compute online student sectioning infos for all sections (see 646 * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}). 647 * @param assignment current assignment 648 */ 649 public void computeOnlineSectioningInfos(Assignment<Request, Enrollment> assignment) { 650 clearOnlineSectioningInfos(); 651 for (Student student : getStudents()) { 652 if (!student.isDummy()) 653 continue; 654 for (Request request : student.getRequests()) { 655 if (!(request instanceof CourseRequest)) 656 continue; 657 CourseRequest courseRequest = (CourseRequest) request; 658 Enrollment enrollment = assignment.getValue(courseRequest); 659 if (enrollment != null) { 660 for (Section section : enrollment.getSections()) { 661 section.setSpaceHeld(courseRequest.getWeight() + section.getSpaceHeld()); 662 } 663 } 664 List<Enrollment> feasibleEnrollments = new ArrayList<Enrollment>(); 665 int totalLimit = 0; 666 for (Enrollment enrl : courseRequest.values(assignment)) { 667 boolean overlaps = false; 668 for (Request otherRequest : student.getRequests()) { 669 if (otherRequest.equals(courseRequest) || !(otherRequest instanceof CourseRequest)) 670 continue; 671 Enrollment otherErollment = assignment.getValue(otherRequest); 672 if (otherErollment == null) 673 continue; 674 if (enrl.isOverlapping(otherErollment)) { 675 overlaps = true; 676 break; 677 } 678 } 679 if (!overlaps) { 680 feasibleEnrollments.add(enrl); 681 if (totalLimit >= 0) { 682 int limit = enrl.getLimit(); 683 if (limit < 0) totalLimit = -1; 684 else totalLimit += limit; 685 } 686 } 687 } 688 double increment = courseRequest.getWeight() / (totalLimit > 0 ? totalLimit : feasibleEnrollments.size()); 689 for (Enrollment feasibleEnrollment : feasibleEnrollments) { 690 for (Section section : feasibleEnrollment.getSections()) { 691 if (totalLimit > 0) { 692 section.setSpaceExpected(section.getSpaceExpected() + increment * feasibleEnrollment.getLimit()); 693 } else { 694 section.setSpaceExpected(section.getSpaceExpected() + increment); 695 } 696 } 697 } 698 } 699 } 700 } 701 702 /** 703 * Sum of weights of all requests that are not assigned (see 704 * {@link Request#getWeight()}). 705 * @param assignment current assignment 706 * @return unassigned request weight 707 */ 708 public double getUnassignedRequestWeight(Assignment<Request, Enrollment> assignment) { 709 double weight = 0.0; 710 for (Request request : assignment.unassignedVariables(this)) { 711 weight += request.getWeight(); 712 } 713 return weight; 714 } 715 716 /** 717 * Sum of weights of all requests (see {@link Request#getWeight()}). 718 * @return total request weight 719 */ 720 public double getTotalRequestWeight() { 721 double weight = 0.0; 722 for (Request request : variables()) { 723 weight += request.getWeight(); 724 } 725 return weight; 726 } 727 728 /** 729 * Set distance conflict extension 730 * @param dc distance conflicts extension 731 */ 732 public void setDistanceConflict(DistanceConflict dc) { 733 iDistanceConflict = dc; 734 } 735 736 /** 737 * Return distance conflict extension 738 * @return distance conflicts extension 739 */ 740 public DistanceConflict getDistanceConflict() { 741 return iDistanceConflict; 742 } 743 744 /** 745 * Set time overlaps extension 746 * @param toc time overlapping conflicts extension 747 */ 748 public void setTimeOverlaps(TimeOverlapsCounter toc) { 749 iTimeOverlaps = toc; 750 } 751 752 /** 753 * Return time overlaps extension 754 * @return time overlapping conflicts extension 755 */ 756 public TimeOverlapsCounter getTimeOverlaps() { 757 return iTimeOverlaps; 758 } 759 760 public StudentQuality getStudentQuality() { return iStudentQuality; } 761 public void setStudentQuality(StudentQuality q, boolean register) { 762 if (iStudentQuality != null) 763 getInfoProviders().remove(iStudentQuality); 764 iStudentQuality = q; 765 if (iStudentQuality != null) 766 getInfoProviders().add(iStudentQuality); 767 if (register) { 768 iStudentQuality.setAssignmentContextReference(createReference(iStudentQuality)); 769 iStudentQuality.register(this); 770 } 771 } 772 773 public void setStudentQuality(StudentQuality q) { 774 setStudentQuality(q, true); 775 } 776 777 /** 778 * Average priority of unassigned requests (see 779 * {@link Request#getPriority()}) 780 * @param assignment current assignment 781 * @return average priority of unassigned requests 782 */ 783 public double avgUnassignPriority(Assignment<Request, Enrollment> assignment) { 784 double totalPriority = 0.0; 785 for (Request request : assignment.unassignedVariables(this)) { 786 if (request.isAlternative()) 787 continue; 788 totalPriority += request.getPriority(); 789 } 790 return 1.0 + totalPriority / assignment.nrUnassignedVariables(this); 791 } 792 793 /** 794 * Average number of requests per student (see {@link Student#getRequests()} 795 * ) 796 * @return average number of requests per student 797 */ 798 public double avgNrRequests() { 799 double totalRequests = 0.0; 800 int totalStudents = 0; 801 for (Student student : getStudents()) { 802 if (student.nrRequests() == 0) 803 continue; 804 totalRequests += student.nrRequests(); 805 totalStudents++; 806 } 807 return totalRequests / totalStudents; 808 } 809 810 /** Number of last like ({@link Student#isDummy()} equals true) students. 811 * @param precise true if to be computed 812 * @return number of last like (projected) students 813 **/ 814 public int getNrLastLikeStudents(boolean precise) { 815 if (!precise) 816 return iNrDummyStudents; 817 int nrLastLikeStudents = 0; 818 for (Student student : getStudents()) { 819 if (student.isDummy()) 820 nrLastLikeStudents++; 821 } 822 return nrLastLikeStudents; 823 } 824 825 /** Number of real ({@link Student#isDummy()} equals false) students. 826 * @param precise true if to be computed 827 * @return number of real students 828 **/ 829 public int getNrRealStudents(boolean precise) { 830 if (!precise) 831 return getStudents().size() - iNrDummyStudents; 832 int nrRealStudents = 0; 833 for (Student student : getStudents()) { 834 if (!student.isDummy()) 835 nrRealStudents++; 836 } 837 return nrRealStudents; 838 } 839 840 /** 841 * Count students with given accommodation 842 */ 843 public int getStudentsWithAccommodation(String acc) { 844 int nrAccStudents = 0; 845 for (Student student : getStudents()) { 846 if (student.hasAccommodation(acc)) 847 nrAccStudents++; 848 } 849 return nrAccStudents; 850 } 851 852 /** 853 * Number of last like ({@link Student#isDummy()} equals true) students with 854 * a complete schedule ({@link Student#isComplete(Assignment)} equals true). 855 * @param assignment current assignment 856 * @param precise true if to be computed 857 * @return number of last like (projected) students with a complete schedule 858 */ 859 public int getNrCompleteLastLikeStudents(Assignment<Request, Enrollment> assignment, boolean precise) { 860 if (!precise) 861 return getContext(assignment).getNrCompleteLastLikeStudents(); 862 int nrLastLikeStudents = 0; 863 for (Student student : getStudents()) { 864 if (student.isComplete(assignment) && student.isDummy()) 865 nrLastLikeStudents++; 866 } 867 return nrLastLikeStudents; 868 } 869 870 /** 871 * Number of real ({@link Student#isDummy()} equals false) students with a 872 * complete schedule ({@link Student#isComplete(Assignment)} equals true). 873 * @param assignment current assignment 874 * @param precise true if to be computed 875 * @return number of real students with a complete schedule 876 */ 877 public int getNrCompleteRealStudents(Assignment<Request, Enrollment> assignment, boolean precise) { 878 if (!precise) 879 return getContext(assignment).nrComplete() - getContext(assignment).getNrCompleteLastLikeStudents(); 880 int nrRealStudents = 0; 881 for (Student student : getStudents()) { 882 if (student.isComplete(assignment) && !student.isDummy()) 883 nrRealStudents++; 884 } 885 return nrRealStudents; 886 } 887 888 /** 889 * Number of requests from projected ({@link Student#isDummy()} equals true) 890 * students. 891 * @param precise true if to be computed 892 * @return number of requests from projected students 893 */ 894 public int getNrLastLikeRequests(boolean precise) { 895 if (!precise) 896 return iNrDummyRequests; 897 int nrLastLikeRequests = 0; 898 for (Request request : variables()) { 899 if (request.getStudent().isDummy()) 900 nrLastLikeRequests++; 901 } 902 return nrLastLikeRequests; 903 } 904 905 /** 906 * Number of requests from real ({@link Student#isDummy()} equals false) 907 * students. 908 * @param precise true if to be computed 909 * @return number of requests from real students 910 */ 911 public int getNrRealRequests(boolean precise) { 912 if (!precise) 913 return variables().size() - iNrDummyRequests; 914 int nrRealRequests = 0; 915 for (Request request : variables()) { 916 if (!request.getStudent().isDummy()) 917 nrRealRequests++; 918 } 919 return nrRealRequests; 920 } 921 922 /** 923 * Number of requests from projected ({@link Student#isDummy()} equals true) 924 * students that are assigned. 925 * @param assignment current assignment 926 * @param precise true if to be computed 927 * @return number of requests from projected students that are assigned 928 */ 929 public int getNrAssignedLastLikeRequests(Assignment<Request, Enrollment> assignment, boolean precise) { 930 if (!precise) 931 return getContext(assignment).getNrAssignedLastLikeRequests(); 932 int nrLastLikeRequests = 0; 933 for (Request request : assignment.assignedVariables()) { 934 if (request.getStudent().isDummy()) 935 nrLastLikeRequests++; 936 } 937 return nrLastLikeRequests; 938 } 939 940 /** 941 * Number of requests from real ({@link Student#isDummy()} equals false) 942 * students that are assigned. 943 * @param assignment current assignment 944 * @param precise true if to be computed 945 * @return number of requests from real students that are assigned 946 */ 947 public int getNrAssignedRealRequests(Assignment<Request, Enrollment> assignment, boolean precise) { 948 if (!precise) 949 return assignment.nrAssignedVariables() - getContext(assignment).getNrAssignedLastLikeRequests(); 950 int nrRealRequests = 0; 951 for (Request request : assignment.assignedVariables()) { 952 if (!request.getStudent().isDummy()) 953 nrRealRequests++; 954 } 955 return nrRealRequests; 956 } 957 958 /** 959 * Model extended info. Some more information (that is more expensive to 960 * compute) is added to an ordinary {@link Model#getInfo(Assignment)}. 961 */ 962 @Override 963 public Map<String, String> getExtendedInfo(Assignment<Request, Enrollment> assignment) { 964 Map<String, String> info = getInfo(assignment); 965 /* 966 int nrLastLikeStudents = getNrLastLikeStudents(true); 967 if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) { 968 int nrRealStudents = getStudents().size() - nrLastLikeStudents; 969 int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(true); 970 int nrRealCompleteStudents = getCompleteStudents().size() - nrLastLikeCompleteStudents; 971 info.put("Projected students with complete schedule", sDecimalFormat.format(100.0 972 * nrLastLikeCompleteStudents / nrLastLikeStudents) 973 + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")"); 974 info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents 975 / nrRealStudents) 976 + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")"); 977 int nrLastLikeRequests = getNrLastLikeRequests(true); 978 int nrRealRequests = variables().size() - nrLastLikeRequests; 979 int nrLastLikeAssignedRequests = getNrAssignedLastLikeRequests(true); 980 int nrRealAssignedRequests = assignedVariables().size() - nrLastLikeAssignedRequests; 981 info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests 982 / nrLastLikeRequests) 983 + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")"); 984 info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests) 985 + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")"); 986 } 987 */ 988 // info.put("Average unassigned priority", sDecimalFormat.format(avgUnassignPriority())); 989 // info.put("Average number of requests", sDecimalFormat.format(avgNrRequests())); 990 991 /* 992 double total = 0; 993 for (Request r: variables()) 994 if (r.getAssignment() != null) 995 total += r.getWeight() * iStudentWeights.getWeight(r.getAssignment()); 996 */ 997 /* 998 double dc = 0; 999 if (getDistanceConflict() != null && getDistanceConflict().getTotalNrConflicts(assignment) != 0) { 1000 Set<DistanceConflict.Conflict> conf = getDistanceConflict().getAllConflicts(assignment); 1001 int sdc = 0; 1002 for (DistanceConflict.Conflict c: conf) { 1003 dc += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 1004 if (c.getStudent().isNeedShortDistances()) sdc ++; 1005 } 1006 if (!conf.isEmpty()) 1007 info.put("Student distance conflicts", conf.size() + (sdc > 0 ? " (" + getDistanceConflict().getDistanceMetric().getShortDistanceAccommodationReference() + ": " + sdc + ", weighted: " : " (weighted: ") + sDecimalFormat.format(dc) + ")"); 1008 } 1009 */ 1010 if (getStudentQuality() == null && getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) { 1011 Set<TimeOverlapsCounter.Conflict> conf = getTimeOverlaps().getContext(assignment).computeAllConflicts(assignment); 1012 int share = 0, crShare = 0; 1013 for (TimeOverlapsCounter.Conflict c: conf) { 1014 share += c.getShare(); 1015 if (c.getR1() instanceof CourseRequest && c.getR2() instanceof CourseRequest) 1016 crShare += c.getShare(); 1017 } 1018 if (share > 0) 1019 info.put("Time overlapping conflicts", sDoubleFormat.format(5.0 * share / iStudents.size()) + " mins per student\n(" + sDoubleFormat.format(5.0 * crShare / iStudents.size()) + " between courses; " + sDoubleFormat.format(getTimeOverlaps().getTotalNrConflicts(assignment) / 12.0) + " hours total)"); 1020 } 1021 /* 1022 info.put("Overall solution value", sDecimalFormat.format(total - dc - toc) + (dc == 0.0 && toc == 0.0 ? "" : 1023 " (" + (dc != 0.0 ? "distance: " + sDecimalFormat.format(dc): "") + (dc != 0.0 && toc != 0.0 ? ", " : "") + 1024 (toc != 0.0 ? "overlap: " + sDecimalFormat.format(toc) : "") + ")") 1025 ); 1026 */ 1027 1028 double disbWeight = 0; 1029 int disbSections = 0; 1030 int disb10Sections = 0; 1031 int disb10Limit = getProperties().getPropertyInt("Info.ListDisbalancedSections", 0); 1032 Set<String> disb10SectionList = (disb10Limit == 0 ? null : new TreeSet<String>()); 1033 for (Offering offering: getOfferings()) { 1034 for (Config config: offering.getConfigs()) { 1035 double enrl = config.getEnrollmentTotalWeight(assignment, null); 1036 for (Subpart subpart: config.getSubparts()) { 1037 if (subpart.getSections().size() <= 1) continue; 1038 if (subpart.getLimit() > 0) { 1039 // sections have limits -> desired size is section limit x (total enrollment / total limit) 1040 double ratio = enrl / subpart.getLimit(); 1041 for (Section section: subpart.getSections()) { 1042 double desired = ratio * section.getLimit(); 1043 disbWeight += Math.abs(section.getEnrollmentTotalWeight(assignment, null) - desired); 1044 disbSections ++; 1045 if (Math.abs(desired - section.getEnrollmentTotalWeight(assignment, null)) >= Math.max(1.0, 0.1 * section.getLimit())) { 1046 disb10Sections++; 1047 if (disb10SectionList != null) 1048 disb10SectionList.add(section.getSubpart().getConfig().getOffering().getName() + " " + section.getSubpart().getName() + " " + section.getName()); 1049 } 1050 } 1051 } else { 1052 // unlimited sections -> desired size is total enrollment / number of sections 1053 for (Section section: subpart.getSections()) { 1054 double desired = enrl / subpart.getSections().size(); 1055 disbWeight += Math.abs(section.getEnrollmentTotalWeight(assignment, null) - desired); 1056 disbSections ++; 1057 if (Math.abs(desired - section.getEnrollmentTotalWeight(assignment, null)) >= Math.max(1.0, 0.1 * desired)) { 1058 disb10Sections++; 1059 if (disb10SectionList != null) 1060 disb10SectionList.add(section.getSubpart().getConfig().getOffering().getName() + " " + section.getSubpart().getName() + " " + section.getName()); 1061 } 1062 } 1063 } 1064 } 1065 } 1066 } 1067 if (disbSections != 0) { 1068 double assignedCRWeight = getContext(assignment).getAssignedCourseRequestWeight(); 1069 info.put("Average disbalance", sDecimalFormat.format(assignedCRWeight == 0 ? 0.0 : 100.0 * disbWeight / assignedCRWeight) + "% (" + sDecimalFormat.format(disbWeight / disbSections) + ")"); 1070 String list = ""; 1071 if (disb10SectionList != null) { 1072 int i = 0; 1073 for (String section: disb10SectionList) { 1074 if (i == disb10Limit) { 1075 list += "\n..."; 1076 break; 1077 } 1078 list += "\n" + section; 1079 i++; 1080 } 1081 } 1082 info.put("Sections disbalanced by 10% or more", sDecimalFormat.format(disbSections == 0 ? 0.0 : 100.0 * disb10Sections / disbSections) + "% (" + disb10Sections + ")" + (list.isEmpty() ? "" : "\n" + list)); 1083 } 1084 1085 int assCR = 0, priCR = 0; 1086 for (Request r: variables()) { 1087 if (r instanceof CourseRequest && !r.getStudent().isDummy()) { 1088 CourseRequest cr = (CourseRequest)r; 1089 Enrollment e = assignment.getValue(cr); 1090 if (e != null) { 1091 assCR ++; 1092 if (!cr.isAlternative() && cr.getCourses().get(0).equals(e.getCourse())) priCR ++; 1093 } 1094 } 1095 } 1096 if (assCR > 0) 1097 info.put("Assigned priority course requests", sDoubleFormat.format(100.0 * priCR / assCR) + "% (" + priCR + "/" + assCR + ")"); 1098 int[] missing = new int[] {0, 0, 0, 0, 0}; 1099 int incomplete = 0; 1100 for (Student student: getStudents()) { 1101 if (student.isDummy()) continue; 1102 int nrRequests = 0; 1103 int nrAssignedRequests = 0; 1104 for (Request r : student.getRequests()) { 1105 if (!(r instanceof CourseRequest)) continue; // ignore free times 1106 if (!r.isAlternative()) nrRequests++; 1107 if (r.isAssigned(assignment)) nrAssignedRequests++; 1108 } 1109 if (nrAssignedRequests < nrRequests) { 1110 missing[Math.min(nrRequests - nrAssignedRequests, missing.length) - 1] ++; 1111 incomplete ++; 1112 } 1113 } 1114 1115 for (int i = 0; i < missing.length; i++) 1116 if (missing[i] > 0) 1117 info.put("Students missing " + (i == 0 ? "1 course" : i + 1 == missing.length ? (i + 1) + " or more courses" : (i + 1) + " courses"), sDecimalFormat.format(100.0 * missing[i] / incomplete) + "% (" + missing[i] + ")"); 1118 1119 info.put("Overall solution value", sDoubleFormat.format(getTotalValue(assignment)));// + " [precise: " + sDoubleFormat.format(getTotalValue(assignment, true)) + "]"); 1120 1121 int nrStudentsBelowMinCredit = 0, nrStudents = 0; 1122 for (Student student: getStudents()) { 1123 if (student.isDummy()) continue; 1124 if (student.hasMinCredit()) { 1125 nrStudents++; 1126 float credit = student.getAssignedCredit(assignment); 1127 if (credit < student.getMinCredit() && !student.isComplete(assignment)) 1128 nrStudentsBelowMinCredit ++; 1129 } 1130 } 1131 if (nrStudentsBelowMinCredit > 0) 1132 info.put("Students below min credit", sDoubleFormat.format(100.0 * nrStudentsBelowMinCredit / nrStudents) + "% (" + nrStudentsBelowMinCredit + "/" + nrStudents + ")"); 1133 1134 int[] notAssignedPriority = new int[] {0, 0, 0, 0, 0, 0, 0}; 1135 int[] assignedChoice = new int[] {0, 0, 0, 0, 0}; 1136 int notAssignedTotal = 0, assignedChoiceTotal = 0; 1137 int avgPriority = 0, avgChoice = 0; 1138 for (Student student: getStudents()) { 1139 if (student.isDummy()) continue; 1140 for (Request r : student.getRequests()) { 1141 if (!(r instanceof CourseRequest)) continue; // ignore free times 1142 Enrollment e = r.getAssignment(assignment); 1143 if (e == null) { 1144 if (!r.isAlternative()) { 1145 notAssignedPriority[Math.min(r.getPriority(), notAssignedPriority.length - 1)] ++; 1146 notAssignedTotal ++; 1147 avgPriority += r.getPriority(); 1148 } 1149 } else { 1150 assignedChoice[Math.min(e.getTruePriority(), assignedChoice.length - 1)] ++; 1151 assignedChoiceTotal ++; 1152 avgChoice += e.getTruePriority(); 1153 } 1154 } 1155 } 1156 for (int i = 0; i < notAssignedPriority.length; i++) 1157 if (notAssignedPriority[i] > 0) 1158 info.put("Priority: Not-assigned priority " + (i + 1 == notAssignedPriority.length ? (i + 1) + "+" : (i + 1)) + " course requests", sDecimalFormat.format(100.0 * notAssignedPriority[i] / notAssignedTotal) + "% (" + notAssignedPriority[i] + ")"); 1159 if (notAssignedTotal > 0) 1160 info.put("Priority: Average not-assigned priority", sDecimalFormat.format(1.0 + ((double)avgPriority) / notAssignedTotal)); 1161 for (int i = 0; i < assignedChoice.length; i++) 1162 if (assignedChoice[i] > 0) 1163 info.put("Choice: assigned " + (i == 0 ? "1st": i == 1 ? "2nd" : i == 2 ? "3rd" : i + 1 == assignedChoice.length ? (i + 1) + "th+" : (i + 1) + "th") + " course choice", sDecimalFormat.format(100.0 * assignedChoice[i] / assignedChoiceTotal) + "% (" + assignedChoice[i] + ")"); 1164 if (assignedChoiceTotal > 0) 1165 info.put("Choice: Average assigned choice", sDecimalFormat.format(1.0 + ((double)avgChoice) / assignedChoiceTotal)); 1166 1167 int nbrSections = 0, nbrFullSections = 0, nbrSections98 = 0, nbrSections95 = 0, nbrSections90 = 0, nbrSectionsDis = 0; 1168 int enrlSections = 0, enrlFullSections = 0, enrlSections98 = 0, enrlSections95 = 0, enrlSections90 = 0, enrlSectionsDis = 0; 1169 int nbrOfferings = 0, nbrFullOfferings = 0, nbrOfferings98 = 0, nbrOfferings95 = 0, nbrOfferings90 = 0; 1170 int enrlOfferings = 0, enrlOfferingsFull = 0, enrlOfferings98 = 0, enrlOfferings95 = 0, enrlOfferings90 = 0; 1171 for (Offering offering: getOfferings()) { 1172 int offeringLimit = 0, offeringEnrollment = 0; 1173 for (Config config: offering.getConfigs()) { 1174 int configLimit = config.getLimit(); 1175 for (Subpart subpart: config.getSubparts()) { 1176 int subpartLimit = 0; 1177 for (Section section: subpart.getSections()) { 1178 if (section.isCancelled()) continue; 1179 int enrl = section.getEnrollments(assignment).size(); 1180 if (section.getLimit() < 0 || subpartLimit < 0) 1181 subpartLimit = -1; 1182 else 1183 subpartLimit += (section.isEnabled() ? section.getLimit() : enrl); 1184 nbrSections ++; 1185 enrlSections += enrl; 1186 if (section.getLimit() >= 0 && section.getLimit() <= enrl) { 1187 nbrFullSections ++; 1188 enrlFullSections += enrl; 1189 } 1190 if (!section.isEnabled() && (enrl > 0 || section.getLimit() >= 0)) { 1191 nbrSectionsDis ++; 1192 enrlSectionsDis += enrl; 1193 } 1194 if (section.getLimit() >= 0 && (section.getLimit() - enrl) <= Math.round(0.02 * section.getLimit())) { 1195 nbrSections98 ++; 1196 enrlSections98 += enrl; 1197 } 1198 if (section.getLimit() >= 0 && (section.getLimit() - enrl) <= Math.round(0.05 * section.getLimit())) { 1199 nbrSections95 ++; 1200 enrlSections95 += enrl; 1201 } 1202 if (section.getLimit() >= 0 && (section.getLimit() - enrl) <= Math.round(0.10 * section.getLimit())) { 1203 nbrSections90 ++; 1204 enrlSections90 += enrl; 1205 } 1206 } 1207 if (configLimit < 0 || subpartLimit < 0) 1208 configLimit = -1; 1209 else 1210 configLimit = Math.min(configLimit, subpartLimit); 1211 } 1212 if (offeringLimit < 0 || configLimit < 0) 1213 offeringLimit = -1; 1214 else 1215 offeringLimit += configLimit; 1216 offeringEnrollment += config.getEnrollments(assignment).size(); 1217 } 1218 nbrOfferings ++; 1219 enrlOfferings += offeringEnrollment; 1220 1221 if (offeringLimit >=0 && offeringEnrollment >= offeringLimit) { 1222 nbrFullOfferings ++; 1223 enrlOfferingsFull += offeringEnrollment; 1224 } 1225 if (offeringLimit >= 0 && (offeringLimit - offeringEnrollment) <= Math.round(0.02 * offeringLimit)) { 1226 nbrOfferings98++; 1227 enrlOfferings98 += offeringEnrollment; 1228 } 1229 if (offeringLimit >= 0 && (offeringLimit - offeringEnrollment) <= Math.round(0.05 * offeringLimit)) { 1230 nbrOfferings95++; 1231 enrlOfferings95 += offeringEnrollment; 1232 } 1233 if (offeringLimit >= 0 && (offeringLimit - offeringEnrollment) <= Math.round(0.10 * offeringLimit)) { 1234 nbrOfferings90++; 1235 enrlOfferings90 += offeringEnrollment; 1236 } 1237 } 1238 if (enrlOfferings90 > 0 && enrlOfferings > 0) 1239 info.put("Full Offerings", (nbrFullOfferings > 0 ? nbrFullOfferings + " with no space (" + sDecimalFormat.format(100.0 * nbrFullOfferings / nbrOfferings) + "% of all offerings, " + 1240 sDecimalFormat.format(100.0 * enrlOfferingsFull / enrlOfferings) + "% assignments)\n" : "")+ 1241 (nbrOfferings98 > nbrFullOfferings ? nbrOfferings98 + " with ≤ 2% available (" + sDecimalFormat.format(100.0 * nbrOfferings98 / nbrOfferings) + "% of all offerings, " + 1242 sDecimalFormat.format(100.0 * enrlOfferings98 / enrlOfferings) + "% assignments)\n" : "")+ 1243 (nbrOfferings95 > nbrOfferings98 ? nbrOfferings95 + " with ≤ 5% available (" + sDecimalFormat.format(100.0 * nbrOfferings95 / nbrOfferings) + "% of all offerings, " + 1244 sDecimalFormat.format(100.0 * enrlOfferings95 / enrlOfferings) + "% assignments)\n" : "")+ 1245 (nbrOfferings90 > nbrOfferings95 ? nbrOfferings90 + " with ≤ 10% available (" + sDecimalFormat.format(100.0 * nbrOfferings90 / nbrOfferings) + "% of all offerings, " + 1246 sDecimalFormat.format(100.0 * enrlOfferings90 / enrlOfferings) + "% assignments)" : "")); 1247 if ((enrlSections90 > 0 || nbrSectionsDis > 0) && enrlSections > 0) 1248 info.put("Full Sections", (nbrFullSections > 0 ? nbrFullSections + " with no space (" + sDecimalFormat.format(100.0 * nbrFullSections / nbrSections) + "% of all sections, "+ 1249 sDecimalFormat.format(100.0 * enrlFullSections / enrlSections) + "% assignments)\n" : "") + 1250 (nbrSectionsDis > 0 ? nbrSectionsDis + " disabled (" + sDecimalFormat.format(100.0 * nbrSectionsDis / nbrSections) + "% of all sections, "+ 1251 sDecimalFormat.format(100.0 * enrlSectionsDis / enrlSections) + "% assignments)\n" : "") + 1252 (enrlSections98 > nbrFullSections ? nbrSections98 + " with ≤ 2% available (" + sDecimalFormat.format(100.0 * nbrSections98 / nbrSections) + "% of all sections, " + 1253 sDecimalFormat.format(100.0 * enrlSections98 / enrlSections) + "% assignments)\n" : "") + 1254 (nbrSections95 > enrlSections98 ? nbrSections95 + " with ≤ 5% available (" + sDecimalFormat.format(100.0 * nbrSections95 / nbrSections) + "% of all sections, " + 1255 sDecimalFormat.format(100.0 * enrlSections95 / enrlSections) + "% assignments)\n" : "") + 1256 (nbrSections90 > nbrSections95 ? nbrSections90 + " with ≤ 10% available (" + sDecimalFormat.format(100.0 * nbrSections90 / nbrSections) + "% of all sections, " + 1257 sDecimalFormat.format(100.0 * enrlSections90 / enrlSections) + "% assignments)" : "")); 1258 if (getStudentQuality() != null) { 1259 int shareCR = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.CourseTimeOverlap, assignment); 1260 int shareFT = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.FreeTimeOverlap, assignment); 1261 int shareUN = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.Unavailability, assignment); 1262 if (shareCR > 0) { 1263 Set<Student> students = new HashSet<Student>(); 1264 for (StudentQuality.Conflict c: getStudentQuality().getContext(assignment).computeAllConflicts(StudentQuality.Type.CourseTimeOverlap, assignment)) { 1265 students.add(c.getStudent()); 1266 } 1267 info.put("Time overlaps: courses", students.size() + " students (avg " + sDoubleFormat.format(5.0 * shareCR / students.size()) + " mins)"); 1268 } 1269 if (shareFT > 0) { 1270 Set<Student> students = new HashSet<Student>(); 1271 for (StudentQuality.Conflict c: getStudentQuality().getContext(assignment).computeAllConflicts(StudentQuality.Type.FreeTimeOverlap, assignment)) { 1272 students.add(c.getStudent()); 1273 } 1274 info.put("Time overlaps: free times", students.size() + " students (avg " + sDoubleFormat.format(5.0 * shareFT / students.size()) + " mins)"); 1275 } 1276 if (shareUN > 0) { 1277 Set<Student> students = new HashSet<Student>(); 1278 for (StudentQuality.Conflict c: getStudentQuality().getContext(assignment).computeAllConflicts(StudentQuality.Type.Unavailability, assignment)) { 1279 students.add(c.getStudent()); 1280 } 1281 info.put("Time overlaps: teaching assignments", students.size() + " students (avg " + sDoubleFormat.format(5.0 * shareUN / students.size()) + " mins)"); 1282 } 1283 } else if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) { 1284 Set<TimeOverlapsCounter.Conflict> conf = getTimeOverlaps().getContext(assignment).computeAllConflicts(assignment); 1285 int shareCR = 0, shareFT = 0, shareUN = 0; 1286 Set<Student> studentsCR = new HashSet<Student>(); 1287 Set<Student> studentsFT = new HashSet<Student>(); 1288 Set<Student> studentsUN = new HashSet<Student>(); 1289 for (TimeOverlapsCounter.Conflict c: conf) { 1290 if (c.getR1() instanceof CourseRequest && c.getR2() instanceof CourseRequest) { 1291 shareCR += c.getShare(); studentsCR.add(c.getStudent()); 1292 } else if (c.getS2() instanceof Unavailability) { 1293 shareUN += c.getShare(); studentsUN.add(c.getStudent()); 1294 } else { 1295 shareFT += c.getShare(); studentsFT.add(c.getStudent()); 1296 } 1297 } 1298 if (shareCR > 0) 1299 info.put("Time overlaps: courses", studentsCR.size() + " students (avg " + sDoubleFormat.format(5.0 * shareCR / studentsCR.size()) + " mins)"); 1300 if (shareFT > 0) 1301 info.put("Time overlaps: free times", studentsFT.size() + " students (avg " + sDoubleFormat.format(5.0 * shareFT / studentsFT.size()) + " mins)"); 1302 if (shareUN > 0) 1303 info.put("Time overlaps: teaching assignments", studentsUN.size() + " students (avg " + sDoubleFormat.format(5.0 * shareUN / studentsUN.size()) + " mins)"); 1304 } 1305 1306 1307 return info; 1308 } 1309 1310 @Override 1311 public void restoreBest(Assignment<Request, Enrollment> assignment) { 1312 restoreBest(assignment, new Comparator<Request>() { 1313 @Override 1314 public int compare(Request r1, Request r2) { 1315 Enrollment e1 = r1.getBestAssignment(); 1316 Enrollment e2 = r2.getBestAssignment(); 1317 // Reservations first 1318 if (e1.getReservation() != null && e2.getReservation() == null) return -1; 1319 if (e1.getReservation() == null && e2.getReservation() != null) return 1; 1320 // Then assignment iteration (i.e., order in which assignments were made) 1321 if (r1.getBestAssignmentIteration() != r2.getBestAssignmentIteration()) 1322 return (r1.getBestAssignmentIteration() < r2.getBestAssignmentIteration() ? -1 : 1); 1323 // Then student and priority 1324 return r1.compareTo(r2); 1325 } 1326 }); 1327 recomputeTotalValue(assignment); 1328 } 1329 1330 public void recomputeTotalValue(Assignment<Request, Enrollment> assignment) { 1331 getContext(assignment).iTotalValue = getTotalValue(assignment, true); 1332 } 1333 1334 @Override 1335 public void saveBest(Assignment<Request, Enrollment> assignment) { 1336 recomputeTotalValue(assignment); 1337 iBestAssignedCourseRequestWeight = getContext(assignment).getAssignedCourseRequestWeight(); 1338 super.saveBest(assignment); 1339 } 1340 1341 public double getBestAssignedCourseRequestWeight() { 1342 return iBestAssignedCourseRequestWeight; 1343 } 1344 1345 @Override 1346 public String toString(Assignment<Request, Enrollment> assignment) { 1347 double groupSpread = 0.0; double groupCount = 0; 1348 for (Offering offering: iOfferings) { 1349 for (Course course: offering.getCourses()) { 1350 for (RequestGroup group: course.getRequestGroups()) { 1351 groupSpread += group.getAverageSpread(assignment) * group.getEnrollmentWeight(assignment, null); 1352 groupCount += group.getEnrollmentWeight(assignment, null); 1353 } 1354 } 1355 } 1356 String priority = ""; 1357 for (StudentPriority sp: StudentPriority.values()) { 1358 if (sp.ordinal() < StudentPriority.Normal.ordinal()) { 1359 if (iTotalPriorityCRWeight[sp.ordinal()] > 0.0) 1360 priority += sp.code() + "PCR:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCRWeight[sp.ordinal()] / iTotalPriorityCRWeight[sp.ordinal()]) + "%, "; 1361 if (iTotalPriorityCriticalCRWeight[RequestPriority.Critical.ordinal()][sp.ordinal()] > 0.0) 1362 priority += sp.code() + "PCC:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCriticalCRWeight[RequestPriority.Critical.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[RequestPriority.Critical.ordinal()][sp.ordinal()]) + "%, "; 1363 if (iTotalPriorityCriticalCRWeight[RequestPriority.Important.ordinal()][sp.ordinal()] > 0.0) 1364 priority += sp.code() + "PCI:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCriticalCRWeight[RequestPriority.Important.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[RequestPriority.Important.ordinal()][sp.ordinal()]) + "%, "; 1365 } 1366 } 1367 return (getNrRealStudents(false) > 0 ? "RRq:" + getNrAssignedRealRequests(assignment, false) + "/" + getNrRealRequests(false) + ", " : "") 1368 + (getNrLastLikeStudents(false) > 0 ? "DRq:" + getNrAssignedLastLikeRequests(assignment, false) + "/" + getNrLastLikeRequests(false) + ", " : "") 1369 + (getNrRealStudents(false) > 0 ? "RS:" + getNrCompleteRealStudents(assignment, false) + "/" + getNrRealStudents(false) + ", " : "") 1370 + (getNrLastLikeStudents(false) > 0 ? "DS:" + getNrCompleteLastLikeStudents(assignment, false) + "/" + getNrLastLikeStudents(false) + ", " : "") 1371 + (iTotalCRWeight > 0.0 ? "CR:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCourseRequestWeight() / iTotalCRWeight) + "%, " : "") 1372 + (iTotalSelCRWeight > 0.0 ? "S:" + sDoubleFormat.format(100.0 * (0.3 * getContext(assignment).iAssignedSelectedConfigWeight + 0.7 * getContext(assignment).iAssignedSelectedSectionWeight) / iTotalSelCRWeight) + "%, ": "") 1373 + (iTotalCriticalCRWeight[RequestPriority.Critical.ordinal()] > 0.0 ? "CC:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCriticalCourseRequestWeight(RequestPriority.Critical) / iTotalCriticalCRWeight[RequestPriority.Critical.ordinal()]) + "%, " : "") 1374 + (iTotalCriticalCRWeight[RequestPriority.Important.ordinal()] > 0.0 ? "IC:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCriticalCourseRequestWeight(RequestPriority.Important) / iTotalCriticalCRWeight[RequestPriority.Important.ordinal()]) + "%, " : "") 1375 + priority 1376 + "V:" + sDecimalFormat.format(-getTotalValue(assignment)) 1377 + (getDistanceConflict() == null ? "" : ", DC:" + getDistanceConflict().getTotalNrConflicts(assignment)) 1378 + (getTimeOverlaps() == null ? "" : ", TOC:" + getTimeOverlaps().getTotalNrConflicts(assignment)) 1379 + (iMPP ? ", IS:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedSameSectionWeight / iTotalMPPCRWeight) + "%" : "") 1380 + (iMPP ? ", IT:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedSameTimeWeight / iTotalMPPCRWeight) + "%" : "") 1381 + ", %:" + sDecimalFormat.format(-100.0 * getTotalValue(assignment) / (getStudents().size() - iNrDummyStudents + 1382 (iProjectedStudentWeight < 0.0 ? iNrDummyStudents * (iTotalDummyWeight / iNrDummyRequests) :iProjectedStudentWeight * iTotalDummyWeight))) 1383 + (groupCount > 0 ? ", SG:" + sDecimalFormat.format(100.0 * groupSpread / groupCount) + "%" : "") 1384 + (getStudentQuality() == null ? "" : ", SQ:{" + getStudentQuality().toString(assignment) + "}"); 1385 } 1386 1387 /** 1388 * Quadratic average of two weights. 1389 * @param w1 first weight 1390 * @param w2 second weight 1391 * @return average of the two weights 1392 */ 1393 public double avg(double w1, double w2) { 1394 return Math.sqrt(w1 * w2); 1395 } 1396 1397 /** 1398 * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit. 1399 * @return maximal domain size, -1 if unlimited 1400 */ 1401 public int getMaxDomainSize() { return iMaxDomainSize; } 1402 1403 /** 1404 * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit. 1405 * @param maxDomainSize maximal domain size, -1 if unlimited 1406 */ 1407 public void setMaxDomainSize(int maxDomainSize) { iMaxDomainSize = maxDomainSize; } 1408 1409 1410 @Override 1411 public StudentSectioningModelContext createAssignmentContext(Assignment<Request, Enrollment> assignment) { 1412 return new StudentSectioningModelContext(assignment); 1413 } 1414 1415 public class StudentSectioningModelContext implements AssignmentConstraintContext<Request, Enrollment>, InfoProvider<Request, Enrollment>{ 1416 private Set<Student> iCompleteStudents = new HashSet<Student>(); 1417 private double iTotalValue = 0.0; 1418 private int iNrAssignedDummyRequests = 0, iNrCompleteDummyStudents = 0; 1419 private double iAssignedCRWeight = 0.0, iAssignedDummyCRWeight = 0.0; 1420 private double[] iAssignedCriticalCRWeight; 1421 private double[][] iAssignedPriorityCriticalCRWeight; 1422 private double iReservedSpace = 0.0, iTotalReservedSpace = 0.0; 1423 private double iAssignedSameSectionWeight = 0.0, iAssignedSameChoiceWeight = 0.0, iAssignedSameTimeWeight = 0.0; 1424 private double iAssignedSelectedSectionWeight = 0.0, iAssignedSelectedConfigWeight = 0.0; 1425 private double iAssignedNoTimeSectionWeight = 0.0; 1426 private double iAssignedOnlineSectionWeight = 0.0; 1427 private double iAssignedPastSectionWeight = 0.0; 1428 private int[] iNrCompletePriorityStudents = null; 1429 private double[] iAssignedPriorityCRWeight = null; 1430 1431 public StudentSectioningModelContext(StudentSectioningModelContext parent) { 1432 iCompleteStudents = new HashSet<Student>(parent.iCompleteStudents); 1433 iTotalValue = parent.iTotalValue; 1434 iNrAssignedDummyRequests = parent.iNrAssignedDummyRequests; 1435 iNrCompleteDummyStudents = parent.iNrCompleteDummyStudents; 1436 iAssignedCRWeight = parent.iAssignedCRWeight; 1437 iAssignedDummyCRWeight = parent.iAssignedDummyCRWeight; 1438 iReservedSpace = parent.iReservedSpace; 1439 iTotalReservedSpace = parent.iTotalReservedSpace; 1440 iAssignedSameSectionWeight = parent.iAssignedSameSectionWeight; 1441 iAssignedSameChoiceWeight = parent.iAssignedSameChoiceWeight; 1442 iAssignedSameTimeWeight = parent.iAssignedSameTimeWeight; 1443 iAssignedSelectedSectionWeight = parent.iAssignedSelectedSectionWeight; 1444 iAssignedSelectedConfigWeight = parent.iAssignedSelectedConfigWeight; 1445 iAssignedNoTimeSectionWeight = parent.iAssignedNoTimeSectionWeight; 1446 iAssignedOnlineSectionWeight = parent.iAssignedOnlineSectionWeight; 1447 iAssignedPastSectionWeight = parent.iAssignedPastSectionWeight; 1448 iAssignedCriticalCRWeight = new double[RequestPriority.values().length]; 1449 iAssignedPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length]; 1450 for (int i = 0; i < RequestPriority.values().length; i++) { 1451 iAssignedCriticalCRWeight[i] = parent.iAssignedCriticalCRWeight[i]; 1452 for (int j = 0; j < StudentPriority.values().length; j++) { 1453 iAssignedPriorityCriticalCRWeight[i][j] = parent.iAssignedPriorityCriticalCRWeight[i][j]; 1454 } 1455 } 1456 iNrCompletePriorityStudents = new int[StudentPriority.values().length]; 1457 iAssignedPriorityCRWeight = new double[StudentPriority.values().length]; 1458 for (int i = 0; i < StudentPriority.values().length; i++) { 1459 iNrCompletePriorityStudents[i] = parent.iNrCompletePriorityStudents[i]; 1460 iAssignedPriorityCRWeight[i] = parent.iAssignedPriorityCRWeight[i]; 1461 } 1462 } 1463 1464 public StudentSectioningModelContext(Assignment<Request, Enrollment> assignment) { 1465 iAssignedCriticalCRWeight = new double[RequestPriority.values().length]; 1466 iAssignedPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length]; 1467 for (int i = 0; i < RequestPriority.values().length; i++) { 1468 iAssignedCriticalCRWeight[i] = 0.0; 1469 for (int j = 0; j < StudentPriority.values().length; j++) { 1470 iAssignedPriorityCriticalCRWeight[i][j] = 0.0; 1471 } 1472 } 1473 iNrCompletePriorityStudents = new int[StudentPriority.values().length]; 1474 iAssignedPriorityCRWeight = new double[StudentPriority.values().length]; 1475 for (int i = 0; i < StudentPriority.values().length; i++) { 1476 iNrCompletePriorityStudents[i] = 0; 1477 iAssignedPriorityCRWeight[i] = 0.0; 1478 } 1479 for (Request request: variables()) { 1480 Enrollment enrollment = assignment.getValue(request); 1481 if (enrollment != null) 1482 assigned(assignment, enrollment); 1483 } 1484 } 1485 1486 /** 1487 * Called after an enrollment was assigned to a request. The list of 1488 * complete students and the overall solution value are updated. 1489 */ 1490 @Override 1491 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 1492 Student student = enrollment.getStudent(); 1493 if (student.isComplete(assignment) && iCompleteStudents.add(student)) { 1494 if (student.isDummy()) iNrCompleteDummyStudents++; 1495 iNrCompletePriorityStudents[student.getPriority().ordinal()]++; 1496 } 1497 double value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(assignment, enrollment); 1498 iTotalValue -= value; 1499 enrollment.variable().getContext(assignment).setLastWeight(value); 1500 if (enrollment.isCourseRequest()) 1501 iAssignedCRWeight += enrollment.getRequest().getWeight(); 1502 if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getStudent().isDummy() && !enrollment.getRequest().isAlternative()) 1503 iAssignedCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()] += enrollment.getRequest().getWeight(); 1504 if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getRequest().isAlternative()) 1505 iAssignedPriorityCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()][enrollment.getStudent().getPriority().ordinal()] += enrollment.getRequest().getWeight(); 1506 if (enrollment.getRequest().isMPP()) { 1507 iAssignedSameSectionWeight += enrollment.getRequest().getWeight() * enrollment.percentInitial(); 1508 iAssignedSameChoiceWeight += enrollment.getRequest().getWeight() * enrollment.percentSelected(); 1509 iAssignedSameTimeWeight += enrollment.getRequest().getWeight() * enrollment.percentSameTime(); 1510 } 1511 if (enrollment.getRequest().hasSelection()) { 1512 iAssignedSelectedSectionWeight += enrollment.getRequest().getWeight() * enrollment.percentSelectedSameSection(); 1513 iAssignedSelectedConfigWeight += enrollment.getRequest().getWeight() * enrollment.percentSelectedSameConfig(); 1514 } 1515 if (enrollment.getReservation() != null) 1516 iReservedSpace += enrollment.getRequest().getWeight(); 1517 if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations()) 1518 iTotalReservedSpace += enrollment.getRequest().getWeight(); 1519 if (student.isDummy()) { 1520 iNrAssignedDummyRequests++; 1521 if (enrollment.isCourseRequest()) 1522 iAssignedDummyCRWeight += enrollment.getRequest().getWeight(); 1523 } 1524 if (enrollment.isCourseRequest()) 1525 iAssignedPriorityCRWeight[enrollment.getStudent().getPriority().ordinal()] += enrollment.getRequest().getWeight(); 1526 if (enrollment.isCourseRequest()) { 1527 int noTime = 0; 1528 int online = 0; 1529 int past = 0; 1530 for (Section section: enrollment.getSections()) { 1531 if (!section.hasTime()) noTime ++; 1532 if (section.isOnline()) online ++; 1533 if (section.isPast()) past ++; 1534 } 1535 if (noTime > 0) 1536 iAssignedNoTimeSectionWeight += enrollment.getRequest().getWeight() * noTime / enrollment.getSections().size(); 1537 if (online > 0) 1538 iAssignedOnlineSectionWeight += enrollment.getRequest().getWeight() * online / enrollment.getSections().size(); 1539 if (past > 0) 1540 iAssignedPastSectionWeight += enrollment.getRequest().getWeight() * past / enrollment.getSections().size(); 1541 } 1542 } 1543 1544 /** 1545 * Called before an enrollment was unassigned from a request. The list of 1546 * complete students and the overall solution value are updated. 1547 */ 1548 @Override 1549 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 1550 Student student = enrollment.getStudent(); 1551 if (enrollment.isCourseRequest() && iCompleteStudents.contains(student)) { 1552 iCompleteStudents.remove(student); 1553 if (student.isDummy()) 1554 iNrCompleteDummyStudents--; 1555 iNrCompletePriorityStudents[student.getPriority().ordinal()]--; 1556 } 1557 Request.RequestContext cx = enrollment.variable().getContext(assignment); 1558 Double value = cx.getLastWeight(); 1559 if (value == null) 1560 value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(assignment, enrollment); 1561 iTotalValue += value; 1562 cx.setLastWeight(null); 1563 if (enrollment.isCourseRequest()) 1564 iAssignedCRWeight -= enrollment.getRequest().getWeight(); 1565 if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getStudent().isDummy() && !enrollment.getRequest().isAlternative()) 1566 iAssignedCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()] -= enrollment.getRequest().getWeight(); 1567 if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getRequest().isAlternative()) 1568 iAssignedPriorityCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()][enrollment.getStudent().getPriority().ordinal()] -= enrollment.getRequest().getWeight(); 1569 if (enrollment.getRequest().isMPP()) { 1570 iAssignedSameSectionWeight -= enrollment.getRequest().getWeight() * enrollment.percentInitial(); 1571 iAssignedSameChoiceWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelected(); 1572 iAssignedSameTimeWeight -= enrollment.getRequest().getWeight() * enrollment.percentSameTime(); 1573 } 1574 if (enrollment.getRequest().hasSelection()) { 1575 iAssignedSelectedSectionWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelectedSameSection(); 1576 iAssignedSelectedConfigWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelectedSameConfig(); 1577 } 1578 if (enrollment.getReservation() != null) 1579 iReservedSpace -= enrollment.getRequest().getWeight(); 1580 if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations()) 1581 iTotalReservedSpace -= enrollment.getRequest().getWeight(); 1582 if (student.isDummy()) { 1583 iNrAssignedDummyRequests--; 1584 if (enrollment.isCourseRequest()) 1585 iAssignedDummyCRWeight -= enrollment.getRequest().getWeight(); 1586 } 1587 if (enrollment.isCourseRequest()) 1588 iAssignedPriorityCRWeight[enrollment.getStudent().getPriority().ordinal()] -= enrollment.getRequest().getWeight(); 1589 if (enrollment.isCourseRequest()) { 1590 int noTime = 0; 1591 int online = 0; 1592 int past = 0; 1593 for (Section section: enrollment.getSections()) { 1594 if (!section.hasTime()) noTime ++; 1595 if (section.isOnline()) online ++; 1596 if (section.isPast()) past ++; 1597 } 1598 if (noTime > 0) 1599 iAssignedNoTimeSectionWeight -= enrollment.getRequest().getWeight() * noTime / enrollment.getSections().size(); 1600 if (online > 0) 1601 iAssignedOnlineSectionWeight -= enrollment.getRequest().getWeight() * online / enrollment.getSections().size(); 1602 if (past > 0) 1603 iAssignedPastSectionWeight -= enrollment.getRequest().getWeight() * past / enrollment.getSections().size(); 1604 } 1605 } 1606 1607 public void add(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) { 1608 iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 1609 } 1610 1611 public void remove(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) { 1612 iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 1613 } 1614 1615 public void add(Assignment<Request, Enrollment> assignment, TimeOverlapsCounter.Conflict c) { 1616 if (c.getR1() != null) iTotalValue += c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c); 1617 if (c.getR2() != null) iTotalValue += c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c); 1618 } 1619 1620 public void remove(Assignment<Request, Enrollment> assignment, TimeOverlapsCounter.Conflict c) { 1621 if (c.getR1() != null) iTotalValue -= c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c); 1622 if (c.getR2() != null) iTotalValue -= c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c); 1623 } 1624 1625 public void add(Assignment<Request, Enrollment> assignment, StudentQuality.Conflict c) { 1626 switch (c.getType().getType()) { 1627 case REQUEST: 1628 if (c.getR1() instanceof CourseRequest) 1629 iTotalValue += c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1630 else 1631 iTotalValue += c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 1632 break; 1633 case BOTH: 1634 iTotalValue += c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1635 iTotalValue += c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 1636 break; 1637 case LOWER: 1638 iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1639 break; 1640 case HIGHER: 1641 iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1642 break; 1643 } 1644 } 1645 1646 public void remove(Assignment<Request, Enrollment> assignment, StudentQuality.Conflict c) { 1647 switch (c.getType().getType()) { 1648 case REQUEST: 1649 if (c.getR1() instanceof CourseRequest) 1650 iTotalValue -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1651 else 1652 iTotalValue -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 1653 break; 1654 case BOTH: 1655 iTotalValue -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1656 iTotalValue -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 1657 break; 1658 case LOWER: 1659 iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1660 break; 1661 case HIGHER: 1662 iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1663 break; 1664 } 1665 } 1666 1667 /** 1668 * Students with complete schedules (see {@link Student#isComplete(Assignment)}) 1669 * @return students with complete schedule 1670 */ 1671 public Set<Student> getCompleteStudents() { 1672 return iCompleteStudents; 1673 } 1674 1675 /** 1676 * Number of students with complete schedule 1677 * @return number of students with complete schedule 1678 */ 1679 public int nrComplete() { 1680 return getCompleteStudents().size(); 1681 } 1682 1683 /** 1684 * Recompute cached request weights 1685 * @param assignment curent assignment 1686 */ 1687 public void requestWeightsChanged(Assignment<Request, Enrollment> assignment) { 1688 iTotalCRWeight = 0.0; 1689 iTotalDummyWeight = 0.0; iTotalDummyCRWeight = 0.0; 1690 iTotalPriorityCRWeight = new double[StudentPriority.values().length]; 1691 iAssignedCRWeight = 0.0; 1692 iAssignedDummyCRWeight = 0.0; 1693 iAssignedCriticalCRWeight = new double[RequestPriority.values().length]; 1694 iAssignedPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length]; 1695 for (int i = 0; i < RequestPriority.values().length; i++) { 1696 iAssignedCriticalCRWeight[i] = 0.0; 1697 for (int j = 0; j < StudentPriority.values().length; j++) { 1698 iAssignedPriorityCriticalCRWeight[i][j] = 0.0; 1699 } 1700 } 1701 iAssignedPriorityCRWeight = new double[StudentPriority.values().length]; 1702 for (int i = 0; i < StudentPriority.values().length; i++) { 1703 iAssignedPriorityCRWeight[i] = 0.0; 1704 } 1705 iNrDummyRequests = 0; iNrAssignedDummyRequests = 0; 1706 iTotalReservedSpace = 0.0; iReservedSpace = 0.0; 1707 iTotalMPPCRWeight = 0.0; 1708 iTotalSelCRWeight = 0.0; 1709 iAssignedNoTimeSectionWeight = 0.0; 1710 iAssignedOnlineSectionWeight = 0.0; 1711 iAssignedPastSectionWeight = 0.0; 1712 for (Request request: variables()) { 1713 boolean cr = (request instanceof CourseRequest); 1714 if (cr && !request.isAlternative()) 1715 iTotalCRWeight += request.getWeight(); 1716 if (request.getStudent().isDummy()) { 1717 iTotalDummyWeight += request.getWeight(); 1718 iNrDummyRequests ++; 1719 if (cr && !request.isAlternative()) 1720 iTotalDummyCRWeight += request.getWeight(); 1721 } 1722 if (cr && !request.isAlternative()) { 1723 iTotalPriorityCRWeight[request.getStudent().getPriority().ordinal()] += request.getWeight(); 1724 } 1725 if (request.isMPP()) 1726 iTotalMPPCRWeight += request.getWeight(); 1727 if (request.hasSelection()) 1728 iTotalSelCRWeight += request.getWeight(); 1729 Enrollment e = assignment.getValue(request); 1730 if (e != null) { 1731 if (cr) 1732 iAssignedCRWeight += request.getWeight(); 1733 if (cr && request.getRequestPriority() != RequestPriority.Normal && !request.getStudent().isDummy() && !request.isAlternative()) 1734 iAssignedCriticalCRWeight[request.getRequestPriority().ordinal()] += request.getWeight(); 1735 if (cr && request.getRequestPriority() != RequestPriority.Normal && !request.isAlternative()) 1736 iAssignedPriorityCriticalCRWeight[request.getRequestPriority().ordinal()][request.getStudent().getPriority().ordinal()] += request.getWeight(); 1737 if (request.isMPP()) { 1738 iAssignedSameSectionWeight += request.getWeight() * e.percentInitial(); 1739 iAssignedSameChoiceWeight += request.getWeight() * e.percentSelected(); 1740 iAssignedSameTimeWeight += request.getWeight() * e.percentSameTime(); 1741 } 1742 if (request.hasSelection()) { 1743 iAssignedSelectedSectionWeight += request.getWeight() * e.percentSelectedSameSection(); 1744 iAssignedSelectedConfigWeight += request.getWeight() * e.percentSelectedSameConfig(); 1745 } 1746 if (e.getReservation() != null) 1747 iReservedSpace += request.getWeight(); 1748 if (cr && ((CourseRequest)request).hasReservations()) 1749 iTotalReservedSpace += request.getWeight(); 1750 if (request.getStudent().isDummy()) { 1751 iNrAssignedDummyRequests ++; 1752 if (cr) 1753 iAssignedDummyCRWeight += request.getWeight(); 1754 } 1755 if (cr) { 1756 iAssignedPriorityCRWeight[request.getStudent().getPriority().ordinal()] += request.getWeight(); 1757 } 1758 if (cr) { 1759 int noTime = 0; 1760 int online = 0; 1761 int past = 0; 1762 for (Section section: e.getSections()) { 1763 if (!section.hasTime()) noTime ++; 1764 if (section.isOnline()) online ++; 1765 if (section.isPast()) past ++; 1766 } 1767 if (noTime > 0) 1768 iAssignedNoTimeSectionWeight += request.getWeight() * noTime / e.getSections().size(); 1769 if (online > 0) 1770 iAssignedOnlineSectionWeight += request.getWeight() * online / e.getSections().size(); 1771 if (past > 0) 1772 iAssignedPastSectionWeight += request.getWeight() * past / e.getSections().size(); 1773 } 1774 } 1775 } 1776 } 1777 1778 /** 1779 * Overall solution value 1780 * @return solution value 1781 */ 1782 public double getTotalValue() { 1783 return iTotalValue; 1784 } 1785 1786 /** 1787 * Number of last like ({@link Student#isDummy()} equals true) students with 1788 * a complete schedule ({@link Student#isComplete(Assignment)} equals true). 1789 * @return number of last like (projected) students with a complete schedule 1790 */ 1791 public int getNrCompleteLastLikeStudents() { 1792 return iNrCompleteDummyStudents; 1793 } 1794 1795 /** 1796 * Number of requests from projected ({@link Student#isDummy()} equals true) 1797 * students that are assigned. 1798 * @return number of real students with a complete schedule 1799 */ 1800 public int getNrAssignedLastLikeRequests() { 1801 return iNrAssignedDummyRequests; 1802 } 1803 1804 @Override 1805 public void getInfo(Assignment<Request, Enrollment> assignment, Map<String, String> info) { 1806 if (iTotalCRWeight > 0.0) { 1807 info.put("Assigned course requests", sDecimalFormat.format(100.0 * iAssignedCRWeight / iTotalCRWeight) + "% (" + (int)Math.round(iAssignedCRWeight) + "/" + (int)Math.round(iTotalCRWeight) + ")"); 1808 if (iNrDummyStudents > 0 && iNrDummyStudents != getStudents().size() && iTotalCRWeight != iTotalDummyCRWeight) { 1809 if (iTotalDummyCRWeight > 0.0) 1810 info.put("Projected assigned course requests", sDecimalFormat.format(100.0 * iAssignedDummyCRWeight / iTotalDummyCRWeight) + "% (" + (int)Math.round(iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalDummyCRWeight) + ")"); 1811 info.put("Real assigned course requests", sDecimalFormat.format(100.0 * (iAssignedCRWeight - iAssignedDummyCRWeight) / (iTotalCRWeight - iTotalDummyCRWeight)) + 1812 "% (" + (int)Math.round(iAssignedCRWeight - iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalCRWeight - iTotalDummyCRWeight) + ")"); 1813 } 1814 if (iAssignedNoTimeSectionWeight > 0.0) { 1815 info.put("Using classes w/o time", sDecimalFormat.format(100.0 * iAssignedNoTimeSectionWeight / iAssignedCRWeight) + "% (" + sDecimalFormat.format(iAssignedNoTimeSectionWeight) + ")"); 1816 } 1817 if (iAssignedOnlineSectionWeight > 0.0) { 1818 info.put("Using online classes", sDecimalFormat.format(100.0 * iAssignedOnlineSectionWeight / iAssignedCRWeight) + "% (" + sDecimalFormat.format(iAssignedOnlineSectionWeight) + ")"); 1819 } 1820 if (iAssignedPastSectionWeight > 0.0) { 1821 info.put("Using past classes", sDecimalFormat.format(100.0 * iAssignedPastSectionWeight / iAssignedCRWeight) + "% (" + sDecimalFormat.format(iAssignedPastSectionWeight) + ")"); 1822 } 1823 } 1824 String priorityAssignedCR = ""; 1825 for (StudentPriority sp: StudentPriority.values()) { 1826 if (sp != StudentPriority.Dummy && iTotalPriorityCRWeight[sp.ordinal()] > 0.0) { 1827 priorityAssignedCR += (priorityAssignedCR.isEmpty() ? "" : "\n") + 1828 sp.name() + ": " + sDecimalFormat.format(100.0 * iAssignedPriorityCRWeight[sp.ordinal()] / iTotalPriorityCRWeight[sp.ordinal()]) + "% (" + (int)Math.round(iAssignedPriorityCRWeight[sp.ordinal()]) + "/" + (int)Math.round(iTotalPriorityCRWeight[sp.ordinal()]) + ")"; 1829 } 1830 } 1831 if (!priorityAssignedCR.isEmpty()) 1832 info.put("Assigned course requests (priority students)", priorityAssignedCR); 1833 for (RequestPriority rp: RequestPriority.values()) { 1834 if (rp == RequestPriority.Normal) continue; 1835 if (iTotalCriticalCRWeight[rp.ordinal()] > 0.0) { 1836 info.put("Assigned " + rp.name().toLowerCase() + " course requests", sDoubleFormat.format(100.0 * iAssignedCriticalCRWeight[rp.ordinal()] / iTotalCriticalCRWeight[rp.ordinal()]) + "% (" + (int)Math.round(iAssignedCriticalCRWeight[rp.ordinal()]) + "/" + (int)Math.round(iTotalCriticalCRWeight[rp.ordinal()]) + ")"); 1837 } 1838 priorityAssignedCR = ""; 1839 for (StudentPriority sp: StudentPriority.values()) { 1840 if (sp != StudentPriority.Dummy && iTotalPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()] > 0.0) { 1841 priorityAssignedCR += (priorityAssignedCR.isEmpty() ? "" : "\n") + 1842 sp.name() + ": " + sDoubleFormat.format(100.0 * iAssignedPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()]) + "% (" + (int)Math.round(iAssignedPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()]) + "/" + (int)Math.round(iTotalPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()]) + ")"; 1843 } 1844 } 1845 if (!priorityAssignedCR.isEmpty()) 1846 info.put("Assigned " + rp.name().toLowerCase() + " course requests (priority students)", priorityAssignedCR); 1847 } 1848 if (iTotalReservedSpace > 0.0) 1849 info.put("Reservations", sDoubleFormat.format(100.0 * iReservedSpace / iTotalReservedSpace) + "% (" + Math.round(iReservedSpace) + "/" + Math.round(iTotalReservedSpace) + ")"); 1850 if (iMPP && iTotalMPPCRWeight > 0.0) { 1851 info.put("Perturbations: same section", sDoubleFormat.format(100.0 * iAssignedSameSectionWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameSectionWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")"); 1852 if (iAssignedSameChoiceWeight > iAssignedSameSectionWeight) 1853 info.put("Perturbations: same choice",sDoubleFormat.format(100.0 * iAssignedSameChoiceWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameChoiceWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")"); 1854 if (iAssignedSameTimeWeight > iAssignedSameChoiceWeight) 1855 info.put("Perturbations: same time", sDoubleFormat.format(100.0 * iAssignedSameTimeWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameTimeWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")"); 1856 } 1857 if (iTotalSelCRWeight > 0.0) { 1858 info.put("Selection",sDoubleFormat.format(100.0 * (0.3 * iAssignedSelectedConfigWeight + 0.7 * iAssignedSelectedSectionWeight) / iTotalSelCRWeight) + 1859 "% (" + Math.round(0.3 * iAssignedSelectedConfigWeight + 0.7 * iAssignedSelectedSectionWeight) + "/" + Math.round(iTotalSelCRWeight) + ")"); 1860 } 1861 } 1862 1863 @Override 1864 public void getInfo(Assignment<Request, Enrollment> assignment, Map<String, String> info, Collection<Request> variables) { 1865 } 1866 1867 public double getAssignedCourseRequestWeight() { 1868 return iAssignedCRWeight; 1869 } 1870 1871 public double getAssignedCriticalCourseRequestWeight(RequestPriority rp) { 1872 return iAssignedCriticalCRWeight[rp.ordinal()]; 1873 } 1874 } 1875 1876 @Override 1877 public InheritedAssignment<Request, Enrollment> createInheritedAssignment(Solution<Request, Enrollment> solution, int index) { 1878 return new OptimisticInheritedAssignment<Request, Enrollment>(solution, index); 1879 } 1880 1881 public DistanceMetric getDistanceMetric() { 1882 return (iStudentQuality != null ? iStudentQuality.getDistanceMetric() : iDistanceConflict != null ? iDistanceConflict.getDistanceMetric() : null); 1883 } 1884 1885 @Override 1886 public StudentSectioningModelContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, StudentSectioningModelContext parentContext) { 1887 return new StudentSectioningModelContext(parentContext); 1888 } 1889 1890}