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