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