001 package net.sf.cpsolver.studentsct; 002 003 import java.text.DecimalFormat; 004 import java.util.ArrayList; 005 import java.util.Collection; 006 import java.util.Comparator; 007 import java.util.HashSet; 008 import java.util.List; 009 import java.util.Map; 010 import java.util.Set; 011 012 import net.sf.cpsolver.ifs.model.Constraint; 013 import net.sf.cpsolver.ifs.model.ConstraintListener; 014 import net.sf.cpsolver.ifs.model.Model; 015 import net.sf.cpsolver.ifs.util.DataProperties; 016 import net.sf.cpsolver.studentsct.constraint.ConfigLimit; 017 import net.sf.cpsolver.studentsct.constraint.CourseLimit; 018 import net.sf.cpsolver.studentsct.constraint.LinkedSections; 019 import net.sf.cpsolver.studentsct.constraint.ReservationLimit; 020 import net.sf.cpsolver.studentsct.constraint.SectionLimit; 021 import net.sf.cpsolver.studentsct.constraint.StudentConflict; 022 import net.sf.cpsolver.studentsct.extension.DistanceConflict; 023 import net.sf.cpsolver.studentsct.extension.TimeOverlapsCounter; 024 import net.sf.cpsolver.studentsct.model.Config; 025 import net.sf.cpsolver.studentsct.model.Course; 026 import net.sf.cpsolver.studentsct.model.CourseRequest; 027 import net.sf.cpsolver.studentsct.model.Enrollment; 028 import net.sf.cpsolver.studentsct.model.Offering; 029 import net.sf.cpsolver.studentsct.model.Request; 030 import net.sf.cpsolver.studentsct.model.Section; 031 import net.sf.cpsolver.studentsct.model.Student; 032 import net.sf.cpsolver.studentsct.model.Subpart; 033 import net.sf.cpsolver.studentsct.reservation.Reservation; 034 import net.sf.cpsolver.studentsct.weights.PriorityStudentWeights; 035 import net.sf.cpsolver.studentsct.weights.StudentWeights; 036 037 import org.apache.log4j.Logger; 038 039 /** 040 * Student sectioning model. 041 * 042 * <br> 043 * <br> 044 * 045 * @version StudentSct 1.2 (Student Sectioning)<br> 046 * Copyright (C) 2007 - 2010 Tomas Muller<br> 047 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 048 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 049 * <br> 050 * This library is free software; you can redistribute it and/or modify 051 * it under the terms of the GNU Lesser General Public License as 052 * published by the Free Software Foundation; either version 3 of the 053 * License, or (at your option) any later version. <br> 054 * <br> 055 * This library is distributed in the hope that it will be useful, but 056 * WITHOUT ANY WARRANTY; without even the implied warranty of 057 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 058 * Lesser General Public License for more details. <br> 059 * <br> 060 * You should have received a copy of the GNU Lesser General Public 061 * License along with this library; if not see 062 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 063 */ 064 public class StudentSectioningModel extends Model<Request, Enrollment> { 065 private static Logger sLog = Logger.getLogger(StudentSectioningModel.class); 066 protected static DecimalFormat sDecimalFormat = new DecimalFormat("0.000"); 067 private List<Student> iStudents = new ArrayList<Student>(); 068 private List<Offering> iOfferings = new ArrayList<Offering>(); 069 private List<LinkedSections> iLinkedSections = new ArrayList<LinkedSections>(); 070 private Set<Student> iCompleteStudents = new java.util.HashSet<Student>(); 071 private double iTotalValue = 0.0; 072 private DataProperties iProperties; 073 private DistanceConflict iDistanceConflict = null; 074 private TimeOverlapsCounter iTimeOverlaps = null; 075 private int iNrDummyStudents = 0, iNrDummyRequests = 0, iNrAssignedDummyRequests = 0, iNrCompleteDummyStudents = 0; 076 private double iTotalDummyWeight = 0.0; 077 private double iTotalCRWeight = 0.0, iTotalDummyCRWeight = 0.0, iAssignedCRWeight = 0.0, iAssignedDummyCRWeight = 0.0; 078 private double iReservedSpace = 0.0, iTotalReservedSpace = 0.0; 079 private StudentWeights iStudentWeights = null; 080 private boolean iReservationCanAssignOverTheLimit; 081 protected double iProjectedStudentWeight = 0.0100; 082 083 084 /** 085 * Constructor 086 * 087 * @param properties 088 * configuration 089 */ 090 @SuppressWarnings("unchecked") 091 public StudentSectioningModel(DataProperties properties) { 092 super(); 093 iReservationCanAssignOverTheLimit = properties.getPropertyBoolean("Reservation.CanAssignOverTheLimit", false); 094 iAssignedVariables = new HashSet<Request>(); 095 iUnassignedVariables = new HashSet<Request>(); 096 iPerturbVariables = new HashSet<Request>(); 097 iStudentWeights = new PriorityStudentWeights(properties); 098 if (properties.getPropertyBoolean("Sectioning.SectionLimit", true)) { 099 SectionLimit sectionLimit = new SectionLimit(properties); 100 addGlobalConstraint(sectionLimit); 101 if (properties.getPropertyBoolean("Sectioning.SectionLimit.Debug", false)) { 102 sectionLimit.addConstraintListener(new ConstraintListener<Enrollment>() { 103 @Override 104 public void constraintBeforeAssigned(long iteration, Constraint<?, Enrollment> constraint, 105 Enrollment enrollment, Set<Enrollment> unassigned) { 106 if (enrollment.getStudent().isDummy()) 107 for (Enrollment conflict : unassigned) { 108 if (!conflict.getStudent().isDummy()) { 109 sLog.warn("Enrolment of a real student " + conflict.getStudent() + " is unassigned " 110 + "\n -- " + conflict + "\ndue to an enrollment of a dummy student " 111 + enrollment.getStudent() + " " + "\n -- " + enrollment); 112 } 113 } 114 } 115 116 @Override 117 public void constraintAfterAssigned(long iteration, Constraint<?, Enrollment> constraint, 118 Enrollment assigned, Set<Enrollment> unassigned) { 119 } 120 }); 121 } 122 } 123 if (properties.getPropertyBoolean("Sectioning.ConfigLimit", true)) { 124 ConfigLimit configLimit = new ConfigLimit(properties); 125 addGlobalConstraint(configLimit); 126 } 127 if (properties.getPropertyBoolean("Sectioning.CourseLimit", true)) { 128 CourseLimit courseLimit = new CourseLimit(properties); 129 addGlobalConstraint(courseLimit); 130 } 131 if (properties.getPropertyBoolean("Sectioning.ReservationLimit", true)) { 132 ReservationLimit reservationLimit = new ReservationLimit(properties); 133 addGlobalConstraint(reservationLimit); 134 } 135 try { 136 Class<StudentWeights> studentWeightsClass = (Class<StudentWeights>)Class.forName(properties.getProperty("StudentWeights.Class", PriorityStudentWeights.class.getName())); 137 iStudentWeights = studentWeightsClass.getConstructor(DataProperties.class).newInstance(properties); 138 } catch (Exception e) { 139 sLog.error("Unable to create custom student weighting model (" + e.getMessage() + "), using default.", e); 140 iStudentWeights = new PriorityStudentWeights(properties); 141 } 142 iProjectedStudentWeight = properties.getPropertyDouble("StudentWeights.ProjectedStudentWeight", iProjectedStudentWeight); 143 iProperties = properties; 144 } 145 146 /** 147 * Return true if reservation that has {@link Reservation#canAssignOverLimit()} can assign enrollments over the limit 148 */ 149 public boolean getReservationCanAssignOverTheLimit() { 150 return iReservationCanAssignOverTheLimit; 151 } 152 153 /** 154 * Return student weighting model 155 */ 156 public StudentWeights getStudentWeights() { 157 return iStudentWeights; 158 } 159 160 /** 161 * Set student weighting model 162 */ 163 public void setStudentWeights(StudentWeights weights) { 164 iStudentWeights = weights; 165 } 166 167 /** 168 * Students 169 */ 170 public List<Student> getStudents() { 171 return iStudents; 172 } 173 174 /** 175 * Students with complete schedules (see {@link Student#isComplete()}) 176 */ 177 public Set<Student> getCompleteStudents() { 178 return iCompleteStudents; 179 } 180 181 /** 182 * Add a student into the model 183 */ 184 public void addStudent(Student student) { 185 iStudents.add(student); 186 if (student.isDummy()) 187 iNrDummyStudents++; 188 for (Request request : student.getRequests()) 189 addVariable(request); 190 if (getProperties().getPropertyBoolean("Sectioning.StudentConflict", true)) { 191 addConstraint(new StudentConflict(student)); 192 } 193 if (student.isComplete()) 194 iCompleteStudents.add(student); 195 } 196 197 @Override 198 public void addVariable(Request request) { 199 super.addVariable(request); 200 if (request instanceof CourseRequest) 201 iTotalCRWeight += request.getWeight(); 202 if (request.getStudent().isDummy()) { 203 iNrDummyRequests++; 204 iTotalDummyWeight += request.getWeight(); 205 if (request instanceof CourseRequest) 206 iTotalDummyCRWeight += request.getWeight(); 207 } 208 } 209 210 /** 211 * Recompute cached request weights 212 */ 213 public void requestWeightsChanged() { 214 iTotalCRWeight = 0.0; 215 iTotalDummyWeight = 0.0; iTotalDummyCRWeight = 0.0; 216 iAssignedCRWeight = 0.0; 217 iAssignedDummyCRWeight = 0.0; 218 iNrDummyRequests = 0; iNrAssignedDummyRequests = 0; 219 iTotalReservedSpace = 0.0; iReservedSpace = 0.0; 220 for (Request request: variables()) { 221 boolean cr = (request instanceof CourseRequest); 222 if (cr) 223 iTotalCRWeight += request.getWeight(); 224 if (request.getStudent().isDummy()) { 225 iTotalDummyWeight += request.getWeight(); 226 iNrDummyRequests ++; 227 if (cr) 228 iTotalDummyCRWeight += request.getWeight(); 229 } 230 if (request.getAssignment() != null) { 231 if (cr) 232 iAssignedCRWeight += request.getWeight(); 233 if (request.getAssignment().getReservation() != null) 234 iReservedSpace += request.getWeight(); 235 if (cr && ((CourseRequest)request).hasReservations()) 236 iTotalReservedSpace += request.getWeight(); 237 if (request.getStudent().isDummy()) { 238 iNrAssignedDummyRequests ++; 239 if (cr) 240 iAssignedDummyCRWeight += request.getWeight(); 241 } 242 } 243 } 244 } 245 246 /** 247 * Remove a student from the model 248 */ 249 public void removeStudent(Student student) { 250 iStudents.remove(student); 251 if (student.isDummy()) 252 iNrDummyStudents--; 253 if (student.isComplete()) 254 iCompleteStudents.remove(student); 255 StudentConflict conflict = null; 256 for (Request request : student.getRequests()) { 257 for (Constraint<Request, Enrollment> c : request.constraints()) { 258 if (c instanceof StudentConflict) { 259 conflict = (StudentConflict) c; 260 break; 261 } 262 } 263 if (conflict != null) 264 conflict.removeVariable(request); 265 removeVariable(request); 266 } 267 if (conflict != null) 268 removeConstraint(conflict); 269 } 270 271 @Override 272 public void removeVariable(Request request) { 273 super.removeVariable(request); 274 if (request instanceof CourseRequest) { 275 CourseRequest cr = (CourseRequest)request; 276 for (Course course: cr.getCourses()) 277 course.getRequests().remove(request); 278 } 279 if (request.getStudent().isDummy()) { 280 iNrDummyRequests--; 281 iTotalDummyWeight -= request.getWeight(); 282 if (request instanceof CourseRequest) 283 iTotalDummyCRWeight -= request.getWeight(); 284 } 285 if (request instanceof CourseRequest) 286 iTotalCRWeight -= request.getWeight(); 287 } 288 289 290 /** 291 * List of offerings 292 */ 293 public List<Offering> getOfferings() { 294 return iOfferings; 295 } 296 297 /** 298 * Add an offering into the model 299 */ 300 public void addOffering(Offering offering) { 301 iOfferings.add(offering); 302 } 303 304 /** 305 * Link sections using {@link LinkedSections} 306 */ 307 public void addLinkedSections(Section... sections) { 308 LinkedSections constraint = new LinkedSections(sections); 309 iLinkedSections.add(constraint); 310 constraint.createConstraints(); 311 } 312 313 /** 314 * Link sections using {@link LinkedSections} 315 */ 316 public void addLinkedSections(Collection<Section> sections) { 317 LinkedSections constraint = new LinkedSections(sections); 318 iLinkedSections.add(constraint); 319 constraint.createConstraints(); 320 } 321 322 /** 323 * List of linked sections 324 */ 325 public List<LinkedSections> getLinkedSections() { 326 return iLinkedSections; 327 } 328 329 /** 330 * Number of students with complete schedule 331 */ 332 public int nrComplete() { 333 return getCompleteStudents().size(); 334 } 335 336 /** 337 * Model info 338 */ 339 @Override 340 public Map<String, String> getInfo() { 341 Map<String, String> info = super.getInfo(); 342 if (!getStudents().isEmpty()) 343 info.put("Students with complete schedule", sDoubleFormat.format(100.0 * nrComplete() / getStudents().size()) 344 + "% (" + nrComplete() + "/" + getStudents().size() + ")"); 345 if (getDistanceConflict() != null && getDistanceConflict().getTotalNrConflicts() != 0) 346 info.put("Student distance conflicts", String.valueOf(getDistanceConflict().getTotalNrConflicts())); 347 if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts() != 0) 348 info.put("Time overlapping conflicts", String.valueOf(getTimeOverlaps().getTotalNrConflicts())); 349 int nrLastLikeStudents = getNrLastLikeStudents(false); 350 if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) { 351 int nrRealStudents = getStudents().size() - nrLastLikeStudents; 352 int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(false); 353 int nrRealCompleteStudents = getCompleteStudents().size() - nrLastLikeCompleteStudents; 354 if (nrLastLikeStudents > 0) 355 info.put("Projected students with complete schedule", sDecimalFormat.format(100.0 356 * nrLastLikeCompleteStudents / nrLastLikeStudents) 357 + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")"); 358 if (nrRealStudents > 0) 359 info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents 360 / nrRealStudents) 361 + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")"); 362 int nrLastLikeRequests = getNrLastLikeRequests(false); 363 int nrRealRequests = variables().size() - nrLastLikeRequests; 364 int nrLastLikeAssignedRequests = getNrAssignedLastLikeRequests(false); 365 int nrRealAssignedRequests = assignedVariables().size() - nrLastLikeAssignedRequests; 366 if (nrLastLikeRequests > 0) 367 info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests / nrLastLikeRequests) 368 + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")"); 369 if (nrRealRequests > 0) 370 info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests) 371 + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")"); 372 if (iTotalCRWeight > 0.0) { 373 info.put("Assigned course requests", sDecimalFormat.format(100.0 * iAssignedCRWeight / iTotalCRWeight) + "% (" + (int)Math.round(iAssignedCRWeight) + "/" + (int)Math.round(iTotalCRWeight) + ")"); 374 if (iTotalDummyCRWeight != iTotalCRWeight) { 375 if (iTotalDummyCRWeight > 0.0) 376 info.put("Projected assigned course requests", sDecimalFormat.format(100.0 * iAssignedDummyCRWeight / iTotalDummyCRWeight) + "% (" + (int)Math.round(iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalDummyCRWeight) + ")"); 377 info.put("Real assigned course requests", sDecimalFormat.format(100.0 * (iAssignedCRWeight - iAssignedDummyCRWeight) / (iTotalCRWeight - iTotalDummyCRWeight)) + 378 "% (" + (int)Math.round(iAssignedCRWeight - iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalCRWeight - iTotalDummyCRWeight) + ")"); 379 } 380 } 381 if (getDistanceConflict() != null && getDistanceConflict().getTotalNrConflicts() > 0) 382 info.put("Student distance conflicts", String.valueOf(getDistanceConflict().getTotalNrConflicts())); 383 if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts() > 0) 384 info.put("Time overlapping conflicts", String.valueOf(getTimeOverlaps().getTotalNrConflicts())); 385 } 386 if (iTotalReservedSpace > 0.0) 387 info.put("Reservations", sDoubleFormat.format(100.0 * iReservedSpace / iTotalReservedSpace) + "% (" + Math.round(iReservedSpace) + "/" + Math.round(iTotalReservedSpace) + ")"); 388 389 return info; 390 } 391 392 /** 393 * Overall solution value 394 */ 395 public double getTotalValue(boolean precise) { 396 if (precise) { 397 double total = 0; 398 for (Request r: assignedVariables()) 399 total += r.getWeight() * iStudentWeights.getWeight(r.getAssignment()); 400 if (iDistanceConflict != null) 401 for (DistanceConflict.Conflict c: iDistanceConflict.computeAllConflicts()) 402 total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(c); 403 if (iTimeOverlaps != null) 404 for (TimeOverlapsCounter.Conflict c: iTimeOverlaps.computeAllConflicts()) { 405 total -= c.getR1().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(c.getE1(), c); 406 total -= c.getR2().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(c.getE2(), c); 407 } 408 return -total; 409 } 410 return iTotalValue; 411 } 412 413 /** 414 * Overall solution value 415 */ 416 @Override 417 public double getTotalValue() { 418 return iTotalValue; 419 } 420 421 422 /** 423 * Called after an enrollment was assigned to a request. The list of 424 * complete students and the overall solution value are updated. 425 */ 426 @Override 427 public void afterAssigned(long iteration, Enrollment enrollment) { 428 super.afterAssigned(iteration, enrollment); 429 Student student = enrollment.getStudent(); 430 if (student.isComplete()) 431 iCompleteStudents.add(student); 432 double value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(enrollment); 433 iTotalValue -= value; 434 enrollment.setExtra(value); 435 if (enrollment.isCourseRequest()) 436 iAssignedCRWeight += enrollment.getRequest().getWeight(); 437 if (enrollment.getReservation() != null) 438 iReservedSpace += enrollment.getRequest().getWeight(); 439 if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations()) 440 iTotalReservedSpace += enrollment.getRequest().getWeight(); 441 if (student.isDummy()) { 442 iNrAssignedDummyRequests++; 443 if (enrollment.isCourseRequest()) 444 iAssignedDummyCRWeight += enrollment.getRequest().getWeight(); 445 if (student.isComplete()) 446 iNrCompleteDummyStudents++; 447 } 448 } 449 450 /** 451 * Called before an enrollment was unassigned from a request. The list of 452 * complete students and the overall solution value are updated. 453 */ 454 @Override 455 public void afterUnassigned(long iteration, Enrollment enrollment) { 456 super.afterUnassigned(iteration, enrollment); 457 Student student = enrollment.getStudent(); 458 if (iCompleteStudents.contains(student) && !student.isComplete()) { 459 iCompleteStudents.remove(student); 460 if (student.isDummy()) 461 iNrCompleteDummyStudents--; 462 } 463 Double value = (Double)enrollment.getExtra(); 464 if (value == null) 465 value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(enrollment); 466 iTotalValue += value; 467 enrollment.setExtra(null); 468 if (enrollment.isCourseRequest()) 469 iAssignedCRWeight -= enrollment.getRequest().getWeight(); 470 if (enrollment.getReservation() != null) 471 iReservedSpace -= enrollment.getRequest().getWeight(); 472 if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations()) 473 iTotalReservedSpace -= enrollment.getRequest().getWeight(); 474 if (student.isDummy()) { 475 iNrAssignedDummyRequests--; 476 if (enrollment.isCourseRequest()) 477 iAssignedDummyCRWeight -= enrollment.getRequest().getWeight(); 478 } 479 } 480 481 /** 482 * Configuration 483 */ 484 public DataProperties getProperties() { 485 return iProperties; 486 } 487 488 /** 489 * Empty online student sectioning infos for all sections (see 490 * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}). 491 */ 492 public void clearOnlineSectioningInfos() { 493 for (Offering offering : iOfferings) { 494 for (Config config : offering.getConfigs()) { 495 for (Subpart subpart : config.getSubparts()) { 496 for (Section section : subpart.getSections()) { 497 section.setSpaceExpected(0); 498 section.setSpaceHeld(0); 499 } 500 } 501 } 502 } 503 } 504 505 /** 506 * Compute online student sectioning infos for all sections (see 507 * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}). 508 */ 509 public void computeOnlineSectioningInfos() { 510 clearOnlineSectioningInfos(); 511 for (Student student : getStudents()) { 512 if (!student.isDummy()) 513 continue; 514 for (Request request : student.getRequests()) { 515 if (!(request instanceof CourseRequest)) 516 continue; 517 CourseRequest courseRequest = (CourseRequest) request; 518 Enrollment enrollment = courseRequest.getAssignment(); 519 if (enrollment != null) { 520 for (Section section : enrollment.getSections()) { 521 section.setSpaceHeld(courseRequest.getWeight() + section.getSpaceHeld()); 522 } 523 } 524 List<Enrollment> feasibleEnrollments = new ArrayList<Enrollment>(); 525 int totalLimit = 0; 526 for (Enrollment enrl : courseRequest.values()) { 527 boolean overlaps = false; 528 for (Request otherRequest : student.getRequests()) { 529 if (otherRequest.equals(courseRequest) || !(otherRequest instanceof CourseRequest)) 530 continue; 531 Enrollment otherErollment = otherRequest.getAssignment(); 532 if (otherErollment == null) 533 continue; 534 if (enrl.isOverlapping(otherErollment)) { 535 overlaps = true; 536 break; 537 } 538 } 539 if (!overlaps) { 540 feasibleEnrollments.add(enrl); 541 if (totalLimit >= 0) { 542 int limit = enrl.getLimit(); 543 if (limit < 0) totalLimit = -1; 544 else totalLimit += limit; 545 } 546 } 547 } 548 double increment = courseRequest.getWeight() / (totalLimit > 0 ? totalLimit : feasibleEnrollments.size()); 549 for (Enrollment feasibleEnrollment : feasibleEnrollments) { 550 for (Section section : feasibleEnrollment.getSections()) { 551 if (totalLimit > 0) { 552 section.setSpaceExpected(section.getSpaceExpected() + increment * feasibleEnrollment.getLimit()); 553 } else { 554 section.setSpaceExpected(section.getSpaceExpected() + increment); 555 } 556 } 557 } 558 } 559 } 560 } 561 562 /** 563 * Sum of weights of all requests that are not assigned (see 564 * {@link Request#getWeight()}). 565 */ 566 public double getUnassignedRequestWeight() { 567 double weight = 0.0; 568 for (Request request : unassignedVariables()) { 569 weight += request.getWeight(); 570 } 571 return weight; 572 } 573 574 /** 575 * Sum of weights of all requests (see {@link Request#getWeight()}). 576 */ 577 public double getTotalRequestWeight() { 578 double weight = 0.0; 579 for (Request request : unassignedVariables()) { 580 weight += request.getWeight(); 581 } 582 return weight; 583 } 584 585 /** 586 * Set distance conflict extension 587 */ 588 public void setDistanceConflict(DistanceConflict dc) { 589 iDistanceConflict = dc; 590 } 591 592 /** 593 * Return distance conflict extension 594 */ 595 public DistanceConflict getDistanceConflict() { 596 return iDistanceConflict; 597 } 598 599 /** 600 * Set time overlaps extension 601 */ 602 public void setTimeOverlaps(TimeOverlapsCounter toc) { 603 iTimeOverlaps = toc; 604 } 605 606 /** 607 * Return time overlaps extension 608 */ 609 public TimeOverlapsCounter getTimeOverlaps() { 610 return iTimeOverlaps; 611 } 612 613 /** 614 * Average priority of unassigned requests (see 615 * {@link Request#getPriority()}) 616 */ 617 public double avgUnassignPriority() { 618 double totalPriority = 0.0; 619 for (Request request : unassignedVariables()) { 620 if (request.isAlternative()) 621 continue; 622 totalPriority += request.getPriority(); 623 } 624 return 1.0 + totalPriority / unassignedVariables().size(); 625 } 626 627 /** 628 * Average number of requests per student (see {@link Student#getRequests()} 629 * ) 630 */ 631 public double avgNrRequests() { 632 double totalRequests = 0.0; 633 int totalStudents = 0; 634 for (Student student : getStudents()) { 635 if (student.nrRequests() == 0) 636 continue; 637 totalRequests += student.nrRequests(); 638 totalStudents++; 639 } 640 return totalRequests / totalStudents; 641 } 642 643 /** Number of last like ({@link Student#isDummy()} equals true) students. */ 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 public int getNrRealStudents(boolean precise) { 657 if (!precise) 658 return getStudents().size() - iNrDummyStudents; 659 int nrRealStudents = 0; 660 for (Student student : getStudents()) { 661 if (!student.isDummy()) 662 nrRealStudents++; 663 } 664 return nrRealStudents; 665 } 666 667 /** 668 * Number of last like ({@link Student#isDummy()} equals true) students with 669 * a complete schedule ({@link Student#isComplete()} equals true). 670 */ 671 public int getNrCompleteLastLikeStudents(boolean precise) { 672 if (!precise) 673 return iNrCompleteDummyStudents; 674 int nrLastLikeStudents = 0; 675 for (Student student : getCompleteStudents()) { 676 if (student.isDummy()) 677 nrLastLikeStudents++; 678 } 679 return nrLastLikeStudents; 680 } 681 682 /** 683 * Number of real ({@link Student#isDummy()} equals false) students with a 684 * complete schedule ({@link Student#isComplete()} equals true). 685 */ 686 public int getNrCompleteRealStudents(boolean precise) { 687 if (!precise) 688 return getCompleteStudents().size() - iNrCompleteDummyStudents; 689 int nrRealStudents = 0; 690 for (Student student : getCompleteStudents()) { 691 if (!student.isDummy()) 692 nrRealStudents++; 693 } 694 return nrRealStudents; 695 } 696 697 /** 698 * Number of requests from projected ({@link Student#isDummy()} equals true) 699 * students. 700 */ 701 public int getNrLastLikeRequests(boolean precise) { 702 if (!precise) 703 return iNrDummyRequests; 704 int nrLastLikeRequests = 0; 705 for (Request request : variables()) { 706 if (request.getStudent().isDummy()) 707 nrLastLikeRequests++; 708 } 709 return nrLastLikeRequests; 710 } 711 712 /** 713 * Number of requests from real ({@link Student#isDummy()} equals false) 714 * students. 715 */ 716 public int getNrRealRequests(boolean precise) { 717 if (!precise) 718 return variables().size() - iNrDummyRequests; 719 int nrRealRequests = 0; 720 for (Request request : variables()) { 721 if (!request.getStudent().isDummy()) 722 nrRealRequests++; 723 } 724 return nrRealRequests; 725 } 726 727 /** 728 * Number of requests from projected ({@link Student#isDummy()} equals true) 729 * students that are assigned. 730 */ 731 public int getNrAssignedLastLikeRequests(boolean precise) { 732 if (!precise) 733 return iNrAssignedDummyRequests; 734 int nrLastLikeRequests = 0; 735 for (Request request : assignedVariables()) { 736 if (request.getStudent().isDummy()) 737 nrLastLikeRequests++; 738 } 739 return nrLastLikeRequests; 740 } 741 742 /** 743 * Number of requests from real ({@link Student#isDummy()} equals false) 744 * students that are assigned. 745 */ 746 public int getNrAssignedRealRequests(boolean precise) { 747 if (!precise) 748 return assignedVariables().size() - iNrAssignedDummyRequests; 749 int nrRealRequests = 0; 750 for (Request request : assignedVariables()) { 751 if (!request.getStudent().isDummy()) 752 nrRealRequests++; 753 } 754 return nrRealRequests; 755 } 756 757 /** 758 * Model extended info. Some more information (that is more expensive to 759 * compute) is added to an ordinary {@link Model#getInfo()}. 760 */ 761 @Override 762 public Map<String, String> getExtendedInfo() { 763 Map<String, String> info = getInfo(); 764 /* 765 int nrLastLikeStudents = getNrLastLikeStudents(true); 766 if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) { 767 int nrRealStudents = getStudents().size() - nrLastLikeStudents; 768 int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(true); 769 int nrRealCompleteStudents = getCompleteStudents().size() - nrLastLikeCompleteStudents; 770 info.put("Projected students with complete schedule", sDecimalFormat.format(100.0 771 * nrLastLikeCompleteStudents / nrLastLikeStudents) 772 + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")"); 773 info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents 774 / nrRealStudents) 775 + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")"); 776 int nrLastLikeRequests = getNrLastLikeRequests(true); 777 int nrRealRequests = variables().size() - nrLastLikeRequests; 778 int nrLastLikeAssignedRequests = getNrAssignedLastLikeRequests(true); 779 int nrRealAssignedRequests = assignedVariables().size() - nrLastLikeAssignedRequests; 780 info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests 781 / nrLastLikeRequests) 782 + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")"); 783 info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests) 784 + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")"); 785 } 786 */ 787 // info.put("Average unassigned priority", sDecimalFormat.format(avgUnassignPriority())); 788 // info.put("Average number of requests", sDecimalFormat.format(avgNrRequests())); 789 790 /* 791 double total = 0; 792 for (Request r: variables()) 793 if (r.getAssignment() != null) 794 total += r.getWeight() * iStudentWeights.getWeight(r.getAssignment()); 795 */ 796 double dc = 0; 797 if (getDistanceConflict() != null && getDistanceConflict().getTotalNrConflicts() != 0) { 798 Set<DistanceConflict.Conflict> conf = getDistanceConflict().getAllConflicts(); 799 for (DistanceConflict.Conflict c: conf) 800 dc += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(c); 801 if (!conf.isEmpty()) 802 info.put("Student distance conflicts", conf.size() + " (weighted: " + sDecimalFormat.format(dc) + ")"); 803 } 804 double toc = 0; 805 if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts() != 0) { 806 Set<TimeOverlapsCounter.Conflict> conf = getTimeOverlaps().getAllConflicts(); 807 int share = 0; 808 for (TimeOverlapsCounter.Conflict c: conf) { 809 toc += c.getR1().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(c.getE1(), c); 810 toc += c.getR2().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(c.getE2(), c); 811 share += c.getShare(); 812 } 813 if (toc != 0.0) 814 info.put("Time overlapping conflicts", share + " (average: " + sDecimalFormat.format(5.0 * share / getStudents().size()) + " min, weighted: " + sDoubleFormat.format(toc) + ")"); 815 } 816 /* 817 info.put("Overall solution value", sDecimalFormat.format(total - dc - toc) + (dc == 0.0 && toc == 0.0 ? "" : 818 " (" + (dc != 0.0 ? "distance: " + sDecimalFormat.format(dc): "") + (dc != 0.0 && toc != 0.0 ? ", " : "") + 819 (toc != 0.0 ? "overlap: " + sDecimalFormat.format(toc) : "") + ")") 820 ); 821 */ 822 823 double disbWeight = 0; 824 int disbSections = 0; 825 int disb10Sections = 0; 826 for (Offering offering: getOfferings()) { 827 for (Config config: offering.getConfigs()) { 828 double enrl = config.getEnrollmentWeight(null); 829 for (Subpart subpart: config.getSubparts()) { 830 if (subpart.getSections().size() <= 1) continue; 831 if (subpart.getLimit() > 0) { 832 // sections have limits -> desired size is section limit x (total enrollment / total limit) 833 double ratio = enrl / subpart.getLimit(); 834 for (Section section: subpart.getSections()) { 835 double desired = ratio * section.getLimit(); 836 disbWeight += Math.abs(section.getEnrollmentWeight(null) - desired); 837 disbSections ++; 838 if (Math.abs(desired - section.getEnrollmentWeight(null)) >= Math.max(1.0, 0.1 * section.getLimit())) 839 disb10Sections++; 840 } 841 } else { 842 // unlimited sections -> desired size is total enrollment / number of sections 843 for (Section section: subpart.getSections()) { 844 double desired = enrl / subpart.getSections().size(); 845 disbWeight += Math.abs(section.getEnrollmentWeight(null) - desired); 846 disbSections ++; 847 if (Math.abs(desired - section.getEnrollmentWeight(null)) >= Math.max(1.0, 0.1 * desired)) 848 disb10Sections++; 849 } 850 } 851 } 852 } 853 } 854 if (disbSections != 0) { 855 info.put("Average disbalance", sDecimalFormat.format(disbWeight / disbSections) + 856 " (" + sDecimalFormat.format(iAssignedCRWeight == 0 ? 0.0 : 100.0 * disbWeight / iAssignedCRWeight) + "%)"); 857 info.put("Sections disbalanced by 10% or more", disb10Sections + " (" + sDecimalFormat.format(disbSections == 0 ? 0.0 : 100.0 * disb10Sections / disbSections) + "%)"); 858 } 859 return info; 860 } 861 862 @Override 863 public void restoreBest() { 864 restoreBest(new Comparator<Request>() { 865 @Override 866 public int compare(Request r1, Request r2) { 867 Enrollment e1 = r1.getBestAssignment(); 868 Enrollment e2 = r2.getBestAssignment(); 869 // Reservations first 870 if (e1.getReservation() != null && e2.getReservation() == null) return -1; 871 if (e1.getReservation() == null && e2.getReservation() != null) return 1; 872 // Then assignment iteration (i.e., order in which assignments were made) 873 if (r1.getBestAssignmentIteration() != r2.getBestAssignmentIteration()) 874 return (r1.getBestAssignmentIteration() < r2.getBestAssignmentIteration() ? -1 : 1); 875 // Then student and priority 876 return r1.compareTo(r2); 877 } 878 }); 879 } 880 881 @Override 882 public String toString() { 883 return (getNrRealStudents(false) > 0 ? "RRq:" + getNrAssignedRealRequests(false) + "/" + getNrRealRequests(false) + ", " : "") 884 + (getNrLastLikeStudents(false) > 0 ? "DRq:" + getNrAssignedLastLikeRequests(false) + "/" + getNrLastLikeRequests(false) + ", " : "") 885 + (getNrRealStudents(false) > 0 ? "RS:" + getNrCompleteRealStudents(false) + "/" + getNrRealStudents(false) + ", " : "") 886 + (getNrLastLikeStudents(false) > 0 ? "DS:" + getNrCompleteLastLikeStudents(false) + "/" + getNrLastLikeStudents(false) + ", " : "") 887 + "V:" 888 + sDecimalFormat.format(-getTotalValue()) 889 + (getDistanceConflict() == null ? "" : ", DC:" + getDistanceConflict().getTotalNrConflicts()) 890 + (getTimeOverlaps() == null ? "" : ", TOC:" + getTimeOverlaps().getTotalNrConflicts()) 891 + ", %:" + sDecimalFormat.format(-100.0 * getTotalValue() / (getStudents().size() - iNrDummyStudents + 892 (iProjectedStudentWeight < 0.0 ? iNrDummyStudents * (iTotalDummyWeight / iNrDummyRequests) :iProjectedStudentWeight * iTotalDummyWeight))); 893 894 } 895 896 /** 897 * Quadratic average of two weights. 898 */ 899 public double avg(double w1, double w2) { 900 return Math.sqrt(w1 * w2); 901 } 902 903 public void add(DistanceConflict.Conflict c) { 904 iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(c); 905 } 906 907 public void remove(DistanceConflict.Conflict c) { 908 iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(c); 909 } 910 911 public void add(TimeOverlapsCounter.Conflict c) { 912 iTotalValue += c.getR1().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(c.getE1(), c); 913 iTotalValue += c.getR2().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(c.getE2(), c); 914 } 915 916 public void remove(TimeOverlapsCounter.Conflict c) { 917 iTotalValue -= c.getR1().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(c.getE1(), c); 918 iTotalValue -= c.getR2().getWeight() * iStudentWeights.getTimeOverlapConflictWeight(c.getE2(), c); 919 } 920 }