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