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