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