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.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.RequiredSections; 036import org.cpsolver.studentsct.constraint.ReservationLimit; 037import org.cpsolver.studentsct.constraint.SectionLimit; 038import org.cpsolver.studentsct.constraint.StudentConflict; 039import org.cpsolver.studentsct.constraint.StudentNotAvailable; 040import org.cpsolver.studentsct.extension.DistanceConflict; 041import org.cpsolver.studentsct.extension.StudentQuality; 042import org.cpsolver.studentsct.extension.TimeOverlapsCounter; 043import org.cpsolver.studentsct.model.Config; 044import org.cpsolver.studentsct.model.Course; 045import org.cpsolver.studentsct.model.CourseRequest; 046import org.cpsolver.studentsct.model.Enrollment; 047import org.cpsolver.studentsct.model.Offering; 048import org.cpsolver.studentsct.model.Request; 049import org.cpsolver.studentsct.model.RequestGroup; 050import org.cpsolver.studentsct.model.Section; 051import org.cpsolver.studentsct.model.Student; 052import org.cpsolver.studentsct.model.Subpart; 053import org.cpsolver.studentsct.reservation.Reservation; 054import org.cpsolver.studentsct.weights.PriorityStudentWeights; 055import org.cpsolver.studentsct.weights.StudentWeights; 056 057/** 058 * Student sectioning model. 059 * 060 * <br> 061 * <br> 062 * 063 * @version StudentSct 1.3 (Student Sectioning)<br> 064 * Copyright (C) 2007 - 2014 Tomas Muller<br> 065 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 066 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 067 * <br> 068 * This library is free software; you can redistribute it and/or modify 069 * it under the terms of the GNU Lesser General Public License as 070 * published by the Free Software Foundation; either version 3 of the 071 * License, or (at your option) any later version. <br> 072 * <br> 073 * This library is distributed in the hope that it will be useful, but 074 * WITHOUT ANY WARRANTY; without even the implied warranty of 075 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 076 * Lesser General Public License for more details. <br> 077 * <br> 078 * You should have received a copy of the GNU Lesser General Public 079 * License along with this library; if not see 080 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 081 */ 082public class StudentSectioningModel extends ModelWithContext<Request, Enrollment, StudentSectioningModel.StudentSectioningModelContext> implements CanInheritContext<Request, Enrollment, StudentSectioningModel.StudentSectioningModelContext> { 083 private static Logger sLog = Logger.getLogger(StudentSectioningModel.class); 084 protected static DecimalFormat sDecimalFormat = new DecimalFormat("0.00"); 085 private List<Student> iStudents = new ArrayList<Student>(); 086 private List<Offering> iOfferings = new ArrayList<Offering>(); 087 private List<LinkedSections> iLinkedSections = new ArrayList<LinkedSections>(); 088 private DataProperties iProperties; 089 private DistanceConflict iDistanceConflict = null; 090 private TimeOverlapsCounter iTimeOverlaps = null; 091 private StudentQuality iStudentQuality = null; 092 private int iNrDummyStudents = 0, iNrDummyRequests = 0; 093 private double iTotalDummyWeight = 0.0; 094 private double iTotalCRWeight = 0.0, iTotalDummyCRWeight = 0.0; 095 private double iTotalCriticalCRWeight = 0.0; 096 private double iTotalMPPCRWeight = 0.0; 097 private double iTotalSelCRWeight = 0.0; 098 private double iBestAssignedCourseRequestWeight = 0.0; 099 private StudentWeights iStudentWeights = null; 100 private boolean iReservationCanAssignOverTheLimit; 101 private boolean iMPP; 102 private boolean iKeepInitials; 103 protected double iProjectedStudentWeight = 0.0100; 104 private int iMaxDomainSize = -1; 105 106 107 /** 108 * Constructor 109 * 110 * @param properties 111 * configuration 112 */ 113 @SuppressWarnings("unchecked") 114 public StudentSectioningModel(DataProperties properties) { 115 super(); 116 iReservationCanAssignOverTheLimit = properties.getPropertyBoolean("Reservation.CanAssignOverTheLimit", false); 117 iMPP = properties.getPropertyBoolean("General.MPP", false); 118 iKeepInitials = properties.getPropertyBoolean("Sectioning.KeepInitialAssignments", false); 119 iStudentWeights = new PriorityStudentWeights(properties); 120 iMaxDomainSize = properties.getPropertyInt("Sectioning.MaxDomainSize", iMaxDomainSize); 121 if (properties.getPropertyBoolean("Sectioning.SectionLimit", true)) { 122 SectionLimit sectionLimit = new SectionLimit(properties); 123 addGlobalConstraint(sectionLimit); 124 if (properties.getPropertyBoolean("Sectioning.SectionLimit.Debug", false)) { 125 sectionLimit.addConstraintListener(new ConstraintListener<Request, Enrollment>() { 126 @Override 127 public void constraintBeforeAssigned(Assignment<Request, Enrollment> assignment, long iteration, Constraint<Request, Enrollment> constraint, Enrollment enrollment, Set<Enrollment> unassigned) { 128 if (enrollment.getStudent().isDummy()) 129 for (Enrollment conflict : unassigned) { 130 if (!conflict.getStudent().isDummy()) { 131 sLog.warn("Enrolment of a real student " + conflict.getStudent() + " is unassigned " 132 + "\n -- " + conflict + "\ndue to an enrollment of a dummy student " 133 + enrollment.getStudent() + " " + "\n -- " + enrollment); 134 } 135 } 136 } 137 138 @Override 139 public void constraintAfterAssigned(Assignment<Request, Enrollment> assignment, long iteration, Constraint<Request, Enrollment> constraint, Enrollment assigned, Set<Enrollment> unassigned) { 140 } 141 }); 142 } 143 } 144 if (properties.getPropertyBoolean("Sectioning.ConfigLimit", true)) { 145 ConfigLimit configLimit = new ConfigLimit(properties); 146 addGlobalConstraint(configLimit); 147 } 148 if (properties.getPropertyBoolean("Sectioning.CourseLimit", true)) { 149 CourseLimit courseLimit = new CourseLimit(properties); 150 addGlobalConstraint(courseLimit); 151 } 152 if (properties.getPropertyBoolean("Sectioning.ReservationLimit", true)) { 153 ReservationLimit reservationLimit = new ReservationLimit(properties); 154 addGlobalConstraint(reservationLimit); 155 } 156 if (properties.getPropertyBoolean("Sectioning.RequiredReservations", true)) { 157 RequiredReservation requiredReservation = new RequiredReservation(); 158 addGlobalConstraint(requiredReservation); 159 } 160 if (properties.getPropertyBoolean("Sectioning.CancelledSections", true)) { 161 CancelledSections cancelledSections = new CancelledSections(); 162 addGlobalConstraint(cancelledSections); 163 } 164 if (properties.getPropertyBoolean("Sectioning.StudentNotAvailable", true)) { 165 StudentNotAvailable studentNotAvailable = new StudentNotAvailable(); 166 addGlobalConstraint(studentNotAvailable); 167 } 168 if (properties.getPropertyBoolean("Sectioning.DisabledSections", true)) { 169 DisabledSections disabledSections = new DisabledSections(); 170 addGlobalConstraint(disabledSections); 171 } 172 if (properties.getPropertyBoolean("Sectioning.RequiredSections", true)) { 173 RequiredSections requiredSections = new RequiredSections(); 174 addGlobalConstraint(requiredSections); 175 } 176 if (iMPP && iKeepInitials) { 177 addGlobalConstraint(new FixInitialAssignments()); 178 } 179 try { 180 Class<StudentWeights> studentWeightsClass = (Class<StudentWeights>)Class.forName(properties.getProperty("StudentWeights.Class", PriorityStudentWeights.class.getName())); 181 iStudentWeights = studentWeightsClass.getConstructor(DataProperties.class).newInstance(properties); 182 } catch (Exception e) { 183 sLog.error("Unable to create custom student weighting model (" + e.getMessage() + "), using default.", e); 184 iStudentWeights = new PriorityStudentWeights(properties); 185 } 186 iProjectedStudentWeight = properties.getPropertyDouble("StudentWeights.ProjectedStudentWeight", iProjectedStudentWeight); 187 iProperties = properties; 188 } 189 190 /** 191 * Return true if reservation that has {@link Reservation#canAssignOverLimit()} can assign enrollments over the limit 192 * @return true if reservation that has {@link Reservation#canAssignOverLimit()} can assign enrollments over the limit 193 */ 194 public boolean getReservationCanAssignOverTheLimit() { 195 return iReservationCanAssignOverTheLimit; 196 } 197 198 /** 199 * Return true if the problem is minimal perturbation problem 200 * @return true if MPP is enabled 201 */ 202 public boolean isMPP() { 203 return iMPP; 204 } 205 206 /** 207 * Return true if the inital assignments are to be kept unchanged 208 * @return true if the initial assignments are to be kept at all cost 209 */ 210 public boolean getKeepInitialAssignments() { 211 return iKeepInitials; 212 } 213 214 /** 215 * Return student weighting model 216 * @return student weighting model 217 */ 218 public StudentWeights getStudentWeights() { 219 return iStudentWeights; 220 } 221 222 /** 223 * Set student weighting model 224 * @param weights student weighting model 225 */ 226 public void setStudentWeights(StudentWeights weights) { 227 iStudentWeights = weights; 228 } 229 230 /** 231 * Students 232 * @return all students in the problem 233 */ 234 public List<Student> getStudents() { 235 return iStudents; 236 } 237 238 /** 239 * Add a student into the model 240 * @param student a student to be added into the problem 241 */ 242 public void addStudent(Student student) { 243 iStudents.add(student); 244 if (student.isDummy()) 245 iNrDummyStudents++; 246 for (Request request : student.getRequests()) 247 addVariable(request); 248 if (getProperties().getPropertyBoolean("Sectioning.StudentConflict", true)) { 249 addConstraint(new StudentConflict(student)); 250 } 251 } 252 253 @Override 254 public void addVariable(Request request) { 255 super.addVariable(request); 256 if (request instanceof CourseRequest && !request.isAlternative()) 257 iTotalCRWeight += request.getWeight(); 258 if (request instanceof CourseRequest && request.isCritical() && !request.getStudent().isDummy() && !request.isAlternative()) 259 iTotalCriticalCRWeight += request.getWeight(); 260 if (request.getStudent().isDummy()) { 261 iNrDummyRequests++; 262 iTotalDummyWeight += request.getWeight(); 263 if (request instanceof CourseRequest && !request.isAlternative()) 264 iTotalDummyCRWeight += request.getWeight(); 265 } 266 if (request.isMPP()) 267 iTotalMPPCRWeight += request.getWeight(); 268 if (request.hasSelection()) 269 iTotalSelCRWeight += request.getWeight(); 270 } 271 272 /** 273 * Recompute cached request weights 274 * @param assignment current assignment 275 */ 276 public void requestWeightsChanged(Assignment<Request, Enrollment> assignment) { 277 getContext(assignment).requestWeightsChanged(assignment); 278 } 279 280 /** 281 * Remove a student from the model 282 * @param student a student to be removed from the problem 283 */ 284 public void removeStudent(Student student) { 285 iStudents.remove(student); 286 if (student.isDummy()) 287 iNrDummyStudents--; 288 StudentConflict conflict = null; 289 for (Request request : student.getRequests()) { 290 for (Constraint<Request, Enrollment> c : request.constraints()) { 291 if (c instanceof StudentConflict) { 292 conflict = (StudentConflict) c; 293 break; 294 } 295 } 296 if (conflict != null) 297 conflict.removeVariable(request); 298 removeVariable(request); 299 } 300 if (conflict != null) 301 removeConstraint(conflict); 302 } 303 304 @Override 305 public void removeVariable(Request request) { 306 super.removeVariable(request); 307 if (request instanceof CourseRequest) { 308 CourseRequest cr = (CourseRequest)request; 309 for (Course course: cr.getCourses()) 310 course.getRequests().remove(request); 311 } 312 if (request.getStudent().isDummy()) { 313 iNrDummyRequests--; 314 iTotalDummyWeight -= request.getWeight(); 315 if (request instanceof CourseRequest && !request.isAlternative()) 316 iTotalDummyCRWeight -= request.getWeight(); 317 } 318 if (request.isMPP()) 319 iTotalMPPCRWeight -= request.getWeight(); 320 if (request.hasSelection()) 321 iTotalSelCRWeight -= request.getWeight(); 322 if (request instanceof CourseRequest && !request.isAlternative()) 323 iTotalCRWeight -= request.getWeight(); 324 if (request instanceof CourseRequest && request.isCritical() && !request.getStudent().isDummy() && !request.isAlternative()) 325 iTotalCriticalCRWeight -= request.getWeight(); 326 } 327 328 329 /** 330 * List of offerings 331 * @return all instructional offerings of the problem 332 */ 333 public List<Offering> getOfferings() { 334 return iOfferings; 335 } 336 337 /** 338 * Add an offering into the model 339 * @param offering an instructional offering to be added into the problem 340 */ 341 public void addOffering(Offering offering) { 342 iOfferings.add(offering); 343 offering.setModel(this); 344 } 345 346 /** 347 * Link sections using {@link LinkedSections} 348 * @param mustBeUsed if true, a pair of linked sections must be used when a student requests both courses 349 * @param sections a linked section constraint to be added into the problem 350 */ 351 public void addLinkedSections(boolean mustBeUsed, Section... sections) { 352 LinkedSections constraint = new LinkedSections(sections); 353 constraint.setMustBeUsed(mustBeUsed); 354 iLinkedSections.add(constraint); 355 constraint.createConstraints(); 356 } 357 358 /** 359 * Link sections using {@link LinkedSections} 360 * @param sections a linked section constraint to be added into the problem 361 */ 362 @Deprecated 363 public void addLinkedSections(Section... sections) { 364 addLinkedSections(false, sections); 365 } 366 367 /** 368 * Link sections using {@link LinkedSections} 369 * @param mustBeUsed if true, a pair of linked sections must be used when a student requests both courses 370 * @param sections a linked section constraint to be added into the problem 371 */ 372 public void addLinkedSections(boolean mustBeUsed, Collection<Section> sections) { 373 LinkedSections constraint = new LinkedSections(sections); 374 constraint.setMustBeUsed(mustBeUsed); 375 iLinkedSections.add(constraint); 376 constraint.createConstraints(); 377 } 378 379 /** 380 * Link sections using {@link LinkedSections} 381 * @param sections a linked section constraint to be added into the problem 382 */ 383 @Deprecated 384 public void addLinkedSections(Collection<Section> sections) { 385 addLinkedSections(false, sections); 386 } 387 388 /** 389 * List of linked sections 390 * @return all linked section constraints of the problem 391 */ 392 public List<LinkedSections> getLinkedSections() { 393 return iLinkedSections; 394 } 395 396 /** 397 * Model info 398 */ 399 @Override 400 public Map<String, String> getInfo(Assignment<Request, Enrollment> assignment) { 401 Map<String, String> info = super.getInfo(assignment); 402 StudentSectioningModelContext context = getContext(assignment); 403 if (!getStudents().isEmpty()) 404 info.put("Students with complete schedule", sDoubleFormat.format(100.0 * context.nrComplete() / getStudents().size()) + "% (" + context.nrComplete() + "/" + getStudents().size() + ")"); 405 if (getStudentQuality() != null) { 406 int confs = getStudentQuality().getTotalPenalty(StudentQuality.Type.Distance, assignment); 407 int shortConfs = getStudentQuality().getTotalPenalty(StudentQuality.Type.ShortDistance, assignment); 408 if (confs > 0 || shortConfs > 0) { 409 info.put("Student distance conflicts", confs + (shortConfs == 0 ? "" : " (" + getDistanceMetric().getShortDistanceAccommodationReference() + ": " + shortConfs + ")")); 410 } 411 } else if (getDistanceConflict() != null) { 412 int confs = getDistanceConflict().getTotalNrConflicts(assignment); 413 if (confs > 0) { 414 int shortConfs = getDistanceConflict().getTotalNrShortConflicts(assignment); 415 info.put("Student distance conflicts", confs + (shortConfs == 0 ? "" : " (" + getDistanceConflict().getDistanceMetric().getShortDistanceAccommodationReference() + ": " + shortConfs + ")")); 416 } 417 } 418 if (getStudentQuality() != null) { 419 int shareCR = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.CourseTimeOverlap, assignment); 420 int shareFT = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.FreeTimeOverlap, assignment); 421 int shareUN = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.Unavailability, assignment); 422 if (shareCR + shareFT + shareUN > 0) 423 info.put("Time overlapping conflicts", sDoubleFormat.format((5.0 * (shareCR + shareFT + shareUN)) / iStudents.size()) + " mins per student\n" + 424 "(" + sDoubleFormat.format(5.0 * shareCR / iStudents.size()) + " between courses, " + sDoubleFormat.format(5.0 * shareFT / iStudents.size()) + " free time" + 425 (shareUN == 0 ? "" : ", " + sDoubleFormat.format(5.0 * shareUN / iStudents.size()) + " teaching assignments") + "; " + sDoubleFormat.format((shareCR + shareFT + shareUN) / 12.0) + " hours total)"); 426 } else if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) { 427 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)"); 428 } 429 if (getStudentQuality() != null) { 430 int confLunch = getStudentQuality().getTotalPenalty(StudentQuality.Type.LunchBreak, assignment); 431 if (confLunch > 0) 432 info.put("Schedule Quality: Lunch conflicts", sDoubleFormat.format(20.0 * confLunch / getNrRealStudents(false)) + "% (" + confLunch + ")"); 433 int confTravel = getStudentQuality().getTotalPenalty(StudentQuality.Type.TravelTime, assignment); 434 if (confTravel > 0) 435 info.put("Schedule Quality: Travel time", sDoubleFormat.format(((double)confTravel) / getNrRealStudents(false)) + " mins per student (" + sDecimalFormat.format(confTravel / 60.0) + " hours total)"); 436 int confBtB = getStudentQuality().getTotalPenalty(StudentQuality.Type.BackToBack, assignment); 437 if (confBtB > 0) 438 info.put("Schedule Quality: Back-to-back classes", sDoubleFormat.format(((double)confBtB) / getNrRealStudents(false)) + " per student (" + confBtB + ")"); 439 int confWorkDay = getStudentQuality().getTotalPenalty(StudentQuality.Type.WorkDay, assignment); 440 if (confWorkDay > 0) 441 info.put("Schedule Quality: Work day", sDoubleFormat.format(5.0 * confWorkDay / getNrRealStudents(false)) + " mins over " + 442 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)"); 443 int early = getStudentQuality().getTotalPenalty(StudentQuality.Type.TooEarly, assignment); 444 if (early > 0) { 445 int min = getProperties().getPropertyInt("WorkDay.EarlySlot", 102) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN; 446 int h = min / 60; 447 int m = min % 60; 448 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); 449 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)"); 450 } 451 int late = getStudentQuality().getTotalPenalty(StudentQuality.Type.TooLate, assignment); 452 if (late > 0) { 453 int min = getProperties().getPropertyInt("WorkDay.LateSlot", 210) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN; 454 int h = min / 60; 455 int m = min % 60; 456 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); 457 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)"); 458 } 459 } 460 int nrLastLikeStudents = getNrLastLikeStudents(false); 461 if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) { 462 int nrRealStudents = getStudents().size() - nrLastLikeStudents; 463 int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(assignment, false); 464 int nrRealCompleteStudents = context.nrComplete() - nrLastLikeCompleteStudents; 465 if (nrLastLikeStudents > 0) 466 info.put("Projected students with complete schedule", sDecimalFormat.format(100.0 467 * nrLastLikeCompleteStudents / nrLastLikeStudents) 468 + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")"); 469 if (nrRealStudents > 0) 470 info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents 471 / nrRealStudents) 472 + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")"); 473 int nrLastLikeRequests = getNrLastLikeRequests(false); 474 int nrRealRequests = variables().size() - nrLastLikeRequests; 475 int nrLastLikeAssignedRequests = context.getNrAssignedLastLikeRequests(); 476 int nrRealAssignedRequests = assignment.nrAssignedVariables() - nrLastLikeAssignedRequests; 477 if (nrLastLikeRequests > 0) 478 info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests / nrLastLikeRequests) 479 + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")"); 480 if (nrRealRequests > 0) 481 info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests) 482 + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")"); 483 } 484 context.getInfo(assignment, info); 485 486 487 double groupSpread = 0.0; double groupCount = 0; 488 for (Offering offering: iOfferings) { 489 for (Course course: offering.getCourses()) { 490 for (RequestGroup group: course.getRequestGroups()) { 491 groupSpread += group.getAverageSpread(assignment) * group.getEnrollmentWeight(assignment, null); 492 groupCount += group.getEnrollmentWeight(assignment, null); 493 } 494 } 495 } 496 if (groupCount > 0) 497 info.put("Same group", sDecimalFormat.format(100.0 * groupSpread / groupCount) + "%"); 498 499 return info; 500 } 501 502 /** 503 * Overall solution value 504 * @param assignment current assignment 505 * @param precise true if should be computed 506 * @return solution value 507 */ 508 public double getTotalValue(Assignment<Request, Enrollment> assignment, boolean precise) { 509 if (precise) { 510 double total = 0; 511 for (Request r: assignment.assignedVariables()) 512 total += r.getWeight() * iStudentWeights.getWeight(assignment, assignment.getValue(r)); 513 if (iDistanceConflict != null) 514 for (DistanceConflict.Conflict c: iDistanceConflict.computeAllConflicts(assignment)) 515 total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 516 if (iTimeOverlaps != null) 517 for (TimeOverlapsCounter.Conflict c: iTimeOverlaps.getContext(assignment).computeAllConflicts(assignment)) { 518 if (c.getR1() != null) total -= c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c); 519 if (c.getR2() != null) total -= c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c); 520 } 521 if (iStudentQuality != null) 522 for (StudentQuality.Type t: StudentQuality.Type.values()) { 523 for (StudentQuality.Conflict c: iStudentQuality.getContext(assignment).computeAllConflicts(t, assignment)) { 524 switch (c.getType().getType()) { 525 case REQUEST: 526 if (c.getR1() instanceof CourseRequest) 527 total -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 528 else 529 total -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 530 break; 531 case BOTH: 532 total -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 533 total -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 534 break; 535 case LOWER: 536 total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 537 break; 538 case HIGHER: 539 total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 540 break; 541 } 542 } 543 } 544 return -total; 545 } 546 return getContext(assignment).getTotalValue(); 547 } 548 549 /** 550 * Overall solution value 551 */ 552 @Override 553 public double getTotalValue(Assignment<Request, Enrollment> assignment) { 554 return getContext(assignment).getTotalValue(); 555 } 556 557 /** 558 * Configuration 559 * @return solver configuration 560 */ 561 public DataProperties getProperties() { 562 return iProperties; 563 } 564 565 /** 566 * Empty online student sectioning infos for all sections (see 567 * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}). 568 */ 569 public void clearOnlineSectioningInfos() { 570 for (Offering offering : iOfferings) { 571 for (Config config : offering.getConfigs()) { 572 for (Subpart subpart : config.getSubparts()) { 573 for (Section section : subpart.getSections()) { 574 section.setSpaceExpected(0); 575 section.setSpaceHeld(0); 576 } 577 } 578 } 579 } 580 } 581 582 /** 583 * Compute online student sectioning infos for all sections (see 584 * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}). 585 * @param assignment current assignment 586 */ 587 public void computeOnlineSectioningInfos(Assignment<Request, Enrollment> assignment) { 588 clearOnlineSectioningInfos(); 589 for (Student student : getStudents()) { 590 if (!student.isDummy()) 591 continue; 592 for (Request request : student.getRequests()) { 593 if (!(request instanceof CourseRequest)) 594 continue; 595 CourseRequest courseRequest = (CourseRequest) request; 596 Enrollment enrollment = assignment.getValue(courseRequest); 597 if (enrollment != null) { 598 for (Section section : enrollment.getSections()) { 599 section.setSpaceHeld(courseRequest.getWeight() + section.getSpaceHeld()); 600 } 601 } 602 List<Enrollment> feasibleEnrollments = new ArrayList<Enrollment>(); 603 int totalLimit = 0; 604 for (Enrollment enrl : courseRequest.values(assignment)) { 605 boolean overlaps = false; 606 for (Request otherRequest : student.getRequests()) { 607 if (otherRequest.equals(courseRequest) || !(otherRequest instanceof CourseRequest)) 608 continue; 609 Enrollment otherErollment = assignment.getValue(otherRequest); 610 if (otherErollment == null) 611 continue; 612 if (enrl.isOverlapping(otherErollment)) { 613 overlaps = true; 614 break; 615 } 616 } 617 if (!overlaps) { 618 feasibleEnrollments.add(enrl); 619 if (totalLimit >= 0) { 620 int limit = enrl.getLimit(); 621 if (limit < 0) totalLimit = -1; 622 else totalLimit += limit; 623 } 624 } 625 } 626 double increment = courseRequest.getWeight() / (totalLimit > 0 ? totalLimit : feasibleEnrollments.size()); 627 for (Enrollment feasibleEnrollment : feasibleEnrollments) { 628 for (Section section : feasibleEnrollment.getSections()) { 629 if (totalLimit > 0) { 630 section.setSpaceExpected(section.getSpaceExpected() + increment * feasibleEnrollment.getLimit()); 631 } else { 632 section.setSpaceExpected(section.getSpaceExpected() + increment); 633 } 634 } 635 } 636 } 637 } 638 } 639 640 /** 641 * Sum of weights of all requests that are not assigned (see 642 * {@link Request#getWeight()}). 643 * @param assignment current assignment 644 * @return unassigned request weight 645 */ 646 public double getUnassignedRequestWeight(Assignment<Request, Enrollment> assignment) { 647 double weight = 0.0; 648 for (Request request : assignment.unassignedVariables(this)) { 649 weight += request.getWeight(); 650 } 651 return weight; 652 } 653 654 /** 655 * Sum of weights of all requests (see {@link Request#getWeight()}). 656 * @return total request weight 657 */ 658 public double getTotalRequestWeight() { 659 double weight = 0.0; 660 for (Request request : variables()) { 661 weight += request.getWeight(); 662 } 663 return weight; 664 } 665 666 /** 667 * Set distance conflict extension 668 * @param dc distance conflicts extension 669 */ 670 public void setDistanceConflict(DistanceConflict dc) { 671 iDistanceConflict = dc; 672 } 673 674 /** 675 * Return distance conflict extension 676 * @return distance conflicts extension 677 */ 678 public DistanceConflict getDistanceConflict() { 679 return iDistanceConflict; 680 } 681 682 /** 683 * Set time overlaps extension 684 * @param toc time overlapping conflicts extension 685 */ 686 public void setTimeOverlaps(TimeOverlapsCounter toc) { 687 iTimeOverlaps = toc; 688 } 689 690 /** 691 * Return time overlaps extension 692 * @return time overlapping conflicts extension 693 */ 694 public TimeOverlapsCounter getTimeOverlaps() { 695 return iTimeOverlaps; 696 } 697 698 public StudentQuality getStudentQuality() { return iStudentQuality; } 699 public void setStudentQuality(StudentQuality q, boolean register) { 700 if (iStudentQuality != null) 701 getInfoProviders().remove(iStudentQuality); 702 iStudentQuality = q; 703 if (iStudentQuality != null) 704 getInfoProviders().add(iStudentQuality); 705 if (register) { 706 iStudentQuality.setAssignmentContextReference(createReference(iStudentQuality)); 707 iStudentQuality.register(this); 708 } 709 } 710 711 public void setStudentQuality(StudentQuality q) { 712 setStudentQuality(q, true); 713 } 714 715 /** 716 * Average priority of unassigned requests (see 717 * {@link Request#getPriority()}) 718 * @param assignment current assignment 719 * @return average priority of unassigned requests 720 */ 721 public double avgUnassignPriority(Assignment<Request, Enrollment> assignment) { 722 double totalPriority = 0.0; 723 for (Request request : assignment.unassignedVariables(this)) { 724 if (request.isAlternative()) 725 continue; 726 totalPriority += request.getPriority(); 727 } 728 return 1.0 + totalPriority / assignment.nrUnassignedVariables(this); 729 } 730 731 /** 732 * Average number of requests per student (see {@link Student#getRequests()} 733 * ) 734 * @return average number of requests per student 735 */ 736 public double avgNrRequests() { 737 double totalRequests = 0.0; 738 int totalStudents = 0; 739 for (Student student : getStudents()) { 740 if (student.nrRequests() == 0) 741 continue; 742 totalRequests += student.nrRequests(); 743 totalStudents++; 744 } 745 return totalRequests / totalStudents; 746 } 747 748 /** Number of last like ({@link Student#isDummy()} equals true) students. 749 * @param precise true if to be computed 750 * @return number of last like (projected) students 751 **/ 752 public int getNrLastLikeStudents(boolean precise) { 753 if (!precise) 754 return iNrDummyStudents; 755 int nrLastLikeStudents = 0; 756 for (Student student : getStudents()) { 757 if (student.isDummy()) 758 nrLastLikeStudents++; 759 } 760 return nrLastLikeStudents; 761 } 762 763 /** Number of real ({@link Student#isDummy()} equals false) students. 764 * @param precise true if to be computed 765 * @return number of real students 766 **/ 767 public int getNrRealStudents(boolean precise) { 768 if (!precise) 769 return getStudents().size() - iNrDummyStudents; 770 int nrRealStudents = 0; 771 for (Student student : getStudents()) { 772 if (!student.isDummy()) 773 nrRealStudents++; 774 } 775 return nrRealStudents; 776 } 777 778 /** 779 * Number of last like ({@link Student#isDummy()} equals true) students with 780 * a complete schedule ({@link Student#isComplete(Assignment)} equals true). 781 * @param assignment current assignment 782 * @param precise true if to be computed 783 * @return number of last like (projected) students with a complete schedule 784 */ 785 public int getNrCompleteLastLikeStudents(Assignment<Request, Enrollment> assignment, boolean precise) { 786 if (!precise) 787 return getContext(assignment).getNrCompleteLastLikeStudents(); 788 int nrLastLikeStudents = 0; 789 for (Student student : getStudents()) { 790 if (student.isComplete(assignment) && student.isDummy()) 791 nrLastLikeStudents++; 792 } 793 return nrLastLikeStudents; 794 } 795 796 /** 797 * Number of real ({@link Student#isDummy()} equals false) students with a 798 * complete schedule ({@link Student#isComplete(Assignment)} equals true). 799 * @param assignment current assignment 800 * @param precise true if to be computed 801 * @return number of real students with a complete schedule 802 */ 803 public int getNrCompleteRealStudents(Assignment<Request, Enrollment> assignment, boolean precise) { 804 if (!precise) 805 return getContext(assignment).nrComplete() - getContext(assignment).getNrCompleteLastLikeStudents(); 806 int nrRealStudents = 0; 807 for (Student student : getStudents()) { 808 if (student.isComplete(assignment) && !student.isDummy()) 809 nrRealStudents++; 810 } 811 return nrRealStudents; 812 } 813 814 /** 815 * Number of requests from projected ({@link Student#isDummy()} equals true) 816 * students. 817 * @param precise true if to be computed 818 * @return number of requests from projected students 819 */ 820 public int getNrLastLikeRequests(boolean precise) { 821 if (!precise) 822 return iNrDummyRequests; 823 int nrLastLikeRequests = 0; 824 for (Request request : variables()) { 825 if (request.getStudent().isDummy()) 826 nrLastLikeRequests++; 827 } 828 return nrLastLikeRequests; 829 } 830 831 /** 832 * Number of requests from real ({@link Student#isDummy()} equals false) 833 * students. 834 * @param precise true if to be computed 835 * @return number of requests from real students 836 */ 837 public int getNrRealRequests(boolean precise) { 838 if (!precise) 839 return variables().size() - iNrDummyRequests; 840 int nrRealRequests = 0; 841 for (Request request : variables()) { 842 if (!request.getStudent().isDummy()) 843 nrRealRequests++; 844 } 845 return nrRealRequests; 846 } 847 848 /** 849 * Number of requests from projected ({@link Student#isDummy()} equals true) 850 * students that are assigned. 851 * @param assignment current assignment 852 * @param precise true if to be computed 853 * @return number of requests from projected students that are assigned 854 */ 855 public int getNrAssignedLastLikeRequests(Assignment<Request, Enrollment> assignment, boolean precise) { 856 if (!precise) 857 return getContext(assignment).getNrAssignedLastLikeRequests(); 858 int nrLastLikeRequests = 0; 859 for (Request request : assignment.assignedVariables()) { 860 if (request.getStudent().isDummy()) 861 nrLastLikeRequests++; 862 } 863 return nrLastLikeRequests; 864 } 865 866 /** 867 * Number of requests from real ({@link Student#isDummy()} equals false) 868 * students that are assigned. 869 * @param assignment current assignment 870 * @param precise true if to be computed 871 * @return number of requests from real students that are assigned 872 */ 873 public int getNrAssignedRealRequests(Assignment<Request, Enrollment> assignment, boolean precise) { 874 if (!precise) 875 return assignment.nrAssignedVariables() - getContext(assignment).getNrAssignedLastLikeRequests(); 876 int nrRealRequests = 0; 877 for (Request request : assignment.assignedVariables()) { 878 if (!request.getStudent().isDummy()) 879 nrRealRequests++; 880 } 881 return nrRealRequests; 882 } 883 884 /** 885 * Model extended info. Some more information (that is more expensive to 886 * compute) is added to an ordinary {@link Model#getInfo(Assignment)}. 887 */ 888 @Override 889 public Map<String, String> getExtendedInfo(Assignment<Request, Enrollment> assignment) { 890 Map<String, String> info = getInfo(assignment); 891 /* 892 int nrLastLikeStudents = getNrLastLikeStudents(true); 893 if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) { 894 int nrRealStudents = getStudents().size() - nrLastLikeStudents; 895 int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(true); 896 int nrRealCompleteStudents = getCompleteStudents().size() - nrLastLikeCompleteStudents; 897 info.put("Projected students with complete schedule", sDecimalFormat.format(100.0 898 * nrLastLikeCompleteStudents / nrLastLikeStudents) 899 + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")"); 900 info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents 901 / nrRealStudents) 902 + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")"); 903 int nrLastLikeRequests = getNrLastLikeRequests(true); 904 int nrRealRequests = variables().size() - nrLastLikeRequests; 905 int nrLastLikeAssignedRequests = getNrAssignedLastLikeRequests(true); 906 int nrRealAssignedRequests = assignedVariables().size() - nrLastLikeAssignedRequests; 907 info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests 908 / nrLastLikeRequests) 909 + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")"); 910 info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests) 911 + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")"); 912 } 913 */ 914 // info.put("Average unassigned priority", sDecimalFormat.format(avgUnassignPriority())); 915 // info.put("Average number of requests", sDecimalFormat.format(avgNrRequests())); 916 917 /* 918 double total = 0; 919 for (Request r: variables()) 920 if (r.getAssignment() != null) 921 total += r.getWeight() * iStudentWeights.getWeight(r.getAssignment()); 922 */ 923 /* 924 double dc = 0; 925 if (getDistanceConflict() != null && getDistanceConflict().getTotalNrConflicts(assignment) != 0) { 926 Set<DistanceConflict.Conflict> conf = getDistanceConflict().getAllConflicts(assignment); 927 int sdc = 0; 928 for (DistanceConflict.Conflict c: conf) { 929 dc += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 930 if (c.getStudent().isNeedShortDistances()) sdc ++; 931 } 932 if (!conf.isEmpty()) 933 info.put("Student distance conflicts", conf.size() + (sdc > 0 ? " (" + getDistanceConflict().getDistanceMetric().getShortDistanceAccommodationReference() + ": " + sdc + ", weighted: " : " (weighted: ") + sDecimalFormat.format(dc) + ")"); 934 } 935 */ 936 if (getStudentQuality() == null && getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) { 937 Set<TimeOverlapsCounter.Conflict> conf = getTimeOverlaps().getContext(assignment).computeAllConflicts(assignment); 938 int share = 0, crShare = 0; 939 for (TimeOverlapsCounter.Conflict c: conf) { 940 share += c.getShare(); 941 if (c.getR1() instanceof CourseRequest && c.getR2() instanceof CourseRequest) 942 crShare += c.getShare(); 943 } 944 if (share > 0) 945 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)"); 946 } 947 /* 948 info.put("Overall solution value", sDecimalFormat.format(total - dc - toc) + (dc == 0.0 && toc == 0.0 ? "" : 949 " (" + (dc != 0.0 ? "distance: " + sDecimalFormat.format(dc): "") + (dc != 0.0 && toc != 0.0 ? ", " : "") + 950 (toc != 0.0 ? "overlap: " + sDecimalFormat.format(toc) : "") + ")") 951 ); 952 */ 953 954 double disbWeight = 0; 955 int disbSections = 0; 956 int disb10Sections = 0; 957 int disb10Limit = getProperties().getPropertyInt("Info.ListDisbalancedSections", 0); 958 Set<String> disb10SectionList = (disb10Limit == 0 ? null : new TreeSet<String>()); 959 for (Offering offering: getOfferings()) { 960 for (Config config: offering.getConfigs()) { 961 double enrl = config.getEnrollmentTotalWeight(assignment, null); 962 for (Subpart subpart: config.getSubparts()) { 963 if (subpart.getSections().size() <= 1) continue; 964 if (subpart.getLimit() > 0) { 965 // sections have limits -> desired size is section limit x (total enrollment / total limit) 966 double ratio = enrl / subpart.getLimit(); 967 for (Section section: subpart.getSections()) { 968 double desired = ratio * section.getLimit(); 969 disbWeight += Math.abs(section.getEnrollmentTotalWeight(assignment, null) - desired); 970 disbSections ++; 971 if (Math.abs(desired - section.getEnrollmentTotalWeight(assignment, null)) >= Math.max(1.0, 0.1 * section.getLimit())) { 972 disb10Sections++; 973 if (disb10SectionList != null) 974 disb10SectionList.add(section.getSubpart().getConfig().getOffering().getName() + " " + section.getSubpart().getName() + " " + section.getName()); 975 } 976 } 977 } else { 978 // unlimited sections -> desired size is total enrollment / number of sections 979 for (Section section: subpart.getSections()) { 980 double desired = enrl / subpart.getSections().size(); 981 disbWeight += Math.abs(section.getEnrollmentTotalWeight(assignment, null) - desired); 982 disbSections ++; 983 if (Math.abs(desired - section.getEnrollmentTotalWeight(assignment, null)) >= Math.max(1.0, 0.1 * desired)) { 984 disb10Sections++; 985 if (disb10SectionList != null) 986 disb10SectionList.add(section.getSubpart().getConfig().getOffering().getName() + " " + section.getSubpart().getName() + " " + section.getName()); 987 } 988 } 989 } 990 } 991 } 992 } 993 if (disbSections != 0) { 994 double assignedCRWeight = getContext(assignment).getAssignedCourseRequestWeight(); 995 info.put("Average disbalance", sDecimalFormat.format(assignedCRWeight == 0 ? 0.0 : 100.0 * disbWeight / assignedCRWeight) + "% (" + sDecimalFormat.format(disbWeight / disbSections) + ")"); 996 String list = ""; 997 if (disb10SectionList != null) { 998 int i = 0; 999 for (String section: disb10SectionList) { 1000 if (i == disb10Limit) { 1001 list += "\n..."; 1002 break; 1003 } 1004 list += "\n" + section; 1005 i++; 1006 } 1007 } 1008 info.put("Sections disbalanced by 10% or more", sDecimalFormat.format(disbSections == 0 ? 0.0 : 100.0 * disb10Sections / disbSections) + "% (" + disb10Sections + ")" + (list.isEmpty() ? "" : "\n" + list)); 1009 } 1010 1011 int assCR = 0, priCR = 0; 1012 for (Request r: variables()) { 1013 if (r instanceof CourseRequest && !r.getStudent().isDummy()) { 1014 CourseRequest cr = (CourseRequest)r; 1015 Enrollment e = assignment.getValue(cr); 1016 if (e != null) { 1017 assCR ++; 1018 if (!cr.isAlternative() && cr.getCourses().get(0).equals(e.getCourse())) priCR ++; 1019 } 1020 } 1021 } 1022 info.put("Assigned priority course requests", sDoubleFormat.format(100.0 * priCR / assCR) + "% (" + priCR + "/" + assCR + ")"); 1023 int[] missing = new int[] {0, 0, 0, 0, 0}; 1024 int incomplete = 0; 1025 for (Student student: getStudents()) { 1026 if (student.isDummy()) continue; 1027 int nrRequests = 0; 1028 int nrAssignedRequests = 0; 1029 for (Request r : student.getRequests()) { 1030 if (!(r instanceof CourseRequest)) continue; // ignore free times 1031 if (!r.isAlternative()) nrRequests++; 1032 if (r.isAssigned(assignment)) nrAssignedRequests++; 1033 } 1034 if (nrAssignedRequests < nrRequests) { 1035 missing[Math.min(nrRequests - nrAssignedRequests, missing.length) - 1] ++; 1036 incomplete ++; 1037 } 1038 } 1039 1040 for (int i = 0; i < missing.length; i++) 1041 if (missing[i] > 0) 1042 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] + ")"); 1043 1044 info.put("Overall solution value", sDoubleFormat.format(getTotalValue(assignment)) + " [precise: " + sDoubleFormat.format(getTotalValue(assignment, true)) + "]"); 1045 1046 int nrStudentsBelowMinCredit = 0, nrStudents = 0; 1047 for (Student student: getStudents()) { 1048 if (student.isDummy()) continue; 1049 if (student.hasMinCredit()) { 1050 nrStudents++; 1051 float credit = student.getAssignedCredit(assignment); 1052 if (credit < student.getMinCredit() && !student.isComplete(assignment)) 1053 nrStudentsBelowMinCredit ++; 1054 } 1055 } 1056 if (nrStudentsBelowMinCredit > 0) 1057 info.put("Students below min credit", sDoubleFormat.format(100.0 * nrStudentsBelowMinCredit / nrStudents) + "% (" + nrStudentsBelowMinCredit + "/" + nrStudents + ")"); 1058 1059 int[] notAssignedPriority = new int[] {0, 0, 0, 0, 0, 0, 0}; 1060 int[] assignedChoice = new int[] {0, 0, 0, 0, 0}; 1061 int notAssignedTotal = 0, assignedChoiceTotal = 0; 1062 int avgPriority = 0, avgChoice = 0; 1063 for (Student student: getStudents()) { 1064 if (student.isDummy()) continue; 1065 for (Request r : student.getRequests()) { 1066 if (!(r instanceof CourseRequest)) continue; // ignore free times 1067 Enrollment e = r.getAssignment(assignment); 1068 if (e == null) { 1069 if (!r.isAlternative()) { 1070 notAssignedPriority[Math.min(r.getPriority(), notAssignedPriority.length - 1)] ++; 1071 notAssignedTotal ++; 1072 avgPriority += r.getPriority(); 1073 } 1074 } else { 1075 assignedChoice[Math.min(e.getTruePriority(), assignedChoice.length - 1)] ++; 1076 assignedChoiceTotal ++; 1077 avgChoice += e.getTruePriority(); 1078 } 1079 } 1080 } 1081 for (int i = 0; i < notAssignedPriority.length; i++) 1082 if (notAssignedPriority[i] > 0) 1083 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] + ")"); 1084 info.put("Priority: Average not-assigned priority", sDecimalFormat.format(1.0 + ((double)avgPriority) / notAssignedTotal)); 1085 for (int i = 0; i < assignedChoice.length; i++) 1086 if (assignedChoice[i] > 0) 1087 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] + ")"); 1088 info.put("Choice: Average assigned choice", sDecimalFormat.format(1.0 + ((double)avgChoice) / assignedChoiceTotal)); 1089 return info; 1090 } 1091 1092 @Override 1093 public void restoreBest(Assignment<Request, Enrollment> assignment) { 1094 restoreBest(assignment, new Comparator<Request>() { 1095 @Override 1096 public int compare(Request r1, Request r2) { 1097 Enrollment e1 = r1.getBestAssignment(); 1098 Enrollment e2 = r2.getBestAssignment(); 1099 // Reservations first 1100 if (e1.getReservation() != null && e2.getReservation() == null) return -1; 1101 if (e1.getReservation() == null && e2.getReservation() != null) return 1; 1102 // Then assignment iteration (i.e., order in which assignments were made) 1103 if (r1.getBestAssignmentIteration() != r2.getBestAssignmentIteration()) 1104 return (r1.getBestAssignmentIteration() < r2.getBestAssignmentIteration() ? -1 : 1); 1105 // Then student and priority 1106 return r1.compareTo(r2); 1107 } 1108 }); 1109 recomputeTotalValue(assignment); 1110 } 1111 1112 public void recomputeTotalValue(Assignment<Request, Enrollment> assignment) { 1113 getContext(assignment).iTotalValue = getTotalValue(assignment, true); 1114 } 1115 1116 @Override 1117 public void saveBest(Assignment<Request, Enrollment> assignment) { 1118 recomputeTotalValue(assignment); 1119 iBestAssignedCourseRequestWeight = getContext(assignment).getAssignedCourseRequestWeight(); 1120 super.saveBest(assignment); 1121 } 1122 1123 public double getBestAssignedCourseRequestWeight() { 1124 return iBestAssignedCourseRequestWeight; 1125 } 1126 1127 @Override 1128 public String toString(Assignment<Request, Enrollment> assignment) { 1129 double groupSpread = 0.0; double groupCount = 0; 1130 for (Offering offering: iOfferings) { 1131 for (Course course: offering.getCourses()) { 1132 for (RequestGroup group: course.getRequestGroups()) { 1133 groupSpread += group.getAverageSpread(assignment) * group.getEnrollmentWeight(assignment, null); 1134 groupCount += group.getEnrollmentWeight(assignment, null); 1135 } 1136 } 1137 } 1138 return (getNrRealStudents(false) > 0 ? "RRq:" + getNrAssignedRealRequests(assignment, false) + "/" + getNrRealRequests(false) + ", " : "") 1139 + (getNrLastLikeStudents(false) > 0 ? "DRq:" + getNrAssignedLastLikeRequests(assignment, false) + "/" + getNrLastLikeRequests(false) + ", " : "") 1140 + (getNrRealStudents(false) > 0 ? "RS:" + getNrCompleteRealStudents(assignment, false) + "/" + getNrRealStudents(false) + ", " : "") 1141 + (getNrLastLikeStudents(false) > 0 ? "DS:" + getNrCompleteLastLikeStudents(assignment, false) + "/" + getNrLastLikeStudents(false) + ", " : "") 1142 + (iTotalCRWeight > 0.0 ? "CR:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCourseRequestWeight() / iTotalCRWeight) + "%, " : "") 1143 + (iTotalSelCRWeight > 0.0 ? "S:" + sDoubleFormat.format(100.0 * (0.3 * getContext(assignment).iAssignedSelectedConfigWeight + 0.7 * getContext(assignment).iAssignedSelectedSectionWeight) / iTotalSelCRWeight) + "%, ": "") 1144 + (iTotalCriticalCRWeight > 0.0 ? "CC:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCriticalCourseRequestWeight() / iTotalCriticalCRWeight) + "%, " : "") 1145 + "V:" 1146 + sDecimalFormat.format(-getTotalValue(assignment)) 1147 + (getDistanceConflict() == null ? "" : ", DC:" + getDistanceConflict().getTotalNrConflicts(assignment)) 1148 + (getTimeOverlaps() == null ? "" : ", TOC:" + getTimeOverlaps().getTotalNrConflicts(assignment)) 1149 + (iMPP ? ", IS:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedSameSectionWeight / iTotalMPPCRWeight) + "%" : "") 1150 + (iMPP ? ", IT:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedSameTimeWeight / iTotalMPPCRWeight) + "%" : "") 1151 + ", %:" + sDecimalFormat.format(-100.0 * getTotalValue(assignment) / (getStudents().size() - iNrDummyStudents + 1152 (iProjectedStudentWeight < 0.0 ? iNrDummyStudents * (iTotalDummyWeight / iNrDummyRequests) :iProjectedStudentWeight * iTotalDummyWeight))) 1153 + (groupCount > 0 ? ", SG:" + sDecimalFormat.format(100.0 * groupSpread / groupCount) + "%" : "") 1154 + (getStudentQuality() == null ? "" : ", SQ:{" + getStudentQuality().toString(assignment) + "}"); 1155 } 1156 1157 /** 1158 * Quadratic average of two weights. 1159 * @param w1 first weight 1160 * @param w2 second weight 1161 * @return average of the two weights 1162 */ 1163 public double avg(double w1, double w2) { 1164 return Math.sqrt(w1 * w2); 1165 } 1166 1167 /** 1168 * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit. 1169 * @return maximal domain size, -1 if unlimited 1170 */ 1171 public int getMaxDomainSize() { return iMaxDomainSize; } 1172 1173 /** 1174 * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit. 1175 * @param maxDomainSize maximal domain size, -1 if unlimited 1176 */ 1177 public void setMaxDomainSize(int maxDomainSize) { iMaxDomainSize = maxDomainSize; } 1178 1179 1180 @Override 1181 public StudentSectioningModelContext createAssignmentContext(Assignment<Request, Enrollment> assignment) { 1182 return new StudentSectioningModelContext(assignment); 1183 } 1184 1185 public class StudentSectioningModelContext implements AssignmentConstraintContext<Request, Enrollment>, InfoProvider<Request, Enrollment>{ 1186 private Set<Student> iCompleteStudents = new HashSet<Student>(); 1187 private double iTotalValue = 0.0; 1188 private int iNrAssignedDummyRequests = 0, iNrCompleteDummyStudents = 0; 1189 private double iAssignedCRWeight = 0.0, iAssignedDummyCRWeight = 0.0, iAssignedCriticalCRWeight = 0.0; 1190 private double iReservedSpace = 0.0, iTotalReservedSpace = 0.0; 1191 private double iAssignedSameSectionWeight = 0.0, iAssignedSameChoiceWeight = 0.0, iAssignedSameTimeWeight = 0.0; 1192 private double iAssignedSelectedSectionWeight = 0.0, iAssignedSelectedConfigWeight = 0.0; 1193 private double iAssignedNoTimeSectionWeight = 0.0; 1194 1195 public StudentSectioningModelContext(StudentSectioningModelContext parent) { 1196 iCompleteStudents = new HashSet<Student>(parent.iCompleteStudents); 1197 iTotalValue = parent.iTotalValue; 1198 iNrAssignedDummyRequests = parent.iNrAssignedDummyRequests; 1199 iNrCompleteDummyStudents = parent.iNrCompleteDummyStudents; 1200 iAssignedCRWeight = parent.iAssignedCRWeight; 1201 iAssignedCriticalCRWeight = parent.iAssignedCriticalCRWeight; 1202 iAssignedDummyCRWeight = parent.iAssignedDummyCRWeight; 1203 iReservedSpace = parent.iReservedSpace; 1204 iTotalReservedSpace = parent.iTotalReservedSpace; 1205 iAssignedSameSectionWeight = parent.iAssignedSameSectionWeight; 1206 iAssignedSameChoiceWeight = parent.iAssignedSameChoiceWeight; 1207 iAssignedSameTimeWeight = parent.iAssignedSameTimeWeight; 1208 iAssignedSelectedSectionWeight = parent.iAssignedSelectedSectionWeight; 1209 iAssignedSelectedConfigWeight = parent.iAssignedSelectedConfigWeight; 1210 iAssignedNoTimeSectionWeight = parent.iAssignedNoTimeSectionWeight; 1211 } 1212 1213 public StudentSectioningModelContext(Assignment<Request, Enrollment> assignment) { 1214 for (Request request: variables()) { 1215 Enrollment enrollment = assignment.getValue(request); 1216 if (enrollment != null) 1217 assigned(assignment, enrollment); 1218 } 1219 } 1220 1221 /** 1222 * Called after an enrollment was assigned to a request. The list of 1223 * complete students and the overall solution value are updated. 1224 */ 1225 @Override 1226 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 1227 Student student = enrollment.getStudent(); 1228 if (student.isComplete(assignment)) 1229 iCompleteStudents.add(student); 1230 double value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(assignment, enrollment); 1231 iTotalValue -= value; 1232 enrollment.variable().getContext(assignment).setLastWeight(value); 1233 if (enrollment.isCourseRequest()) 1234 iAssignedCRWeight += enrollment.getRequest().getWeight(); 1235 if (enrollment.isCourseRequest() && enrollment.getRequest().isCritical() && !enrollment.getStudent().isDummy() && !enrollment.getRequest().isAlternative()) 1236 iAssignedCriticalCRWeight += enrollment.getRequest().getWeight(); 1237 if (enrollment.getRequest().isMPP()) { 1238 iAssignedSameSectionWeight += enrollment.getRequest().getWeight() * enrollment.percentInitial(); 1239 iAssignedSameChoiceWeight += enrollment.getRequest().getWeight() * enrollment.percentSelected(); 1240 iAssignedSameTimeWeight += enrollment.getRequest().getWeight() * enrollment.percentSameTime(); 1241 } 1242 if (enrollment.getRequest().hasSelection()) { 1243 iAssignedSelectedSectionWeight += enrollment.getRequest().getWeight() * enrollment.percentSelectedSameSection(); 1244 iAssignedSelectedConfigWeight += enrollment.getRequest().getWeight() * enrollment.percentSelectedSameConfig(); 1245 } 1246 if (enrollment.getReservation() != null) 1247 iReservedSpace += enrollment.getRequest().getWeight(); 1248 if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations()) 1249 iTotalReservedSpace += enrollment.getRequest().getWeight(); 1250 if (student.isDummy()) { 1251 iNrAssignedDummyRequests++; 1252 if (enrollment.isCourseRequest()) 1253 iAssignedDummyCRWeight += enrollment.getRequest().getWeight(); 1254 if (student.isComplete(assignment)) 1255 iNrCompleteDummyStudents++; 1256 } 1257 if (enrollment.isCourseRequest()) { 1258 int noTime = 0; 1259 for (Section section: enrollment.getSections()) 1260 if (section.getTime() == null) noTime ++; 1261 if (noTime > 0) 1262 iAssignedNoTimeSectionWeight += enrollment.getRequest().getWeight() * noTime / enrollment.getSections().size(); 1263 } 1264 } 1265 1266 /** 1267 * Called before an enrollment was unassigned from a request. The list of 1268 * complete students and the overall solution value are updated. 1269 */ 1270 @Override 1271 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 1272 Student student = enrollment.getStudent(); 1273 if (iCompleteStudents.contains(student)) { 1274 iCompleteStudents.remove(student); 1275 if (student.isDummy()) 1276 iNrCompleteDummyStudents--; 1277 } 1278 Request.RequestContext cx = enrollment.variable().getContext(assignment); 1279 Double value = cx.getLastWeight(); 1280 if (value == null) 1281 value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(assignment, enrollment); 1282 iTotalValue += value; 1283 cx.setLastWeight(null); 1284 if (enrollment.isCourseRequest()) 1285 iAssignedCRWeight -= enrollment.getRequest().getWeight(); 1286 if (enrollment.isCourseRequest() && enrollment.getRequest().isCritical() && !enrollment.getStudent().isDummy() && !enrollment.getRequest().isAlternative()) 1287 iAssignedCriticalCRWeight -= enrollment.getRequest().getWeight(); 1288 if (enrollment.getRequest().isMPP()) { 1289 iAssignedSameSectionWeight -= enrollment.getRequest().getWeight() * enrollment.percentInitial(); 1290 iAssignedSameChoiceWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelected(); 1291 iAssignedSameTimeWeight -= enrollment.getRequest().getWeight() * enrollment.percentSameTime(); 1292 } 1293 if (enrollment.getRequest().hasSelection()) { 1294 iAssignedSelectedSectionWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelectedSameSection(); 1295 iAssignedSelectedConfigWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelectedSameConfig(); 1296 } 1297 if (enrollment.getReservation() != null) 1298 iReservedSpace -= enrollment.getRequest().getWeight(); 1299 if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations()) 1300 iTotalReservedSpace -= enrollment.getRequest().getWeight(); 1301 if (student.isDummy()) { 1302 iNrAssignedDummyRequests--; 1303 if (enrollment.isCourseRequest()) 1304 iAssignedDummyCRWeight -= enrollment.getRequest().getWeight(); 1305 } 1306 if (enrollment.isCourseRequest()) { 1307 int noTime = 0; 1308 for (Section section: enrollment.getSections()) 1309 if (section.getTime() == null) noTime ++; 1310 if (noTime > 0) 1311 iAssignedNoTimeSectionWeight -= enrollment.getRequest().getWeight() * noTime / enrollment.getSections().size(); 1312 } 1313 } 1314 1315 public void add(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) { 1316 iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 1317 } 1318 1319 public void remove(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) { 1320 iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 1321 } 1322 1323 public void add(Assignment<Request, Enrollment> assignment, TimeOverlapsCounter.Conflict c) { 1324 if (c.getR1() != null) iTotalValue += c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c); 1325 if (c.getR2() != null) iTotalValue += c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c); 1326 } 1327 1328 public void remove(Assignment<Request, Enrollment> assignment, TimeOverlapsCounter.Conflict c) { 1329 if (c.getR1() != null) iTotalValue -= c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c); 1330 if (c.getR2() != null) iTotalValue -= c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c); 1331 } 1332 1333 public void add(Assignment<Request, Enrollment> assignment, StudentQuality.Conflict c) { 1334 switch (c.getType().getType()) { 1335 case REQUEST: 1336 if (c.getR1() instanceof CourseRequest) 1337 iTotalValue += c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1338 else 1339 iTotalValue += c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 1340 break; 1341 case BOTH: 1342 iTotalValue += c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1343 iTotalValue += c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 1344 break; 1345 case LOWER: 1346 iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1347 break; 1348 case HIGHER: 1349 iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1350 break; 1351 } 1352 } 1353 1354 public void remove(Assignment<Request, Enrollment> assignment, StudentQuality.Conflict c) { 1355 switch (c.getType().getType()) { 1356 case REQUEST: 1357 if (c.getR1() instanceof CourseRequest) 1358 iTotalValue -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1359 else 1360 iTotalValue -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 1361 break; 1362 case BOTH: 1363 iTotalValue -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1364 iTotalValue -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c); 1365 break; 1366 case LOWER: 1367 iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1368 break; 1369 case HIGHER: 1370 iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c); 1371 break; 1372 } 1373 } 1374 1375 /** 1376 * Students with complete schedules (see {@link Student#isComplete(Assignment)}) 1377 * @return students with complete schedule 1378 */ 1379 public Set<Student> getCompleteStudents() { 1380 return iCompleteStudents; 1381 } 1382 1383 /** 1384 * Number of students with complete schedule 1385 * @return number of students with complete schedule 1386 */ 1387 public int nrComplete() { 1388 return getCompleteStudents().size(); 1389 } 1390 1391 /** 1392 * Recompute cached request weights 1393 * @param assignment curent assignment 1394 */ 1395 public void requestWeightsChanged(Assignment<Request, Enrollment> assignment) { 1396 iTotalCRWeight = 0.0; 1397 iTotalDummyWeight = 0.0; iTotalDummyCRWeight = 0.0; 1398 iAssignedCRWeight = 0.0; 1399 iAssignedDummyCRWeight = 0.0; 1400 iAssignedCriticalCRWeight = 0.0; 1401 iNrDummyRequests = 0; iNrAssignedDummyRequests = 0; 1402 iTotalReservedSpace = 0.0; iReservedSpace = 0.0; 1403 iTotalMPPCRWeight = 0.0; 1404 iTotalSelCRWeight = 0.0; 1405 iAssignedNoTimeSectionWeight = 0.0; 1406 for (Request request: variables()) { 1407 boolean cr = (request instanceof CourseRequest); 1408 if (cr && !request.isAlternative()) 1409 iTotalCRWeight += request.getWeight(); 1410 if (request.getStudent().isDummy()) { 1411 iTotalDummyWeight += request.getWeight(); 1412 iNrDummyRequests ++; 1413 if (cr && !request.isAlternative()) 1414 iTotalDummyCRWeight += request.getWeight(); 1415 } 1416 if (request.isMPP()) 1417 iTotalMPPCRWeight += request.getWeight(); 1418 if (request.hasSelection()) 1419 iTotalSelCRWeight += request.getWeight(); 1420 Enrollment e = assignment.getValue(request); 1421 if (e != null) { 1422 if (cr) 1423 iAssignedCRWeight += request.getWeight(); 1424 if (cr && request.isCritical() && !request.getStudent().isDummy() && !request.isAlternative()) 1425 iAssignedCriticalCRWeight += request.getWeight(); 1426 if (request.isMPP()) { 1427 iAssignedSameSectionWeight += request.getWeight() * e.percentInitial(); 1428 iAssignedSameChoiceWeight += request.getWeight() * e.percentSelected(); 1429 iAssignedSameTimeWeight += request.getWeight() * e.percentSameTime(); 1430 } 1431 if (request.hasSelection()) { 1432 iAssignedSelectedSectionWeight += request.getWeight() * e.percentSelectedSameSection(); 1433 iAssignedSelectedConfigWeight += request.getWeight() * e.percentSelectedSameConfig(); 1434 } 1435 if (e.getReservation() != null) 1436 iReservedSpace += request.getWeight(); 1437 if (cr && ((CourseRequest)request).hasReservations()) 1438 iTotalReservedSpace += request.getWeight(); 1439 if (request.getStudent().isDummy()) { 1440 iNrAssignedDummyRequests ++; 1441 if (cr) 1442 iAssignedDummyCRWeight += request.getWeight(); 1443 } 1444 if (cr) { 1445 int noTime = 0; 1446 for (Section section: e.getSections()) 1447 if (section.getTime() == null) noTime ++; 1448 if (noTime > 0) 1449 iAssignedNoTimeSectionWeight += request.getWeight() * noTime / e.getSections().size(); 1450 } 1451 } 1452 } 1453 } 1454 1455 /** 1456 * Overall solution value 1457 * @return solution value 1458 */ 1459 public double getTotalValue() { 1460 return iTotalValue; 1461 } 1462 1463 /** 1464 * Number of last like ({@link Student#isDummy()} equals true) students with 1465 * a complete schedule ({@link Student#isComplete(Assignment)} equals true). 1466 * @return number of last like (projected) students with a complete schedule 1467 */ 1468 public int getNrCompleteLastLikeStudents() { 1469 return iNrCompleteDummyStudents; 1470 } 1471 1472 /** 1473 * Number of requests from projected ({@link Student#isDummy()} equals true) 1474 * students that are assigned. 1475 * @return number of real students with a complete schedule 1476 */ 1477 public int getNrAssignedLastLikeRequests() { 1478 return iNrAssignedDummyRequests; 1479 } 1480 1481 @Override 1482 public void getInfo(Assignment<Request, Enrollment> assignment, Map<String, String> info) { 1483 if (iTotalCRWeight > 0.0) { 1484 info.put("Assigned course requests", sDecimalFormat.format(100.0 * iAssignedCRWeight / iTotalCRWeight) + "% (" + (int)Math.round(iAssignedCRWeight) + "/" + (int)Math.round(iTotalCRWeight) + ")"); 1485 if (iNrDummyStudents > 0 && iNrDummyStudents != getStudents().size() && iTotalCRWeight != iTotalDummyCRWeight) { 1486 if (iTotalDummyCRWeight > 0.0) 1487 info.put("Projected assigned course requests", sDecimalFormat.format(100.0 * iAssignedDummyCRWeight / iTotalDummyCRWeight) + "% (" + (int)Math.round(iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalDummyCRWeight) + ")"); 1488 info.put("Real assigned course requests", sDecimalFormat.format(100.0 * (iAssignedCRWeight - iAssignedDummyCRWeight) / (iTotalCRWeight - iTotalDummyCRWeight)) + 1489 "% (" + (int)Math.round(iAssignedCRWeight - iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalCRWeight - iTotalDummyCRWeight) + ")"); 1490 } 1491 if (iAssignedNoTimeSectionWeight > 0.0) { 1492 info.put("Using classes w/o time", sDecimalFormat.format(100.0 * iAssignedNoTimeSectionWeight / iAssignedCRWeight) + "% (" + sDecimalFormat.format(iAssignedNoTimeSectionWeight) + ")"); 1493 } 1494 } 1495 if (iTotalCriticalCRWeight > 0.0) { 1496 info.put("Assigned critical course requests", sDoubleFormat.format(100.0 * iAssignedCriticalCRWeight / iTotalCriticalCRWeight) + "% (" + (int)Math.round(iAssignedCriticalCRWeight) + "/" + (int)Math.round(iTotalCriticalCRWeight) + ")"); 1497 } 1498 if (iTotalReservedSpace > 0.0) 1499 info.put("Reservations", sDoubleFormat.format(100.0 * iReservedSpace / iTotalReservedSpace) + "% (" + Math.round(iReservedSpace) + "/" + Math.round(iTotalReservedSpace) + ")"); 1500 if (iMPP && iTotalMPPCRWeight > 0.0) { 1501 info.put("Perturbations: same section", sDoubleFormat.format(100.0 * iAssignedSameSectionWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameSectionWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")"); 1502 if (iAssignedSameChoiceWeight > iAssignedSameSectionWeight) 1503 info.put("Perturbations: same choice",sDoubleFormat.format(100.0 * iAssignedSameChoiceWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameChoiceWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")"); 1504 if (iAssignedSameTimeWeight > iAssignedSameChoiceWeight) 1505 info.put("Perturbations: same time", sDoubleFormat.format(100.0 * iAssignedSameTimeWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameTimeWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")"); 1506 } 1507 if (iTotalSelCRWeight > 0.0) { 1508 info.put("Selection",sDoubleFormat.format(100.0 * (0.3 * iAssignedSelectedConfigWeight + 0.7 * iAssignedSelectedSectionWeight) / iTotalSelCRWeight) + 1509 "% (" + Math.round(0.3 * iAssignedSelectedSectionWeight + 0.7 * iAssignedSelectedSectionWeight) + "/" + Math.round(iTotalSelCRWeight) + ")"); 1510 } 1511 } 1512 1513 @Override 1514 public void getInfo(Assignment<Request, Enrollment> assignment, Map<String, String> info, Collection<Request> variables) { 1515 } 1516 1517 public double getAssignedCourseRequestWeight() { 1518 return iAssignedCRWeight; 1519 } 1520 1521 public double getAssignedCriticalCourseRequestWeight() { 1522 return iAssignedCriticalCRWeight; 1523 } 1524 } 1525 1526 @Override 1527 public InheritedAssignment<Request, Enrollment> createInheritedAssignment(Solution<Request, Enrollment> solution, int index) { 1528 return new OptimisticInheritedAssignment<Request, Enrollment>(solution, index); 1529 } 1530 1531 public DistanceMetric getDistanceMetric() { 1532 return (iStudentQuality != null ? iStudentQuality.getDistanceMetric() : iDistanceConflict != null ? iDistanceConflict.getDistanceMetric() : null); 1533 } 1534 1535 @Override 1536 public StudentSectioningModelContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, StudentSectioningModelContext parentContext) { 1537 return new StudentSectioningModelContext(parentContext); 1538 } 1539 1540}