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