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