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.logging.log4j.Logger;
014import org.cpsolver.coursett.Constants;
015import org.cpsolver.ifs.assignment.Assignment;
016import org.cpsolver.ifs.assignment.InheritedAssignment;
017import org.cpsolver.ifs.assignment.OptimisticInheritedAssignment;
018import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
019import org.cpsolver.ifs.assignment.context.CanInheritContext;
020import org.cpsolver.ifs.assignment.context.ModelWithContext;
021import org.cpsolver.ifs.model.Constraint;
022import org.cpsolver.ifs.model.ConstraintListener;
023import org.cpsolver.ifs.model.InfoProvider;
024import org.cpsolver.ifs.model.Model;
025import org.cpsolver.ifs.solution.Solution;
026import org.cpsolver.ifs.util.DataProperties;
027import org.cpsolver.ifs.util.DistanceMetric;
028import org.cpsolver.studentsct.constraint.CancelledSections;
029import org.cpsolver.studentsct.constraint.ConfigLimit;
030import org.cpsolver.studentsct.constraint.CourseLimit;
031import org.cpsolver.studentsct.constraint.DisabledSections;
032import org.cpsolver.studentsct.constraint.FixInitialAssignments;
033import org.cpsolver.studentsct.constraint.LinkedSections;
034import org.cpsolver.studentsct.constraint.RequiredReservation;
035import org.cpsolver.studentsct.constraint.RequiredRestrictions;
036import org.cpsolver.studentsct.constraint.RequiredSections;
037import org.cpsolver.studentsct.constraint.ReservationLimit;
038import org.cpsolver.studentsct.constraint.SectionLimit;
039import org.cpsolver.studentsct.constraint.StudentConflict;
040import org.cpsolver.studentsct.constraint.StudentNotAvailable;
041import org.cpsolver.studentsct.extension.DistanceConflict;
042import org.cpsolver.studentsct.extension.StudentQuality;
043import org.cpsolver.studentsct.extension.TimeOverlapsCounter;
044import org.cpsolver.studentsct.model.Config;
045import org.cpsolver.studentsct.model.Course;
046import org.cpsolver.studentsct.model.CourseRequest;
047import org.cpsolver.studentsct.model.Enrollment;
048import org.cpsolver.studentsct.model.Offering;
049import org.cpsolver.studentsct.model.Request;
050import org.cpsolver.studentsct.model.RequestGroup;
051import org.cpsolver.studentsct.model.Section;
052import org.cpsolver.studentsct.model.Student;
053import org.cpsolver.studentsct.model.Subpart;
054import org.cpsolver.studentsct.model.Unavailability;
055import org.cpsolver.studentsct.model.Request.RequestPriority;
056import org.cpsolver.studentsct.model.Student.BackToBackPreference;
057import org.cpsolver.studentsct.model.Student.ModalityPreference;
058import org.cpsolver.studentsct.model.Student.StudentPriority;
059import org.cpsolver.studentsct.reservation.Reservation;
060import org.cpsolver.studentsct.weights.PriorityStudentWeights;
061import org.cpsolver.studentsct.weights.StudentWeights;
062
063/**
064 * Student sectioning model.
065 * 
066 * <br>
067 * <br>
068 * 
069 * @author  Tomáš Müller
070 * @version StudentSct 1.3 (Student Sectioning)<br>
071 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
072 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
073 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
074 * <br>
075 *          This library is free software; you can redistribute it and/or modify
076 *          it under the terms of the GNU Lesser General Public License as
077 *          published by the Free Software Foundation; either version 3 of the
078 *          License, or (at your option) any later version. <br>
079 * <br>
080 *          This library is distributed in the hope that it will be useful, but
081 *          WITHOUT ANY WARRANTY; without even the implied warranty of
082 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
083 *          Lesser General Public License for more details. <br>
084 * <br>
085 *          You should have received a copy of the GNU Lesser General Public
086 *          License along with this library; if not see
087 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
088 */
089public class StudentSectioningModel extends ModelWithContext<Request, Enrollment, StudentSectioningModel.StudentSectioningModelContext> implements CanInheritContext<Request, Enrollment, StudentSectioningModel.StudentSectioningModelContext> {
090    private static Logger sLog = org.apache.logging.log4j.LogManager.getLogger(StudentSectioningModel.class);
091    protected static DecimalFormat sDecimalFormat = new DecimalFormat("0.00");
092    private List<Student> iStudents = new ArrayList<Student>();
093    private List<Offering> iOfferings = new ArrayList<Offering>();
094    private List<LinkedSections> iLinkedSections = new ArrayList<LinkedSections>();
095    private DataProperties iProperties;
096    private DistanceConflict iDistanceConflict = null;
097    private TimeOverlapsCounter iTimeOverlaps = null;
098    private StudentQuality iStudentQuality = null;
099    private int iNrDummyStudents = 0, iNrDummyRequests = 0;
100    private int[] iNrPriorityStudents = null;
101    private double iTotalDummyWeight = 0.0;
102    private double iTotalCRWeight = 0.0, iTotalDummyCRWeight = 0.0;
103    private double[] iTotalPriorityCRWeight = null;
104    private double[] iTotalCriticalCRWeight;
105    private double[][] iTotalPriorityCriticalCRWeight;
106    private double iTotalMPPCRWeight = 0.0;
107    private double iTotalSelCRWeight = 0.0;
108    private double iBestAssignedCourseRequestWeight = 0.0;
109    private StudentWeights iStudentWeights = null;
110    private boolean iReservationCanAssignOverTheLimit;
111    private boolean iMPP;
112    private boolean iKeepInitials;
113    protected double iProjectedStudentWeight = 0.0100;
114    private int iMaxDomainSize = -1; 
115    private int iDayOfWeekOffset = 0;
116
117
118    /**
119     * Constructor
120     * 
121     * @param properties
122     *            configuration
123     */
124    @SuppressWarnings("unchecked")
125    public StudentSectioningModel(DataProperties properties) {
126        super();
127        iTotalCriticalCRWeight = new double[RequestPriority.values().length];
128        iTotalPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length];
129        for (int i = 0; i < RequestPriority.values().length; i++) {
130            iTotalCriticalCRWeight[i] = 0.0;
131            for (int j = 0; j < StudentPriority.values().length; j++) {
132                iTotalPriorityCriticalCRWeight[i][j] = 0.0;
133            }
134        }
135        iNrPriorityStudents = new int[StudentPriority.values().length];
136        iTotalPriorityCRWeight = new double[StudentPriority.values().length];
137        for (int i = 0; i < StudentPriority.values().length; i++) {
138            iNrPriorityStudents[i] = 0;
139            iTotalPriorityCRWeight[i] = 0.0;
140        }
141        iReservationCanAssignOverTheLimit =  properties.getPropertyBoolean("Reservation.CanAssignOverTheLimit", false);
142        iMPP = properties.getPropertyBoolean("General.MPP", false);
143        iKeepInitials = properties.getPropertyBoolean("Sectioning.KeepInitialAssignments", false);
144        iStudentWeights = new PriorityStudentWeights(properties);
145        iMaxDomainSize = properties.getPropertyInt("Sectioning.MaxDomainSize", iMaxDomainSize);
146        iDayOfWeekOffset = properties.getPropertyInt("DatePattern.DayOfWeekOffset", 0);
147        if (properties.getPropertyBoolean("Sectioning.SectionLimit", true)) {
148            SectionLimit sectionLimit = new SectionLimit(properties);
149            addGlobalConstraint(sectionLimit);
150            if (properties.getPropertyBoolean("Sectioning.SectionLimit.Debug", false)) {
151                sectionLimit.addConstraintListener(new ConstraintListener<Request, Enrollment>() {
152                    @Override
153                    public void constraintBeforeAssigned(Assignment<Request, Enrollment> assignment, long iteration, Constraint<Request, Enrollment> constraint, Enrollment enrollment, Set<Enrollment> unassigned) {
154                        if (enrollment.getStudent().isDummy())
155                            for (Enrollment conflict : unassigned) {
156                                if (!conflict.getStudent().isDummy()) {
157                                    sLog.warn("Enrolment of a real student " + conflict.getStudent() + " is unassigned "
158                                            + "\n  -- " + conflict + "\ndue to an enrollment of a dummy student "
159                                            + enrollment.getStudent() + " " + "\n  -- " + enrollment);
160                                }
161                            }
162                    }
163
164                    @Override
165                    public void constraintAfterAssigned(Assignment<Request, Enrollment> assignment, long iteration, Constraint<Request, Enrollment> constraint, Enrollment assigned, Set<Enrollment> unassigned) {
166                    }
167                });
168            }
169        }
170        if (properties.getPropertyBoolean("Sectioning.ConfigLimit", true)) {
171            ConfigLimit configLimit = new ConfigLimit(properties);
172            addGlobalConstraint(configLimit);
173        }
174        if (properties.getPropertyBoolean("Sectioning.CourseLimit", true)) {
175            CourseLimit courseLimit = new CourseLimit(properties);
176            addGlobalConstraint(courseLimit);
177        }
178        if (properties.getPropertyBoolean("Sectioning.ReservationLimit", true)) {
179            ReservationLimit reservationLimit = new ReservationLimit(properties);
180            addGlobalConstraint(reservationLimit);
181        }
182        if (properties.getPropertyBoolean("Sectioning.RequiredReservations", true)) {
183            RequiredReservation requiredReservation = new RequiredReservation();
184            addGlobalConstraint(requiredReservation);
185        }
186        if (properties.getPropertyBoolean("Sectioning.CancelledSections", true)) {
187            CancelledSections cancelledSections = new CancelledSections();
188            addGlobalConstraint(cancelledSections);
189        }
190        if (properties.getPropertyBoolean("Sectioning.StudentNotAvailable", true)) {
191            StudentNotAvailable studentNotAvailable = new StudentNotAvailable();
192            addGlobalConstraint(studentNotAvailable);
193        }
194        if (properties.getPropertyBoolean("Sectioning.DisabledSections", true)) {
195            DisabledSections disabledSections = new DisabledSections();
196            addGlobalConstraint(disabledSections);
197        }
198        if (properties.getPropertyBoolean("Sectioning.RequiredSections", true)) {
199            RequiredSections requiredSections = new RequiredSections();
200            addGlobalConstraint(requiredSections);
201        }
202        if (properties.getPropertyBoolean("Sectioning.RequiredRestrictions", true)) {
203            RequiredRestrictions requiredRestrictions = new RequiredRestrictions();
204            addGlobalConstraint(requiredRestrictions);
205        }
206        if (iMPP && iKeepInitials) {
207            addGlobalConstraint(new FixInitialAssignments());
208        }
209        try {
210            Class<StudentWeights> studentWeightsClass = (Class<StudentWeights>)Class.forName(properties.getProperty("StudentWeights.Class", PriorityStudentWeights.class.getName()));
211            iStudentWeights = studentWeightsClass.getConstructor(DataProperties.class).newInstance(properties);
212        } catch (Exception e) {
213            sLog.error("Unable to create custom student weighting model (" + e.getMessage() + "), using default.", e);
214            iStudentWeights = new PriorityStudentWeights(properties);
215        }
216        iProjectedStudentWeight = properties.getPropertyDouble("StudentWeights.ProjectedStudentWeight", iProjectedStudentWeight);
217        iProperties = properties;
218    }
219    
220    /**
221     * Return true if reservation that has {@link Reservation#canAssignOverLimit()} can assign enrollments over the limit
222     * @return true if reservation that has {@link Reservation#canAssignOverLimit()} can assign enrollments over the limit
223     */
224    public boolean getReservationCanAssignOverTheLimit() {
225        return iReservationCanAssignOverTheLimit;
226    }
227    
228    /**
229     * Return true if the problem is minimal perturbation problem 
230     * @return true if MPP is enabled
231     */
232    public boolean isMPP() {
233        return iMPP;
234    }
235    
236    /**
237     * Return true if the inital assignments are to be kept unchanged 
238     * @return true if the initial assignments are to be kept at all cost
239     */
240    public boolean getKeepInitialAssignments() {
241        return iKeepInitials;
242    }
243    
244    /**
245     * Return student weighting model
246     * @return student weighting model
247     */
248    public StudentWeights getStudentWeights() {
249        return iStudentWeights;
250    }
251
252    /**
253     * Set student weighting model
254     * @param weights student weighting model
255     */
256    public void setStudentWeights(StudentWeights weights) {
257        iStudentWeights = weights;
258    }
259
260    /**
261     * Students
262     * @return all students in the problem
263     */
264    public List<Student> getStudents() {
265        return iStudents;
266    }
267
268    /**
269     * Add a student into the model
270     * @param student a student to be added into the problem
271     */
272    public void addStudent(Student student) {
273        iStudents.add(student);
274        if (student.isDummy())
275            iNrDummyStudents++;
276        iNrPriorityStudents[student.getPriority().ordinal()]++;
277        for (Request request : student.getRequests())
278            addVariable(request);
279        if (getProperties().getPropertyBoolean("Sectioning.StudentConflict", true)) {
280            addConstraint(new StudentConflict(student));
281        }
282    }
283    
284    public int getNbrStudents(StudentPriority priority) {
285        return iNrPriorityStudents[priority.ordinal()];
286    }
287    
288    @Override
289    public void addVariable(Request request) {
290        super.addVariable(request);
291        if (request instanceof CourseRequest && !request.isAlternative())
292            iTotalCRWeight += request.getWeight();
293        if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.getStudent().isDummy() && !request.isAlternative())
294            iTotalCriticalCRWeight[request.getRequestPriority().ordinal()] += request.getWeight();
295        if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.isAlternative())
296            iTotalPriorityCriticalCRWeight[request.getRequestPriority().ordinal()][request.getStudent().getPriority().ordinal()] += request.getWeight();
297        if (request.getStudent().isDummy()) {
298            iNrDummyRequests++;
299            iTotalDummyWeight += request.getWeight();
300            if (request instanceof CourseRequest && !request.isAlternative())
301                iTotalDummyCRWeight += request.getWeight();
302        }
303        if (request instanceof CourseRequest && !request.isAlternative())
304            iTotalPriorityCRWeight[request.getStudent().getPriority().ordinal()] += request.getWeight();
305        if (request.isMPP())
306            iTotalMPPCRWeight += request.getWeight();
307        if (request.hasSelection())
308            iTotalSelCRWeight += request.getWeight();
309    }
310    
311    public void setCourseRequestPriority(CourseRequest request, RequestPriority priority) {
312        if (request.getRequestPriority() != RequestPriority.Normal && !request.getStudent().isDummy() && !request.isAlternative())
313            iTotalCriticalCRWeight[request.getRequestPriority().ordinal()] -= request.getWeight();
314        if (request.getRequestPriority() != RequestPriority.Normal && !request.isAlternative())
315            iTotalPriorityCriticalCRWeight[request.getRequestPriority().ordinal()][request.getStudent().getPriority().ordinal()] -= request.getWeight();
316        request.setRequestPriority(priority);
317        if (request.getRequestPriority() != RequestPriority.Normal && !request.getStudent().isDummy() && !request.isAlternative())
318            iTotalCriticalCRWeight[request.getRequestPriority().ordinal()] += request.getWeight();
319        if (request.getRequestPriority() != RequestPriority.Normal && !request.isAlternative())
320            iTotalPriorityCriticalCRWeight[request.getRequestPriority().ordinal()][request.getStudent().getPriority().ordinal()] += request.getWeight();
321    }
322    
323    /** 
324     * Recompute cached request weights
325     * @param assignment current assignment
326     */
327    public void requestWeightsChanged(Assignment<Request, Enrollment> assignment) {
328        getContext(assignment).requestWeightsChanged(assignment);
329    }
330
331    /**
332     * Remove a student from the model
333     * @param student a student to be removed from the problem
334     */
335    public void removeStudent(Student student) {
336        iStudents.remove(student);
337        if (student.isDummy())
338            iNrDummyStudents--;
339        iNrPriorityStudents[student.getPriority().ordinal()]--;
340        StudentConflict conflict = null;
341        for (Request request : student.getRequests()) {
342            for (Constraint<Request, Enrollment> c : request.constraints()) {
343                if (c instanceof StudentConflict) {
344                    conflict = (StudentConflict) c;
345                    break;
346                }
347            }
348            if (conflict != null) 
349                conflict.removeVariable(request);
350            removeVariable(request);
351        }
352        if (conflict != null) 
353            removeConstraint(conflict);
354    }
355    
356    @Override
357    public void removeVariable(Request request) {
358        super.removeVariable(request);
359        if (request instanceof CourseRequest) {
360            CourseRequest cr = (CourseRequest)request;
361            for (Course course: cr.getCourses())
362                course.getRequests().remove(request);
363        }
364        if (request.getStudent().isDummy()) {
365            iNrDummyRequests--;
366            iTotalDummyWeight -= request.getWeight();
367            if (request instanceof CourseRequest && !request.isAlternative())
368                iTotalDummyCRWeight -= request.getWeight();
369        }
370        if (request instanceof CourseRequest && !request.isAlternative())
371            iTotalPriorityCRWeight[request.getStudent().getPriority().ordinal()] -= request.getWeight();
372        if (request.isMPP())
373            iTotalMPPCRWeight -= request.getWeight();
374        if (request.hasSelection())
375            iTotalSelCRWeight -= request.getWeight();
376        if (request instanceof CourseRequest && !request.isAlternative())
377            iTotalCRWeight -= request.getWeight();
378        if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.getStudent().isDummy() && !request.isAlternative())
379            iTotalCriticalCRWeight[request.getRequestPriority().ordinal()] -= request.getWeight();
380        if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.isAlternative())
381            iTotalPriorityCriticalCRWeight[request.getRequestPriority().ordinal()][request.getStudent().getPriority().ordinal()] -= request.getWeight();
382    }
383
384
385    /**
386     * List of offerings
387     * @return all instructional offerings of the problem
388     */
389    public List<Offering> getOfferings() {
390        return iOfferings;
391    }
392
393    /**
394     * Add an offering into the model
395     * @param offering an instructional offering to be added into the problem
396     */
397    public void addOffering(Offering offering) {
398        iOfferings.add(offering);
399        offering.setModel(this);
400    }
401    
402    /**
403     * Link sections using {@link LinkedSections}
404     * @param mustBeUsed if true,  a pair of linked sections must be used when a student requests both courses 
405     * @param sections a linked section constraint to be added into the problem
406     */
407    public void addLinkedSections(boolean mustBeUsed, Section... sections) {
408        LinkedSections constraint = new LinkedSections(sections);
409        constraint.setMustBeUsed(mustBeUsed);
410        iLinkedSections.add(constraint);
411        constraint.createConstraints();
412    }
413    
414    /**
415     * Link sections using {@link LinkedSections}
416     * @param sections a linked section constraint to be added into the problem
417     */
418    @Deprecated
419    public void addLinkedSections(Section... sections) {
420        addLinkedSections(false, sections);
421    }
422
423    /**
424     * Link sections using {@link LinkedSections}
425     * @param mustBeUsed if true,  a pair of linked sections must be used when a student requests both courses 
426     * @param sections a linked section constraint to be added into the problem
427     */
428    public void addLinkedSections(boolean mustBeUsed, Collection<Section> sections) {
429        LinkedSections constraint = new LinkedSections(sections);
430        constraint.setMustBeUsed(mustBeUsed);
431        iLinkedSections.add(constraint);
432        constraint.createConstraints();
433    }
434    
435    /**
436     * Link sections using {@link LinkedSections}
437     * @param sections a linked section constraint to be added into the problem
438     */
439    @Deprecated
440    public void addLinkedSections(Collection<Section> sections) {
441        addLinkedSections(false, sections);
442    }
443
444    /**
445     * List of linked sections
446     * @return all linked section constraints of the problem
447     */
448    public List<LinkedSections> getLinkedSections() {
449        return iLinkedSections;
450    }
451
452    /**
453     * Model info
454     */
455    @Override
456    public Map<String, String> getInfo(Assignment<Request, Enrollment> assignment) {
457        Map<String, String> info = super.getInfo(assignment);
458        StudentSectioningModelContext context = getContext(assignment);
459        if (!getStudents().isEmpty())
460            info.put("Students with complete schedule", sDoubleFormat.format(100.0 * context.nrComplete() / getStudents().size()) + "% (" + context.nrComplete() + "/" + getStudents().size() + ")");
461        String priorityComplete = "";
462        for (StudentPriority sp: StudentPriority.values()) {
463            if (sp != StudentPriority.Dummy && iNrPriorityStudents[sp.ordinal()] > 0)
464                priorityComplete += (priorityComplete.isEmpty() ? "" : "\n") +
465                    sp.name() + ": " + sDoubleFormat.format(100.0 * context.iNrCompletePriorityStudents[sp.ordinal()] / iNrPriorityStudents[sp.ordinal()]) + "% (" + context.iNrCompletePriorityStudents[sp.ordinal()] + "/" + iNrPriorityStudents[sp.ordinal()] + ")";
466        }
467        if (!priorityComplete.isEmpty())
468            info.put("Students with complete schedule (priority students)", priorityComplete);
469        if (getStudentQuality() != null) {
470            int confs = getStudentQuality().getTotalPenalty(StudentQuality.Type.Distance, assignment);
471            int shortConfs = getStudentQuality().getTotalPenalty(StudentQuality.Type.ShortDistance, assignment);
472            int unavConfs = getStudentQuality().getTotalPenalty(StudentQuality.Type.UnavailabilityDistance, assignment);
473            if (confs > 0 || shortConfs > 0) {
474                info.put("Student distance conflicts", confs + (shortConfs == 0 ? "" : " (" + getDistanceMetric().getShortDistanceAccommodationReference() + ": " + shortConfs + ")"));
475            }
476            if (unavConfs > 0) {
477                info.put("Unavailabilities: Distance conflicts", String.valueOf(unavConfs));
478            }
479        } else if (getDistanceConflict() != null) {
480            int confs = getDistanceConflict().getTotalNrConflicts(assignment);
481            if (confs > 0) {
482                int shortConfs = getDistanceConflict().getTotalNrShortConflicts(assignment);
483                info.put("Student distance conflicts", confs + (shortConfs == 0 ? "" : " (" + getDistanceConflict().getDistanceMetric().getShortDistanceAccommodationReference() + ": " + shortConfs + ")"));
484            }
485        }
486        if (getStudentQuality() != null) {
487            int shareCR = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.CourseTimeOverlap, assignment);
488            int shareFT = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.FreeTimeOverlap, assignment);
489            int shareUN = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.Unavailability, assignment);
490            if (shareCR + shareFT + shareUN > 0)
491                info.put("Time overlapping conflicts", sDoubleFormat.format((5.0 * (shareCR + shareFT + shareUN)) / iStudents.size()) + " mins per student\n" + 
492                        "(" + sDoubleFormat.format(5.0 * shareCR / iStudents.size()) + " between courses, " + sDoubleFormat.format(5.0 * shareFT / iStudents.size()) + " free time" +
493                        (shareUN == 0 ? "" : ", " + sDoubleFormat.format(5.0 * shareUN / iStudents.size()) + " teaching assignments & unavailabilities") + "; " + sDoubleFormat.format((shareCR + shareFT + shareUN) / 12.0) + " hours total)");
494        } else if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) {
495            info.put("Time overlapping conflicts", sDoubleFormat.format(5.0 * getTimeOverlaps().getTotalNrConflicts(assignment) / iStudents.size()) + " mins per student (" + sDoubleFormat.format(getTimeOverlaps().getTotalNrConflicts(assignment) / 12.0) + " hours total)");
496        }
497        if (getStudentQuality() != null) {
498            int confLunch = getStudentQuality().getTotalPenalty(StudentQuality.Type.LunchBreak, assignment);
499            if (confLunch > 0)
500                info.put("Schedule Quality: Lunch conflicts", sDoubleFormat.format(20.0 * confLunch / getNrRealStudents(false)) + "% (" + confLunch + ")");
501            int confTravel = getStudentQuality().getTotalPenalty(StudentQuality.Type.TravelTime, assignment);
502            if (confTravel > 0)
503                info.put("Schedule Quality: Travel time", sDoubleFormat.format(((double)confTravel) / getNrRealStudents(false)) + " mins per student (" + sDecimalFormat.format(confTravel / 60.0) + " hours total)");
504            int confBtB = getStudentQuality().getTotalPenalty(StudentQuality.Type.BackToBack, assignment);
505            if (confBtB != 0)
506                info.put("Schedule Quality: Back-to-back classes", sDoubleFormat.format(((double)confBtB) / getNrRealStudents(false)) + " per student (" + confBtB + ")");
507            int confMod = getStudentQuality().getTotalPenalty(StudentQuality.Type.Modality, assignment);
508            if (confMod > 0)
509                info.put("Schedule Quality: Online class preference", sDoubleFormat.format(((double)confMod) / getNrRealStudents(false)) + " per student (" + confMod + ")");
510            int confWorkDay = getStudentQuality().getTotalPenalty(StudentQuality.Type.WorkDay, assignment);
511            if (confWorkDay > 0)
512                info.put("Schedule Quality: Work day", sDoubleFormat.format(5.0 * confWorkDay / getNrRealStudents(false)) + " mins over " +
513                        new DecimalFormat("0.#").format(getProperties().getPropertyInt("WorkDay.WorkDayLimit", 6*12) / 12.0) + " hours a day per student\n(from start to end, " + sDoubleFormat.format(confWorkDay / 12.0) + " hours total)");
514            int early = getStudentQuality().getTotalPenalty(StudentQuality.Type.TooEarly, assignment);
515            if (early > 0) {
516                int min = getProperties().getPropertyInt("WorkDay.EarlySlot", 102) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
517                int h = min / 60;
518                int m = min % 60;
519                String time = (getProperties().getPropertyBoolean("General.UseAmPm", true) ? (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a") : h + ":" + (m < 10 ? "0" : "") + m);
520                info.put("Schedule Quality: Early classes", sDoubleFormat.format(5.0 * early / iStudents.size()) + " mins before " + time + " per student (" + sDoubleFormat.format(early / 12.0) + " hours total)");
521            }
522            int late = getStudentQuality().getTotalPenalty(StudentQuality.Type.TooLate, assignment);
523            if (late > 0) {
524                int min = getProperties().getPropertyInt("WorkDay.LateSlot", 210) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
525                int h = min / 60;
526                int m = min % 60;
527                String time = (getProperties().getPropertyBoolean("General.UseAmPm", true) ? (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a") : h + ":" + (m < 10 ? "0" : "") + m);
528                info.put("Schedule Quality: Late classes", sDoubleFormat.format(5.0 * late / iStudents.size()) + " mins after " + time + " per student (" + sDoubleFormat.format(late / 12.0) + " hours total)");
529            }
530            int accFT = getStudentQuality().getTotalPenalty(StudentQuality.Type.AccFreeTimeOverlap, assignment);
531            if (accFT > 0) {
532                info.put("Accommodations: Free time conflicts", sDoubleFormat.format(5.0 * accFT / getStudentsWithAccommodation(getStudentQuality().getStudentQualityContext().getFreeTimeAccommodation())) + " mins per student, " + sDoubleFormat.format(accFT / 12.0) + " hours total");
533            }
534            int accBtB = getStudentQuality().getTotalPenalty(StudentQuality.Type.AccBackToBack, assignment);
535            if (accBtB > 0) {
536                info.put("Accommodations: Back-to-back classes", sDoubleFormat.format(((double)accBtB) / getStudentsWithAccommodation(getStudentQuality().getStudentQualityContext().getBackToBackAccommodation())) + " non-BTB classes per student, " + accBtB + " total");
537            }
538            int accBbc = getStudentQuality().getTotalPenalty(StudentQuality.Type.AccBreaksBetweenClasses, assignment);
539            if (accBbc > 0) {
540                info.put("Accommodations: Break between classes", sDoubleFormat.format(((double)accBbc) / getStudentsWithAccommodation(getStudentQuality().getStudentQualityContext().getBreakBetweenClassesAccommodation())) + " BTB classes per student, " + accBbc + " total");
541            }
542            int shortConfs = getStudentQuality().getTotalPenalty(StudentQuality.Type.ShortDistance, assignment);
543            if (shortConfs > 0) {
544                info.put("Accommodations: Distance conflicts", sDoubleFormat.format(((double)shortConfs) / getStudentsWithAccommodation(getStudentQuality().getDistanceMetric().getShortDistanceAccommodationReference())) + " short distance conflicts per student, " + shortConfs + " total");
545            }
546        }
547        int nrLastLikeStudents = getNrLastLikeStudents(false);
548        if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) {
549            int nrRealStudents = getStudents().size() - nrLastLikeStudents;
550            int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(assignment, false);
551            int nrRealCompleteStudents = context.nrComplete() - nrLastLikeCompleteStudents;
552            if (nrLastLikeStudents > 0)
553                info.put("Projected students with complete schedule", sDecimalFormat.format(100.0
554                        * nrLastLikeCompleteStudents / nrLastLikeStudents)
555                        + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")");
556            if (nrRealStudents > 0)
557                info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents
558                        / nrRealStudents)
559                        + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")");
560            int nrLastLikeRequests = getNrLastLikeRequests(false);
561            int nrRealRequests = variables().size() - nrLastLikeRequests;
562            int nrLastLikeAssignedRequests = context.getNrAssignedLastLikeRequests();
563            int nrRealAssignedRequests = assignment.nrAssignedVariables() - nrLastLikeAssignedRequests;
564            if (nrLastLikeRequests > 0)
565                info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests / nrLastLikeRequests)
566                        + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")");
567            if (nrRealRequests > 0)
568                info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests)
569                        + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")");
570        }
571        context.getInfo(assignment, info);
572        
573        double groupSpread = 0.0; double groupCount = 0;
574        for (Offering offering: iOfferings) {
575            for (Course course: offering.getCourses()) {
576                for (RequestGroup group: course.getRequestGroups()) {
577                    groupSpread += group.getAverageSpread(assignment) * group.getEnrollmentWeight(assignment, null);
578                    groupCount += group.getEnrollmentWeight(assignment, null);
579                }
580            }
581        }
582        if (groupCount > 0)
583            info.put("Same group", sDecimalFormat.format(100.0 * groupSpread / groupCount) + "%");
584
585        return info;
586    }
587
588    /**
589     * Overall solution value
590     * @param assignment current assignment
591     * @param precise true if should be computed
592     * @return solution value
593     */
594    public double getTotalValue(Assignment<Request, Enrollment> assignment, boolean precise) {
595        if (precise) {
596            double total = 0;
597            for (Request r: assignment.assignedVariables())
598                total += r.getWeight() * iStudentWeights.getWeight(assignment, assignment.getValue(r));
599            if (iDistanceConflict != null)
600                for (DistanceConflict.Conflict c: iDistanceConflict.computeAllConflicts(assignment))
601                    total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c);
602            if (iTimeOverlaps != null)
603                for (TimeOverlapsCounter.Conflict c: iTimeOverlaps.getContext(assignment).computeAllConflicts(assignment)) {
604                    if (c.getR1() != null) total -= c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c);
605                    if (c.getR2() != null) total -= c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c);
606                }
607            if (iStudentQuality != null)
608                for (StudentQuality.Type t: StudentQuality.Type.values()) {
609                    for (StudentQuality.Conflict c: iStudentQuality.getContext(assignment).computeAllConflicts(t, assignment)) {
610                        switch (c.getType().getType()) {
611                            case REQUEST:
612                                if (c.getR1() instanceof CourseRequest)
613                                    total -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
614                                else
615                                    total -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
616                                break;
617                            case BOTH:
618                                total -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);  
619                                total -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
620                                break;
621                            case LOWER:
622                                total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
623                                break;
624                            case HIGHER:
625                                total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
626                                break;
627                        }
628                    }    
629                }
630            return -total;
631        }
632        return getContext(assignment).getTotalValue();
633    }
634    
635    /**
636     * Overall solution value
637     */
638    @Override
639    public double getTotalValue(Assignment<Request, Enrollment> assignment) {
640        return getContext(assignment).getTotalValue();
641    }
642
643    /**
644     * Configuration
645     * @return solver configuration
646     */
647    public DataProperties getProperties() {
648        return iProperties;
649    }
650
651    /**
652     * Empty online student sectioning infos for all sections (see
653     * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}).
654     */
655    public void clearOnlineSectioningInfos() {
656        for (Offering offering : iOfferings) {
657            for (Config config : offering.getConfigs()) {
658                for (Subpart subpart : config.getSubparts()) {
659                    for (Section section : subpart.getSections()) {
660                        section.setSpaceExpected(0);
661                        section.setSpaceHeld(0);
662                    }
663                }
664            }
665        }
666    }
667
668    /**
669     * Compute online student sectioning infos for all sections (see
670     * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}).
671     * @param assignment current assignment
672     */
673    public void computeOnlineSectioningInfos(Assignment<Request, Enrollment> assignment) {
674        clearOnlineSectioningInfos();
675        for (Student student : getStudents()) {
676            if (!student.isDummy())
677                continue;
678            for (Request request : student.getRequests()) {
679                if (!(request instanceof CourseRequest))
680                    continue;
681                CourseRequest courseRequest = (CourseRequest) request;
682                Enrollment enrollment = assignment.getValue(courseRequest);
683                if (enrollment != null) {
684                    for (Section section : enrollment.getSections()) {
685                        section.setSpaceHeld(courseRequest.getWeight() + section.getSpaceHeld());
686                    }
687                }
688                List<Enrollment> feasibleEnrollments = new ArrayList<Enrollment>();
689                int totalLimit = 0;
690                for (Enrollment enrl : courseRequest.values(assignment)) {
691                    boolean overlaps = false;
692                    for (Request otherRequest : student.getRequests()) {
693                        if (otherRequest.equals(courseRequest) || !(otherRequest instanceof CourseRequest))
694                            continue;
695                        Enrollment otherErollment = assignment.getValue(otherRequest);
696                        if (otherErollment == null)
697                            continue;
698                        if (enrl.isOverlapping(otherErollment)) {
699                            overlaps = true;
700                            break;
701                        }
702                    }
703                    if (!overlaps) {
704                        feasibleEnrollments.add(enrl);
705                        if (totalLimit >= 0) {
706                            int limit = enrl.getLimit();
707                            if (limit < 0) totalLimit = -1;
708                            else totalLimit += limit;
709                        }
710                    }
711                }
712                double increment = courseRequest.getWeight() / (totalLimit > 0 ? totalLimit : feasibleEnrollments.size());
713                for (Enrollment feasibleEnrollment : feasibleEnrollments) {
714                    for (Section section : feasibleEnrollment.getSections()) {
715                        if (totalLimit > 0) {
716                            section.setSpaceExpected(section.getSpaceExpected() + increment * feasibleEnrollment.getLimit());
717                        } else {
718                            section.setSpaceExpected(section.getSpaceExpected() + increment);
719                        }
720                    }
721                }
722            }
723        }
724    }
725
726    /**
727     * Sum of weights of all requests that are not assigned (see
728     * {@link Request#getWeight()}).
729     * @param assignment current assignment
730     * @return unassigned request weight
731     */
732    public double getUnassignedRequestWeight(Assignment<Request, Enrollment> assignment) {
733        double weight = 0.0;
734        for (Request request : assignment.unassignedVariables(this)) {
735            weight += request.getWeight();
736        }
737        return weight;
738    }
739
740    /**
741     * Sum of weights of all requests (see {@link Request#getWeight()}).
742     * @return total request weight
743     */
744    public double getTotalRequestWeight() {
745        double weight = 0.0;
746        for (Request request : variables()) {
747            weight += request.getWeight();
748        }
749        return weight;
750    }
751
752    /**
753     * Set distance conflict extension
754     * @param dc distance conflicts extension
755     */
756    public void setDistanceConflict(DistanceConflict dc) {
757        iDistanceConflict = dc;
758    }
759
760    /**
761     * Return distance conflict extension
762     * @return distance conflicts extension
763     */
764    public DistanceConflict getDistanceConflict() {
765        return iDistanceConflict;
766    }
767
768    /**
769     * Set time overlaps extension
770     * @param toc time overlapping conflicts extension
771     */
772    public void setTimeOverlaps(TimeOverlapsCounter toc) {
773        iTimeOverlaps = toc;
774    }
775
776    /**
777     * Return time overlaps extension
778     * @return time overlapping conflicts extension
779     */
780    public TimeOverlapsCounter getTimeOverlaps() {
781        return iTimeOverlaps;
782    }
783    
784    public StudentQuality getStudentQuality() { return iStudentQuality; }
785    public void setStudentQuality(StudentQuality q, boolean register) {
786        if (iStudentQuality != null)
787            getInfoProviders().remove(iStudentQuality);
788        iStudentQuality = q;
789        if (iStudentQuality != null)
790            getInfoProviders().add(iStudentQuality);
791        if (register) {
792            iStudentQuality.setAssignmentContextReference(createReference(iStudentQuality));
793            iStudentQuality.register(this);
794        }
795    }
796    
797    public void setStudentQuality(StudentQuality q) {
798        setStudentQuality(q, true);
799    }
800
801    /**
802     * Average priority of unassigned requests (see
803     * {@link Request#getPriority()})
804     * @param assignment current assignment
805     * @return average priority of unassigned requests
806     */
807    public double avgUnassignPriority(Assignment<Request, Enrollment> assignment) {
808        double totalPriority = 0.0;
809        for (Request request : assignment.unassignedVariables(this)) {
810            if (request.isAlternative())
811                continue;
812            totalPriority += request.getPriority();
813        }
814        return 1.0 + totalPriority / assignment.nrUnassignedVariables(this);
815    }
816
817    /**
818     * Average number of requests per student (see {@link Student#getRequests()}
819     * )
820     * @return average number of requests per student
821     */
822    public double avgNrRequests() {
823        double totalRequests = 0.0;
824        int totalStudents = 0;
825        for (Student student : getStudents()) {
826            if (student.nrRequests() == 0)
827                continue;
828            totalRequests += student.nrRequests();
829            totalStudents++;
830        }
831        return totalRequests / totalStudents;
832    }
833
834    /** Number of last like ({@link Student#isDummy()} equals true) students. 
835     * @param precise true if to be computed
836     * @return number of last like (projected) students
837     **/
838    public int getNrLastLikeStudents(boolean precise) {
839        if (!precise)
840            return iNrDummyStudents;
841        int nrLastLikeStudents = 0;
842        for (Student student : getStudents()) {
843            if (student.isDummy())
844                nrLastLikeStudents++;
845        }
846        return nrLastLikeStudents;
847    }
848
849    /** Number of real ({@link Student#isDummy()} equals false) students. 
850     * @param precise true if to be computed
851     * @return number of real students
852     **/
853    public int getNrRealStudents(boolean precise) {
854        if (!precise)
855            return getStudents().size() - iNrDummyStudents;
856        int nrRealStudents = 0;
857        for (Student student : getStudents()) {
858            if (!student.isDummy())
859                nrRealStudents++;
860        }
861        return nrRealStudents;
862    }
863    
864    /**
865     * Count students with given accommodation
866     */
867    public int getStudentsWithAccommodation(String acc) {
868        int nrAccStudents = 0;
869        for (Student student : getStudents()) {
870            if (student.hasAccommodation(acc))
871                nrAccStudents++;
872        }
873        return nrAccStudents;
874    }
875
876    /**
877     * Number of last like ({@link Student#isDummy()} equals true) students with
878     * a complete schedule ({@link Student#isComplete(Assignment)} equals true).
879     * @param assignment current assignment
880     * @param precise true if to be computed
881     * @return number of last like (projected) students with a complete schedule
882     */
883    public int getNrCompleteLastLikeStudents(Assignment<Request, Enrollment> assignment, boolean precise) {
884        if (!precise)
885            return getContext(assignment).getNrCompleteLastLikeStudents();
886        int nrLastLikeStudents = 0;
887        for (Student student : getStudents()) {
888            if (student.isComplete(assignment) && student.isDummy())
889                nrLastLikeStudents++;
890        }
891        return nrLastLikeStudents;
892    }
893
894    /**
895     * Number of real ({@link Student#isDummy()} equals false) students with a
896     * complete schedule ({@link Student#isComplete(Assignment)} equals true).
897     * @param assignment current assignment
898     * @param precise true if to be computed
899     * @return number of real students with a complete schedule
900     */
901    public int getNrCompleteRealStudents(Assignment<Request, Enrollment> assignment, boolean precise) {
902        if (!precise)
903            return getContext(assignment).nrComplete() - getContext(assignment).getNrCompleteLastLikeStudents();
904        int nrRealStudents = 0;
905        for (Student student : getStudents()) {
906            if (student.isComplete(assignment) && !student.isDummy())
907                nrRealStudents++;
908        }
909        return nrRealStudents;
910    }
911
912    /**
913     * Number of requests from projected ({@link Student#isDummy()} equals true)
914     * students.
915     * @param precise true if to be computed
916     * @return number of requests from projected students 
917     */
918    public int getNrLastLikeRequests(boolean precise) {
919        if (!precise)
920            return iNrDummyRequests;
921        int nrLastLikeRequests = 0;
922        for (Request request : variables()) {
923            if (request.getStudent().isDummy())
924                nrLastLikeRequests++;
925        }
926        return nrLastLikeRequests;
927    }
928
929    /**
930     * Number of requests from real ({@link Student#isDummy()} equals false)
931     * students.
932     * @param precise true if to be computed
933     * @return number of requests from real students 
934     */
935    public int getNrRealRequests(boolean precise) {
936        if (!precise)
937            return variables().size() - iNrDummyRequests;
938        int nrRealRequests = 0;
939        for (Request request : variables()) {
940            if (!request.getStudent().isDummy())
941                nrRealRequests++;
942        }
943        return nrRealRequests;
944    }
945
946    /**
947     * Number of requests from projected ({@link Student#isDummy()} equals true)
948     * students that are assigned.
949     * @param assignment current assignment
950     * @param precise true if to be computed
951     * @return number of requests from projected students that are assigned
952     */
953    public int getNrAssignedLastLikeRequests(Assignment<Request, Enrollment> assignment, boolean precise) {
954        if (!precise)
955            return getContext(assignment).getNrAssignedLastLikeRequests();
956        int nrLastLikeRequests = 0;
957        for (Request request : assignment.assignedVariables()) {
958            if (request.getStudent().isDummy())
959                nrLastLikeRequests++;
960        }
961        return nrLastLikeRequests;
962    }
963
964    /**
965     * Number of requests from real ({@link Student#isDummy()} equals false)
966     * students that are assigned.
967     * @param assignment current assignment
968     * @param precise true if to be computed
969     * @return number of requests from real students that are assigned
970     */
971    public int getNrAssignedRealRequests(Assignment<Request, Enrollment> assignment, boolean precise) {
972        if (!precise)
973            return assignment.nrAssignedVariables() - getContext(assignment).getNrAssignedLastLikeRequests();
974        int nrRealRequests = 0;
975        for (Request request : assignment.assignedVariables()) {
976            if (!request.getStudent().isDummy())
977                nrRealRequests++;
978        }
979        return nrRealRequests;
980    }
981
982    /**
983     * Model extended info. Some more information (that is more expensive to
984     * compute) is added to an ordinary {@link Model#getInfo(Assignment)}.
985     */
986    @Override
987    public Map<String, String> getExtendedInfo(Assignment<Request, Enrollment> assignment) {
988        Map<String, String> info = getInfo(assignment);
989        /*
990        int nrLastLikeStudents = getNrLastLikeStudents(true);
991        if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) {
992            int nrRealStudents = getStudents().size() - nrLastLikeStudents;
993            int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(true);
994            int nrRealCompleteStudents = getCompleteStudents().size() - nrLastLikeCompleteStudents;
995            info.put("Projected students with complete schedule", sDecimalFormat.format(100.0
996                    * nrLastLikeCompleteStudents / nrLastLikeStudents)
997                    + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")");
998            info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents
999                    / nrRealStudents)
1000                    + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")");
1001            int nrLastLikeRequests = getNrLastLikeRequests(true);
1002            int nrRealRequests = variables().size() - nrLastLikeRequests;
1003            int nrLastLikeAssignedRequests = getNrAssignedLastLikeRequests(true);
1004            int nrRealAssignedRequests = assignedVariables().size() - nrLastLikeAssignedRequests;
1005            info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests
1006                    / nrLastLikeRequests)
1007                    + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")");
1008            info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests)
1009                    + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")");
1010        }
1011        */
1012        // info.put("Average unassigned priority", sDecimalFormat.format(avgUnassignPriority()));
1013        // info.put("Average number of requests", sDecimalFormat.format(avgNrRequests()));
1014        
1015        /*
1016        double total = 0;
1017        for (Request r: variables())
1018            if (r.getAssignment() != null)
1019                total += r.getWeight() * iStudentWeights.getWeight(r.getAssignment());
1020        */
1021        /*
1022        double dc = 0;
1023        if (getDistanceConflict() != null && getDistanceConflict().getTotalNrConflicts(assignment) != 0) {
1024            Set<DistanceConflict.Conflict> conf = getDistanceConflict().getAllConflicts(assignment);
1025            int sdc = 0;
1026            for (DistanceConflict.Conflict c: conf) {
1027                dc += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c);
1028                if (c.getStudent().isNeedShortDistances()) sdc ++;
1029            }
1030            if (!conf.isEmpty())
1031                info.put("Student distance conflicts", conf.size() + (sdc > 0 ? " (" + getDistanceConflict().getDistanceMetric().getShortDistanceAccommodationReference() + ": " + sdc + ", weighted: " : " (weighted: ") + sDecimalFormat.format(dc) + ")");
1032        }
1033        */
1034        if (getStudentQuality() == null && getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) {
1035            Set<TimeOverlapsCounter.Conflict> conf = getTimeOverlaps().getContext(assignment).computeAllConflicts(assignment);
1036            int share = 0, crShare = 0;
1037            for (TimeOverlapsCounter.Conflict c: conf) {
1038                share += c.getShare();
1039                if (c.getR1() instanceof CourseRequest && c.getR2() instanceof CourseRequest)
1040                    crShare += c.getShare();
1041            }
1042            if (share > 0)
1043                info.put("Time overlapping conflicts", sDoubleFormat.format(5.0 * share / iStudents.size()) + " mins per student\n(" + sDoubleFormat.format(5.0 * crShare / iStudents.size()) + " between courses; " + sDoubleFormat.format(getTimeOverlaps().getTotalNrConflicts(assignment) / 12.0) + " hours total)");
1044        }
1045        if (getStudentQuality() != null) {
1046            int confBtB = getStudentQuality().getTotalPenalty(StudentQuality.Type.BackToBack, assignment);
1047            if (confBtB != 0) {
1048                int prefBtb = 0, discBtb = 0;
1049                int prefStd = 0, discStd = 0;
1050                int prefPairs = 0, discPairs = 0;
1051                for (Student s: getStudents()) {
1052                    if (s.isDummy() || s.getBackToBackPreference() == BackToBackPreference.NO_PREFERENCE) continue;
1053                    int[] classesPerDay = new int[] {0, 0, 0, 0, 0, 0, 0};
1054                    for (Request r: s.getRequests()) {
1055                        Enrollment e = r.getAssignment(assignment);
1056                        if (e == null || !e.isCourseRequest()) continue;
1057                        for (Section x: e.getSections()) {
1058                            if (x.getTime() != null)
1059                                for (int i = 0; i < Constants.DAY_CODES.length; i++)
1060                                    if ((x.getTime().getDayCode() & Constants.DAY_CODES[i]) != 0)
1061                                        classesPerDay[i] ++;
1062                        }
1063                    }
1064                    int max = 0;
1065                    for (int c: classesPerDay)
1066                        if (c > 1) max += c - 1;
1067                    int btb = getStudentQuality().getContext(assignment).allPenalty(StudentQuality.Type.BackToBack, assignment, s);
1068                    if (s.getBackToBackPreference() == BackToBackPreference.BTB_PREFERRED) {
1069                        prefStd ++;
1070                        prefBtb += btb;
1071                        prefPairs += Math.max(btb, max);
1072                    } else if (s.getBackToBackPreference() == BackToBackPreference.BTB_DISCOURAGED) {
1073                        discStd ++;
1074                        discBtb -= btb;
1075                        discPairs += Math.max(btb, max);
1076                    }
1077                }
1078                if (prefStd > 0)
1079                    info.put("Schedule Quality: Back-to-back preferred", sDoubleFormat.format((100.0 * prefBtb) / prefPairs) + "% back-to-backs on average (" + prefBtb + "/" + prefPairs + " BTBs for " + prefStd + " students)");
1080                if (discStd > 0)
1081                    info.put("Schedule Quality: Back-to-back discouraged", sDoubleFormat.format(100.0 - (100.0 * discBtb) / discPairs) + "% non back-to-backs on average (" + discBtb + "/" + discPairs + " BTBs for " + discStd + " students)");
1082            }
1083            int confMod = getStudentQuality().getTotalPenalty(StudentQuality.Type.Modality, assignment);
1084            if (confMod > 0) {
1085                int prefOnl = 0, discOnl = 0;
1086                int prefStd = 0, discStd = 0;
1087                int prefCls = 0, discCls = 0;
1088                for (Student s: getStudents()) {
1089                    if (s.isDummy()) continue;
1090                    if (s.isDummy() || s.getModalityPreference() == ModalityPreference.NO_PREFERENCE || s.getModalityPreference() == ModalityPreference.ONLINE_REQUIRED) continue;
1091                    int classes = 0;
1092                    for (Request r: s.getRequests()) {
1093                        Enrollment e = r.getAssignment(assignment);
1094                        if (e == null || !e.isCourseRequest()) continue;
1095                        classes += e.getSections().size();
1096                    }
1097                    if (s.getModalityPreference() == ModalityPreference.ONLINE_PREFERRED) {
1098                        prefStd ++;
1099                        prefOnl += getStudentQuality().getContext(assignment).allPenalty(StudentQuality.Type.Modality, assignment, s);
1100                        prefCls += classes;
1101                    } else if (s.getModalityPreference() == ModalityPreference.ONILNE_DISCOURAGED) {
1102                        discStd ++;
1103                        discOnl += getStudentQuality().getContext(assignment).allPenalty(StudentQuality.Type.Modality, assignment, s);
1104                        discCls += classes;
1105                    }
1106                }
1107                if (prefStd > 0)
1108                    info.put("Schedule Quality: Online preferred", sDoubleFormat.format(100.0 - (100.0 * prefOnl) / prefCls) + "% online classes on average (" + prefOnl + "/" + prefCls + " classes for " + prefStd + " students)");
1109                if (discStd > 0)
1110                    info.put("Schedule Quality: Online discouraged", sDoubleFormat.format(100.0 - (100.0 * discOnl) / discCls) + "% face-to-face classes on average (" + discOnl + "/" + discCls + " classes for " + discStd + " students)");
1111            }
1112        }
1113        /*
1114        info.put("Overall solution value", sDecimalFormat.format(total - dc - toc) + (dc == 0.0 && toc == 0.0 ? "" :
1115            " (" + (dc != 0.0 ? "distance: " + sDecimalFormat.format(dc): "") + (dc != 0.0 && toc != 0.0 ? ", " : "") + 
1116            (toc != 0.0 ? "overlap: " + sDecimalFormat.format(toc) : "") + ")")
1117            );
1118        */
1119        
1120        double disbWeight = 0;
1121        int disbSections = 0;
1122        int disb10Sections = 0;
1123        int disb10Limit = getProperties().getPropertyInt("Info.ListDisbalancedSections", 0);
1124        Set<String> disb10SectionList = (disb10Limit == 0 ? null : new TreeSet<String>()); 
1125        for (Offering offering: getOfferings()) {
1126            if (offering.isDummy()) continue;
1127            for (Config config: offering.getConfigs()) {
1128                double enrl = config.getEnrollmentTotalWeight(assignment, null);
1129                for (Subpart subpart: config.getSubparts()) {
1130                    if (subpart.getSections().size() <= 1) continue;
1131                    if (subpart.getLimit() > 0) {
1132                        // sections have limits -> desired size is section limit x (total enrollment / total limit)
1133                        double ratio = enrl / subpart.getLimit();
1134                        for (Section section: subpart.getSections()) {
1135                            double desired = ratio * section.getLimit();
1136                            disbWeight += Math.abs(section.getEnrollmentTotalWeight(assignment, null) - desired);
1137                            disbSections ++;
1138                            if (Math.abs(desired - section.getEnrollmentTotalWeight(assignment, null)) >= Math.max(1.0, 0.1 * section.getLimit())) {
1139                                disb10Sections++;
1140                                if (disb10SectionList != null)
1141                                        disb10SectionList.add(section.getSubpart().getConfig().getOffering().getName() + " " + section.getSubpart().getName() + " " + section.getName()); 
1142                            }
1143                        }
1144                    } else {
1145                        // unlimited sections -> desired size is total enrollment / number of sections
1146                        for (Section section: subpart.getSections()) {
1147                            double desired = enrl / subpart.getSections().size();
1148                            disbWeight += Math.abs(section.getEnrollmentTotalWeight(assignment, null) - desired);
1149                            disbSections ++;
1150                            if (Math.abs(desired - section.getEnrollmentTotalWeight(assignment, null)) >= Math.max(1.0, 0.1 * desired)) {
1151                                disb10Sections++;
1152                                if (disb10SectionList != null)
1153                                        disb10SectionList.add(section.getSubpart().getConfig().getOffering().getName() + " " + section.getSubpart().getName() + " " + section.getName());
1154                            }
1155                        }
1156                    }
1157                }
1158            }
1159        }
1160        if (disbSections != 0) {
1161            double assignedCRWeight = getContext(assignment).getAssignedCourseRequestWeight();
1162            info.put("Average disbalance", sDecimalFormat.format(assignedCRWeight == 0 ? 0.0 : 100.0 * disbWeight / assignedCRWeight) + "% (" + sDecimalFormat.format(disbWeight / disbSections) + ")");
1163            String list = "";
1164            if (disb10SectionList != null) {
1165                int i = 0;
1166                for (String section: disb10SectionList) {
1167                    if (i == disb10Limit) {
1168                        list += "\n...";
1169                        break;
1170                    }
1171                    list += "\n" + section;
1172                    i++;
1173                }
1174            }
1175            info.put("Sections disbalanced by 10% or more", sDecimalFormat.format(disbSections == 0 ? 0.0 : 100.0 * disb10Sections / disbSections) + "% (" + disb10Sections + ")" + (list.isEmpty() ? "" : "\n" + list));
1176        }
1177        
1178        int assCR = 0, priCR = 0;
1179        for (Request r: variables()) {
1180            if (r instanceof CourseRequest && !r.getStudent().isDummy()) {
1181                CourseRequest cr = (CourseRequest)r;
1182                Enrollment e = assignment.getValue(cr);
1183                if (e != null) {
1184                    assCR ++;
1185                    if (!cr.isAlternative() && cr.getCourses().get(0).equals(e.getCourse())) priCR ++;
1186                }
1187            }
1188        }
1189        if (assCR > 0)
1190            info.put("Assigned priority course requests", sDoubleFormat.format(100.0 * priCR / assCR) + "% (" + priCR + "/" + assCR + ")");
1191        int[] missing = new int[] {0, 0, 0, 0, 0};
1192        int incomplete = 0;
1193        for (Student student: getStudents()) {
1194            if (student.isDummy()) continue;
1195            int nrRequests = 0;
1196            int nrAssignedRequests = 0;
1197            for (Request r : student.getRequests()) {
1198                if (!(r instanceof CourseRequest)) continue; // ignore free times
1199                if (!r.isAlternative()) nrRequests++;
1200                if (r.isAssigned(assignment)) nrAssignedRequests++;
1201            }
1202            if (nrAssignedRequests < nrRequests) {
1203                missing[Math.min(nrRequests - nrAssignedRequests, missing.length) - 1] ++;
1204                incomplete ++;
1205            }
1206        }
1207
1208        for (int i = 0; i < missing.length; i++)
1209            if (missing[i] > 0)
1210                info.put("Students missing " + (i == 0 ? "1 course" : i + 1 == missing.length ? (i + 1) + " or more courses" : (i + 1) + " courses"), sDecimalFormat.format(100.0 * missing[i] / incomplete) + "% (" + missing[i] + ")");
1211
1212        info.put("Overall solution value", sDoubleFormat.format(getTotalValue(assignment)));// + " [precise: " + sDoubleFormat.format(getTotalValue(assignment, true)) + "]");
1213        
1214        int nrStudentsBelowMinCredit = 0, nrStudents = 0;
1215        for (Student student: getStudents()) {
1216            if (student.isDummy()) continue;
1217            if (student.hasMinCredit()) {
1218                nrStudents++;
1219                float credit = student.getAssignedCredit(assignment); 
1220                if (credit < student.getMinCredit() && !student.isComplete(assignment))
1221                    nrStudentsBelowMinCredit ++;
1222            }
1223        }
1224        if (nrStudentsBelowMinCredit > 0)
1225            info.put("Students below min credit", sDoubleFormat.format(100.0 * nrStudentsBelowMinCredit / nrStudents) + "% (" + nrStudentsBelowMinCredit + "/" + nrStudents + ")");
1226        
1227        int[] notAssignedPriority = new int[] {0, 0, 0, 0, 0, 0, 0};
1228        int[] assignedChoice = new int[] {0, 0, 0, 0, 0};
1229        int notAssignedTotal = 0, assignedChoiceTotal = 0;
1230        int avgPriority = 0, avgChoice = 0;
1231        for (Student student: getStudents()) {
1232            if (student.isDummy()) continue;
1233            for (Request r : student.getRequests()) {
1234                if (!(r instanceof CourseRequest)) continue; // ignore free times
1235                Enrollment e = r.getAssignment(assignment);
1236                if (e == null) {
1237                    if (!r.isAlternative()) {
1238                        notAssignedPriority[Math.min(r.getPriority(), notAssignedPriority.length - 1)] ++;
1239                        notAssignedTotal ++;
1240                        avgPriority += r.getPriority();
1241                    }
1242                } else {
1243                    assignedChoice[Math.min(e.getTruePriority(), assignedChoice.length - 1)] ++;
1244                    assignedChoiceTotal ++;
1245                    avgChoice += e.getTruePriority();
1246                }
1247            }
1248        }
1249        for (int i = 0; i < notAssignedPriority.length; i++)
1250            if (notAssignedPriority[i] > 0)
1251                info.put("Priority: Not-assigned priority " + (i + 1 == notAssignedPriority.length ? (i + 1) + "+" : (i + 1)) + " course requests", sDecimalFormat.format(100.0 * notAssignedPriority[i] / notAssignedTotal) + "% (" + notAssignedPriority[i] + ")");
1252        if (notAssignedTotal > 0)
1253            info.put("Priority: Average not-assigned priority", sDecimalFormat.format(1.0 + ((double)avgPriority) / notAssignedTotal));
1254        for (int i = 0; i < assignedChoice.length; i++)
1255            if (assignedChoice[i] > 0)
1256                info.put("Choice: assigned " + (i == 0 ? "1st": i == 1 ? "2nd" : i == 2 ? "3rd" : i + 1 == assignedChoice.length ? (i + 1) + "th+" : (i + 1) + "th") + " course choice", sDecimalFormat.format(100.0 * assignedChoice[i] / assignedChoiceTotal) + "% (" + assignedChoice[i] + ")");
1257        if (assignedChoiceTotal > 0)
1258            info.put("Choice: Average assigned choice", sDecimalFormat.format(1.0 + ((double)avgChoice) / assignedChoiceTotal));
1259        
1260        int nbrSections = 0, nbrFullSections = 0, nbrSections98 = 0, nbrSections95 = 0, nbrSections90 = 0, nbrSectionsDis = 0;
1261        int enrlSections = 0, enrlFullSections = 0, enrlSections98 = 0, enrlSections95 = 0, enrlSections90 = 0, enrlSectionsDis = 0;
1262        int nbrOfferings = 0, nbrFullOfferings = 0, nbrOfferings98 = 0, nbrOfferings95 = 0, nbrOfferings90 = 0;
1263        int enrlOfferings = 0, enrlOfferingsFull = 0, enrlOfferings98 = 0, enrlOfferings95 = 0, enrlOfferings90 = 0;
1264        for (Offering offering: getOfferings()) {
1265            if (offering.isDummy()) continue;
1266            int offeringLimit = 0, offeringEnrollment = 0;
1267            for (Config config: offering.getConfigs()) {
1268                int configLimit = config.getLimit();
1269                for (Subpart subpart: config.getSubparts()) {
1270                    int subpartLimit = 0;
1271                    for (Section section: subpart.getSections()) {
1272                        if (section.isCancelled()) continue;
1273                        int enrl = section.getEnrollments(assignment).size();
1274                        if (section.getLimit() < 0 || subpartLimit < 0)
1275                            subpartLimit = -1;
1276                        else
1277                            subpartLimit += (section.isEnabled() ? section.getLimit() : enrl);
1278                        nbrSections ++;
1279                        enrlSections += enrl;
1280                        if (section.getLimit() >= 0 && section.getLimit() <= enrl) {
1281                            nbrFullSections ++;
1282                            enrlFullSections += enrl;
1283                        }
1284                        if (!section.isEnabled() && (enrl > 0 || section.getLimit() >= 0)) {
1285                            nbrSectionsDis ++;
1286                            enrlSectionsDis += enrl;
1287                        }
1288                        if (section.getLimit() >= 0 && (section.getLimit() - enrl) <= Math.round(0.02 * section.getLimit())) {
1289                            nbrSections98 ++;
1290                            enrlSections98 += enrl;
1291                        }
1292                        if (section.getLimit() >= 0 && (section.getLimit() - enrl) <= Math.round(0.05 * section.getLimit())) {
1293                            nbrSections95 ++;
1294                            enrlSections95 += enrl;
1295                        }
1296                        if (section.getLimit() >= 0 && (section.getLimit() - enrl) <= Math.round(0.10 * section.getLimit())) {
1297                            nbrSections90 ++;
1298                            enrlSections90 += enrl;
1299                        }
1300                    }
1301                    if (configLimit < 0 || subpartLimit < 0)
1302                        configLimit = -1;
1303                    else
1304                        configLimit = Math.min(configLimit, subpartLimit);
1305                }
1306                if (offeringLimit < 0 || configLimit < 0)
1307                    offeringLimit = -1;
1308                else
1309                    offeringLimit += configLimit;
1310                offeringEnrollment += config.getEnrollments(assignment).size();
1311            }
1312            nbrOfferings ++;
1313            enrlOfferings += offeringEnrollment;
1314            
1315            if (offeringLimit >=0 && offeringEnrollment >= offeringLimit) {
1316                nbrFullOfferings ++;
1317                enrlOfferingsFull += offeringEnrollment;
1318            }
1319            if (offeringLimit >= 0 && (offeringLimit - offeringEnrollment) <= Math.round(0.02 * offeringLimit)) {
1320                nbrOfferings98++;
1321                enrlOfferings98 += offeringEnrollment;
1322            }
1323            if (offeringLimit >= 0 && (offeringLimit - offeringEnrollment) <= Math.round(0.05 * offeringLimit)) {
1324                nbrOfferings95++;
1325                enrlOfferings95 += offeringEnrollment;
1326            }
1327            if (offeringLimit >= 0 && (offeringLimit - offeringEnrollment) <= Math.round(0.10 * offeringLimit)) {
1328                nbrOfferings90++;
1329                enrlOfferings90 += offeringEnrollment;
1330            }
1331        }
1332        if (enrlOfferings90 > 0 && enrlOfferings > 0) 
1333            info.put("Full Offerings", (nbrFullOfferings > 0 ? nbrFullOfferings + " with no space (" + sDecimalFormat.format(100.0 * nbrFullOfferings / nbrOfferings) + "% of all offerings, " +
1334                    sDecimalFormat.format(100.0 * enrlOfferingsFull / enrlOfferings) + "% assignments)\n" : "")+
1335                    (nbrOfferings98 > nbrFullOfferings ? nbrOfferings98 + " with &leq; 2% available (" + sDecimalFormat.format(100.0 * nbrOfferings98 / nbrOfferings) + "% of all offerings, " +
1336                    sDecimalFormat.format(100.0 * enrlOfferings98 / enrlOfferings) + "% assignments)\n" : "")+
1337                    (nbrOfferings95 > nbrOfferings98 ? nbrOfferings95 + " with &leq; 5% available (" + sDecimalFormat.format(100.0 * nbrOfferings95 / nbrOfferings) + "% of all offerings, " +
1338                    sDecimalFormat.format(100.0 * enrlOfferings95 / enrlOfferings) + "% assignments)\n" : "")+
1339                    (nbrOfferings90 > nbrOfferings95 ? nbrOfferings90 + " with &leq; 10% available (" + sDecimalFormat.format(100.0 * nbrOfferings90 / nbrOfferings) + "% of all offerings, " +
1340                    sDecimalFormat.format(100.0 * enrlOfferings90 / enrlOfferings) + "% assignments)" : ""));
1341        if ((enrlSections90 > 0 || nbrSectionsDis > 0) && enrlSections > 0)
1342            info.put("Full Sections", (nbrFullSections > 0 ? nbrFullSections + " with no space (" + sDecimalFormat.format(100.0 * nbrFullSections / nbrSections) + "% of all sections, "+
1343                    sDecimalFormat.format(100.0 * enrlFullSections / enrlSections) + "% assignments)\n" : "") +
1344                    (nbrSectionsDis > 0 ? nbrSectionsDis + " disabled (" + sDecimalFormat.format(100.0 * nbrSectionsDis / nbrSections) + "% of all sections, "+
1345                    sDecimalFormat.format(100.0 * enrlSectionsDis / enrlSections) + "% assignments)\n" : "") +
1346                    (enrlSections98 > nbrFullSections ? nbrSections98 + " with &leq; 2% available (" + sDecimalFormat.format(100.0 * nbrSections98 / nbrSections) + "% of all sections, " +
1347                    sDecimalFormat.format(100.0 * enrlSections98 / enrlSections) + "% assignments)\n" : "") +
1348                    (nbrSections95 > enrlSections98 ? nbrSections95 + " with &leq; 5% available (" + sDecimalFormat.format(100.0 * nbrSections95 / nbrSections) + "% of all sections, " +
1349                    sDecimalFormat.format(100.0 * enrlSections95 / enrlSections) + "% assignments)\n" : "") +
1350                    (nbrSections90 > nbrSections95 ? nbrSections90 + " with &leq; 10% available (" + sDecimalFormat.format(100.0 * nbrSections90 / nbrSections) + "% of all sections, " +
1351                    sDecimalFormat.format(100.0 * enrlSections90 / enrlSections) + "% assignments)" : ""));
1352        if (getStudentQuality() != null) {
1353            int shareCR = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.CourseTimeOverlap, assignment);
1354            int shareFT = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.FreeTimeOverlap, assignment);
1355            int shareUN = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.Unavailability, assignment);
1356            int shareUND = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.UnavailabilityDistance, assignment);
1357            if (shareCR > 0) {
1358                Set<Student> students = new HashSet<Student>();
1359                for (StudentQuality.Conflict c: getStudentQuality().getContext(assignment).computeAllConflicts(StudentQuality.Type.CourseTimeOverlap, assignment)) {
1360                    students.add(c.getStudent());
1361                }
1362                info.put("Time overlaps: courses", students.size() + " students (avg " + sDoubleFormat.format(5.0 * shareCR / students.size()) + " mins)");
1363            }
1364            if (shareFT > 0) {
1365                Set<Student> students = new HashSet<Student>();
1366                for (StudentQuality.Conflict c: getStudentQuality().getContext(assignment).computeAllConflicts(StudentQuality.Type.FreeTimeOverlap, assignment)) {
1367                    students.add(c.getStudent());
1368                }
1369                info.put("Time overlaps: free times", students.size() + " students (avg " + sDoubleFormat.format(5.0 * shareFT / students.size()) + " mins)");
1370            }
1371            if (shareUN > 0) {
1372                Set<Student> students = new HashSet<Student>();
1373                for (StudentQuality.Conflict c: getStudentQuality().getContext(assignment).computeAllConflicts(StudentQuality.Type.Unavailability, assignment)) {
1374                    students.add(c.getStudent());
1375                }
1376                info.put("Unavailabilities: Time conflicts", students.size() + " students (avg " + sDoubleFormat.format(5.0 * shareUN / students.size()) + " mins)");
1377            }
1378            if (shareUND > 0) {
1379                Set<Student> students = new HashSet<Student>();
1380                for (StudentQuality.Conflict c: getStudentQuality().getContext(assignment).computeAllConflicts(StudentQuality.Type.UnavailabilityDistance, assignment)) {
1381                    students.add(c.getStudent());
1382                }
1383                info.put("Unavailabilities: Distance conflicts", students.size() + " students (avg " + sDoubleFormat.format(shareUND / students.size()) + " travels)");
1384            }
1385        } else if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) {
1386            Set<TimeOverlapsCounter.Conflict> conf = getTimeOverlaps().getContext(assignment).computeAllConflicts(assignment);
1387            int shareCR = 0, shareFT = 0, shareUN = 0;
1388            Set<Student> studentsCR = new HashSet<Student>();
1389            Set<Student> studentsFT = new HashSet<Student>();
1390            Set<Student> studentsUN = new HashSet<Student>();
1391            for (TimeOverlapsCounter.Conflict c: conf) {
1392                if (c.getR1() instanceof CourseRequest && c.getR2() instanceof CourseRequest) {
1393                    shareCR += c.getShare(); studentsCR.add(c.getStudent());
1394                } else if (c.getS2() instanceof Unavailability) {
1395                    shareUN += c.getShare(); studentsUN.add(c.getStudent());
1396                } else {
1397                    shareFT += c.getShare(); studentsFT.add(c.getStudent());
1398                }
1399            }
1400            if (shareCR > 0)
1401                info.put("Time overlaps: courses", studentsCR.size() + " students (avg " + sDoubleFormat.format(5.0 * shareCR / studentsCR.size()) + " mins)");
1402            if (shareFT > 0)
1403                info.put("Time overlaps: free times", studentsFT.size() + " students (avg " + sDoubleFormat.format(5.0 * shareFT / studentsFT.size()) + " mins)");
1404            if (shareUN > 0)
1405                info.put("Time overlaps: teaching assignments", studentsUN.size() + " students (avg " + sDoubleFormat.format(5.0 * shareUN / studentsUN.size()) + " mins)");
1406        }
1407
1408        
1409        return info;
1410    }
1411    
1412    @Override
1413    public void restoreBest(Assignment<Request, Enrollment> assignment) {
1414        restoreBest(assignment, new Comparator<Request>() {
1415            @Override
1416            public int compare(Request r1, Request r2) {
1417                Enrollment e1 = r1.getBestAssignment();
1418                Enrollment e2 = r2.getBestAssignment();
1419                // Reservations first
1420                if (e1.getReservation() != null && e2.getReservation() == null) return -1;
1421                if (e1.getReservation() == null && e2.getReservation() != null) return 1;
1422                // Then assignment iteration (i.e., order in which assignments were made)
1423                if (r1.getBestAssignmentIteration() != r2.getBestAssignmentIteration())
1424                    return (r1.getBestAssignmentIteration() < r2.getBestAssignmentIteration() ? -1 : 1);
1425                // Then student and priority
1426                return r1.compareTo(r2);
1427            }
1428        });
1429        recomputeTotalValue(assignment);
1430    }
1431    
1432    public void recomputeTotalValue(Assignment<Request, Enrollment> assignment) {
1433        getContext(assignment).iTotalValue = getTotalValue(assignment, true);
1434    }
1435    
1436    @Override
1437    public void saveBest(Assignment<Request, Enrollment> assignment) {
1438        recomputeTotalValue(assignment);
1439        iBestAssignedCourseRequestWeight = getContext(assignment).getAssignedCourseRequestWeight();
1440        super.saveBest(assignment);
1441    }
1442    
1443    public double getBestAssignedCourseRequestWeight() {
1444        return iBestAssignedCourseRequestWeight;
1445    }
1446        
1447    @Override
1448    public String toString(Assignment<Request, Enrollment> assignment) {
1449        double groupSpread = 0.0; double groupCount = 0;
1450        for (Offering offering: iOfferings) {
1451            for (Course course: offering.getCourses()) {
1452                for (RequestGroup group: course.getRequestGroups()) {
1453                    groupSpread += group.getAverageSpread(assignment) * group.getEnrollmentWeight(assignment, null);
1454                    groupCount += group.getEnrollmentWeight(assignment, null);
1455                }
1456            }
1457        }
1458        String priority = "";
1459        for (StudentPriority sp: StudentPriority.values()) {
1460            if (sp.ordinal() < StudentPriority.Normal.ordinal()) {
1461                if (iTotalPriorityCRWeight[sp.ordinal()] > 0.0)
1462                    priority += sp.code() + "PCR:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCRWeight[sp.ordinal()] / iTotalPriorityCRWeight[sp.ordinal()]) + "%, ";
1463                if (iTotalPriorityCriticalCRWeight[RequestPriority.LC.ordinal()][sp.ordinal()] > 0.0)
1464                    priority += sp.code() + "PCL:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCriticalCRWeight[RequestPriority.LC.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[RequestPriority.LC.ordinal()][sp.ordinal()]) + "%, ";
1465                if (iTotalPriorityCriticalCRWeight[RequestPriority.Critical.ordinal()][sp.ordinal()] > 0.0)
1466                    priority += sp.code() + "PCC:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCriticalCRWeight[RequestPriority.Critical.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[RequestPriority.Critical.ordinal()][sp.ordinal()]) + "%, ";
1467                if (iTotalPriorityCriticalCRWeight[RequestPriority.Important.ordinal()][sp.ordinal()] > 0.0)
1468                    priority += sp.code() + "PCI:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCriticalCRWeight[RequestPriority.Important.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[RequestPriority.Important.ordinal()][sp.ordinal()]) + "%, ";
1469                if (iTotalPriorityCriticalCRWeight[RequestPriority.Vital.ordinal()][sp.ordinal()] > 0.0)
1470                    priority += sp.code() + "PCV:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCriticalCRWeight[RequestPriority.Vital.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[RequestPriority.Vital.ordinal()][sp.ordinal()]) + "%, ";
1471                if (iTotalPriorityCriticalCRWeight[RequestPriority.VisitingF2F.ordinal()][sp.ordinal()] > 0.0)
1472                    priority += sp.code() + "PCVF:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCriticalCRWeight[RequestPriority.VisitingF2F.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[RequestPriority.VisitingF2F.ordinal()][sp.ordinal()]) + "%, ";
1473            }
1474        }
1475        return   (getNrRealStudents(false) > 0 ? "RRq:" + getNrAssignedRealRequests(assignment, false) + "/" + getNrRealRequests(false) + ", " : "")
1476                + (getNrLastLikeStudents(false) > 0 ? "DRq:" + getNrAssignedLastLikeRequests(assignment, false) + "/" + getNrLastLikeRequests(false) + ", " : "")
1477                + (getNrRealStudents(false) > 0 ? "RS:" + getNrCompleteRealStudents(assignment, false) + "/" + getNrRealStudents(false) + ", " : "")
1478                + (getNrLastLikeStudents(false) > 0 ? "DS:" + getNrCompleteLastLikeStudents(assignment, false) + "/" + getNrLastLikeStudents(false) + ", " : "")
1479                + (iTotalCRWeight > 0.0 ? "CR:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCourseRequestWeight() / iTotalCRWeight) + "%, " : "")
1480                + (iTotalSelCRWeight > 0.0 ? "S:" + sDoubleFormat.format(100.0 * (0.3 * getContext(assignment).iAssignedSelectedConfigWeight + 0.7 * getContext(assignment).iAssignedSelectedSectionWeight) / iTotalSelCRWeight) + "%, ": "")
1481                + (iTotalCriticalCRWeight[RequestPriority.LC.ordinal()] > 0.0 ? "LC:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCriticalCourseRequestWeight(RequestPriority.LC) / iTotalCriticalCRWeight[RequestPriority.LC.ordinal()]) + "%, " : "")
1482                + (iTotalCriticalCRWeight[RequestPriority.Critical.ordinal()] > 0.0 ? "CC:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCriticalCourseRequestWeight(RequestPriority.Critical) / iTotalCriticalCRWeight[RequestPriority.Critical.ordinal()]) + "%, " : "")
1483                + (iTotalCriticalCRWeight[RequestPriority.Important.ordinal()] > 0.0 ? "IC:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCriticalCourseRequestWeight(RequestPriority.Important) / iTotalCriticalCRWeight[RequestPriority.Important.ordinal()]) + "%, " : "")
1484                + (iTotalCriticalCRWeight[RequestPriority.Vital.ordinal()] > 0.0 ? "VC:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCriticalCourseRequestWeight(RequestPriority.Vital) / iTotalCriticalCRWeight[RequestPriority.Vital.ordinal()]) + "%, " : "")
1485                + (iTotalCriticalCRWeight[RequestPriority.VisitingF2F.ordinal()] > 0.0 ? "VFC:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCriticalCourseRequestWeight(RequestPriority.VisitingF2F) / iTotalCriticalCRWeight[RequestPriority.VisitingF2F.ordinal()]) + "%, " : "")
1486                + priority
1487                + "V:" + sDecimalFormat.format(-getTotalValue(assignment))
1488                + (getDistanceConflict() == null ? "" : ", DC:" + getDistanceConflict().getTotalNrConflicts(assignment))
1489                + (getTimeOverlaps() == null ? "" : ", TOC:" + getTimeOverlaps().getTotalNrConflicts(assignment))
1490                + (iMPP ? ", IS:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedSameSectionWeight / iTotalMPPCRWeight) + "%" : "")
1491                + (iMPP ? ", IT:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedSameTimeWeight / iTotalMPPCRWeight) + "%" : "")
1492                + ", %:" + sDecimalFormat.format(-100.0 * getTotalValue(assignment) / (getStudents().size() - iNrDummyStudents + 
1493                        (iProjectedStudentWeight < 0.0 ? iNrDummyStudents * (iTotalDummyWeight / iNrDummyRequests) :iProjectedStudentWeight * iTotalDummyWeight)))
1494                + (groupCount > 0 ? ", SG:" + sDecimalFormat.format(100.0 * groupSpread / groupCount) + "%" : "")
1495                + (getStudentQuality() == null ? "" : ", SQ:{" + getStudentQuality().toString(assignment) + "}");
1496    }
1497    
1498    /**
1499     * Quadratic average of two weights.
1500     * @param w1 first weight
1501     * @param w2 second weight
1502     * @return average of the two weights
1503     */
1504    public double avg(double w1, double w2) {
1505        return Math.sqrt(w1 * w2);
1506    }
1507
1508    /**
1509     * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit.
1510     * @return maximal domain size, -1 if unlimited
1511     */
1512    public int getMaxDomainSize() { return iMaxDomainSize; }
1513
1514    /**
1515     * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit.
1516     * @param maxDomainSize maximal domain size, -1 if unlimited
1517     */
1518    public void setMaxDomainSize(int maxDomainSize) { iMaxDomainSize = maxDomainSize; }
1519    
1520    public int getDayOfWeekOffset() { return iDayOfWeekOffset; }
1521    public void setDayOfWeekOffset(int dayOfWeekOffset) {
1522        iDayOfWeekOffset = dayOfWeekOffset;
1523        if (iProperties != null)
1524            iProperties.setProperty("DatePattern.DayOfWeekOffset", Integer.toString(dayOfWeekOffset));
1525    }
1526
1527    @Override
1528    public StudentSectioningModelContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
1529        return new StudentSectioningModelContext(assignment);
1530    }
1531    
1532    public class StudentSectioningModelContext implements AssignmentConstraintContext<Request, Enrollment>, InfoProvider<Request, Enrollment>{
1533        private Set<Student> iCompleteStudents = new HashSet<Student>();
1534        private double iTotalValue = 0.0;
1535        private int iNrAssignedDummyRequests = 0, iNrCompleteDummyStudents = 0;
1536        private double iAssignedCRWeight = 0.0, iAssignedDummyCRWeight = 0.0;
1537        private double[] iAssignedCriticalCRWeight;
1538        private double[][] iAssignedPriorityCriticalCRWeight;
1539        private double iReservedSpace = 0.0, iTotalReservedSpace = 0.0;
1540        private double iAssignedSameSectionWeight = 0.0, iAssignedSameChoiceWeight = 0.0, iAssignedSameTimeWeight = 0.0;
1541        private double iAssignedSelectedSectionWeight = 0.0, iAssignedSelectedConfigWeight = 0.0;
1542        private double iAssignedNoTimeSectionWeight = 0.0;
1543        private double iAssignedOnlineSectionWeight = 0.0;
1544        private double iAssignedPastSectionWeight = 0.0;
1545        private int[] iNrCompletePriorityStudents = null;
1546        private double[] iAssignedPriorityCRWeight = null;
1547        
1548        public StudentSectioningModelContext(StudentSectioningModelContext parent) {
1549            iCompleteStudents = new HashSet<Student>(parent.iCompleteStudents);
1550            iTotalValue = parent.iTotalValue;
1551            iNrAssignedDummyRequests = parent.iNrAssignedDummyRequests;
1552            iNrCompleteDummyStudents = parent.iNrCompleteDummyStudents;
1553            iAssignedCRWeight = parent.iAssignedCRWeight;
1554            iAssignedDummyCRWeight = parent.iAssignedDummyCRWeight;
1555            iReservedSpace = parent.iReservedSpace;
1556            iTotalReservedSpace = parent.iTotalReservedSpace;
1557            iAssignedSameSectionWeight = parent.iAssignedSameSectionWeight;
1558            iAssignedSameChoiceWeight = parent.iAssignedSameChoiceWeight;
1559            iAssignedSameTimeWeight = parent.iAssignedSameTimeWeight;
1560            iAssignedSelectedSectionWeight = parent.iAssignedSelectedSectionWeight;
1561            iAssignedSelectedConfigWeight = parent.iAssignedSelectedConfigWeight;
1562            iAssignedNoTimeSectionWeight = parent.iAssignedNoTimeSectionWeight;
1563            iAssignedOnlineSectionWeight = parent.iAssignedOnlineSectionWeight;
1564            iAssignedPastSectionWeight = parent.iAssignedPastSectionWeight;
1565            iAssignedCriticalCRWeight = new double[RequestPriority.values().length];
1566            iAssignedPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length];
1567            for (int i = 0; i < RequestPriority.values().length; i++) {
1568                iAssignedCriticalCRWeight[i] = parent.iAssignedCriticalCRWeight[i];
1569                for (int j = 0; j < StudentPriority.values().length; j++) {
1570                    iAssignedPriorityCriticalCRWeight[i][j] = parent.iAssignedPriorityCriticalCRWeight[i][j];
1571                }
1572            }   
1573            iNrCompletePriorityStudents = new int[StudentPriority.values().length];
1574            iAssignedPriorityCRWeight = new double[StudentPriority.values().length];
1575            for (int i = 0; i < StudentPriority.values().length; i++) {
1576                iNrCompletePriorityStudents[i] = parent.iNrCompletePriorityStudents[i];
1577                iAssignedPriorityCRWeight[i] = parent.iAssignedPriorityCRWeight[i];
1578            }
1579        }
1580
1581        public StudentSectioningModelContext(Assignment<Request, Enrollment> assignment) {
1582            iAssignedCriticalCRWeight = new double[RequestPriority.values().length];
1583            iAssignedPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length];
1584            for (int i = 0; i < RequestPriority.values().length; i++) {
1585                iAssignedCriticalCRWeight[i] = 0.0;
1586                for (int j = 0; j < StudentPriority.values().length; j++) {
1587                    iAssignedPriorityCriticalCRWeight[i][j] = 0.0;
1588                }
1589            }
1590            iNrCompletePriorityStudents = new int[StudentPriority.values().length];
1591            iAssignedPriorityCRWeight = new double[StudentPriority.values().length];
1592            for (int i = 0; i < StudentPriority.values().length; i++) {
1593                iNrCompletePriorityStudents[i] = 0;
1594                iAssignedPriorityCRWeight[i] = 0.0;
1595            }
1596            for (Request request: variables()) {
1597                Enrollment enrollment = assignment.getValue(request);
1598                if (enrollment != null)
1599                    assigned(assignment, enrollment);
1600            }
1601        }
1602
1603        /**
1604         * Called after an enrollment was assigned to a request. The list of
1605         * complete students and the overall solution value are updated.
1606         */
1607        @Override
1608        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
1609            Student student = enrollment.getStudent();
1610            if (student.isComplete(assignment) && iCompleteStudents.add(student)) {
1611                if (student.isDummy()) iNrCompleteDummyStudents++;
1612                iNrCompletePriorityStudents[student.getPriority().ordinal()]++;
1613            }
1614            double value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(assignment, enrollment);
1615            iTotalValue -= value;
1616            enrollment.variable().getContext(assignment).setLastWeight(value);
1617            if (enrollment.isCourseRequest())
1618                iAssignedCRWeight += enrollment.getRequest().getWeight();
1619            if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getStudent().isDummy() && !enrollment.getRequest().isAlternative())
1620                iAssignedCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()] += enrollment.getRequest().getWeight();
1621            if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getRequest().isAlternative())
1622                iAssignedPriorityCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()][enrollment.getStudent().getPriority().ordinal()] += enrollment.getRequest().getWeight();
1623            if (enrollment.getRequest().isMPP()) {
1624                iAssignedSameSectionWeight += enrollment.getRequest().getWeight() * enrollment.percentInitial();
1625                iAssignedSameChoiceWeight += enrollment.getRequest().getWeight() * enrollment.percentSelected();
1626                iAssignedSameTimeWeight += enrollment.getRequest().getWeight() * enrollment.percentSameTime();
1627            }
1628            if (enrollment.getRequest().hasSelection()) {
1629                iAssignedSelectedSectionWeight += enrollment.getRequest().getWeight() * enrollment.percentSelectedSameSection();
1630                iAssignedSelectedConfigWeight += enrollment.getRequest().getWeight() * enrollment.percentSelectedSameConfig();
1631            }
1632            if (enrollment.getReservation() != null)
1633                iReservedSpace += enrollment.getRequest().getWeight();
1634            if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations())
1635                iTotalReservedSpace += enrollment.getRequest().getWeight();
1636            if (student.isDummy()) {
1637                iNrAssignedDummyRequests++;
1638                if (enrollment.isCourseRequest())
1639                    iAssignedDummyCRWeight += enrollment.getRequest().getWeight();
1640            }
1641            if (enrollment.isCourseRequest())
1642                iAssignedPriorityCRWeight[enrollment.getStudent().getPriority().ordinal()] += enrollment.getRequest().getWeight();
1643            if (enrollment.isCourseRequest()) {
1644                int noTime = 0;
1645                int online = 0;
1646                int past = 0;
1647                for (Section section: enrollment.getSections()) {
1648                    if (!section.hasTime()) noTime ++;
1649                    if (section.isOnline()) online ++;
1650                    if (section.isPast()) past ++;
1651                }
1652                if (noTime > 0)
1653                    iAssignedNoTimeSectionWeight += enrollment.getRequest().getWeight() * noTime / enrollment.getSections().size();
1654                if (online > 0)
1655                    iAssignedOnlineSectionWeight += enrollment.getRequest().getWeight() * online / enrollment.getSections().size();
1656                if (past > 0)
1657                    iAssignedPastSectionWeight += enrollment.getRequest().getWeight() * past / enrollment.getSections().size();
1658            }
1659        }
1660
1661        /**
1662         * Called before an enrollment was unassigned from a request. The list of
1663         * complete students and the overall solution value are updated.
1664         */
1665        @Override
1666        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
1667            Student student = enrollment.getStudent();
1668            if (enrollment.isCourseRequest() && iCompleteStudents.contains(student)) {
1669                iCompleteStudents.remove(student);
1670                if (student.isDummy())
1671                    iNrCompleteDummyStudents--;
1672                iNrCompletePriorityStudents[student.getPriority().ordinal()]--;
1673            }
1674            Request.RequestContext cx = enrollment.variable().getContext(assignment);
1675            Double value = cx.getLastWeight();
1676            if (value == null)
1677                value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(assignment, enrollment);
1678            iTotalValue += value;
1679            cx.setLastWeight(null);
1680            if (enrollment.isCourseRequest())
1681                iAssignedCRWeight -= enrollment.getRequest().getWeight();
1682            if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getStudent().isDummy() && !enrollment.getRequest().isAlternative())
1683                iAssignedCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()] -= enrollment.getRequest().getWeight();
1684            if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getRequest().isAlternative())
1685                iAssignedPriorityCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()][enrollment.getStudent().getPriority().ordinal()] -= enrollment.getRequest().getWeight();
1686            if (enrollment.getRequest().isMPP()) {
1687                iAssignedSameSectionWeight -= enrollment.getRequest().getWeight() * enrollment.percentInitial();
1688                iAssignedSameChoiceWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelected();
1689                iAssignedSameTimeWeight -= enrollment.getRequest().getWeight() * enrollment.percentSameTime();
1690            }
1691            if (enrollment.getRequest().hasSelection()) {
1692                iAssignedSelectedSectionWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelectedSameSection();
1693                iAssignedSelectedConfigWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelectedSameConfig();
1694            }
1695            if (enrollment.getReservation() != null)
1696                iReservedSpace -= enrollment.getRequest().getWeight();
1697            if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations())
1698                iTotalReservedSpace -= enrollment.getRequest().getWeight();
1699            if (student.isDummy()) {
1700                iNrAssignedDummyRequests--;
1701                if (enrollment.isCourseRequest())
1702                    iAssignedDummyCRWeight -= enrollment.getRequest().getWeight();
1703            }
1704            if (enrollment.isCourseRequest())
1705                iAssignedPriorityCRWeight[enrollment.getStudent().getPriority().ordinal()] -= enrollment.getRequest().getWeight();
1706            if (enrollment.isCourseRequest()) {
1707                int noTime = 0;
1708                int online = 0;
1709                int past = 0;
1710                for (Section section: enrollment.getSections()) {
1711                    if (!section.hasTime()) noTime ++;
1712                    if (section.isOnline()) online ++;
1713                    if (section.isPast()) past ++;
1714                }
1715                if (noTime > 0)
1716                    iAssignedNoTimeSectionWeight -= enrollment.getRequest().getWeight() * noTime / enrollment.getSections().size();
1717                if (online > 0)
1718                    iAssignedOnlineSectionWeight -= enrollment.getRequest().getWeight() * online / enrollment.getSections().size();
1719                if (past > 0)
1720                    iAssignedPastSectionWeight -= enrollment.getRequest().getWeight() * past / enrollment.getSections().size();
1721            }
1722        }
1723        
1724        public void add(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) {
1725            iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c);
1726        }
1727
1728        public void remove(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) {
1729            iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c);
1730        }
1731        
1732        public void add(Assignment<Request, Enrollment> assignment, TimeOverlapsCounter.Conflict c) {
1733            if (c.getR1() != null) iTotalValue += c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c);
1734            if (c.getR2() != null) iTotalValue += c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c);
1735        }
1736
1737        public void remove(Assignment<Request, Enrollment> assignment, TimeOverlapsCounter.Conflict c) {
1738            if (c.getR1() != null) iTotalValue -= c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c);
1739            if (c.getR2() != null) iTotalValue -= c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c);
1740        }
1741        
1742        public void add(Assignment<Request, Enrollment> assignment, StudentQuality.Conflict c) {
1743            switch (c.getType().getType()) {
1744                case REQUEST:
1745                    if (c.getR1() instanceof CourseRequest)
1746                        iTotalValue += c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
1747                    else
1748                        iTotalValue += c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
1749                    break;
1750                case BOTH:
1751                    iTotalValue += c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);  
1752                    iTotalValue += c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
1753                    break;
1754                case LOWER:
1755                    iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
1756                    break;
1757                case HIGHER:
1758                    iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
1759                    break;
1760            }
1761        }
1762
1763        public void remove(Assignment<Request, Enrollment> assignment, StudentQuality.Conflict c) {
1764            switch (c.getType().getType()) {
1765                case REQUEST:
1766                    if (c.getR1() instanceof CourseRequest)
1767                        iTotalValue -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
1768                    else
1769                        iTotalValue -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
1770                    break;
1771                case BOTH:
1772                    iTotalValue -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);  
1773                    iTotalValue -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
1774                    break;
1775                case LOWER:
1776                    iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
1777                    break;
1778                case HIGHER:
1779                    iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
1780                    break;
1781            }
1782        }
1783        
1784        /**
1785         * Students with complete schedules (see {@link Student#isComplete(Assignment)})
1786         * @return students with complete schedule
1787         */
1788        public Set<Student> getCompleteStudents() {
1789            return iCompleteStudents;
1790        }
1791        
1792        /**
1793         * Number of students with complete schedule
1794         * @return number of students with complete schedule
1795         */
1796        public int nrComplete() {
1797            return getCompleteStudents().size();
1798        }
1799        
1800        /** 
1801         * Recompute cached request weights
1802         * @param assignment curent assignment
1803         */
1804        public void requestWeightsChanged(Assignment<Request, Enrollment> assignment) {
1805            iTotalCRWeight = 0.0;
1806            iTotalDummyWeight = 0.0; iTotalDummyCRWeight = 0.0;
1807            iTotalPriorityCRWeight = new double[StudentPriority.values().length];
1808            iAssignedCRWeight = 0.0;
1809            iAssignedDummyCRWeight = 0.0;
1810            iAssignedCriticalCRWeight = new double[RequestPriority.values().length];
1811            iAssignedPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length];
1812            for (int i = 0; i < RequestPriority.values().length; i++) {
1813                iAssignedCriticalCRWeight[i] = 0.0;
1814                for (int j = 0; j < StudentPriority.values().length; j++) {
1815                    iAssignedPriorityCriticalCRWeight[i][j] = 0.0;
1816                }
1817            }
1818            iAssignedPriorityCRWeight = new double[StudentPriority.values().length];
1819            for (int i = 0; i < StudentPriority.values().length; i++) {
1820                iAssignedPriorityCRWeight[i] = 0.0;
1821            }
1822            iNrDummyRequests = 0; iNrAssignedDummyRequests = 0;
1823            iTotalReservedSpace = 0.0; iReservedSpace = 0.0;
1824            iTotalMPPCRWeight = 0.0;
1825            iTotalSelCRWeight = 0.0;
1826            iAssignedNoTimeSectionWeight = 0.0;
1827            iAssignedOnlineSectionWeight = 0.0;
1828            iAssignedPastSectionWeight = 0.0;
1829            for (Request request: variables()) {
1830                boolean cr = (request instanceof CourseRequest);
1831                if (cr && !request.isAlternative())
1832                    iTotalCRWeight += request.getWeight();
1833                if (request.getStudent().isDummy()) {
1834                    iTotalDummyWeight += request.getWeight();
1835                    iNrDummyRequests ++;
1836                    if (cr && !request.isAlternative())
1837                        iTotalDummyCRWeight += request.getWeight();
1838                }
1839                if (cr && !request.isAlternative()) {
1840                    iTotalPriorityCRWeight[request.getStudent().getPriority().ordinal()] += request.getWeight();
1841                }
1842                if (request.isMPP())
1843                    iTotalMPPCRWeight += request.getWeight();
1844                if (request.hasSelection())
1845                    iTotalSelCRWeight += request.getWeight();
1846                Enrollment e = assignment.getValue(request);
1847                if (e != null) {
1848                    if (cr)
1849                        iAssignedCRWeight += request.getWeight();
1850                    if (cr && request.getRequestPriority() != RequestPriority.Normal && !request.getStudent().isDummy() && !request.isAlternative())
1851                        iAssignedCriticalCRWeight[request.getRequestPriority().ordinal()] += request.getWeight();
1852                    if (cr && request.getRequestPriority() != RequestPriority.Normal && !request.isAlternative())
1853                        iAssignedPriorityCriticalCRWeight[request.getRequestPriority().ordinal()][request.getStudent().getPriority().ordinal()] += request.getWeight();
1854                    if (request.isMPP()) {
1855                        iAssignedSameSectionWeight += request.getWeight() * e.percentInitial();
1856                        iAssignedSameChoiceWeight += request.getWeight() * e.percentSelected();
1857                        iAssignedSameTimeWeight += request.getWeight() * e.percentSameTime();
1858                    }
1859                    if (request.hasSelection()) {
1860                        iAssignedSelectedSectionWeight += request.getWeight() * e.percentSelectedSameSection();
1861                        iAssignedSelectedConfigWeight += request.getWeight() * e.percentSelectedSameConfig();
1862                    }
1863                    if (e.getReservation() != null)
1864                        iReservedSpace += request.getWeight();
1865                    if (cr && ((CourseRequest)request).hasReservations())
1866                        iTotalReservedSpace += request.getWeight();
1867                    if (request.getStudent().isDummy()) {
1868                        iNrAssignedDummyRequests ++;
1869                        if (cr)
1870                            iAssignedDummyCRWeight += request.getWeight();
1871                    }
1872                    if (cr) {
1873                        iAssignedPriorityCRWeight[request.getStudent().getPriority().ordinal()] += request.getWeight();
1874                    }
1875                    if (cr) {
1876                        int noTime = 0;
1877                        int online = 0;
1878                        int past = 0;
1879                        for (Section section: e.getSections()) {
1880                            if (!section.hasTime()) noTime ++;
1881                            if (section.isOnline()) online ++;
1882                            if (section.isPast()) past ++;
1883                        }
1884                        if (noTime > 0)
1885                            iAssignedNoTimeSectionWeight += request.getWeight() * noTime / e.getSections().size();
1886                        if (online > 0)
1887                            iAssignedOnlineSectionWeight += request.getWeight() * online / e.getSections().size();
1888                        if (past > 0)
1889                            iAssignedPastSectionWeight += request.getWeight() * past / e.getSections().size();
1890                    }
1891                }
1892            }
1893        }
1894        
1895        /**
1896         * Overall solution value
1897         * @return solution value
1898         */
1899        public double getTotalValue() {
1900            return iTotalValue;
1901        }
1902        
1903        /**
1904         * Number of last like ({@link Student#isDummy()} equals true) students with
1905         * a complete schedule ({@link Student#isComplete(Assignment)} equals true).
1906         * @return number of last like (projected) students with a complete schedule
1907         */
1908        public int getNrCompleteLastLikeStudents() {
1909            return iNrCompleteDummyStudents;
1910        }
1911        
1912        /**
1913         * Number of requests from projected ({@link Student#isDummy()} equals true)
1914         * students that are assigned.
1915         * @return number of real students with a complete schedule
1916         */
1917        public int getNrAssignedLastLikeRequests() {
1918            return iNrAssignedDummyRequests;
1919        }
1920
1921        @Override
1922        public void getInfo(Assignment<Request, Enrollment> assignment, Map<String, String> info) {
1923            if (iTotalCRWeight > 0.0) {
1924                info.put("Assigned course requests", sDecimalFormat.format(100.0 * iAssignedCRWeight / iTotalCRWeight) + "% (" + (int)Math.round(iAssignedCRWeight) + "/" + (int)Math.round(iTotalCRWeight) + ")");
1925                if (iNrDummyStudents > 0 && iNrDummyStudents != getStudents().size() && iTotalCRWeight != iTotalDummyCRWeight) {
1926                    if (iTotalDummyCRWeight > 0.0)
1927                        info.put("Projected assigned course requests", sDecimalFormat.format(100.0 * iAssignedDummyCRWeight / iTotalDummyCRWeight) + "% (" + (int)Math.round(iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalDummyCRWeight) + ")");
1928                    info.put("Real assigned course requests", sDecimalFormat.format(100.0 * (iAssignedCRWeight - iAssignedDummyCRWeight) / (iTotalCRWeight - iTotalDummyCRWeight)) +
1929                            "% (" + (int)Math.round(iAssignedCRWeight - iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalCRWeight - iTotalDummyCRWeight) + ")");
1930                }
1931                if (iAssignedNoTimeSectionWeight > 0.0) {
1932                    info.put("Using classes w/o time", sDecimalFormat.format(100.0 * iAssignedNoTimeSectionWeight / iAssignedCRWeight) + "% (" + sDecimalFormat.format(iAssignedNoTimeSectionWeight) + ")"); 
1933                }
1934                if (iAssignedOnlineSectionWeight > 0.0) {
1935                    info.put("Using online classes", sDecimalFormat.format(100.0 * iAssignedOnlineSectionWeight / iAssignedCRWeight) + "% (" + sDecimalFormat.format(iAssignedOnlineSectionWeight) + ")"); 
1936                }
1937                if (iAssignedPastSectionWeight > 0.0) {
1938                    info.put("Using past classes", sDecimalFormat.format(100.0 * iAssignedPastSectionWeight / iAssignedCRWeight) + "% (" + sDecimalFormat.format(iAssignedPastSectionWeight) + ")");
1939                }
1940            }
1941            String priorityAssignedCR = "";
1942            for (StudentPriority sp: StudentPriority.values()) {
1943                if (sp != StudentPriority.Dummy && iTotalPriorityCRWeight[sp.ordinal()] > 0.0) {
1944                    priorityAssignedCR += (priorityAssignedCR.isEmpty() ? "" : "\n") +
1945                            sp.name() + ": " + sDecimalFormat.format(100.0 * iAssignedPriorityCRWeight[sp.ordinal()] / iTotalPriorityCRWeight[sp.ordinal()]) + "% (" + (int)Math.round(iAssignedPriorityCRWeight[sp.ordinal()]) + "/" + (int)Math.round(iTotalPriorityCRWeight[sp.ordinal()]) + ")";
1946                }
1947            }
1948            if (!priorityAssignedCR.isEmpty())
1949                info.put("Assigned course requests (priority students)", priorityAssignedCR);
1950            for (RequestPriority rp: RequestPriority.values()) {
1951                if (rp == RequestPriority.Normal) continue;
1952                if (iTotalCriticalCRWeight[rp.ordinal()] > 0.0) {
1953                    info.put("Assigned " + rp.name().toLowerCase() + " course requests", sDoubleFormat.format(100.0 * iAssignedCriticalCRWeight[rp.ordinal()] / iTotalCriticalCRWeight[rp.ordinal()]) + "% (" + (int)Math.round(iAssignedCriticalCRWeight[rp.ordinal()]) + "/" + (int)Math.round(iTotalCriticalCRWeight[rp.ordinal()]) + ")");
1954                }
1955                priorityAssignedCR = "";
1956                for (StudentPriority sp: StudentPriority.values()) {
1957                    if (sp != StudentPriority.Dummy && iTotalPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()] > 0.0) {
1958                        priorityAssignedCR += (priorityAssignedCR.isEmpty() ? "" : "\n") +
1959                                sp.name() + ": " + sDoubleFormat.format(100.0 * iAssignedPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()]) + "% (" + (int)Math.round(iAssignedPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()]) + "/" + (int)Math.round(iTotalPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()]) + ")";
1960                    }
1961                }
1962                if (!priorityAssignedCR.isEmpty())
1963                    info.put("Assigned " + rp.name().toLowerCase() + " course requests (priority students)", priorityAssignedCR);
1964            }
1965            if (iTotalReservedSpace > 0.0)
1966                info.put("Reservations", sDoubleFormat.format(100.0 * iReservedSpace / iTotalReservedSpace) + "% (" + Math.round(iReservedSpace) + "/" + Math.round(iTotalReservedSpace) + ")");
1967            if (iMPP && iTotalMPPCRWeight > 0.0) {
1968                info.put("Perturbations: same section", sDoubleFormat.format(100.0 * iAssignedSameSectionWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameSectionWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")");
1969                if (iAssignedSameChoiceWeight > iAssignedSameSectionWeight)
1970                    info.put("Perturbations: same choice",sDoubleFormat.format(100.0 * iAssignedSameChoiceWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameChoiceWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")");
1971                if (iAssignedSameTimeWeight > iAssignedSameChoiceWeight)
1972                    info.put("Perturbations: same time", sDoubleFormat.format(100.0 * iAssignedSameTimeWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameTimeWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")");
1973            }
1974            if (iTotalSelCRWeight > 0.0) {
1975                info.put("Selection",sDoubleFormat.format(100.0 * (0.3 * iAssignedSelectedConfigWeight + 0.7 * iAssignedSelectedSectionWeight) / iTotalSelCRWeight) +
1976                        "% (" + Math.round(0.3 * iAssignedSelectedConfigWeight + 0.7 * iAssignedSelectedSectionWeight) + "/" + Math.round(iTotalSelCRWeight) + ")");
1977            }
1978        }
1979
1980        @Override
1981        public void getInfo(Assignment<Request, Enrollment> assignment, Map<String, String> info, Collection<Request> variables) {
1982        }
1983        
1984        public double getAssignedCourseRequestWeight() {
1985            return iAssignedCRWeight;
1986        }
1987        
1988        public double getAssignedCriticalCourseRequestWeight(RequestPriority rp) {
1989            return iAssignedCriticalCRWeight[rp.ordinal()];
1990        }
1991    }
1992    
1993    @Override
1994    public InheritedAssignment<Request, Enrollment> createInheritedAssignment(Solution<Request, Enrollment> solution, int index) {
1995        return new OptimisticInheritedAssignment<Request, Enrollment>(solution, index);
1996    }
1997    
1998    public DistanceMetric getDistanceMetric() {
1999        return (iStudentQuality != null ? iStudentQuality.getDistanceMetric() : iDistanceConflict != null ? iDistanceConflict.getDistanceMetric() : null);
2000    }
2001
2002    @Override
2003    public StudentSectioningModelContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, StudentSectioningModelContext parentContext) {
2004        return new StudentSectioningModelContext(parentContext);
2005    }
2006
2007}