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