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