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