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