    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.coursett.Constants;
    015import org.cpsolver.ifs.assignment.Assignment;
    016import org.cpsolver.ifs.assignment.InheritedAssignment;
    017import org.cpsolver.ifs.assignment.OptimisticInheritedAssignment;
    018import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
    019import org.cpsolver.ifs.assignment.context.CanInheritContext;
    020import org.cpsolver.ifs.assignment.context.ModelWithContext;
    021import org.cpsolver.ifs.model.Constraint;
    022import org.cpsolver.ifs.model.ConstraintListener;
    023import org.cpsolver.ifs.model.InfoProvider;
    024import org.cpsolver.ifs.model.Model;
    025import org.cpsolver.ifs.solution.Solution;
    026import org.cpsolver.ifs.util.DataProperties;
    027import org.cpsolver.ifs.util.DistanceMetric;
    028import org.cpsolver.studentsct.constraint.CancelledSections;
    029import org.cpsolver.studentsct.constraint.ConfigLimit;
    030import org.cpsolver.studentsct.constraint.CourseLimit;
    031import org.cpsolver.studentsct.constraint.DisabledSections;
    032import org.cpsolver.studentsct.constraint.FixInitialAssignments;
    033import org.cpsolver.studentsct.constraint.LinkedSections;
    034import org.cpsolver.studentsct.constraint.RequiredReservation;
    035import org.cpsolver.studentsct.constraint.RequiredRestrictions;
    036import org.cpsolver.studentsct.constraint.RequiredSections;
    037import org.cpsolver.studentsct.constraint.ReservationLimit;
    038import org.cpsolver.studentsct.constraint.SectionLimit;
    039import org.cpsolver.studentsct.constraint.StudentConflict;
    040import org.cpsolver.studentsct.constraint.StudentNotAvailable;
    041import org.cpsolver.studentsct.extension.DistanceConflict;
    042import org.cpsolver.studentsct.extension.StudentQuality;
    043import org.cpsolver.studentsct.extension.TimeOverlapsCounter;
    044import org.cpsolver.studentsct.model.Config;
    045import org.cpsolver.studentsct.model.Course;
    046import org.cpsolver.studentsct.model.CourseRequest;
    047import org.cpsolver.studentsct.model.Enrollment;
    048import org.cpsolver.studentsct.model.Offering;
    049import org.cpsolver.studentsct.model.Request;
    050import org.cpsolver.studentsct.model.RequestGroup;
    051import org.cpsolver.studentsct.model.Section;
    052import org.cpsolver.studentsct.model.Student;
    053import org.cpsolver.studentsct.model.Subpart;
    054import org.cpsolver.studentsct.model.Unavailability;
    055import org.cpsolver.studentsct.model.Request.RequestPriority;
    056import org.cpsolver.studentsct.model.Student.StudentPriority;
    057import org.cpsolver.studentsct.reservation.Reservation;
    058import org.cpsolver.studentsct.weights.PriorityStudentWeights;
    059import org.cpsolver.studentsct.weights.StudentWeights;
    060
    061/**
    062 * Student sectioning model.
    063 *
    064 * <br>
    065 * <br>
    066 *
    067 * @version StudentSct 1.3 (Student Sectioning)<br>
    068 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    069 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    070 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    071 * <br>
    072 *          This library is free software; you can redistribute it and/or modify
    073 *          it under the terms of the GNU Lesser General Public License as
    074 *          published by the Free Software Foundation; either version 3 of the
    075 *          License, or (at your option) any later version. <br>
    076 * <br>
    077 *          This library is distributed in the hope that it will be useful, but
    078 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    079 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    080 *          Lesser General Public License for more details. <br>
    081 * <br>
    082 *          You should have received a copy of the GNU Lesser General Public
    083 *          License along with this library; if not see
    084 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    085 */
    086public class StudentSectioningModel extends ModelWithContext<Request, Enrollment, StudentSectioningModel.StudentSectioningModelContext> implements CanInheritContext<Request, Enrollment, StudentSectioningModel.StudentSectioningModelContext> {
    087    private static Logger sLog = Logger.getLogger(StudentSectioningModel.class);
    088    protected static DecimalFormat sDecimalFormat = new DecimalFormat("0.00");
    089    private List<Student> iStudents = new ArrayList<Student>();
    090    private List<Offering> iOfferings = new ArrayList<Offering>();
    091    private List<LinkedSections> iLinkedSections = new ArrayList<LinkedSections>();
    092    private DataProperties iProperties;
    093    private DistanceConflict iDistanceConflict = null;
    094    private TimeOverlapsCounter iTimeOverlaps = null;
    095    private StudentQuality iStudentQuality = null;
    096    private int iNrDummyStudents = 0, iNrDummyRequests = 0;
    097    private int[] iNrPriorityStudents = null;
    098    private double iTotalDummyWeight = 0.0;
    099    private double iTotalCRWeight = 0.0, iTotalDummyCRWeight = 0.0;
    100    private double[] iTotalPriorityCRWeight = null;
    101    private double[] iTotalCriticalCRWeight;
    102    private double[][] iTotalPriorityCriticalCRWeight;
    103    private double iTotalMPPCRWeight = 0.0;
    104    private double iTotalSelCRWeight = 0.0;
    105    private double iBestAssignedCourseRequestWeight = 0.0;
    106    private StudentWeights iStudentWeights = null;
    107    private boolean iReservationCanAssignOverTheLimit;
    108    private boolean iMPP;
    109    private boolean iKeepInitials;
    110    protected double iProjectedStudentWeight = 0.0100;
    111    private int iMaxDomainSize = -1;
    112
    113
    114    /**
    115     * Constructor
    116     *
    117     * @param properties
    118     *            configuration
    119     */
    120    @SuppressWarnings("unchecked")
    121    public StudentSectioningModel(DataProperties properties) {
    122        super();
    123        iTotalCriticalCRWeight = new double[RequestPriority.values().length];
    124        iTotalPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length];
    125        for (int i = 0; i < RequestPriority.values().length; i++) {
    126            iTotalCriticalCRWeight[i] = 0.0;
    127            for (int j = 0; j < StudentPriority.values().length; j++) {
    128                iTotalPriorityCriticalCRWeight[i][j] = 0.0;
    129            }
    130        }
    131        iNrPriorityStudents = new int[StudentPriority.values().length];
    132        iTotalPriorityCRWeight = new double[StudentPriority.values().length];
    133        for (int i = 0; i < StudentPriority.values().length; i++) {
    134            iNrPriorityStudents[i] = 0;
    135            iTotalPriorityCRWeight[i] = 0.0;
    136        }
    137        iReservationCanAssignOverTheLimit =  properties.getPropertyBoolean("Reservation.CanAssignOverTheLimit", false);
    138        iMPP = properties.getPropertyBoolean("General.MPP", false);
    139        iKeepInitials = properties.getPropertyBoolean("Sectioning.KeepInitialAssignments", false);
    140        iStudentWeights = new PriorityStudentWeights(properties);
    141        iMaxDomainSize = properties.getPropertyInt("Sectioning.MaxDomainSize", iMaxDomainSize);
    142        if (properties.getPropertyBoolean("Sectioning.SectionLimit", true)) {
    143            SectionLimit sectionLimit = new SectionLimit(properties);
    144            addGlobalConstraint(sectionLimit);
    145            if (properties.getPropertyBoolean("Sectioning.SectionLimit.Debug", false)) {
    146                sectionLimit.addConstraintListener(new ConstraintListener<Request, Enrollment>() {
    147                    @Override
    148                    public void constraintBeforeAssigned(Assignment<Request, Enrollment> assignment, long iteration, Constraint<Request, Enrollment> constraint, Enrollment enrollment, Set<Enrollment> unassigned) {
    149                        if (enrollment.getStudent().isDummy())
    150                            for (Enrollment conflict : unassigned) {
    151                                if (!conflict.getStudent().isDummy()) {
    152                                    sLog.warn("Enrolment of a real student " + conflict.getStudent() + " is unassigned "
    153                                            + "\n  -- " + conflict + "\ndue to an enrollment of a dummy student "
    154                                            + enrollment.getStudent() + " " + "\n  -- " + enrollment);
    155                                }
    156                            }
    157                    }
    158
    159                    @Override
    160                    public void constraintAfterAssigned(Assignment<Request, Enrollment> assignment, long iteration, Constraint<Request, Enrollment> constraint, Enrollment assigned, Set<Enrollment> unassigned) {
    161                    }
    162                });
    163            }
    164        }
    165        if (properties.getPropertyBoolean("Sectioning.ConfigLimit", true)) {
    166            ConfigLimit configLimit = new ConfigLimit(properties);
    167            addGlobalConstraint(configLimit);
    168        }
    169        if (properties.getPropertyBoolean("Sectioning.CourseLimit", true)) {
    170            CourseLimit courseLimit = new CourseLimit(properties);
    171            addGlobalConstraint(courseLimit);
    172        }
    173        if (properties.getPropertyBoolean("Sectioning.ReservationLimit", true)) {
    174            ReservationLimit reservationLimit = new ReservationLimit(properties);
    175            addGlobalConstraint(reservationLimit);
    176        }
    177        if (properties.getPropertyBoolean("Sectioning.RequiredReservations", true)) {
    178            RequiredReservation requiredReservation = new RequiredReservation();
    179            addGlobalConstraint(requiredReservation);
    180        }
    181        if (properties.getPropertyBoolean("Sectioning.CancelledSections", true)) {
    182            CancelledSections cancelledSections = new CancelledSections();
    183            addGlobalConstraint(cancelledSections);
    184        }
    185        if (properties.getPropertyBoolean("Sectioning.StudentNotAvailable", true)) {
    186            StudentNotAvailable studentNotAvailable = new StudentNotAvailable();
    187            addGlobalConstraint(studentNotAvailable);
    188        }
    189        if (properties.getPropertyBoolean("Sectioning.DisabledSections", true)) {
    190            DisabledSections disabledSections = new DisabledSections();
    191            addGlobalConstraint(disabledSections);
    192        }
    193        if (properties.getPropertyBoolean("Sectioning.RequiredSections", true)) {
    194            RequiredSections requiredSections = new RequiredSections();
    195            addGlobalConstraint(requiredSections);
    196        }
    197        if (properties.getPropertyBoolean("Sectioning.RequiredRestrictions", true)) {
    198            RequiredRestrictions requiredRestrictions = new RequiredRestrictions();
    199            addGlobalConstraint(requiredRestrictions);
    200        }
    201        if (iMPP && iKeepInitials) {
    202            addGlobalConstraint(new FixInitialAssignments());
    203        }
    204        try {
    205            Class<StudentWeights> studentWeightsClass = (Class<StudentWeights>)Class.forName(properties.getProperty("StudentWeights.Class", PriorityStudentWeights.class.getName()));
    206            iStudentWeights = studentWeightsClass.getConstructor(DataProperties.class).newInstance(properties);
    207        } catch (Exception e) {
    208            sLog.error("Unable to create custom student weighting model (" + e.getMessage() + "), using default.", e);
    209            iStudentWeights = new PriorityStudentWeights(properties);
    210        }
    211        iProjectedStudentWeight = properties.getPropertyDouble("StudentWeights.ProjectedStudentWeight", iProjectedStudentWeight);
    212        iProperties = properties;
    213    }
    214
    215    /**
    216     * Return true if reservation that has {@link Reservation#canAssignOverLimit()} can assign enrollments over the limit
    217     * @return true if reservation that has {@link Reservation#canAssignOverLimit()} can assign enrollments over the limit
    218     */
    219    public boolean getReservationCanAssignOverTheLimit() {
    220        return iReservationCanAssignOverTheLimit;
    221    }
    222
    223    /**
    224     * Return true if the problem is minimal perturbation problem
    225     * @return true if MPP is enabled
    226     */
    227    public boolean isMPP() {
    228        return iMPP;
    229    }
    230
    231    /**
    232     * Return true if the inital assignments are to be kept unchanged
    233     * @return true if the initial assignments are to be kept at all cost
    234     */
    235    public boolean getKeepInitialAssignments() {
    236        return iKeepInitials;
    237    }
    238
    239    /**
    240     * Return student weighting model
    241     * @return student weighting model
    242     */
    243    public StudentWeights getStudentWeights() {
    244        return iStudentWeights;
    245    }
    246
    247    /**
    248     * Set student weighting model
    249     * @param weights student weighting model
    250     */
    251    public void setStudentWeights(StudentWeights weights) {
    252        iStudentWeights = weights;
    253    }
    254
    255    /**
    256     * Students
    257     * @return all students in the problem
    258     */
    259    public List<Student> getStudents() {
    260        return iStudents;
    261    }
    262
    263    /**
    264     * Add a student into the model
    265     * @param student a student to be added into the problem
    266     */
    267    public void addStudent(Student student) {
    268        iStudents.add(student);
    269        if (student.isDummy())
    270            iNrDummyStudents++;
    271        iNrPriorityStudents[student.getPriority().ordinal()]++;
    272        for (Request request : student.getRequests())
    273            addVariable(request);
    274        if (getProperties().getPropertyBoolean("Sectioning.StudentConflict", true)) {
    275            addConstraint(new StudentConflict(student));
    276        }
    277    }
    278
    279    public int getNbrStudents(StudentPriority priority) {
    280        return iNrPriorityStudents[priority.ordinal()];
    281    }
    282
    283    @Override
    284    public void addVariable(Request request) {
    285        super.addVariable(request);
    286        if (request instanceof CourseRequest && !request.isAlternative())
    287            iTotalCRWeight += request.getWeight();
    288        if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.getStudent().isDummy() && !request.isAlternative())
    289            iTotalCriticalCRWeight[request.getRequestPriority().ordinal()] += request.getWeight();
    290        if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.isAlternative())
    291            iTotalPriorityCriticalCRWeight[request.getRequestPriority().ordinal()][request.getStudent().getPriority().ordinal()] += request.getWeight();
    292        if (request.getStudent().isDummy()) {
    293            iNrDummyRequests++;
    294            iTotalDummyWeight += request.getWeight();
    295            if (request instanceof CourseRequest && !request.isAlternative())
    296                iTotalDummyCRWeight += request.getWeight();
    297        }
    298        if (request instanceof CourseRequest && !request.isAlternative())
    299            iTotalPriorityCRWeight[request.getStudent().getPriority().ordinal()] += request.getWeight();
    300        if (request.isMPP())
    301            iTotalMPPCRWeight += request.getWeight();
    302        if (request.hasSelection())
    303            iTotalSelCRWeight += request.getWeight();
    304    }
    305
    306    /**
    307     * Recompute cached request weights
    308     * @param assignment current assignment
    309     */
    310    public void requestWeightsChanged(Assignment<Request, Enrollment> assignment) {
    311        getContext(assignment).requestWeightsChanged(assignment);
    312    }
    313
    314    /**
    315     * Remove a student from the model
    316     * @param student a student to be removed from the problem
    317     */
    318    public void removeStudent(Student student) {
    319        iStudents.remove(student);
    320        if (student.isDummy())
    321            iNrDummyStudents--;
    322        iNrPriorityStudents[student.getPriority().ordinal()]--;
    323        StudentConflict conflict = null;
    324        for (Request request : student.getRequests()) {
    325            for (Constraint<Request, Enrollment> c : request.constraints()) {
    326                if (c instanceof StudentConflict) {
    327                    conflict = (StudentConflict) c;
    328                    break;
    329                }
    330            }
    331            if (conflict != null)
    332                conflict.removeVariable(request);
    333            removeVariable(request);
    334        }
    335        if (conflict != null)
    336            removeConstraint(conflict);
    337    }
    338
    339    @Override
    340    public void removeVariable(Request request) {
    341        super.removeVariable(request);
    342        if (request instanceof CourseRequest) {
    343            CourseRequest cr = (CourseRequest)request;
    344            for (Course course: cr.getCourses())
    345                course.getRequests().remove(request);
    346        }
    347        if (request.getStudent().isDummy()) {
    348            iNrDummyRequests--;
    349            iTotalDummyWeight -= request.getWeight();
    350            if (request instanceof CourseRequest && !request.isAlternative())
    351                iTotalDummyCRWeight -= request.getWeight();
    352        }
    353        if (request instanceof CourseRequest && !request.isAlternative())
    354            iTotalPriorityCRWeight[request.getStudent().getPriority().ordinal()] -= request.getWeight();
    355        if (request.isMPP())
    356            iTotalMPPCRWeight -= request.getWeight();
    357        if (request.hasSelection())
    358            iTotalSelCRWeight -= request.getWeight();
    359        if (request instanceof CourseRequest && !request.isAlternative())
    360            iTotalCRWeight -= request.getWeight();
    361        if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.getStudent().isDummy() && !request.isAlternative())
    362            iTotalCriticalCRWeight[request.getRequestPriority().ordinal()] -= request.getWeight();
    363        if (request instanceof CourseRequest && request.getRequestPriority() != RequestPriority.Normal && !request.isAlternative())
    364            iTotalPriorityCriticalCRWeight[request.getRequestPriority().ordinal()][request.getStudent().getPriority().ordinal()] -= request.getWeight();
    365    }
    366
    367
    368    /**
    369     * List of offerings
    370     * @return all instructional offerings of the problem
    371     */
    372    public List<Offering> getOfferings() {
    373        return iOfferings;
    374    }
    375
    376    /**
    377     * Add an offering into the model
    378     * @param offering an instructional offering to be added into the problem
    379     */
    380    public void addOffering(Offering offering) {
    381        iOfferings.add(offering);
    382        offering.setModel(this);
    383    }
    384
    385    /**
    386     * Link sections using {@link LinkedSections}
    387     * @param mustBeUsed if true,  a pair of linked sections must be used when a student requests both courses
    388     * @param sections a linked section constraint to be added into the problem
    389     */
    390    public void addLinkedSections(boolean mustBeUsed, Section... sections) {
    391        LinkedSections constraint = new LinkedSections(sections);
    392        constraint.setMustBeUsed(mustBeUsed);
    393        iLinkedSections.add(constraint);
    394        constraint.createConstraints();
    395    }
    396
    397    /**
    398     * Link sections using {@link LinkedSections}
    399     * @param sections a linked section constraint to be added into the problem
    400     */
    401    @Deprecated
    402    public void addLinkedSections(Section... sections) {
    403        addLinkedSections(false, sections);
    404    }
    405
    406    /**
    407     * Link sections using {@link LinkedSections}
    408     * @param mustBeUsed if true,  a pair of linked sections must be used when a student requests both courses
    409     * @param sections a linked section constraint to be added into the problem
    410     */
    411    public void addLinkedSections(boolean mustBeUsed, Collection<Section> sections) {
    412        LinkedSections constraint = new LinkedSections(sections);
    413        constraint.setMustBeUsed(mustBeUsed);
    414        iLinkedSections.add(constraint);
    415        constraint.createConstraints();
    416    }
    417
    418    /**
    419     * Link sections using {@link LinkedSections}
    420     * @param sections a linked section constraint to be added into the problem
    421     */
    422    @Deprecated
    423    public void addLinkedSections(Collection<Section> sections) {
    424        addLinkedSections(false, sections);
    425    }
    426
    427    /**
    428     * List of linked sections
    429     * @return all linked section constraints of the problem
    430     */
    431    public List<LinkedSections> getLinkedSections() {
    432        return iLinkedSections;
    433    }
    434
    435    /**
    436     * Model info
    437     */
    438    @Override
    439    public Map<String, String> getInfo(Assignment<Request, Enrollment> assignment) {
    440        Map<String, String> info = super.getInfo(assignment);
    441        StudentSectioningModelContext context = getContext(assignment);
    442        if (!getStudents().isEmpty())
    443            info.put("Students with complete schedule", sDoubleFormat.format(100.0 * context.nrComplete() / getStudents().size()) + "% (" + context.nrComplete() + "/" + getStudents().size() + ")");
    444        String priorityComplete = "";
    445        for (StudentPriority sp: StudentPriority.values()) {
    446            if (sp != StudentPriority.Dummy && iNrPriorityStudents[sp.ordinal()] > 0)
    447                priorityComplete += (priorityComplete.isEmpty() ? "" : "\n") +
    448                    sp.name() + ": " + sDoubleFormat.format(100.0 * context.iNrCompletePriorityStudents[sp.ordinal()] / iNrPriorityStudents[sp.ordinal()]) + "% (" + context.iNrCompletePriorityStudents[sp.ordinal()] + "/" + iNrPriorityStudents[sp.ordinal()] + ")";
    449        }
    450        if (!priorityComplete.isEmpty())
    451            info.put("Students with complete schedule (priority students)", priorityComplete);
    452        if (getStudentQuality() != null) {
    453            int confs = getStudentQuality().getTotalPenalty(StudentQuality.Type.Distance, assignment);
    454            int shortConfs = getStudentQuality().getTotalPenalty(StudentQuality.Type.ShortDistance, assignment);
    455            if (confs > 0 || shortConfs > 0) {
    456                info.put("Student distance conflicts", confs + (shortConfs == 0 ? "" : " (" + getDistanceMetric().getShortDistanceAccommodationReference() + ": " + shortConfs + ")"));
    457            }
    458        } else if (getDistanceConflict() != null) {
    459            int confs = getDistanceConflict().getTotalNrConflicts(assignment);
    460            if (confs > 0) {
    461                int shortConfs = getDistanceConflict().getTotalNrShortConflicts(assignment);
    462                info.put("Student distance conflicts", confs + (shortConfs == 0 ? "" : " (" + getDistanceConflict().getDistanceMetric().getShortDistanceAccommodationReference() + ": " + shortConfs + ")"));
    463            }
    464        }
    465        if (getStudentQuality() != null) {
    466            int shareCR = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.CourseTimeOverlap, assignment);
    467            int shareFT = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.FreeTimeOverlap, assignment);
    468            int shareUN = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.Unavailability, assignment);
    469            if (shareCR + shareFT + shareUN > 0)
    470                info.put("Time overlapping conflicts", sDoubleFormat.format((5.0 * (shareCR + shareFT + shareUN)) / iStudents.size()) + " mins per student\n" +
    471                        "(" + sDoubleFormat.format(5.0 * shareCR / iStudents.size()) + " between courses, " + sDoubleFormat.format(5.0 * shareFT / iStudents.size()) + " free time" +
    472                        (shareUN == 0 ? "" : ", " + sDoubleFormat.format(5.0 * shareUN / iStudents.size()) + " teaching assignments") + "; " + sDoubleFormat.format((shareCR + shareFT + shareUN) / 12.0) + " hours total)");
    473        } else if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) {
    474            info.put("Time overlapping conflicts", sDoubleFormat.format(5.0 * getTimeOverlaps().getTotalNrConflicts(assignment) / iStudents.size()) + " mins per student (" + sDoubleFormat.format(getTimeOverlaps().getTotalNrConflicts(assignment) / 12.0) + " hours total)");
    475        }
    476        if (getStudentQuality() != null) {
    477            int confLunch = getStudentQuality().getTotalPenalty(StudentQuality.Type.LunchBreak, assignment);
    478            if (confLunch > 0)
    479                info.put("Schedule Quality: Lunch conflicts", sDoubleFormat.format(20.0 * confLunch / getNrRealStudents(false)) + "% (" + confLunch + ")");
    480            int confTravel = getStudentQuality().getTotalPenalty(StudentQuality.Type.TravelTime, assignment);
    481            if (confTravel > 0)
    482                info.put("Schedule Quality: Travel time", sDoubleFormat.format(((double)confTravel) / getNrRealStudents(false)) + " mins per student (" + sDecimalFormat.format(confTravel / 60.0) + " hours total)");
    483            int confBtB = getStudentQuality().getTotalPenalty(StudentQuality.Type.BackToBack, assignment);
    484            if (confBtB > 0)
    485                info.put("Schedule Quality: Back-to-back classes", sDoubleFormat.format(((double)confBtB) / getNrRealStudents(false)) + " per student (" + confBtB + ")");
    486            int confWorkDay = getStudentQuality().getTotalPenalty(StudentQuality.Type.WorkDay, assignment);
    487            if (confWorkDay > 0)
    488                info.put("Schedule Quality: Work day", sDoubleFormat.format(5.0 * confWorkDay / getNrRealStudents(false)) + " mins over " +
    489                        new DecimalFormat("0.#").format(getProperties().getPropertyInt("WorkDay.WorkDayLimit", 6*12) / 12.0) + " hours a day per student\n(from start to end, " + sDoubleFormat.format(confWorkDay / 12.0) + " hours total)");
    490            int early = getStudentQuality().getTotalPenalty(StudentQuality.Type.TooEarly, assignment);
    491            if (early > 0) {
    492                int min = getProperties().getPropertyInt("WorkDay.EarlySlot", 102) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
    493                int h = min / 60;
    494                int m = min % 60;
    495                String time = (getProperties().getPropertyBoolean("General.UseAmPm", true) ? (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a") : h + ":" + (m < 10 ? "0" : "") + m);
    496                info.put("Schedule Quality: Early classes", sDoubleFormat.format(5.0 * early / iStudents.size()) + " mins before " + time + " per student (" + sDoubleFormat.format(early / 12.0) + " hours total)");
    497            }
    498            int late = getStudentQuality().getTotalPenalty(StudentQuality.Type.TooLate, assignment);
    499            if (late > 0) {
    500                int min = getProperties().getPropertyInt("WorkDay.LateSlot", 210) * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN;
    501                int h = min / 60;
    502                int m = min % 60;
    503                String time = (getProperties().getPropertyBoolean("General.UseAmPm", true) ? (h > 12 ? h - 12 : h) + ":" + (m < 10 ? "0" : "") + m + (h >= 12 ? "p" : "a") : h + ":" + (m < 10 ? "0" : "") + m);
    504                info.put("Schedule Quality: Late classes", sDoubleFormat.format(5.0 * late / iStudents.size()) + " mins after " + time + " per student (" + sDoubleFormat.format(late / 12.0) + " hours total)");
    505            }
    506            int accFT = getStudentQuality().getTotalPenalty(StudentQuality.Type.AccFreeTimeOverlap, assignment);
    507            if (accFT > 0) {
    508                info.put("Accommodations: Free time conflicts", sDoubleFormat.format(5.0 * accFT / getStudentsWithAccommodation(getStudentQuality().getStudentQualityContext().getFreeTimeAccommodation())) + " mins per student, " + sDoubleFormat.format(accFT / 12.0) + " hours total");
    509            }
    510            int accBtB = getStudentQuality().getTotalPenalty(StudentQuality.Type.AccBackToBack, assignment);
    511            if (accBtB > 0) {
    512                info.put("Accommodations: Back-to-back classes", sDoubleFormat.format(((double)accBtB) / getStudentsWithAccommodation(getStudentQuality().getStudentQualityContext().getBackToBackAccommodation())) + " non-BTB classes per student, " + accBtB + " total");
    513            }
    514            int accBbc = getStudentQuality().getTotalPenalty(StudentQuality.Type.AccBreaksBetweenClasses, assignment);
    515            if (accBbc > 0) {
    516                info.put("Accommodations: Break between classes", sDoubleFormat.format(((double)accBbc) / getStudentsWithAccommodation(getStudentQuality().getStudentQualityContext().getBreakBetweenClassesAccommodation())) + " BTB classes per student, " + accBbc + " total");
    517            }
    518            int shortConfs = getStudentQuality().getTotalPenalty(StudentQuality.Type.ShortDistance, assignment);
    519            if (shortConfs > 0) {
    520                info.put("Accommodations: Distance conflicts", sDoubleFormat.format(((double)shortConfs) / getStudentsWithAccommodation(getStudentQuality().getDistanceMetric().getShortDistanceAccommodationReference())) + " short distance conflicts per student, " + shortConfs + " total");
    521            }
    522        }
    523        int nrLastLikeStudents = getNrLastLikeStudents(false);
    524        if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) {
    525            int nrRealStudents = getStudents().size() - nrLastLikeStudents;
    526            int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(assignment, false);
    527            int nrRealCompleteStudents = context.nrComplete() - nrLastLikeCompleteStudents;
    528            if (nrLastLikeStudents > 0)
    529                info.put("Projected students with complete schedule", sDecimalFormat.format(100.0
    530                        * nrLastLikeCompleteStudents / nrLastLikeStudents)
    531                        + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")");
    532            if (nrRealStudents > 0)
    533                info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents
    534                        / nrRealStudents)
    535                        + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")");
    536            int nrLastLikeRequests = getNrLastLikeRequests(false);
    537            int nrRealRequests = variables().size() - nrLastLikeRequests;
    538            int nrLastLikeAssignedRequests = context.getNrAssignedLastLikeRequests();
    539            int nrRealAssignedRequests = assignment.nrAssignedVariables() - nrLastLikeAssignedRequests;
    540            if (nrLastLikeRequests > 0)
    541                info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests / nrLastLikeRequests)
    542                        + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")");
    543            if (nrRealRequests > 0)
    544                info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests)
    545                        + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")");
    546        }
    547        context.getInfo(assignment, info);
    548
    549        double groupSpread = 0.0; double groupCount = 0;
    550        for (Offering offering: iOfferings) {
    551            for (Course course: offering.getCourses()) {
    552                for (RequestGroup group: course.getRequestGroups()) {
    553                    groupSpread += group.getAverageSpread(assignment) * group.getEnrollmentWeight(assignment, null);
    554                    groupCount += group.getEnrollmentWeight(assignment, null);
    555                }
    556            }
    557        }
    558        if (groupCount > 0)
    559            info.put("Same group", sDecimalFormat.format(100.0 * groupSpread / groupCount) + "%");
    560
    561        return info;
    562    }
    563
    564    /**
    565     * Overall solution value
    566     * @param assignment current assignment
    567     * @param precise true if should be computed
    568     * @return solution value
    569     */
    570    public double getTotalValue(Assignment<Request, Enrollment> assignment, boolean precise) {
    571        if (precise) {
    572            double total = 0;
    573            for (Request r: assignment.assignedVariables())
    574                total += r.getWeight() * iStudentWeights.getWeight(assignment, assignment.getValue(r));
    575            if (iDistanceConflict != null)
    576                for (DistanceConflict.Conflict c: iDistanceConflict.computeAllConflicts(assignment))
    577                    total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c);
    578            if (iTimeOverlaps != null)
    579                for (TimeOverlapsCounter.Conflict c: iTimeOverlaps.getContext(assignment).computeAllConflicts(assignment)) {
    580                    if (c.getR1() != null) total -= c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c);
    581                    if (c.getR2() != null) total -= c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c);
    582                }
    583            if (iStudentQuality != null)
    584                for (StudentQuality.Type t: StudentQuality.Type.values()) {
    585                    for (StudentQuality.Conflict c: iStudentQuality.getContext(assignment).computeAllConflicts(t, assignment)) {
    586                        switch (c.getType().getType()) {
    587                            case REQUEST:
    588                                if (c.getR1() instanceof CourseRequest)
    589                                    total -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    590                                else
    591                                    total -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
    592                                break;
    593                            case BOTH:
    594                                total -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    595                                total -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
    596                                break;
    597                            case LOWER:
    598                                total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    599                                break;
    600                            case HIGHER:
    601                                total -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    602                                break;
    603                        }
    604                    }
    605                }
    606            return -total;
    607        }
    608        return getContext(assignment).getTotalValue();
    609    }
    610
    611    /**
    612     * Overall solution value
    613     */
    614    @Override
    615    public double getTotalValue(Assignment<Request, Enrollment> assignment) {
    616        return getContext(assignment).getTotalValue();
    617    }
    618
    619    /**
    620     * Configuration
    621     * @return solver configuration
    622     */
    623    public DataProperties getProperties() {
    624        return iProperties;
    625    }
    626
    627    /**
    628     * Empty online student sectioning infos for all sections (see
    629     * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}).
    630     */
    631    public void clearOnlineSectioningInfos() {
    632        for (Offering offering : iOfferings) {
    633            for (Config config : offering.getConfigs()) {
    634                for (Subpart subpart : config.getSubparts()) {
    635                    for (Section section : subpart.getSections()) {
    636                        section.setSpaceExpected(0);
    637                        section.setSpaceHeld(0);
    638                    }
    639                }
    640            }
    641        }
    642    }
    643
    644    /**
    645     * Compute online student sectioning infos for all sections (see
    646     * {@link Section#getSpaceExpected()} and {@link Section#getSpaceHeld()}).
    647     * @param assignment current assignment
    648     */
    649    public void computeOnlineSectioningInfos(Assignment<Request, Enrollment> assignment) {
    650        clearOnlineSectioningInfos();
    651        for (Student student : getStudents()) {
    652            if (!student.isDummy())
    653                continue;
    654            for (Request request : student.getRequests()) {
    655                if (!(request instanceof CourseRequest))
    656                    continue;
    657                CourseRequest courseRequest = (CourseRequest) request;
    658                Enrollment enrollment = assignment.getValue(courseRequest);
    659                if (enrollment != null) {
    660                    for (Section section : enrollment.getSections()) {
    661                        section.setSpaceHeld(courseRequest.getWeight() + section.getSpaceHeld());
    662                    }
    663                }
    664                List<Enrollment> feasibleEnrollments = new ArrayList<Enrollment>();
    665                int totalLimit = 0;
    666                for (Enrollment enrl : courseRequest.values(assignment)) {
    667                    boolean overlaps = false;
    668                    for (Request otherRequest : student.getRequests()) {
    669                        if (otherRequest.equals(courseRequest) || !(otherRequest instanceof CourseRequest))
    670                            continue;
    671                        Enrollment otherErollment = assignment.getValue(otherRequest);
    672                        if (otherErollment == null)
    673                            continue;
    674                        if (enrl.isOverlapping(otherErollment)) {
    675                            overlaps = true;
    676                            break;
    677                        }
    678                    }
    679                    if (!overlaps) {
    680                        feasibleEnrollments.add(enrl);
    681                        if (totalLimit >= 0) {
    682                            int limit = enrl.getLimit();
    683                            if (limit < 0) totalLimit = -1;
    684                            else totalLimit += limit;
    685                        }
    686                    }
    687                }
    688                double increment = courseRequest.getWeight() / (totalLimit > 0 ? totalLimit : feasibleEnrollments.size());
    689                for (Enrollment feasibleEnrollment : feasibleEnrollments) {
    690                    for (Section section : feasibleEnrollment.getSections()) {
    691                        if (totalLimit > 0) {
    692                            section.setSpaceExpected(section.getSpaceExpected() + increment * feasibleEnrollment.getLimit());
    693                        } else {
    694                            section.setSpaceExpected(section.getSpaceExpected() + increment);
    695                        }
    696                    }
    697                }
    698            }
    699        }
    700    }
    701
    702    /**
    703     * Sum of weights of all requests that are not assigned (see
    704     * {@link Request#getWeight()}).
    705     * @param assignment current assignment
    706     * @return unassigned request weight
    707     */
    708    public double getUnassignedRequestWeight(Assignment<Request, Enrollment> assignment) {
    709        double weight = 0.0;
    710        for (Request request : assignment.unassignedVariables(this)) {
    711            weight += request.getWeight();
    712        }
    713        return weight;
    714    }
    715
    716    /**
    717     * Sum of weights of all requests (see {@link Request#getWeight()}).
    718     * @return total request weight
    719     */
    720    public double getTotalRequestWeight() {
    721        double weight = 0.0;
    722        for (Request request : variables()) {
    723            weight += request.getWeight();
    724        }
    725        return weight;
    726    }
    727
    728    /**
    729     * Set distance conflict extension
    730     * @param dc distance conflicts extension
    731     */
    732    public void setDistanceConflict(DistanceConflict dc) {
    733        iDistanceConflict = dc;
    734    }
    735
    736    /**
    737     * Return distance conflict extension
    738     * @return distance conflicts extension
    739     */
    740    public DistanceConflict getDistanceConflict() {
    741        return iDistanceConflict;
    742    }
    743
    744    /**
    745     * Set time overlaps extension
    746     * @param toc time overlapping conflicts extension
    747     */
    748    public void setTimeOverlaps(TimeOverlapsCounter toc) {
    749        iTimeOverlaps = toc;
    750    }
    751
    752    /**
    753     * Return time overlaps extension
    754     * @return time overlapping conflicts extension
    755     */
    756    public TimeOverlapsCounter getTimeOverlaps() {
    757        return iTimeOverlaps;
    758    }
    759
    760    public StudentQuality getStudentQuality() { return iStudentQuality; }
    761    public void setStudentQuality(StudentQuality q, boolean register) {
    762        if (iStudentQuality != null)
    763            getInfoProviders().remove(iStudentQuality);
    764        iStudentQuality = q;
    765        if (iStudentQuality != null)
    766            getInfoProviders().add(iStudentQuality);
    767        if (register) {
    768            iStudentQuality.setAssignmentContextReference(createReference(iStudentQuality));
    769            iStudentQuality.register(this);
    770        }
    771    }
    772
    773    public void setStudentQuality(StudentQuality q) {
    774        setStudentQuality(q, true);
    775    }
    776
    777    /**
    778     * Average priority of unassigned requests (see
    779     * {@link Request#getPriority()})
    780     * @param assignment current assignment
    781     * @return average priority of unassigned requests
    782     */
    783    public double avgUnassignPriority(Assignment<Request, Enrollment> assignment) {
    784        double totalPriority = 0.0;
    785        for (Request request : assignment.unassignedVariables(this)) {
    786            if (request.isAlternative())
    787                continue;
    788            totalPriority += request.getPriority();
    789        }
    790        return 1.0 + totalPriority / assignment.nrUnassignedVariables(this);
    791    }
    792
    793    /**
    794     * Average number of requests per student (see {@link Student#getRequests()}
    795     * )
    796     * @return average number of requests per student
    797     */
    798    public double avgNrRequests() {
    799        double totalRequests = 0.0;
    800        int totalStudents = 0;
    801        for (Student student : getStudents()) {
    802            if (student.nrRequests() == 0)
    803                continue;
    804            totalRequests += student.nrRequests();
    805            totalStudents++;
    806        }
    807        return totalRequests / totalStudents;
    808    }
    809
    810    /** Number of last like ({@link Student#isDummy()} equals true) students.
    811     * @param precise true if to be computed
    812     * @return number of last like (projected) students
    813     **/
    814    public int getNrLastLikeStudents(boolean precise) {
    815        if (!precise)
    816            return iNrDummyStudents;
    817        int nrLastLikeStudents = 0;
    818        for (Student student : getStudents()) {
    819            if (student.isDummy())
    820                nrLastLikeStudents++;
    821        }
    822        return nrLastLikeStudents;
    823    }
    824
    825    /** Number of real ({@link Student#isDummy()} equals false) students.
    826     * @param precise true if to be computed
    827     * @return number of real students
    828     **/
    829    public int getNrRealStudents(boolean precise) {
    830        if (!precise)
    831            return getStudents().size() - iNrDummyStudents;
    832        int nrRealStudents = 0;
    833        for (Student student : getStudents()) {
    834            if (!student.isDummy())
    835                nrRealStudents++;
    836        }
    837        return nrRealStudents;
    838    }
    839
    840    /**
    841     * Count students with given accommodation
    842     */
    843    public int getStudentsWithAccommodation(String acc) {
    844        int nrAccStudents = 0;
    845        for (Student student : getStudents()) {
    846            if (student.hasAccommodation(acc))
    847                nrAccStudents++;
    848        }
    849        return nrAccStudents;
    850    }
    851
    852    /**
    853     * Number of last like ({@link Student#isDummy()} equals true) students with
    854     * a complete schedule ({@link Student#isComplete(Assignment)} equals true).
    855     * @param assignment current assignment
    856     * @param precise true if to be computed
    857     * @return number of last like (projected) students with a complete schedule
    858     */
    859    public int getNrCompleteLastLikeStudents(Assignment<Request, Enrollment> assignment, boolean precise) {
    860        if (!precise)
    861            return getContext(assignment).getNrCompleteLastLikeStudents();
    862        int nrLastLikeStudents = 0;
    863        for (Student student : getStudents()) {
    864            if (student.isComplete(assignment) && student.isDummy())
    865                nrLastLikeStudents++;
    866        }
    867        return nrLastLikeStudents;
    868    }
    869
    870    /**
    871     * Number of real ({@link Student#isDummy()} equals false) students with a
    872     * complete schedule ({@link Student#isComplete(Assignment)} equals true).
    873     * @param assignment current assignment
    874     * @param precise true if to be computed
    875     * @return number of real students with a complete schedule
    876     */
    877    public int getNrCompleteRealStudents(Assignment<Request, Enrollment> assignment, boolean precise) {
    878        if (!precise)
    879            return getContext(assignment).nrComplete() - getContext(assignment).getNrCompleteLastLikeStudents();
    880        int nrRealStudents = 0;
    881        for (Student student : getStudents()) {
    882            if (student.isComplete(assignment) && !student.isDummy())
    883                nrRealStudents++;
    884        }
    885        return nrRealStudents;
    886    }
    887
    888    /**
    889     * Number of requests from projected ({@link Student#isDummy()} equals true)
    890     * students.
    891     * @param precise true if to be computed
    892     * @return number of requests from projected students
    893     */
    894    public int getNrLastLikeRequests(boolean precise) {
    895        if (!precise)
    896            return iNrDummyRequests;
    897        int nrLastLikeRequests = 0;
    898        for (Request request : variables()) {
    899            if (request.getStudent().isDummy())
    900                nrLastLikeRequests++;
    901        }
    902        return nrLastLikeRequests;
    903    }
    904
    905    /**
    906     * Number of requests from real ({@link Student#isDummy()} equals false)
    907     * students.
    908     * @param precise true if to be computed
    909     * @return number of requests from real students
    910     */
    911    public int getNrRealRequests(boolean precise) {
    912        if (!precise)
    913            return variables().size() - iNrDummyRequests;
    914        int nrRealRequests = 0;
    915        for (Request request : variables()) {
    916            if (!request.getStudent().isDummy())
    917                nrRealRequests++;
    918        }
    919        return nrRealRequests;
    920    }
    921
    922    /**
    923     * Number of requests from projected ({@link Student#isDummy()} equals true)
    924     * students that are assigned.
    925     * @param assignment current assignment
    926     * @param precise true if to be computed
    927     * @return number of requests from projected students that are assigned
    928     */
    929    public int getNrAssignedLastLikeRequests(Assignment<Request, Enrollment> assignment, boolean precise) {
    930        if (!precise)
    931            return getContext(assignment).getNrAssignedLastLikeRequests();
    932        int nrLastLikeRequests = 0;
    933        for (Request request : assignment.assignedVariables()) {
    934            if (request.getStudent().isDummy())
    935                nrLastLikeRequests++;
    936        }
    937        return nrLastLikeRequests;
    938    }
    939
    940    /**
    941     * Number of requests from real ({@link Student#isDummy()} equals false)
    942     * students that are assigned.
    943     * @param assignment current assignment
    944     * @param precise true if to be computed
    945     * @return number of requests from real students that are assigned
    946     */
    947    public int getNrAssignedRealRequests(Assignment<Request, Enrollment> assignment, boolean precise) {
    948        if (!precise)
    949            return assignment.nrAssignedVariables() - getContext(assignment).getNrAssignedLastLikeRequests();
    950        int nrRealRequests = 0;
    951        for (Request request : assignment.assignedVariables()) {
    952            if (!request.getStudent().isDummy())
    953                nrRealRequests++;
    954        }
    955        return nrRealRequests;
    956    }
    957
    958    /**
    959     * Model extended info. Some more information (that is more expensive to
    960     * compute) is added to an ordinary {@link Model#getInfo(Assignment)}.
    961     */
    962    @Override
    963    public Map<String, String> getExtendedInfo(Assignment<Request, Enrollment> assignment) {
    964        Map<String, String> info = getInfo(assignment);
    965        /*
    966        int nrLastLikeStudents = getNrLastLikeStudents(true);
    967        if (nrLastLikeStudents != 0 && nrLastLikeStudents != getStudents().size()) {
    968            int nrRealStudents = getStudents().size() - nrLastLikeStudents;
    969            int nrLastLikeCompleteStudents = getNrCompleteLastLikeStudents(true);
    970            int nrRealCompleteStudents = getCompleteStudents().size() - nrLastLikeCompleteStudents;
    971            info.put("Projected students with complete schedule", sDecimalFormat.format(100.0
    972                    * nrLastLikeCompleteStudents / nrLastLikeStudents)
    973                    + "% (" + nrLastLikeCompleteStudents + "/" + nrLastLikeStudents + ")");
    974            info.put("Real students with complete schedule", sDecimalFormat.format(100.0 * nrRealCompleteStudents
    975                    / nrRealStudents)
    976                    + "% (" + nrRealCompleteStudents + "/" + nrRealStudents + ")");
    977            int nrLastLikeRequests = getNrLastLikeRequests(true);
    978            int nrRealRequests = variables().size() - nrLastLikeRequests;
    979            int nrLastLikeAssignedRequests = getNrAssignedLastLikeRequests(true);
    980            int nrRealAssignedRequests = assignedVariables().size() - nrLastLikeAssignedRequests;
    981            info.put("Projected assigned requests", sDecimalFormat.format(100.0 * nrLastLikeAssignedRequests
    982                    / nrLastLikeRequests)
    983                    + "% (" + nrLastLikeAssignedRequests + "/" + nrLastLikeRequests + ")");
    984            info.put("Real assigned requests", sDecimalFormat.format(100.0 * nrRealAssignedRequests / nrRealRequests)
    985                    + "% (" + nrRealAssignedRequests + "/" + nrRealRequests + ")");
    986        }
    987        */
    988        // info.put("Average unassigned priority", sDecimalFormat.format(avgUnassignPriority()));
    989        // info.put("Average number of requests", sDecimalFormat.format(avgNrRequests()));
    990
    991        /*
    992        double total = 0;
    993        for (Request r: variables())
    994            if (r.getAssignment() != null)
    995                total += r.getWeight() * iStudentWeights.getWeight(r.getAssignment());
    996        */
    997        /*
    998        double dc = 0;
    999        if (getDistanceConflict() != null && getDistanceConflict().getTotalNrConflicts(assignment) != 0) {
    1000            Set<DistanceConflict.Conflict> conf = getDistanceConflict().getAllConflicts(assignment);
    1001            int sdc = 0;
    1002            for (DistanceConflict.Conflict c: conf) {
    1003                dc += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c);
    1004                if (c.getStudent().isNeedShortDistances()) sdc ++;
    1005            }
    1006            if (!conf.isEmpty())
    1007                info.put("Student distance conflicts", conf.size() + (sdc > 0 ? " (" + getDistanceConflict().getDistanceMetric().getShortDistanceAccommodationReference() + ": " + sdc + ", weighted: " : " (weighted: ") + sDecimalFormat.format(dc) + ")");
    1008        }
    1009        */
    1010        if (getStudentQuality() == null && getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) {
    1011            Set<TimeOverlapsCounter.Conflict> conf = getTimeOverlaps().getContext(assignment).computeAllConflicts(assignment);
    1012            int share = 0, crShare = 0;
    1013            for (TimeOverlapsCounter.Conflict c: conf) {
    1014                share += c.getShare();
    1015                if (c.getR1() instanceof CourseRequest && c.getR2() instanceof CourseRequest)
    1016                    crShare += c.getShare();
    1017            }
    1018            if (share > 0)
    1019                info.put("Time overlapping conflicts", sDoubleFormat.format(5.0 * share / iStudents.size()) + " mins per student\n(" + sDoubleFormat.format(5.0 * crShare / iStudents.size()) + " between courses; " + sDoubleFormat.format(getTimeOverlaps().getTotalNrConflicts(assignment) / 12.0) + " hours total)");
    1020        }
    1021        /*
    1022        info.put("Overall solution value", sDecimalFormat.format(total - dc - toc) + (dc == 0.0 && toc == 0.0 ? "" :
    1023            " (" + (dc != 0.0 ? "distance: " + sDecimalFormat.format(dc): "") + (dc != 0.0 && toc != 0.0 ? ", " : "") +
    1024            (toc != 0.0 ? "overlap: " + sDecimalFormat.format(toc) : "") + ")")
    1025            );
    1026        */
    1027
    1028        double disbWeight = 0;
    1029        int disbSections = 0;
    1030        int disb10Sections = 0;
    1031        int disb10Limit = getProperties().getPropertyInt("Info.ListDisbalancedSections", 0);
    1032        Set<String> disb10SectionList = (disb10Limit == 0 ? null : new TreeSet<String>());
    1033        for (Offering offering: getOfferings()) {
    1034            for (Config config: offering.getConfigs()) {
    1035                double enrl = config.getEnrollmentTotalWeight(assignment, null);
    1036                for (Subpart subpart: config.getSubparts()) {
    1037                    if (subpart.getSections().size() <= 1) continue;
    1038                    if (subpart.getLimit() > 0) {
    1039                        // sections have limits -> desired size is section limit x (total enrollment / total limit)
    1040                        double ratio = enrl / subpart.getLimit();
    1041                        for (Section section: subpart.getSections()) {
    1042                            double desired = ratio * section.getLimit();
    1043                            disbWeight += Math.abs(section.getEnrollmentTotalWeight(assignment, null) - desired);
    1044                            disbSections ++;
    1045                            if (Math.abs(desired - section.getEnrollmentTotalWeight(assignment, null)) >= Math.max(1.0, 0.1 * section.getLimit())) {
    1046                                disb10Sections++;
    1047                                if (disb10SectionList != null)
    1048                                        disb10SectionList.add(section.getSubpart().getConfig().getOffering().getName() + " " + section.getSubpart().getName() + " " + section.getName());
    1049                            }
    1050                        }
    1051                    } else {
    1052                        // unlimited sections -> desired size is total enrollment / number of sections
    1053                        for (Section section: subpart.getSections()) {
    1054                            double desired = enrl / subpart.getSections().size();
    1055                            disbWeight += Math.abs(section.getEnrollmentTotalWeight(assignment, null) - desired);
    1056                            disbSections ++;
    1057                            if (Math.abs(desired - section.getEnrollmentTotalWeight(assignment, null)) >= Math.max(1.0, 0.1 * desired)) {
    1058                                disb10Sections++;
    1059                                if (disb10SectionList != null)
    1060                                        disb10SectionList.add(section.getSubpart().getConfig().getOffering().getName() + " " + section.getSubpart().getName() + " " + section.getName());
    1061                            }
    1062                        }
    1063                    }
    1064                }
    1065            }
    1066        }
    1067        if (disbSections != 0) {
    1068            double assignedCRWeight = getContext(assignment).getAssignedCourseRequestWeight();
    1069            info.put("Average disbalance", sDecimalFormat.format(assignedCRWeight == 0 ? 0.0 : 100.0 * disbWeight / assignedCRWeight) + "% (" + sDecimalFormat.format(disbWeight / disbSections) + ")");
    1070            String list = "";
    1071            if (disb10SectionList != null) {
    1072                int i = 0;
    1073                for (String section: disb10SectionList) {
    1074                    if (i == disb10Limit) {
    1075                        list += "\n...";
    1076                        break;
    1077                    }
    1078                    list += "\n" + section;
    1079                    i++;
    1080                }
    1081            }
    1082            info.put("Sections disbalanced by 10% or more", sDecimalFormat.format(disbSections == 0 ? 0.0 : 100.0 * disb10Sections / disbSections) + "% (" + disb10Sections + ")" + (list.isEmpty() ? "" : "\n" + list));
    1083        }
    1084
    1085        int assCR = 0, priCR = 0;
    1086        for (Request r: variables()) {
    1087            if (r instanceof CourseRequest && !r.getStudent().isDummy()) {
    1088                CourseRequest cr = (CourseRequest)r;
    1089                Enrollment e = assignment.getValue(cr);
    1090                if (e != null) {
    1091                    assCR ++;
    1092                    if (!cr.isAlternative() && cr.getCourses().get(0).equals(e.getCourse())) priCR ++;
    1093                }
    1094            }
    1095        }
    1096        if (assCR > 0)
    1097            info.put("Assigned priority course requests", sDoubleFormat.format(100.0 * priCR / assCR) + "% (" + priCR + "/" + assCR + ")");
    1098        int[] missing = new int[] {0, 0, 0, 0, 0};
    1099        int incomplete = 0;
    1100        for (Student student: getStudents()) {
    1101            if (student.isDummy()) continue;
    1102            int nrRequests = 0;
    1103            int nrAssignedRequests = 0;
    1104            for (Request r : student.getRequests()) {
    1105                if (!(r instanceof CourseRequest)) continue; // ignore free times
    1106                if (!r.isAlternative()) nrRequests++;
    1107                if (r.isAssigned(assignment)) nrAssignedRequests++;
    1108            }
    1109            if (nrAssignedRequests < nrRequests) {
    1110                missing[Math.min(nrRequests - nrAssignedRequests, missing.length) - 1] ++;
    1111                incomplete ++;
    1112            }
    1113        }
    1114
    1115        for (int i = 0; i < missing.length; i++)
    1116            if (missing[i] > 0)
    1117                info.put("Students missing " + (i == 0 ? "1 course" : i + 1 == missing.length ? (i + 1) + " or more courses" : (i + 1) + " courses"), sDecimalFormat.format(100.0 * missing[i] / incomplete) + "% (" + missing[i] + ")");
    1118
    1119        info.put("Overall solution value", sDoubleFormat.format(getTotalValue(assignment)));// + " [precise: " + sDoubleFormat.format(getTotalValue(assignment, true)) + "]");
    1120
    1121        int nrStudentsBelowMinCredit = 0, nrStudents = 0;
    1122        for (Student student: getStudents()) {
    1123            if (student.isDummy()) continue;
    1124            if (student.hasMinCredit()) {
    1125                nrStudents++;
    1126                float credit = student.getAssignedCredit(assignment);
    1127                if (credit < student.getMinCredit() && !student.isComplete(assignment))
    1128                    nrStudentsBelowMinCredit ++;
    1129            }
    1130        }
    1131        if (nrStudentsBelowMinCredit > 0)
    1132            info.put("Students below min credit", sDoubleFormat.format(100.0 * nrStudentsBelowMinCredit / nrStudents) + "% (" + nrStudentsBelowMinCredit + "/" + nrStudents + ")");
    1133
    1134        int[] notAssignedPriority = new int[] {0, 0, 0, 0, 0, 0, 0};
    1135        int[] assignedChoice = new int[] {0, 0, 0, 0, 0};
    1136        int notAssignedTotal = 0, assignedChoiceTotal = 0;
    1137        int avgPriority = 0, avgChoice = 0;
    1138        for (Student student: getStudents()) {
    1139            if (student.isDummy()) continue;
    1140            for (Request r : student.getRequests()) {
    1141                if (!(r instanceof CourseRequest)) continue; // ignore free times
    1142                Enrollment e = r.getAssignment(assignment);
    1143                if (e == null) {
    1144                    if (!r.isAlternative()) {
    1145                        notAssignedPriority[Math.min(r.getPriority(), notAssignedPriority.length - 1)] ++;
    1146                        notAssignedTotal ++;
    1147                        avgPriority += r.getPriority();
    1148                    }
    1149                } else {
    1150                    assignedChoice[Math.min(e.getTruePriority(), assignedChoice.length - 1)] ++;
    1151                    assignedChoiceTotal ++;
    1152                    avgChoice += e.getTruePriority();
    1153                }
    1154            }
    1155        }
    1156        for (int i = 0; i < notAssignedPriority.length; i++)
    1157            if (notAssignedPriority[i] > 0)
    1158                info.put("Priority: Not-assigned priority " + (i + 1 == notAssignedPriority.length ? (i + 1) + "+" : (i + 1)) + " course requests", sDecimalFormat.format(100.0 * notAssignedPriority[i] / notAssignedTotal) + "% (" + notAssignedPriority[i] + ")");
    1159        if (notAssignedTotal > 0)
    1160            info.put("Priority: Average not-assigned priority", sDecimalFormat.format(1.0 + ((double)avgPriority) / notAssignedTotal));
    1161        for (int i = 0; i < assignedChoice.length; i++)
    1162            if (assignedChoice[i] > 0)
    1163                info.put("Choice: assigned " + (i == 0 ? "1st": i == 1 ? "2nd" : i == 2 ? "3rd" : i + 1 == assignedChoice.length ? (i + 1) + "th+" : (i + 1) + "th") + " course choice", sDecimalFormat.format(100.0 * assignedChoice[i] / assignedChoiceTotal) + "% (" + assignedChoice[i] + ")");
    1164        if (assignedChoiceTotal > 0)
    1165            info.put("Choice: Average assigned choice", sDecimalFormat.format(1.0 + ((double)avgChoice) / assignedChoiceTotal));
    1166
    1167        int nbrSections = 0, nbrFullSections = 0, nbrSections98 = 0, nbrSections95 = 0, nbrSections90 = 0, nbrSectionsDis = 0;
    1168        int enrlSections = 0, enrlFullSections = 0, enrlSections98 = 0, enrlSections95 = 0, enrlSections90 = 0, enrlSectionsDis = 0;
    1169        int nbrOfferings = 0, nbrFullOfferings = 0, nbrOfferings98 = 0, nbrOfferings95 = 0, nbrOfferings90 = 0;
    1170        int enrlOfferings = 0, enrlOfferingsFull = 0, enrlOfferings98 = 0, enrlOfferings95 = 0, enrlOfferings90 = 0;
    1171        for (Offering offering: getOfferings()) {
    1172            int offeringLimit = 0, offeringEnrollment = 0;
    1173            for (Config config: offering.getConfigs()) {
    1174                int configLimit = config.getLimit();
    1175                for (Subpart subpart: config.getSubparts()) {
    1176                    int subpartLimit = 0;
    1177                    for (Section section: subpart.getSections()) {
    1178                        if (section.isCancelled()) continue;
    1179                        int enrl = section.getEnrollments(assignment).size();
    1180                        if (section.getLimit() < 0 || subpartLimit < 0)
    1181                            subpartLimit = -1;
    1182                        else
    1183                            subpartLimit += (section.isEnabled() ? section.getLimit() : enrl);
    1184                        nbrSections ++;
    1185                        enrlSections += enrl;
    1186                        if (section.getLimit() >= 0 && section.getLimit() <= enrl) {
    1187                            nbrFullSections ++;
    1188                            enrlFullSections += enrl;
    1189                        }
    1190                        if (!section.isEnabled() && (enrl > 0 || section.getLimit() >= 0)) {
    1191                            nbrSectionsDis ++;
    1192                            enrlSectionsDis += enrl;
    1193                        }
    1194                        if (section.getLimit() >= 0 && (section.getLimit() - enrl) <= Math.round(0.02 * section.getLimit())) {
    1195                            nbrSections98 ++;
    1196                            enrlSections98 += enrl;
    1197                        }
    1198                        if (section.getLimit() >= 0 && (section.getLimit() - enrl) <= Math.round(0.05 * section.getLimit())) {
    1199                            nbrSections95 ++;
    1200                            enrlSections95 += enrl;
    1201                        }
    1202                        if (section.getLimit() >= 0 && (section.getLimit() - enrl) <= Math.round(0.10 * section.getLimit())) {
    1203                            nbrSections90 ++;
    1204                            enrlSections90 += enrl;
    1205                        }
    1206                    }
    1207                    if (configLimit < 0 || subpartLimit < 0)
    1208                        configLimit = -1;
    1209                    else
    1210                        configLimit = Math.min(configLimit, subpartLimit);
    1211                }
    1212                if (offeringLimit < 0 || configLimit < 0)
    1213                    offeringLimit = -1;
    1214                else
    1215                    offeringLimit += configLimit;
    1216                offeringEnrollment += config.getEnrollments(assignment).size();
    1217            }
    1218            nbrOfferings ++;
    1219            enrlOfferings += offeringEnrollment;
    1220
    1221            if (offeringLimit >=0 && offeringEnrollment >= offeringLimit) {
    1222                nbrFullOfferings ++;
    1223                enrlOfferingsFull += offeringEnrollment;
    1224            }
    1225            if (offeringLimit >= 0 && (offeringLimit - offeringEnrollment) <= Math.round(0.02 * offeringLimit)) {
    1226                nbrOfferings98++;
    1227                enrlOfferings98 += offeringEnrollment;
    1228            }
    1229            if (offeringLimit >= 0 && (offeringLimit - offeringEnrollment) <= Math.round(0.05 * offeringLimit)) {
    1230                nbrOfferings95++;
    1231                enrlOfferings95 += offeringEnrollment;
    1232            }
    1233            if (offeringLimit >= 0 && (offeringLimit - offeringEnrollment) <= Math.round(0.10 * offeringLimit)) {
    1234                nbrOfferings90++;
    1235                enrlOfferings90 += offeringEnrollment;
    1236            }
    1237        }
    1238        if (enrlOfferings90 > 0 && enrlOfferings > 0)
    1239            info.put("Full Offerings", (nbrFullOfferings > 0 ? nbrFullOfferings + " with no space (" + sDecimalFormat.format(100.0 * nbrFullOfferings / nbrOfferings) + "% of all offerings, " +
    1240                    sDecimalFormat.format(100.0 * enrlOfferingsFull / enrlOfferings) + "% assignments)\n" : "")+
    1241                    (nbrOfferings98 > nbrFullOfferings ? nbrOfferings98 + " with &leq; 2% available (" + sDecimalFormat.format(100.0 * nbrOfferings98 / nbrOfferings) + "% of all offerings, " +
    1242                    sDecimalFormat.format(100.0 * enrlOfferings98 / enrlOfferings) + "% assignments)\n" : "")+
    1243                    (nbrOfferings95 > nbrOfferings98 ? nbrOfferings95 + " with &leq; 5% available (" + sDecimalFormat.format(100.0 * nbrOfferings95 / nbrOfferings) + "% of all offerings, " +
    1244                    sDecimalFormat.format(100.0 * enrlOfferings95 / enrlOfferings) + "% assignments)\n" : "")+
    1245                    (nbrOfferings90 > nbrOfferings95 ? nbrOfferings90 + " with &leq; 10% available (" + sDecimalFormat.format(100.0 * nbrOfferings90 / nbrOfferings) + "% of all offerings, " +
    1246                    sDecimalFormat.format(100.0 * enrlOfferings90 / enrlOfferings) + "% assignments)" : ""));
    1247        if ((enrlSections90 > 0 || nbrSectionsDis > 0) && enrlSections > 0)
    1248            info.put("Full Sections", (nbrFullSections > 0 ? nbrFullSections + " with no space (" + sDecimalFormat.format(100.0 * nbrFullSections / nbrSections) + "% of all sections, "+
    1249                    sDecimalFormat.format(100.0 * enrlFullSections / enrlSections) + "% assignments)\n" : "") +
    1250                    (nbrSectionsDis > 0 ? nbrSectionsDis + " disabled (" + sDecimalFormat.format(100.0 * nbrSectionsDis / nbrSections) + "% of all sections, "+
    1251                    sDecimalFormat.format(100.0 * enrlSectionsDis / enrlSections) + "% assignments)\n" : "") +
    1252                    (enrlSections98 > nbrFullSections ? nbrSections98 + " with &leq; 2% available (" + sDecimalFormat.format(100.0 * nbrSections98 / nbrSections) + "% of all sections, " +
    1253                    sDecimalFormat.format(100.0 * enrlSections98 / enrlSections) + "% assignments)\n" : "") +
    1254                    (nbrSections95 > enrlSections98 ? nbrSections95 + " with &leq; 5% available (" + sDecimalFormat.format(100.0 * nbrSections95 / nbrSections) + "% of all sections, " +
    1255                    sDecimalFormat.format(100.0 * enrlSections95 / enrlSections) + "% assignments)\n" : "") +
    1256                    (nbrSections90 > nbrSections95 ? nbrSections90 + " with &leq; 10% available (" + sDecimalFormat.format(100.0 * nbrSections90 / nbrSections) + "% of all sections, " +
    1257                    sDecimalFormat.format(100.0 * enrlSections90 / enrlSections) + "% assignments)" : ""));
    1258        if (getStudentQuality() != null) {
    1259            int shareCR = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.CourseTimeOverlap, assignment);
    1260            int shareFT = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.FreeTimeOverlap, assignment);
    1261            int shareUN = getStudentQuality().getContext(assignment).countTotalPenalty(StudentQuality.Type.Unavailability, assignment);
    1262            if (shareCR > 0) {
    1263                Set<Student> students = new HashSet<Student>();
    1264                for (StudentQuality.Conflict c: getStudentQuality().getContext(assignment).computeAllConflicts(StudentQuality.Type.CourseTimeOverlap, assignment)) {
    1265                    students.add(c.getStudent());
    1266                }
    1267                info.put("Time overlaps: courses", students.size() + " students (avg " + sDoubleFormat.format(5.0 * shareCR / students.size()) + " mins)");
    1268            }
    1269            if (shareFT > 0) {
    1270                Set<Student> students = new HashSet<Student>();
    1271                for (StudentQuality.Conflict c: getStudentQuality().getContext(assignment).computeAllConflicts(StudentQuality.Type.FreeTimeOverlap, assignment)) {
    1272                    students.add(c.getStudent());
    1273                }
    1274                info.put("Time overlaps: free times", students.size() + " students (avg " + sDoubleFormat.format(5.0 * shareFT / students.size()) + " mins)");
    1275            }
    1276            if (shareUN > 0) {
    1277                Set<Student> students = new HashSet<Student>();
    1278                for (StudentQuality.Conflict c: getStudentQuality().getContext(assignment).computeAllConflicts(StudentQuality.Type.Unavailability, assignment)) {
    1279                    students.add(c.getStudent());
    1280                }
    1281                info.put("Time overlaps: teaching assignments", students.size() + " students (avg " + sDoubleFormat.format(5.0 * shareUN / students.size()) + " mins)");
    1282            }
    1283        } else if (getTimeOverlaps() != null && getTimeOverlaps().getTotalNrConflicts(assignment) != 0) {
    1284            Set<TimeOverlapsCounter.Conflict> conf = getTimeOverlaps().getContext(assignment).computeAllConflicts(assignment);
    1285            int shareCR = 0, shareFT = 0, shareUN = 0;
    1286            Set<Student> studentsCR = new HashSet<Student>();
    1287            Set<Student> studentsFT = new HashSet<Student>();
    1288            Set<Student> studentsUN = new HashSet<Student>();
    1289            for (TimeOverlapsCounter.Conflict c: conf) {
    1290                if (c.getR1() instanceof CourseRequest && c.getR2() instanceof CourseRequest) {
    1291                    shareCR += c.getShare(); studentsCR.add(c.getStudent());
    1292                } else if (c.getS2() instanceof Unavailability) {
    1293                    shareUN += c.getShare(); studentsUN.add(c.getStudent());
    1294                } else {
    1295                    shareFT += c.getShare(); studentsFT.add(c.getStudent());
    1296                }
    1297            }
    1298            if (shareCR > 0)
    1299                info.put("Time overlaps: courses", studentsCR.size() + " students (avg " + sDoubleFormat.format(5.0 * shareCR / studentsCR.size()) + " mins)");
    1300            if (shareFT > 0)
    1301                info.put("Time overlaps: free times", studentsFT.size() + " students (avg " + sDoubleFormat.format(5.0 * shareFT / studentsFT.size()) + " mins)");
    1302            if (shareUN > 0)
    1303                info.put("Time overlaps: teaching assignments", studentsUN.size() + " students (avg " + sDoubleFormat.format(5.0 * shareUN / studentsUN.size()) + " mins)");
    1304        }
    1305
    1306
    1307        return info;
    1308    }
    1309
    1310    @Override
    1311    public void restoreBest(Assignment<Request, Enrollment> assignment) {
    1312        restoreBest(assignment, new Comparator<Request>() {
    1313            @Override
    1314            public int compare(Request r1, Request r2) {
    1315                Enrollment e1 = r1.getBestAssignment();
    1316                Enrollment e2 = r2.getBestAssignment();
    1317                // Reservations first
    1318                if (e1.getReservation() != null && e2.getReservation() == null) return -1;
    1319                if (e1.getReservation() == null && e2.getReservation() != null) return 1;
    1320                // Then assignment iteration (i.e., order in which assignments were made)
    1321                if (r1.getBestAssignmentIteration() != r2.getBestAssignmentIteration())
    1322                    return (r1.getBestAssignmentIteration() < r2.getBestAssignmentIteration() ? -1 : 1);
    1323                // Then student and priority
    1324                return r1.compareTo(r2);
    1325            }
    1326        });
    1327        recomputeTotalValue(assignment);
    1328    }
    1329
    1330    public void recomputeTotalValue(Assignment<Request, Enrollment> assignment) {
    1331        getContext(assignment).iTotalValue = getTotalValue(assignment, true);
    1332    }
    1333
    1334    @Override
    1335    public void saveBest(Assignment<Request, Enrollment> assignment) {
    1336        recomputeTotalValue(assignment);
    1337        iBestAssignedCourseRequestWeight = getContext(assignment).getAssignedCourseRequestWeight();
    1338        super.saveBest(assignment);
    1339    }
    1340
    1341    public double getBestAssignedCourseRequestWeight() {
    1342        return iBestAssignedCourseRequestWeight;
    1343    }
    1344
    1345    @Override
    1346    public String toString(Assignment<Request, Enrollment> assignment) {
    1347        double groupSpread = 0.0; double groupCount = 0;
    1348        for (Offering offering: iOfferings) {
    1349            for (Course course: offering.getCourses()) {
    1350                for (RequestGroup group: course.getRequestGroups()) {
    1351                    groupSpread += group.getAverageSpread(assignment) * group.getEnrollmentWeight(assignment, null);
    1352                    groupCount += group.getEnrollmentWeight(assignment, null);
    1353                }
    1354            }
    1355        }
    1356        String priority = "";
    1357        for (StudentPriority sp: StudentPriority.values()) {
    1358            if (sp.ordinal() < StudentPriority.Normal.ordinal()) {
    1359                if (iTotalPriorityCRWeight[sp.ordinal()] > 0.0)
    1360                    priority += sp.code() + "PCR:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCRWeight[sp.ordinal()] / iTotalPriorityCRWeight[sp.ordinal()]) + "%, ";
    1361                if (iTotalPriorityCriticalCRWeight[RequestPriority.Critical.ordinal()][sp.ordinal()] > 0.0)
    1362                    priority += sp.code() + "PCC:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCriticalCRWeight[RequestPriority.Critical.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[RequestPriority.Critical.ordinal()][sp.ordinal()]) + "%, ";
    1363                if (iTotalPriorityCriticalCRWeight[RequestPriority.Important.ordinal()][sp.ordinal()] > 0.0)
    1364                    priority += sp.code() + "PCI:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedPriorityCriticalCRWeight[RequestPriority.Important.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[RequestPriority.Important.ordinal()][sp.ordinal()]) + "%, ";
    1365            }
    1366        }
    1367        return   (getNrRealStudents(false) > 0 ? "RRq:" + getNrAssignedRealRequests(assignment, false) + "/" + getNrRealRequests(false) + ", " : "")
    1368                + (getNrLastLikeStudents(false) > 0 ? "DRq:" + getNrAssignedLastLikeRequests(assignment, false) + "/" + getNrLastLikeRequests(false) + ", " : "")
    1369                + (getNrRealStudents(false) > 0 ? "RS:" + getNrCompleteRealStudents(assignment, false) + "/" + getNrRealStudents(false) + ", " : "")
    1370                + (getNrLastLikeStudents(false) > 0 ? "DS:" + getNrCompleteLastLikeStudents(assignment, false) + "/" + getNrLastLikeStudents(false) + ", " : "")
    1371                + (iTotalCRWeight > 0.0 ? "CR:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCourseRequestWeight() / iTotalCRWeight) + "%, " : "")
    1372                + (iTotalSelCRWeight > 0.0 ? "S:" + sDoubleFormat.format(100.0 * (0.3 * getContext(assignment).iAssignedSelectedConfigWeight + 0.7 * getContext(assignment).iAssignedSelectedSectionWeight) / iTotalSelCRWeight) + "%, ": "")
    1373                + (iTotalCriticalCRWeight[RequestPriority.Critical.ordinal()] > 0.0 ? "CC:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCriticalCourseRequestWeight(RequestPriority.Critical) / iTotalCriticalCRWeight[RequestPriority.Critical.ordinal()]) + "%, " : "")
    1374                + (iTotalCriticalCRWeight[RequestPriority.Important.ordinal()] > 0.0 ? "IC:" + sDecimalFormat.format(100.0 * getContext(assignment).getAssignedCriticalCourseRequestWeight(RequestPriority.Important) / iTotalCriticalCRWeight[RequestPriority.Important.ordinal()]) + "%, " : "")
    1375                + priority
    1376                + "V:" + sDecimalFormat.format(-getTotalValue(assignment))
    1377                + (getDistanceConflict() == null ? "" : ", DC:" + getDistanceConflict().getTotalNrConflicts(assignment))
    1378                + (getTimeOverlaps() == null ? "" : ", TOC:" + getTimeOverlaps().getTotalNrConflicts(assignment))
    1379                + (iMPP ? ", IS:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedSameSectionWeight / iTotalMPPCRWeight) + "%" : "")
    1380                + (iMPP ? ", IT:" + sDecimalFormat.format(100.0 * getContext(assignment).iAssignedSameTimeWeight / iTotalMPPCRWeight) + "%" : "")
    1381                + ", %:" + sDecimalFormat.format(-100.0 * getTotalValue(assignment) / (getStudents().size() - iNrDummyStudents +
    1382                        (iProjectedStudentWeight < 0.0 ? iNrDummyStudents * (iTotalDummyWeight / iNrDummyRequests) :iProjectedStudentWeight * iTotalDummyWeight)))
    1383                + (groupCount > 0 ? ", SG:" + sDecimalFormat.format(100.0 * groupSpread / groupCount) + "%" : "")
    1384                + (getStudentQuality() == null ? "" : ", SQ:{" + getStudentQuality().toString(assignment) + "}");
    1385    }
    1386
    1387    /**
    1388     * Quadratic average of two weights.
    1389     * @param w1 first weight
    1390     * @param w2 second weight
    1391     * @return average of the two weights
    1392     */
    1393    public double avg(double w1, double w2) {
    1394        return Math.sqrt(w1 * w2);
    1395    }
    1396
    1397    /**
    1398     * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit.
    1399     * @return maximal domain size, -1 if unlimited
    1400     */
    1401    public int getMaxDomainSize() { return iMaxDomainSize; }
    1402
    1403    /**
    1404     * Maximal domain size (i.e., number of enrollments of a course request), -1 if there is no limit.
    1405     * @param maxDomainSize maximal domain size, -1 if unlimited
    1406     */
    1407    public void setMaxDomainSize(int maxDomainSize) { iMaxDomainSize = maxDomainSize; }
    1408
    1409
    1410    @Override
    1411    public StudentSectioningModelContext createAssignmentContext(Assignment<Request, Enrollment> assignment) {
    1412        return new StudentSectioningModelContext(assignment);
    1413    }
    1414
    1415    public class StudentSectioningModelContext implements AssignmentConstraintContext<Request, Enrollment>, InfoProvider<Request, Enrollment>{
    1416        private Set<Student> iCompleteStudents = new HashSet<Student>();
    1417        private double iTotalValue = 0.0;
    1418        private int iNrAssignedDummyRequests = 0, iNrCompleteDummyStudents = 0;
    1419        private double iAssignedCRWeight = 0.0, iAssignedDummyCRWeight = 0.0;
    1420        private double[] iAssignedCriticalCRWeight;
    1421        private double[][] iAssignedPriorityCriticalCRWeight;
    1422        private double iReservedSpace = 0.0, iTotalReservedSpace = 0.0;
    1423        private double iAssignedSameSectionWeight = 0.0, iAssignedSameChoiceWeight = 0.0, iAssignedSameTimeWeight = 0.0;
    1424        private double iAssignedSelectedSectionWeight = 0.0, iAssignedSelectedConfigWeight = 0.0;
    1425        private double iAssignedNoTimeSectionWeight = 0.0;
    1426        private double iAssignedOnlineSectionWeight = 0.0;
    1427        private double iAssignedPastSectionWeight = 0.0;
    1428        private int[] iNrCompletePriorityStudents = null;
    1429        private double[] iAssignedPriorityCRWeight = null;
    1430
    1431        public StudentSectioningModelContext(StudentSectioningModelContext parent) {
    1432            iCompleteStudents = new HashSet<Student>(parent.iCompleteStudents);
    1433            iTotalValue = parent.iTotalValue;
    1434            iNrAssignedDummyRequests = parent.iNrAssignedDummyRequests;
    1435            iNrCompleteDummyStudents = parent.iNrCompleteDummyStudents;
    1436            iAssignedCRWeight = parent.iAssignedCRWeight;
    1437            iAssignedDummyCRWeight = parent.iAssignedDummyCRWeight;
    1438            iReservedSpace = parent.iReservedSpace;
    1439            iTotalReservedSpace = parent.iTotalReservedSpace;
    1440            iAssignedSameSectionWeight = parent.iAssignedSameSectionWeight;
    1441            iAssignedSameChoiceWeight = parent.iAssignedSameChoiceWeight;
    1442            iAssignedSameTimeWeight = parent.iAssignedSameTimeWeight;
    1443            iAssignedSelectedSectionWeight = parent.iAssignedSelectedSectionWeight;
    1444            iAssignedSelectedConfigWeight = parent.iAssignedSelectedConfigWeight;
    1445            iAssignedNoTimeSectionWeight = parent.iAssignedNoTimeSectionWeight;
    1446            iAssignedOnlineSectionWeight = parent.iAssignedOnlineSectionWeight;
    1447            iAssignedPastSectionWeight = parent.iAssignedPastSectionWeight;
    1448            iAssignedCriticalCRWeight = new double[RequestPriority.values().length];
    1449            iAssignedPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length];
    1450            for (int i = 0; i < RequestPriority.values().length; i++) {
    1451                iAssignedCriticalCRWeight[i] = parent.iAssignedCriticalCRWeight[i];
    1452                for (int j = 0; j < StudentPriority.values().length; j++) {
    1453                    iAssignedPriorityCriticalCRWeight[i][j] = parent.iAssignedPriorityCriticalCRWeight[i][j];
    1454                }
    1455            }
    1456            iNrCompletePriorityStudents = new int[StudentPriority.values().length];
    1457            iAssignedPriorityCRWeight = new double[StudentPriority.values().length];
    1458            for (int i = 0; i < StudentPriority.values().length; i++) {
    1459                iNrCompletePriorityStudents[i] = parent.iNrCompletePriorityStudents[i];
    1460                iAssignedPriorityCRWeight[i] = parent.iAssignedPriorityCRWeight[i];
    1461            }
    1462        }
    1463
    1464        public StudentSectioningModelContext(Assignment<Request, Enrollment> assignment) {
    1465            iAssignedCriticalCRWeight = new double[RequestPriority.values().length];
    1466            iAssignedPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length];
    1467            for (int i = 0; i < RequestPriority.values().length; i++) {
    1468                iAssignedCriticalCRWeight[i] = 0.0;
    1469                for (int j = 0; j < StudentPriority.values().length; j++) {
    1470                    iAssignedPriorityCriticalCRWeight[i][j] = 0.0;
    1471                }
    1472            }
    1473            iNrCompletePriorityStudents = new int[StudentPriority.values().length];
    1474            iAssignedPriorityCRWeight = new double[StudentPriority.values().length];
    1475            for (int i = 0; i < StudentPriority.values().length; i++) {
    1476                iNrCompletePriorityStudents[i] = 0;
    1477                iAssignedPriorityCRWeight[i] = 0.0;
    1478            }
    1479            for (Request request: variables()) {
    1480                Enrollment enrollment = assignment.getValue(request);
    1481                if (enrollment != null)
    1482                    assigned(assignment, enrollment);
    1483            }
    1484        }
    1485
    1486        /**
    1487         * Called after an enrollment was assigned to a request. The list of
    1488         * complete students and the overall solution value are updated.
    1489         */
    1490        @Override
    1491        public void assigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    1492            Student student = enrollment.getStudent();
    1493            if (student.isComplete(assignment) && iCompleteStudents.add(student)) {
    1494                if (student.isDummy()) iNrCompleteDummyStudents++;
    1495                iNrCompletePriorityStudents[student.getPriority().ordinal()]++;
    1496            }
    1497            double value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(assignment, enrollment);
    1498            iTotalValue -= value;
    1499            enrollment.variable().getContext(assignment).setLastWeight(value);
    1500            if (enrollment.isCourseRequest())
    1501                iAssignedCRWeight += enrollment.getRequest().getWeight();
    1502            if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getStudent().isDummy() && !enrollment.getRequest().isAlternative())
    1503                iAssignedCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()] += enrollment.getRequest().getWeight();
    1504            if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getRequest().isAlternative())
    1505                iAssignedPriorityCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()][enrollment.getStudent().getPriority().ordinal()] += enrollment.getRequest().getWeight();
    1506            if (enrollment.getRequest().isMPP()) {
    1507                iAssignedSameSectionWeight += enrollment.getRequest().getWeight() * enrollment.percentInitial();
    1508                iAssignedSameChoiceWeight += enrollment.getRequest().getWeight() * enrollment.percentSelected();
    1509                iAssignedSameTimeWeight += enrollment.getRequest().getWeight() * enrollment.percentSameTime();
    1510            }
    1511            if (enrollment.getRequest().hasSelection()) {
    1512                iAssignedSelectedSectionWeight += enrollment.getRequest().getWeight() * enrollment.percentSelectedSameSection();
    1513                iAssignedSelectedConfigWeight += enrollment.getRequest().getWeight() * enrollment.percentSelectedSameConfig();
    1514            }
    1515            if (enrollment.getReservation() != null)
    1516                iReservedSpace += enrollment.getRequest().getWeight();
    1517            if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations())
    1518                iTotalReservedSpace += enrollment.getRequest().getWeight();
    1519            if (student.isDummy()) {
    1520                iNrAssignedDummyRequests++;
    1521                if (enrollment.isCourseRequest())
    1522                    iAssignedDummyCRWeight += enrollment.getRequest().getWeight();
    1523            }
    1524            if (enrollment.isCourseRequest())
    1525                iAssignedPriorityCRWeight[enrollment.getStudent().getPriority().ordinal()] += enrollment.getRequest().getWeight();
    1526            if (enrollment.isCourseRequest()) {
    1527                int noTime = 0;
    1528                int online = 0;
    1529                int past = 0;
    1530                for (Section section: enrollment.getSections()) {
    1531                    if (!section.hasTime()) noTime ++;
    1532                    if (section.isOnline()) online ++;
    1533                    if (section.isPast()) past ++;
    1534                }
    1535                if (noTime > 0)
    1536                    iAssignedNoTimeSectionWeight += enrollment.getRequest().getWeight() * noTime / enrollment.getSections().size();
    1537                if (online > 0)
    1538                    iAssignedOnlineSectionWeight += enrollment.getRequest().getWeight() * online / enrollment.getSections().size();
    1539                if (past > 0)
    1540                    iAssignedPastSectionWeight += enrollment.getRequest().getWeight() * past / enrollment.getSections().size();
    1541            }
    1542        }
    1543
    1544        /**
    1545         * Called before an enrollment was unassigned from a request. The list of
    1546         * complete students and the overall solution value are updated.
    1547         */
    1548        @Override
    1549        public void unassigned(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    1550            Student student = enrollment.getStudent();
    1551            if (enrollment.isCourseRequest() && iCompleteStudents.contains(student)) {
    1552                iCompleteStudents.remove(student);
    1553                if (student.isDummy())
    1554                    iNrCompleteDummyStudents--;
    1555                iNrCompletePriorityStudents[student.getPriority().ordinal()]--;
    1556            }
    1557            Request.RequestContext cx = enrollment.variable().getContext(assignment);
    1558            Double value = cx.getLastWeight();
    1559            if (value == null)
    1560                value = enrollment.getRequest().getWeight() * iStudentWeights.getWeight(assignment, enrollment);
    1561            iTotalValue += value;
    1562            cx.setLastWeight(null);
    1563            if (enrollment.isCourseRequest())
    1564                iAssignedCRWeight -= enrollment.getRequest().getWeight();
    1565            if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getStudent().isDummy() && !enrollment.getRequest().isAlternative())
    1566                iAssignedCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()] -= enrollment.getRequest().getWeight();
    1567            if (enrollment.isCourseRequest() && enrollment.getRequest().getRequestPriority() != RequestPriority.Normal && !enrollment.getRequest().isAlternative())
    1568                iAssignedPriorityCriticalCRWeight[enrollment.getRequest().getRequestPriority().ordinal()][enrollment.getStudent().getPriority().ordinal()] -= enrollment.getRequest().getWeight();
    1569            if (enrollment.getRequest().isMPP()) {
    1570                iAssignedSameSectionWeight -= enrollment.getRequest().getWeight() * enrollment.percentInitial();
    1571                iAssignedSameChoiceWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelected();
    1572                iAssignedSameTimeWeight -= enrollment.getRequest().getWeight() * enrollment.percentSameTime();
    1573            }
    1574            if (enrollment.getRequest().hasSelection()) {
    1575                iAssignedSelectedSectionWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelectedSameSection();
    1576                iAssignedSelectedConfigWeight -= enrollment.getRequest().getWeight() * enrollment.percentSelectedSameConfig();
    1577            }
    1578            if (enrollment.getReservation() != null)
    1579                iReservedSpace -= enrollment.getRequest().getWeight();
    1580            if (enrollment.isCourseRequest() && ((CourseRequest)enrollment.getRequest()).hasReservations())
    1581                iTotalReservedSpace -= enrollment.getRequest().getWeight();
    1582            if (student.isDummy()) {
    1583                iNrAssignedDummyRequests--;
    1584                if (enrollment.isCourseRequest())
    1585                    iAssignedDummyCRWeight -= enrollment.getRequest().getWeight();
    1586            }
    1587            if (enrollment.isCourseRequest())
    1588                iAssignedPriorityCRWeight[enrollment.getStudent().getPriority().ordinal()] -= enrollment.getRequest().getWeight();
    1589            if (enrollment.isCourseRequest()) {
    1590                int noTime = 0;
    1591                int online = 0;
    1592                int past = 0;
    1593                for (Section section: enrollment.getSections()) {
    1594                    if (!section.hasTime()) noTime ++;
    1595                    if (section.isOnline()) online ++;
    1596                    if (section.isPast()) past ++;
    1597                }
    1598                if (noTime > 0)
    1599                    iAssignedNoTimeSectionWeight -= enrollment.getRequest().getWeight() * noTime / enrollment.getSections().size();
    1600                if (online > 0)
    1601                    iAssignedOnlineSectionWeight -= enrollment.getRequest().getWeight() * online / enrollment.getSections().size();
    1602                if (past > 0)
    1603                    iAssignedPastSectionWeight -= enrollment.getRequest().getWeight() * past / enrollment.getSections().size();
    1604            }
    1605        }
    1606
    1607        public void add(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) {
    1608            iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c);
    1609        }
    1610
    1611        public void remove(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) {
    1612            iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getDistanceConflictWeight(assignment, c);
    1613        }
    1614
    1615        public void add(Assignment<Request, Enrollment> assignment, TimeOverlapsCounter.Conflict c) {
    1616            if (c.getR1() != null) iTotalValue += c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c);
    1617            if (c.getR2() != null) iTotalValue += c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c);
    1618        }
    1619
    1620        public void remove(Assignment<Request, Enrollment> assignment, TimeOverlapsCounter.Conflict c) {
    1621            if (c.getR1() != null) iTotalValue -= c.getR1Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE1(), c);
    1622            if (c.getR2() != null) iTotalValue -= c.getR2Weight() * iStudentWeights.getTimeOverlapConflictWeight(assignment, c.getE2(), c);
    1623        }
    1624
    1625        public void add(Assignment<Request, Enrollment> assignment, StudentQuality.Conflict c) {
    1626            switch (c.getType().getType()) {
    1627                case REQUEST:
    1628                    if (c.getR1() instanceof CourseRequest)
    1629                        iTotalValue += c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    1630                    else
    1631                        iTotalValue += c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
    1632                    break;
    1633                case BOTH:
    1634                    iTotalValue += c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    1635                    iTotalValue += c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
    1636                    break;
    1637                case LOWER:
    1638                    iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    1639                    break;
    1640                case HIGHER:
    1641                    iTotalValue += avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    1642                    break;
    1643            }
    1644        }
    1645
    1646        public void remove(Assignment<Request, Enrollment> assignment, StudentQuality.Conflict c) {
    1647            switch (c.getType().getType()) {
    1648                case REQUEST:
    1649                    if (c.getR1() instanceof CourseRequest)
    1650                        iTotalValue -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    1651                    else
    1652                        iTotalValue -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
    1653                    break;
    1654                case BOTH:
    1655                    iTotalValue -= c.getR1Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    1656                    iTotalValue -= c.getR2Weight() * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE2(), c);
    1657                    break;
    1658                case LOWER:
    1659                    iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    1660                    break;
    1661                case HIGHER:
    1662                    iTotalValue -= avg(c.getR1().getWeight(), c.getR2().getWeight()) * iStudentWeights.getStudentQualityConflictWeight(assignment, c.getE1(), c);
    1663                    break;
    1664            }
    1665        }
    1666
    1667        /**
    1668         * Students with complete schedules (see {@link Student#isComplete(Assignment)})
    1669         * @return students with complete schedule
    1670         */
    1671        public Set<Student> getCompleteStudents() {
    1672            return iCompleteStudents;
    1673        }
    1674
    1675        /**
    1676         * Number of students with complete schedule
    1677         * @return number of students with complete schedule
    1678         */
    1679        public int nrComplete() {
    1680            return getCompleteStudents().size();
    1681        }
    1682
    1683        /**
    1684         * Recompute cached request weights
    1685         * @param assignment curent assignment
    1686         */
    1687        public void requestWeightsChanged(Assignment<Request, Enrollment> assignment) {
    1688            iTotalCRWeight = 0.0;
    1689            iTotalDummyWeight = 0.0; iTotalDummyCRWeight = 0.0;
    1690            iTotalPriorityCRWeight = new double[StudentPriority.values().length];
    1691            iAssignedCRWeight = 0.0;
    1692            iAssignedDummyCRWeight = 0.0;
    1693            iAssignedCriticalCRWeight = new double[RequestPriority.values().length];
    1694            iAssignedPriorityCriticalCRWeight = new double[RequestPriority.values().length][StudentPriority.values().length];
    1695            for (int i = 0; i < RequestPriority.values().length; i++) {
    1696                iAssignedCriticalCRWeight[i] = 0.0;
    1697                for (int j = 0; j < StudentPriority.values().length; j++) {
    1698                    iAssignedPriorityCriticalCRWeight[i][j] = 0.0;
    1699                }
    1700            }
    1701            iAssignedPriorityCRWeight = new double[StudentPriority.values().length];
    1702            for (int i = 0; i < StudentPriority.values().length; i++) {
    1703                iAssignedPriorityCRWeight[i] = 0.0;
    1704            }
    1705            iNrDummyRequests = 0; iNrAssignedDummyRequests = 0;
    1706            iTotalReservedSpace = 0.0; iReservedSpace = 0.0;
    1707            iTotalMPPCRWeight = 0.0;
    1708            iTotalSelCRWeight = 0.0;
    1709            iAssignedNoTimeSectionWeight = 0.0;
    1710            iAssignedOnlineSectionWeight = 0.0;
    1711            iAssignedPastSectionWeight = 0.0;
    1712            for (Request request: variables()) {
    1713                boolean cr = (request instanceof CourseRequest);
    1714                if (cr && !request.isAlternative())
    1715                    iTotalCRWeight += request.getWeight();
    1716                if (request.getStudent().isDummy()) {
    1717                    iTotalDummyWeight += request.getWeight();
    1718                    iNrDummyRequests ++;
    1719                    if (cr && !request.isAlternative())
    1720                        iTotalDummyCRWeight += request.getWeight();
    1721                }
    1722                if (cr && !request.isAlternative()) {
    1723                    iTotalPriorityCRWeight[request.getStudent().getPriority().ordinal()] += request.getWeight();
    1724                }
    1725                if (request.isMPP())
    1726                    iTotalMPPCRWeight += request.getWeight();
    1727                if (request.hasSelection())
    1728                    iTotalSelCRWeight += request.getWeight();
    1729                Enrollment e = assignment.getValue(request);
    1730                if (e != null) {
    1731                    if (cr)
    1732                        iAssignedCRWeight += request.getWeight();
    1733                    if (cr && request.getRequestPriority() != RequestPriority.Normal && !request.getStudent().isDummy() && !request.isAlternative())
    1734                        iAssignedCriticalCRWeight[request.getRequestPriority().ordinal()] += request.getWeight();
    1735                    if (cr && request.getRequestPriority() != RequestPriority.Normal && !request.isAlternative())
    1736                        iAssignedPriorityCriticalCRWeight[request.getRequestPriority().ordinal()][request.getStudent().getPriority().ordinal()] += request.getWeight();
    1737                    if (request.isMPP()) {
    1738                        iAssignedSameSectionWeight += request.getWeight() * e.percentInitial();
    1739                        iAssignedSameChoiceWeight += request.getWeight() * e.percentSelected();
    1740                        iAssignedSameTimeWeight += request.getWeight() * e.percentSameTime();
    1741                    }
    1742                    if (request.hasSelection()) {
    1743                        iAssignedSelectedSectionWeight += request.getWeight() * e.percentSelectedSameSection();
    1744                        iAssignedSelectedConfigWeight += request.getWeight() * e.percentSelectedSameConfig();
    1745                    }
    1746                    if (e.getReservation() != null)
    1747                        iReservedSpace += request.getWeight();
    1748                    if (cr && ((CourseRequest)request).hasReservations())
    1749                        iTotalReservedSpace += request.getWeight();
    1750                    if (request.getStudent().isDummy()) {
    1751                        iNrAssignedDummyRequests ++;
    1752                        if (cr)
    1753                            iAssignedDummyCRWeight += request.getWeight();
    1754                    }
    1755                    if (cr) {
    1756                        iAssignedPriorityCRWeight[request.getStudent().getPriority().ordinal()] += request.getWeight();
    1757                    }
    1758                    if (cr) {
    1759                        int noTime = 0;
    1760                        int online = 0;
    1761                        int past = 0;
    1762                        for (Section section: e.getSections()) {
    1763                            if (!section.hasTime()) noTime ++;
    1764                            if (section.isOnline()) online ++;
    1765                            if (section.isPast()) past ++;
    1766                        }
    1767                        if (noTime > 0)
    1768                            iAssignedNoTimeSectionWeight += request.getWeight() * noTime / e.getSections().size();
    1769                        if (online > 0)
    1770                            iAssignedOnlineSectionWeight += request.getWeight() * online / e.getSections().size();
    1771                        if (past > 0)
    1772                            iAssignedPastSectionWeight += request.getWeight() * past / e.getSections().size();
    1773                    }
    1774                }
    1775            }
    1776        }
    1777
    1778        /**
    1779         * Overall solution value
    1780         * @return solution value
    1781         */
    1782        public double getTotalValue() {
    1783            return iTotalValue;
    1784        }
    1785
    1786        /**
    1787         * Number of last like ({@link Student#isDummy()} equals true) students with
    1788         * a complete schedule ({@link Student#isComplete(Assignment)} equals true).
    1789         * @return number of last like (projected) students with a complete schedule
    1790         */
    1791        public int getNrCompleteLastLikeStudents() {
    1792            return iNrCompleteDummyStudents;
    1793        }
    1794
    1795        /**
    1796         * Number of requests from projected ({@link Student#isDummy()} equals true)
    1797         * students that are assigned.
    1798         * @return number of real students with a complete schedule
    1799         */
    1800        public int getNrAssignedLastLikeRequests() {
    1801            return iNrAssignedDummyRequests;
    1802        }
    1803
    1804        @Override
    1805        public void getInfo(Assignment<Request, Enrollment> assignment, Map<String, String> info) {
    1806            if (iTotalCRWeight > 0.0) {
    1807                info.put("Assigned course requests", sDecimalFormat.format(100.0 * iAssignedCRWeight / iTotalCRWeight) + "% (" + (int)Math.round(iAssignedCRWeight) + "/" + (int)Math.round(iTotalCRWeight) + ")");
    1808                if (iNrDummyStudents > 0 && iNrDummyStudents != getStudents().size() && iTotalCRWeight != iTotalDummyCRWeight) {
    1809                    if (iTotalDummyCRWeight > 0.0)
    1810                        info.put("Projected assigned course requests", sDecimalFormat.format(100.0 * iAssignedDummyCRWeight / iTotalDummyCRWeight) + "% (" + (int)Math.round(iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalDummyCRWeight) + ")");
    1811                    info.put("Real assigned course requests", sDecimalFormat.format(100.0 * (iAssignedCRWeight - iAssignedDummyCRWeight) / (iTotalCRWeight - iTotalDummyCRWeight)) +
    1812                            "% (" + (int)Math.round(iAssignedCRWeight - iAssignedDummyCRWeight) + "/" + (int)Math.round(iTotalCRWeight - iTotalDummyCRWeight) + ")");
    1813                }
    1814                if (iAssignedNoTimeSectionWeight > 0.0) {
    1815                    info.put("Using classes w/o time", sDecimalFormat.format(100.0 * iAssignedNoTimeSectionWeight / iAssignedCRWeight) + "% (" + sDecimalFormat.format(iAssignedNoTimeSectionWeight) + ")");
    1816                }
    1817                if (iAssignedOnlineSectionWeight > 0.0) {
    1818                    info.put("Using online classes", sDecimalFormat.format(100.0 * iAssignedOnlineSectionWeight / iAssignedCRWeight) + "% (" + sDecimalFormat.format(iAssignedOnlineSectionWeight) + ")");
    1819                }
    1820                if (iAssignedPastSectionWeight > 0.0) {
    1821                    info.put("Using past classes", sDecimalFormat.format(100.0 * iAssignedPastSectionWeight / iAssignedCRWeight) + "% (" + sDecimalFormat.format(iAssignedPastSectionWeight) + ")");
    1822                }
    1823            }
    1824            String priorityAssignedCR = "";
    1825            for (StudentPriority sp: StudentPriority.values()) {
    1826                if (sp != StudentPriority.Dummy && iTotalPriorityCRWeight[sp.ordinal()] > 0.0) {
    1827                    priorityAssignedCR += (priorityAssignedCR.isEmpty() ? "" : "\n") +
    1828                            sp.name() + ": " + sDecimalFormat.format(100.0 * iAssignedPriorityCRWeight[sp.ordinal()] / iTotalPriorityCRWeight[sp.ordinal()]) + "% (" + (int)Math.round(iAssignedPriorityCRWeight[sp.ordinal()]) + "/" + (int)Math.round(iTotalPriorityCRWeight[sp.ordinal()]) + ")";
    1829                }
    1830            }
    1831            if (!priorityAssignedCR.isEmpty())
    1832                info.put("Assigned course requests (priority students)", priorityAssignedCR);
    1833            for (RequestPriority rp: RequestPriority.values()) {
    1834                if (rp == RequestPriority.Normal) continue;
    1835                if (iTotalCriticalCRWeight[rp.ordinal()] > 0.0) {
    1836                    info.put("Assigned " + rp.name().toLowerCase() + " course requests", sDoubleFormat.format(100.0 * iAssignedCriticalCRWeight[rp.ordinal()] / iTotalCriticalCRWeight[rp.ordinal()]) + "% (" + (int)Math.round(iAssignedCriticalCRWeight[rp.ordinal()]) + "/" + (int)Math.round(iTotalCriticalCRWeight[rp.ordinal()]) + ")");
    1837                }
    1838                priorityAssignedCR = "";
    1839                for (StudentPriority sp: StudentPriority.values()) {
    1840                    if (sp != StudentPriority.Dummy && iTotalPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()] > 0.0) {
    1841                        priorityAssignedCR += (priorityAssignedCR.isEmpty() ? "" : "\n") +
    1842                                sp.name() + ": " + sDoubleFormat.format(100.0 * iAssignedPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()] / iTotalPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()]) + "% (" + (int)Math.round(iAssignedPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()]) + "/" + (int)Math.round(iTotalPriorityCriticalCRWeight[rp.ordinal()][sp.ordinal()]) + ")";
    1843                    }
    1844                }
    1845                if (!priorityAssignedCR.isEmpty())
    1846                    info.put("Assigned " + rp.name().toLowerCase() + " course requests (priority students)", priorityAssignedCR);
    1847            }
    1848            if (iTotalReservedSpace > 0.0)
    1849                info.put("Reservations", sDoubleFormat.format(100.0 * iReservedSpace / iTotalReservedSpace) + "% (" + Math.round(iReservedSpace) + "/" + Math.round(iTotalReservedSpace) + ")");
    1850            if (iMPP && iTotalMPPCRWeight > 0.0) {
    1851                info.put("Perturbations: same section", sDoubleFormat.format(100.0 * iAssignedSameSectionWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameSectionWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")");
    1852                if (iAssignedSameChoiceWeight > iAssignedSameSectionWeight)
    1853                    info.put("Perturbations: same choice",sDoubleFormat.format(100.0 * iAssignedSameChoiceWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameChoiceWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")");
    1854                if (iAssignedSameTimeWeight > iAssignedSameChoiceWeight)
    1855                    info.put("Perturbations: same time", sDoubleFormat.format(100.0 * iAssignedSameTimeWeight / iTotalMPPCRWeight) + "% (" + Math.round(iAssignedSameTimeWeight) + "/" + Math.round(iTotalMPPCRWeight) + ")");
    1856            }
    1857            if (iTotalSelCRWeight > 0.0) {
    1858                info.put("Selection",sDoubleFormat.format(100.0 * (0.3 * iAssignedSelectedConfigWeight + 0.7 * iAssignedSelectedSectionWeight) / iTotalSelCRWeight) +
    1859                        "% (" + Math.round(0.3 * iAssignedSelectedConfigWeight + 0.7 * iAssignedSelectedSectionWeight) + "/" + Math.round(iTotalSelCRWeight) + ")");
    1860            }
    1861        }
    1862
    1863        @Override
    1864        public void getInfo(Assignment<Request, Enrollment> assignment, Map<String, String> info, Collection<Request> variables) {
    1865        }
    1866
    1867        public double getAssignedCourseRequestWeight() {
    1868            return iAssignedCRWeight;
    1869        }
    1870
    1871        public double getAssignedCriticalCourseRequestWeight(RequestPriority rp) {
    1872            return iAssignedCriticalCRWeight[rp.ordinal()];
    1873        }
    1874    }
    1875
    1876    @Override
    1877    public InheritedAssignment<Request, Enrollment> createInheritedAssignment(Solution<Request, Enrollment> solution, int index) {
    1878        return new OptimisticInheritedAssignment<Request, Enrollment>(solution, index);
    1879    }
    1880
    1881    public DistanceMetric getDistanceMetric() {
    1882        return (iStudentQuality != null ? iStudentQuality.getDistanceMetric() : iDistanceConflict != null ? iDistanceConflict.getDistanceMetric() : null);
    1883    }
    1884
    1885    @Override
    1886    public StudentSectioningModelContext inheritAssignmentContext(Assignment<Request, Enrollment> assignment, StudentSectioningModelContext parentContext) {
    1887        return new StudentSectioningModelContext(parentContext);
    1888    }
    1889
    1890}