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