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