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