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