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 mustBeUsed if true, a pair of linked sections must be used when a student requests both courses 318 * @param sections a linked section constraint to be added into the problem 319 */ 320 public void addLinkedSections(boolean mustBeUsed, Section... sections) { 321 LinkedSections constraint = new LinkedSections(sections); 322 constraint.setMustBeUsed(mustBeUsed); 323 iLinkedSections.add(constraint); 324 constraint.createConstraints(); 325 } 326 327 /** 328 * Link sections using {@link LinkedSections} 329 * @param sections a linked section constraint to be added into the problem 330 */ 331 @Deprecated 332 public void addLinkedSections(Section... sections) { 333 addLinkedSections(false, sections); 334 } 335 336 /** 337 * Link sections using {@link LinkedSections} 338 * @param mustBeUsed if true, a pair of linked sections must be used when a student requests both courses 339 * @param sections a linked section constraint to be added into the problem 340 */ 341 public void addLinkedSections(boolean mustBeUsed, Collection<Section> sections) { 342 LinkedSections constraint = new LinkedSections(sections); 343 constraint.setMustBeUsed(mustBeUsed); 344 iLinkedSections.add(constraint); 345 constraint.createConstraints(); 346 } 347 348 /** 349 * Link sections using {@link LinkedSections} 350 * @param sections a linked section constraint to be added into the problem 351 */ 352 @Deprecated 353 public void addLinkedSections(Collection<Section> sections) { 354 addLinkedSections(false, sections); 355 } 356 357 /** 358 * List of linked sections 359 * @return all linked section constraints of the problem 360 */ 361 public List<LinkedSections> getLinkedSections() { 362 return iLinkedSections; 363 } 364 365 /** 366 * Model info 367 */ 368 @Override 369 public Map<String, String> getInfo(Assignment<Request, Enrollment> assignment) { 370 Map<String, String> info = super.getInfo(assignment); 371 StudentSectioningModelContext context = getContext(assignment); 372 if (!getStudents().isEmpty()) 373 info.put("Students with complete schedule", sDoubleFormat.format(100.0 * context.nrComplete() / getStudents().size()) + "% (" + context.nrComplete() + "/" + getStudents().size() + ")"); 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 int nrLastLikeStudents = getNrLastLikeStudents(false); 379 if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) { 380 int nrRealStudents = getStudents().size() - nrLastLikeStudents; 381 int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(assignment, false); 382 int nrRealCompleteStudents = context.nrComplete() - nrLastLikeCompleteStudents; 383 if (nrLastLikeStudents > 0) 384 info.put("Projected students with complete schedule", sDecimalFormat.format(100.0 385 * nrLastLikeCompleteStudents / nrLastLikeStudents) 386 + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")"); 387 if (nrRealStudents > 0) 388 info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents 389 / nrRealStudents) 390 + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")"); 391 int nrLastLikeRequests = getNrLastLikeRequests(false); 392 int nrRealRequests = variables().size() - nrLastLikeRequests; 393 int nrLastLikeAssignedRequests = context.getNrAssignedLastLikeRequests(); 394 int nrRealAssignedRequests = assignment.nrAssignedVariables() - nrLastLikeAssignedRequests; 395 if (nrLastLikeRequests > 0) 396 info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests / nrLastLikeRequests) 397 + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")"); 398 if (nrRealRequests > 0) 399 info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests) 400 + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")"); 401 if (getDistanceConflict() != null && getDistanceConflict().getTotalNrConflicts(assignment) > 0) 402 info.put("Student distance conflicts", String.valueOf(getDistanceConflict().getTotalNrConflicts(assignment))); 403 if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) > 0) 404 info.put("Time overlapping conflicts", String.valueOf(getTimeOverlaps().getTotalNrConflicts(assignment))); 405 } 406 context.getInfo(assignment, info); 407 408 return info; 409 } 410 411 /** 412 * Overall solution value 413 * @param assignment current assignment 414 * @param precise true if should be computed 415 * @return solution value 416 */ 417 public double getTotalValue(Assignment<Request, Enrollment> assignment, boolean precise) { 418 if (precise) { 419 double total = 0; 420 for (Request r: assignment.assignedVariables()) 421 total += r.getWeight() * iStudentWeights.getWeight(assignment, assignment.getValue(r)); 422 if (iDistanceConflict != null) 423 for (DistanceConflict.Conflict c: iDistanceConflict.computeAllConflicts(assignment)) 424 total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 425 if (iTimeOverlaps != null) 426 for (TimeOverlapsCounter.Conflict c: iTimeOverlaps.getContext(assignment).computeAllConflicts(assignment)) { 427 total -= c.getR1().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c); 428 total -= c.getR2().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c); 429 } 430 return -total; 431 } 432 return getContext(assignment).getTotalValue(); 433 } 434 435 /** 436 * Overall solution value 437 */ 438 @Override 439 public double getTotalValue(Assignment<Request, Enrollment> assignment) { 440 return getContext(assignment).getTotalValue(); 441 } 442 443 /** 444 * Configuration 445 * @return solver configuration 446 */ 447 public DataProperties getProperties() { 448 return iProperties; 449 } 450 451 /** 452 * Empty online student sectioning infos for all sections (see 453 * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}). 454 */ 455 public void clearOnlineSectioningInfos() { 456 for (Offering offering : iOfferings) { 457 for (Config config : offering.getConfigs()) { 458 for (Subpart subpart : config.getSubparts()) { 459 for (Section section : subpart.getSections()) { 460 section.setSpaceExpected(0); 461 section.setSpaceHeld(0); 462 } 463 } 464 } 465 } 466 } 467 468 /** 469 * Compute online student sectioning infos for all sections (see 470 * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}). 471 * @param assignment current assignment 472 */ 473 public void computeOnlineSectioningInfos(Assignment<Request, Enrollment> assignment) { 474 clearOnlineSectioningInfos(); 475 for (Student student : getStudents()) { 476 if (!student.isDummy()) 477 continue; 478 for (Request request : student.getRequests()) { 479 if (!(request instanceof CourseRequest)) 480 continue; 481 CourseRequest courseRequest = (CourseRequest) request; 482 Enrollment enrollment = assignment.getValue(courseRequest); 483 if (enrollment != null) { 484 for (Section section : enrollment.getSections()) { 485 section.setSpaceHeld(courseRequest.getWeight() + section.getSpaceHeld()); 486 } 487 } 488 List<Enrollment> feasibleEnrollments = new ArrayList<Enrollment>(); 489 int totalLimit = 0; 490 for (Enrollment enrl : courseRequest.values(assignment)) { 491 boolean overlaps = false; 492 for (Request otherRequest : student.getRequests()) { 493 if (otherRequest.equals(courseRequest) || !(otherRequest instanceof CourseRequest)) 494 continue; 495 Enrollment otherErollment = assignment.getValue(otherRequest); 496 if (otherErollment == null) 497 continue; 498 if (enrl.isOverlapping(otherErollment)) { 499 overlaps = true; 500 break; 501 } 502 } 503 if (!overlaps) { 504 feasibleEnrollments.add(enrl); 505 if (totalLimit >= 0) { 506 int limit = enrl.getLimit(); 507 if (limit < 0) totalLimit = -1; 508 else totalLimit += limit; 509 } 510 } 511 } 512 double increment = courseRequest.getWeight() / (totalLimit > 0 ? totalLimit : feasibleEnrollments.size()); 513 for (Enrollment feasibleEnrollment : feasibleEnrollments) { 514 for (Section section : feasibleEnrollment.getSections()) { 515 if (totalLimit > 0) { 516 section.setSpaceExpected(section.getSpaceExpected() + increment * feasibleEnrollment.getLimit()); 517 } else { 518 section.setSpaceExpected(section.getSpaceExpected() + increment); 519 } 520 } 521 } 522 } 523 } 524 } 525 526 /** 527 * Sum of weights of all requests that are not assigned (see 528 * {@link Request#getWeight()}). 529 * @param assignment current assignment 530 * @return unassigned request weight 531 */ 532 public double getUnassignedRequestWeight(Assignment<Request, Enrollment> assignment) { 533 double weight = 0.0; 534 for (Request request : assignment.unassignedVariables(this)) { 535 weight += request.getWeight(); 536 } 537 return weight; 538 } 539 540 /** 541 * Sum of weights of all requests (see {@link Request#getWeight()}). 542 * @return total request weight 543 */ 544 public double getTotalRequestWeight() { 545 double weight = 0.0; 546 for (Request request : variables()) { 547 weight += request.getWeight(); 548 } 549 return weight; 550 } 551 552 /** 553 * Set distance conflict extension 554 * @param dc distance conflicts extension 555 */ 556 public void setDistanceConflict(DistanceConflict dc) { 557 iDistanceConflict = dc; 558 } 559 560 /** 561 * Return distance conflict extension 562 * @return distance conflicts extension 563 */ 564 public DistanceConflict getDistanceConflict() { 565 return iDistanceConflict; 566 } 567 568 /** 569 * Set time overlaps extension 570 * @param toc time overlapping conflicts extension 571 */ 572 public void setTimeOverlaps(TimeOverlapsCounter toc) { 573 iTimeOverlaps = toc; 574 } 575 576 /** 577 * Return time overlaps extension 578 * @return time overlapping conflicts extension 579 */ 580 public TimeOverlapsCounter getTimeOverlaps() { 581 return iTimeOverlaps; 582 } 583 584 /** 585 * Average priority of unassigned requests (see 586 * {@link Request#getPriority()}) 587 * @param assignment current assignment 588 * @return average priority of unassigned requests 589 */ 590 public double avgUnassignPriority(Assignment<Request, Enrollment> assignment) { 591 double totalPriority = 0.0; 592 for (Request request : assignment.unassignedVariables(this)) { 593 if (request.isAlternative()) 594 continue; 595 totalPriority += request.getPriority(); 596 } 597 return 1.0 + totalPriority / assignment.nrUnassignedVariables(this); 598 } 599 600 /** 601 * Average number of requests per student (see {@link Student#getRequests()} 602 * ) 603 * @return average number of requests per student 604 */ 605 public double avgNrRequests() { 606 double totalRequests = 0.0; 607 int totalStudents = 0; 608 for (Student student : getStudents()) { 609 if (student.nrRequests() == 0) 610 continue; 611 totalRequests += student.nrRequests(); 612 totalStudents++; 613 } 614 return totalRequests / totalStudents; 615 } 616 617 /** Number of last like ({@link Student#isDummy()} equals true) students. 618 * @param precise true if to be computed 619 * @return number of last like (projected) students 620 **/ 621 public int getNrLastLikeStudents(boolean precise) { 622 if (!precise) 623 return iNrDummyStudents; 624 int nrLastLikeStudents = 0; 625 for (Student student : getStudents()) { 626 if (student.isDummy()) 627 nrLastLikeStudents++; 628 } 629 return nrLastLikeStudents; 630 } 631 632 /** Number of real ({@link Student#isDummy()} equals false) students. 633 * @param precise true if to be computed 634 * @return number of real students 635 **/ 636 public int getNrRealStudents(boolean precise) { 637 if (!precise) 638 return getStudents().size() - iNrDummyStudents; 639 int nrRealStudents = 0; 640 for (Student student : getStudents()) { 641 if (!student.isDummy()) 642 nrRealStudents++; 643 } 644 return nrRealStudents; 645 } 646 647 /** 648 * Number of last like ({@link Student#isDummy()} equals true) students with 649 * a complete schedule ({@link Student#isComplete(Assignment)} equals true). 650 * @param assignment current assignment 651 * @param precise true if to be computed 652 * @return number of last like (projected) students with a complete schedule 653 */ 654 public int getNrCompleteLastLikeStudents(Assignment<Request, Enrollment> assignment, boolean precise) { 655 if (!precise) 656 return getContext(assignment).getNrCompleteLastLikeStudents(); 657 int nrLastLikeStudents = 0; 658 for (Student student : getStudents()) { 659 if (student.isComplete(assignment) && student.isDummy()) 660 nrLastLikeStudents++; 661 } 662 return nrLastLikeStudents; 663 } 664 665 /** 666 * Number of real ({@link Student#isDummy()} equals false) students with a 667 * complete schedule ({@link Student#isComplete(Assignment)} equals true). 668 * @param assignment current assignment 669 * @param precise true if to be computed 670 * @return number of real students with a complete schedule 671 */ 672 public int getNrCompleteRealStudents(Assignment<Request, Enrollment> assignment, boolean precise) { 673 if (!precise) 674 return getContext(assignment).nrComplete() - getContext(assignment).getNrCompleteLastLikeStudents(); 675 int nrRealStudents = 0; 676 for (Student student : getStudents()) { 677 if (student.isComplete(assignment) && !student.isDummy()) 678 nrRealStudents++; 679 } 680 return nrRealStudents; 681 } 682 683 /** 684 * Number of requests from projected ({@link Student#isDummy()} equals true) 685 * students. 686 * @param precise true if to be computed 687 * @return number of requests from projected students 688 */ 689 public int getNrLastLikeRequests(boolean precise) { 690 if (!precise) 691 return iNrDummyRequests; 692 int nrLastLikeRequests = 0; 693 for (Request request : variables()) { 694 if (request.getStudent().isDummy()) 695 nrLastLikeRequests++; 696 } 697 return nrLastLikeRequests; 698 } 699 700 /** 701 * Number of requests from real ({@link Student#isDummy()} equals false) 702 * students. 703 * @param precise true if to be computed 704 * @return number of requests from real students 705 */ 706 public int getNrRealRequests(boolean precise) { 707 if (!precise) 708 return variables().size() - iNrDummyRequests; 709 int nrRealRequests = 0; 710 for (Request request : variables()) { 711 if (!request.getStudent().isDummy()) 712 nrRealRequests++; 713 } 714 return nrRealRequests; 715 } 716 717 /** 718 * Number of requests from projected ({@link Student#isDummy()} equals true) 719 * students that are assigned. 720 * @param assignment current assignment 721 * @param precise true if to be computed 722 * @return number of requests from projected students that are assigned 723 */ 724 public int getNrAssignedLastLikeRequests(Assignment<Request, Enrollment> assignment, boolean precise) { 725 if (!precise) 726 return getContext(assignment).getNrAssignedLastLikeRequests(); 727 int nrLastLikeRequests = 0; 728 for (Request request : assignment.assignedVariables()) { 729 if (request.getStudent().isDummy()) 730 nrLastLikeRequests++; 731 } 732 return nrLastLikeRequests; 733 } 734 735 /** 736 * Number of requests from real ({@link Student#isDummy()} equals false) 737 * students that are assigned. 738 * @param assignment current assignment 739 * @param precise true if to be computed 740 * @return number of requests from real students that are assigned 741 */ 742 public int getNrAssignedRealRequests(Assignment<Request, Enrollment> assignment, boolean precise) { 743 if (!precise) 744 return assignment.nrAssignedVariables() - getContext(assignment).getNrAssignedLastLikeRequests(); 745 int nrRealRequests = 0; 746 for (Request request : assignment.assignedVariables()) { 747 if (!request.getStudent().isDummy()) 748 nrRealRequests++; 749 } 750 return nrRealRequests; 751 } 752 753 /** 754 * Model extended info. Some more information (that is more expensive to 755 * compute) is added to an ordinary {@link Model#getInfo(Assignment)}. 756 */ 757 @Override 758 public Map<String, String> getExtendedInfo(Assignment<Request, Enrollment> assignment) { 759 Map<String, String> info = getInfo(assignment); 760 /* 761 int nrLastLikeStudents = getNrLastLikeStudents(true); 762 if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) { 763 int nrRealStudents = getStudents().size() - nrLastLikeStudents; 764 int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(true); 765 int nrRealCompleteStudents = getCompleteStudents().size() - nrLastLikeCompleteStudents; 766 info.put("Projected students with complete schedule", sDecimalFormat.format(100.0 767 * nrLastLikeCompleteStudents / nrLastLikeStudents) 768 + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")"); 769 info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents 770 / nrRealStudents) 771 + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")"); 772 int nrLastLikeRequests = getNrLastLikeRequests(true); 773 int nrRealRequests = variables().size() - nrLastLikeRequests; 774 int nrLastLikeAssignedRequests = getNrAssignedLastLikeRequests(true); 775 int nrRealAssignedRequests = assignedVariables().size() - nrLastLikeAssignedRequests; 776 info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests 777 / nrLastLikeRequests) 778 + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")"); 779 info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests) 780 + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")"); 781 } 782 */ 783 // info.put("Average unassigned priority", sDecimalFormat.format(avgUnassignPriority())); 784 // info.put("Average number of requests", sDecimalFormat.format(avgNrRequests())); 785 786 /* 787 double total = 0; 788 for (Request r: variables()) 789 if (r.getAssignment() != null) 790 total += r.getWeight() * iStudentWeights.getWeight(r.getAssignment()); 791 */ 792 double dc = 0; 793 if (getDistanceConflict() != null && getDistanceConflict().getTotalNrConflicts(assignment) != 0) { 794 Set<DistanceConflict.Conflict> conf = getDistanceConflict().getAllConflicts(assignment); 795 for (DistanceConflict.Conflict c: conf) 796 dc += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 797 if (!conf.isEmpty()) 798 info.put("Student distance conflicts", conf.size() + " (weighted: " + sDecimalFormat.format(dc) + ")"); 799 } 800 double toc = 0; 801 if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) { 802 Set<TimeOverlapsCounter.Conflict> conf = getTimeOverlaps().getAllConflicts(assignment); 803 int share = 0; 804 for (TimeOverlapsCounter.Conflict c: conf) { 805 toc += c.getR1().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c); 806 toc += c.getR2().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c); 807 share += c.getShare(); 808 } 809 if (toc != 0.0) 810 info.put("Time overlapping conflicts", share + " (average: " + sDecimalFormat.format(5.0 * share / getStudents().size()) + " min, weighted: " + sDoubleFormat.format(toc) + ")"); 811 } 812 /* 813 info.put("Overall solution value", sDecimalFormat.format(total - dc - toc) + (dc == 0.0 && toc == 0.0 ? "" : 814 " (" + (dc != 0.0 ? "distance: " + sDecimalFormat.format(dc): "") + (dc != 0.0 && toc != 0.0 ? ", " : "") + 815 (toc != 0.0 ? "overlap: " + sDecimalFormat.format(toc) : "") + ")") 816 ); 817 */ 818 819 double disbWeight = 0; 820 int disbSections = 0; 821 int disb10Sections = 0; 822 int disb10Limit = getProperties().getPropertyInt("Info.ListDisbalancedSections", 0); 823 Set<String> disb10SectionList = (disb10Limit == 0 ? null : new TreeSet<String>()); 824 for (Offering offering: getOfferings()) { 825 for (Config config: offering.getConfigs()) { 826 double enrl = config.getEnrollmentWeight(assignment, null); 827 for (Subpart subpart: config.getSubparts()) { 828 if (subpart.getSections().size() <= 1) continue; 829 if (subpart.getLimit() > 0) { 830 // sections have limits -> desired size is section limit x (total enrollment / total limit) 831 double ratio = enrl / subpart.getLimit(); 832 for (Section section: subpart.getSections()) { 833 double desired = ratio * section.getLimit(); 834 disbWeight += Math.abs(section.getEnrollmentWeight(assignment, null) - desired); 835 disbSections ++; 836 if (Math.abs(desired - section.getEnrollmentWeight(assignment, null)) >= Math.max(1.0, 0.1 * section.getLimit())) { 837 disb10Sections++; 838 if (disb10SectionList != null) 839 disb10SectionList.add(section.getSubpart().getConfig().getOffering().getName() + " " + section.getSubpart().getName() + " " + section.getName()); 840 } 841 } 842 } else { 843 // unlimited sections -> desired size is total enrollment / number of sections 844 for (Section section: subpart.getSections()) { 845 double desired = enrl / subpart.getSections().size(); 846 disbWeight += Math.abs(section.getEnrollmentWeight(assignment, null) - desired); 847 disbSections ++; 848 if (Math.abs(desired - section.getEnrollmentWeight(assignment, null)) >= Math.max(1.0, 0.1 * desired)) { 849 disb10Sections++; 850 if (disb10SectionList != null) 851 disb10SectionList.add(section.getSubpart().getConfig().getOffering().getName() + " " + section.getSubpart().getName() + " " + section.getName()); 852 } 853 } 854 } 855 } 856 } 857 } 858 if (disbSections != 0) { 859 double assignedCRWeight = getContext(assignment).getAssignedCourseRequestWeight(); 860 info.put("Average disbalance", sDecimalFormat.format(disbWeight / disbSections) + " (" + sDecimalFormat.format(assignedCRWeight == 0 ? 0.0 : 100.0 * disbWeight / assignedCRWeight) + "%)"); 861 String list = ""; 862 if (disb10SectionList != null) { 863 int i = 0; 864 for (String section: disb10SectionList) { 865 if (i == disb10Limit) { 866 list += "<br>..."; 867 break; 868 } 869 list += "<br>" + section; 870 i++; 871 } 872 } 873 info.put("Sections disbalanced by 10% or more", disb10Sections + " (" + sDecimalFormat.format(disbSections == 0 ? 0.0 : 100.0 * disb10Sections / disbSections) + "%)" + list); 874 } 875 876 double groupSpread = 0.0; double groupCount = 0; 877 for (Offering offering: iOfferings) { 878 for (Course course: offering.getCourses()) { 879 for (RequestGroup group: course.getRequestGroups()) { 880 groupSpread += group.getAverageSpread(assignment) * group.getEnrollmentWeight(assignment, null); 881 groupCount += group.getEnrollmentWeight(assignment, null); 882 } 883 } 884 } 885 if (groupCount > 0) 886 info.put("Same group", sDecimalFormat.format(100.0 * groupSpread / groupCount) + "%"); 887 888 return info; 889 } 890 891 @Override 892 public void restoreBest(Assignment<Request, Enrollment> assignment) { 893 restoreBest(assignment, new Comparator<Request>() { 894 @Override 895 public int compare(Request r1, Request r2) { 896 Enrollment e1 = r1.getBestAssignment(); 897 Enrollment e2 = r2.getBestAssignment(); 898 // Reservations first 899 if (e1.getReservation() != null && e2.getReservation() == null) return -1; 900 if (e1.getReservation() == null && e2.getReservation() != null) return 1; 901 // Then assignment iteration (i.e., order in which assignments were made) 902 if (r1.getBestAssignmentIteration() != r2.getBestAssignmentIteration()) 903 return (r1.getBestAssignmentIteration() < r2.getBestAssignmentIteration() ? -1 : 1); 904 // Then student and priority 905 return r1.compareTo(r2); 906 } 907 }); 908 } 909 910 @Override 911 public String toString(Assignment<Request, Enrollment> assignment) { 912 double groupSpread = 0.0; double groupCount = 0; 913 for (Offering offering: iOfferings) { 914 for (Course course: offering.getCourses()) { 915 for (RequestGroup group: course.getRequestGroups()) { 916 groupSpread += group.getAverageSpread(assignment) * group.getEnrollmentWeight(assignment, null); 917 groupCount += group.getEnrollmentWeight(assignment, null); 918 } 919 } 920 } 921 return (getNrRealStudents(false) > 0 ? "RRq:" + getNrAssignedRealRequests(assignment, false) + "/" + getNrRealRequests(false) + ", " : "") 922 + (getNrLastLikeStudents(false) > 0 ? "DRq:" + getNrAssignedLastLikeRequests(assignment, false) + "/" + getNrLastLikeRequests(false) + ", " : "") 923 + (getNrRealStudents(false) > 0 ? "RS:" + getNrCompleteRealStudents(assignment, false) + "/" + getNrRealStudents(false) + ", " : "") 924 + (getNrLastLikeStudents(false) > 0 ? "DS:" + getNrCompleteLastLikeStudents(assignment, false) + "/" + getNrLastLikeStudents(false) + ", " : "") 925 + "V:" 926 + sDecimalFormat.format(-getTotalValue(assignment)) 927 + (getDistanceConflict() == null ? "" : ", DC:" + getDistanceConflict().getTotalNrConflicts(assignment)) 928 + (getTimeOverlaps() == null ? "" : ", TOC:" + getTimeOverlaps().getTotalNrConflicts(assignment)) 929 + (iMPP ? ", IS:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedSameSectionWeight / iTotalMPPCRWeight) + "%" : "") 930 + (iMPP ? ", IT:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedSameTimeWeight / iTotalMPPCRWeight) + "%" : "") 931 + ", %:" + sDecimalFormat.format(-100.0 * getTotalValue(assignment) / (getStudents().size() - iNrDummyStudents + 932 (iProjectedStudentWeight < 0.0 ? iNrDummyStudents * (iTotalDummyWeight / iNrDummyRequests) :iProjectedStudentWeight * iTotalDummyWeight))) 933 + (groupCount > 0 ? ", SG:" + sDecimalFormat.format(100.0 * groupSpread / groupCount) + "%" : ""); 934 935 } 936 937 /** 938 * Quadratic average of two weights. 939 * @param w1 first weight 940 * @param w2 second weight 941 * @return average of the two weights 942 */ 943 public double avg(double w1, double w2) { 944 return Math.sqrt(w1 * w2); 945 } 946 947 /** 948 * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit. 949 * @return maximal domain size, -1 if unlimited 950 */ 951 public int getMaxDomainSize() { return iMaxDomainSize; } 952 953 /** 954 * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit. 955 * @param maxDomainSize maximal domain size, -1 if unlimited 956 */ 957 public void setMaxDomainSize(int maxDomainSize) { iMaxDomainSize = maxDomainSize; } 958 959 960 @Override 961 public StudentSectioningModelContext createAssignmentContext(Assignment<Request, Enrollment> assignment) { 962 return new StudentSectioningModelContext(assignment); 963 } 964 965 public class StudentSectioningModelContext implements AssignmentConstraintContext<Request, Enrollment>, InfoProvider<Request, Enrollment>{ 966 private Set<Student> iCompleteStudents = new HashSet<Student>(); 967 private double iTotalValue = 0.0; 968 private int iNrAssignedDummyRequests = 0, iNrCompleteDummyStudents = 0; 969 private double iAssignedCRWeight = 0.0, iAssignedDummyCRWeight = 0.0; 970 private double iReservedSpace = 0.0, iTotalReservedSpace = 0.0; 971 private double iAssignedSameSectionWeight = 0.0, iAssignedSameChoiceWeight = 0.0, iAssignedSameTimeWeight = 0.0; 972 973 public StudentSectioningModelContext(Assignment<Request, Enrollment> assignment) { 974 for (Request request: variables()) { 975 Enrollment enrollment = assignment.getValue(request); 976 if (enrollment != null) 977 assigned(assignment, enrollment); 978 } 979 } 980 981 /** 982 * Called after an enrollment was assigned to a request. The list of 983 * complete students and the overall solution value are updated. 984 */ 985 @Override 986 public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 987 Student student = enrollment.getStudent(); 988 if (student.isComplete(assignment)) 989 iCompleteStudents.add(student); 990 double value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(assignment, enrollment); 991 iTotalValue -= value; 992 enrollment.variable().getContext(assignment).setLastWeight(value); 993 if (enrollment.isCourseRequest()) 994 iAssignedCRWeight += enrollment.getRequest().getWeight(); 995 if (enrollment.getRequest().isMPP()) { 996 iAssignedSameSectionWeight += enrollment.getRequest().getWeight() * enrollment.percentInitial(); 997 iAssignedSameChoiceWeight += enrollment.getRequest().getWeight() * enrollment.percentSelected(); 998 iAssignedSameTimeWeight += enrollment.getRequest().getWeight() * enrollment.percentSameTime(); 999 } 1000 if (enrollment.getReservation() != null) 1001 iReservedSpace += enrollment.getRequest().getWeight(); 1002 if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations()) 1003 iTotalReservedSpace += enrollment.getRequest().getWeight(); 1004 if (student.isDummy()) { 1005 iNrAssignedDummyRequests++; 1006 if (enrollment.isCourseRequest()) 1007 iAssignedDummyCRWeight += enrollment.getRequest().getWeight(); 1008 if (student.isComplete(assignment)) 1009 iNrCompleteDummyStudents++; 1010 } 1011 } 1012 1013 /** 1014 * Called before an enrollment was unassigned from a request. The list of 1015 * complete students and the overall solution value are updated. 1016 */ 1017 @Override 1018 public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) { 1019 Student student = enrollment.getStudent(); 1020 if (iCompleteStudents.contains(student)) { 1021 iCompleteStudents.remove(student); 1022 if (student.isDummy()) 1023 iNrCompleteDummyStudents--; 1024 } 1025 Request.RequestContext cx = enrollment.variable().getContext(assignment); 1026 Double value = cx.getLastWeight(); 1027 if (value == null) 1028 value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(assignment, enrollment); 1029 iTotalValue += value; 1030 cx.setLastWeight(null); 1031 if (enrollment.isCourseRequest()) 1032 iAssignedCRWeight -= enrollment.getRequest().getWeight(); 1033 if (enrollment.getRequest().isMPP()) { 1034 iAssignedSameSectionWeight -= enrollment.getRequest().getWeight() * enrollment.percentInitial(); 1035 iAssignedSameChoiceWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelected(); 1036 iAssignedSameTimeWeight -= enrollment.getRequest().getWeight() * enrollment.percentSameTime(); 1037 } 1038 if (enrollment.getReservation() != null) 1039 iReservedSpace -= enrollment.getRequest().getWeight(); 1040 if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations()) 1041 iTotalReservedSpace -= enrollment.getRequest().getWeight(); 1042 if (student.isDummy()) { 1043 iNrAssignedDummyRequests--; 1044 if (enrollment.isCourseRequest()) 1045 iAssignedDummyCRWeight -= enrollment.getRequest().getWeight(); 1046 } 1047 } 1048 1049 public void add(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) { 1050 iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 1051 } 1052 1053 public void remove(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) { 1054 iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c); 1055 } 1056 1057 public void add(Assignment<Request, Enrollment> assignment, TimeOverlapsCounter.Conflict c) { 1058 iTotalValue += c.getR1().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c); 1059 iTotalValue += c.getR2().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c); 1060 } 1061 1062 public void remove(Assignment<Request, Enrollment> assignment, TimeOverlapsCounter.Conflict c) { 1063 iTotalValue -= c.getR1().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c); 1064 iTotalValue -= c.getR2().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c); 1065 } 1066 1067 /** 1068 * Students with complete schedules (see {@link Student#isComplete(Assignment)}) 1069 * @return students with complete schedule 1070 */ 1071 public Set<Student> getCompleteStudents() { 1072 return iCompleteStudents; 1073 } 1074 1075 /** 1076 * Number of students with complete schedule 1077 * @return number of students with complete schedule 1078 */ 1079 public int nrComplete() { 1080 return getCompleteStudents().size(); 1081 } 1082 1083 /** 1084 * Recompute cached request weights 1085 * @param assignment curent assignment 1086 */ 1087 public void requestWeightsChanged(Assignment<Request, Enrollment> assignment) { 1088 iTotalCRWeight = 0.0; 1089 iTotalDummyWeight = 0.0; iTotalDummyCRWeight = 0.0; 1090 iAssignedCRWeight = 0.0; 1091 iAssignedDummyCRWeight = 0.0; 1092 iNrDummyRequests = 0; iNrAssignedDummyRequests = 0; 1093 iTotalReservedSpace = 0.0; iReservedSpace = 0.0; 1094 iTotalMPPCRWeight = 0.0; 1095 for (Request request: variables()) { 1096 boolean cr = (request instanceof CourseRequest); 1097 if (cr) 1098 iTotalCRWeight += request.getWeight(); 1099 if (request.getStudent().isDummy()) { 1100 iTotalDummyWeight += request.getWeight(); 1101 iNrDummyRequests ++; 1102 if (cr) 1103 iTotalDummyCRWeight += request.getWeight(); 1104 } 1105 if (request.isMPP()) 1106 iTotalMPPCRWeight += request.getWeight(); 1107 Enrollment e = assignment.getValue(request); 1108 if (e != null) { 1109 if (cr) 1110 iAssignedCRWeight += request.getWeight(); 1111 if (request.isMPP()) { 1112 iAssignedSameSectionWeight += request.getWeight() * e.percentInitial(); 1113 iAssignedSameChoiceWeight += request.getWeight() * e.percentSelected(); 1114 iAssignedSameTimeWeight += request.getWeight() * e.percentSameTime(); 1115 } 1116 if (e.getReservation() != null) 1117 iReservedSpace += request.getWeight(); 1118 if (cr && ((CourseRequest)request).hasReservations()) 1119 iTotalReservedSpace += request.getWeight(); 1120 if (request.getStudent().isDummy()) { 1121 iNrAssignedDummyRequests ++; 1122 if (cr) 1123 iAssignedDummyCRWeight += request.getWeight(); 1124 } 1125 } 1126 } 1127 } 1128 1129 /** 1130 * Overall solution value 1131 * @return solution value 1132 */ 1133 public double getTotalValue() { 1134 return iTotalValue; 1135 } 1136 1137 /** 1138 * Number of last like ({@link Student#isDummy()} equals true) students with 1139 * a complete schedule ({@link Student#isComplete(Assignment)} equals true). 1140 * @return number of last like (projected) students with a complete schedule 1141 */ 1142 public int getNrCompleteLastLikeStudents() { 1143 return iNrCompleteDummyStudents; 1144 } 1145 1146 /** 1147 * Number of requests from projected ({@link Student#isDummy()} equals true) 1148 * students that are assigned. 1149 * @return number of real students with a complete schedule 1150 */ 1151 public int getNrAssignedLastLikeRequests() { 1152 return iNrAssignedDummyRequests; 1153 } 1154 1155 @Override 1156 public void getInfo(Assignment<Request, Enrollment> assignment, Map<String, String> info) { 1157 if (iTotalCRWeight > 0.0) { 1158 info.put("Assigned course requests", sDecimalFormat.format(100.0 * iAssignedCRWeight / iTotalCRWeight) + "% (" + (int)Math.round(iAssignedCRWeight) + "/" + (int)Math.round(iTotalCRWeight) + ")"); 1159 if (iNrDummyStudents > 0 && iNrDummyStudents != getStudents().size() && iTotalCRWeight != iTotalDummyCRWeight) { 1160 if (iTotalDummyCRWeight > 0.0) 1161 info.put("Projected assigned course requests", sDecimalFormat.format(100.0 * iAssignedDummyCRWeight / iTotalDummyCRWeight) + "% (" + (int)Math.round(iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalDummyCRWeight) + ")"); 1162 info.put("Real assigned course requests", sDecimalFormat.format(100.0 * (iAssignedCRWeight - iAssignedDummyCRWeight) / (iTotalCRWeight - iTotalDummyCRWeight)) + 1163 "% (" + (int)Math.round(iAssignedCRWeight - iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalCRWeight - iTotalDummyCRWeight) + ")"); 1164 } 1165 } 1166 if (iTotalReservedSpace > 0.0) 1167 info.put("Reservations", sDoubleFormat.format(100.0 * iReservedSpace / iTotalReservedSpace) + "% (" + Math.round(iReservedSpace) + "/" + Math.round(iTotalReservedSpace) + ")"); 1168 if (iMPP && iTotalMPPCRWeight > 0.0) { 1169 info.put("Perturbations: same section", sDoubleFormat.format(100.0 * iAssignedSameSectionWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameSectionWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")"); 1170 if (iAssignedSameChoiceWeight > iAssignedSameSectionWeight) 1171 info.put("Perturbations: same choice",sDoubleFormat.format(100.0 * iAssignedSameChoiceWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameChoiceWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")"); 1172 if (iAssignedSameTimeWeight > iAssignedSameChoiceWeight) 1173 info.put("Perturbations: same time", sDoubleFormat.format(100.0 * iAssignedSameTimeWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameTimeWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")"); 1174 } 1175 } 1176 1177 @Override 1178 public void getInfo(Assignment<Request, Enrollment> assignment, Map<String, String> info, Collection<Request> variables) { 1179 } 1180 1181 public double getAssignedCourseRequestWeight() { 1182 return iAssignedCRWeight; 1183 } 1184 } 1185 1186 @Override 1187 public InheritedAssignment<Request, Enrollment> createInheritedAssignment(Solution<Request, Enrollment> solution, int index) { 1188 return new OptimisticInheritedAssignment<Request, Enrollment>(solution, index); 1189 } 1190 1191}