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