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