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