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