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