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