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 }