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