    001package org.cpsolver.studentsct.weights;
    002
    003import java.text.DecimalFormat;
    004import java.util.ArrayList;
    005import java.util.BitSet;
    006import java.util.HashSet;
    007import java.util.Set;
    008
    009import org.cpsolver.coursett.model.Placement;
    010import org.cpsolver.coursett.model.RoomLocation;
    011import org.cpsolver.coursett.model.TimeLocation;
    012import org.cpsolver.ifs.assignment.Assignment;
    013import org.cpsolver.ifs.assignment.DefaultSingleAssignment;
    014import org.cpsolver.ifs.solution.Solution;
    015import org.cpsolver.ifs.util.DataProperties;
    016import org.cpsolver.ifs.util.ToolBox;
    017import org.cpsolver.studentsct.StudentSectioningModel;
    018import org.cpsolver.studentsct.extension.DistanceConflict;
    019import org.cpsolver.studentsct.extension.StudentQuality;
    020import org.cpsolver.studentsct.extension.StudentQuality.Conflict;
    021import org.cpsolver.studentsct.extension.TimeOverlapsCounter;
    022import org.cpsolver.studentsct.model.Choice;
    023import org.cpsolver.studentsct.model.Config;
    024import org.cpsolver.studentsct.model.Course;
    025import org.cpsolver.studentsct.model.CourseRequest;
    026import org.cpsolver.studentsct.model.Enrollment;
    027import org.cpsolver.studentsct.model.Instructor;
    028import org.cpsolver.studentsct.model.Offering;
    029import org.cpsolver.studentsct.model.Request;
    030import org.cpsolver.studentsct.model.RequestGroup;
    031import org.cpsolver.studentsct.model.SctAssignment;
    032import org.cpsolver.studentsct.model.Section;
    033import org.cpsolver.studentsct.model.Student;
    034import org.cpsolver.studentsct.model.Subpart;
    035
    036
    037/**
    038 * New weighting model. It tries to obey the following principles:
    039 * <ul>
    040 *      <li> Total student weight is between zero and one (one means student got the best schedule)
    041 *      <li> Weight of the given priority course is higher than sum of the remaining weights the student can get
    042 *      <li> First alternative is better than the following course
    043 *      <li> Second alternative is better than the second following course
    044 *      <li> Distance conflicts are considered secondary (priorities should be maximized first)
    045 *      <li> If alternative sections are otherwise equal, use the better balanced one
    046 * </ul>
    047 *
    048 * @version StudentSct 1.3 (Student Sectioning)<br>
    049 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    050 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    051 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    052 * <br>
    053 *          This library is free software; you can redistribute it and/or modify
    054 *          it under the terms of the GNU Lesser General Public License as
    055 *          published by the Free Software Foundation; either version 3 of the
    056 *          License, or (at your option) any later version. <br>
    057 * <br>
    058 *          This library is distributed in the hope that it will be useful, but
    059 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    060 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    061 *          Lesser General Public License for more details. <br>
    062 * <br>
    063 *          You should have received a copy of the GNU Lesser General Public
    064 *          License along with this library; if not see
    065 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    066 */
    067
    068public class PriorityStudentWeights implements StudentWeights {
    069    protected double iPriorityFactor = 0.5010;
    070    protected double iFirstAlternativeFactor = 0.5010;
    071    protected double iSecondAlternativeFactor = 0.2510;
    072    protected double iDistanceConflict = 0.0100;
    073    protected double iShortDistanceConflict = 0.1000;
    074    protected double iTimeOverlapFactor = 0.5000;
    075    protected double iTimeOverlapMaxLimit = 0.5000;
    076    protected boolean iLeftoverSpread = false;
    077    protected double iBalancingFactor = 0.0050;
    078    protected double iNoTimeFactor = 0.0100;
    079    protected double iOnlineFactor = 0.0100;
    080    protected double iPastFactor = 0.0000;
    081    protected double iReservationNotFollowedFactor = 0.1000;
    082    protected double iAlternativeRequestFactor = 0.1260;
    083    protected double iProjectedStudentWeight = 0.0100;
    084    protected boolean iMPP = false;
    085    protected double iPerturbationFactor = 0.100;
    086    protected double iSelectionFactor = 0.100;
    087    protected double iSameChoiceWeight = 0.900;
    088    protected double iSameTimeWeight = 0.700;
    089    protected double iSameConfigWeight = 0.500;
    090    protected double iGroupFactor = 0.100;
    091    protected double iGroupBestRatio = 0.95;
    092    protected double iGroupFillRatio = 0.05;
    093    protected boolean iAdditiveWeights = false;
    094    protected boolean iMaximizeAssignment = false;
    095    protected boolean iPreciseComparison = false;
    096    protected double[] iQalityWeights;
    097    protected boolean iImprovedBound = true;
    098    protected double iCriticalBoost = 1.0;
    099    protected double iPriortyBoost = 1.0;
    100
    101    public PriorityStudentWeights(DataProperties config) {
    102        iPriorityFactor = config.getPropertyDouble("StudentWeights.Priority", iPriorityFactor);
    103        iFirstAlternativeFactor = config.getPropertyDouble("StudentWeights.FirstAlternative", iFirstAlternativeFactor);
    104        iSecondAlternativeFactor = config.getPropertyDouble("StudentWeights.SecondAlternative", iSecondAlternativeFactor);
    105        iDistanceConflict = config.getPropertyDouble("StudentWeights.DistanceConflict", iDistanceConflict);
    106        iShortDistanceConflict = config.getPropertyDouble("StudentWeights.ShortDistanceConflict", iShortDistanceConflict);
    107        iTimeOverlapFactor = config.getPropertyDouble("StudentWeights.TimeOverlapFactor", iTimeOverlapFactor);
    108        iTimeOverlapMaxLimit = config.getPropertyDouble("StudentWeights.TimeOverlapMaxLimit", iTimeOverlapMaxLimit);
    109        iLeftoverSpread = config.getPropertyBoolean("StudentWeights.LeftoverSpread", iLeftoverSpread);
    110        iBalancingFactor = config.getPropertyDouble("StudentWeights.BalancingFactor", iBalancingFactor);
    111        iAlternativeRequestFactor = config.getPropertyDouble("StudentWeights.AlternativeRequestFactor", iAlternativeRequestFactor);
    112        iProjectedStudentWeight = config.getPropertyDouble("StudentWeights.ProjectedStudentWeight", iProjectedStudentWeight);
    113        iMPP = config.getPropertyBoolean("General.MPP", false);
    114        iPerturbationFactor = config.getPropertyDouble("StudentWeights.Perturbation", iPerturbationFactor);
    115        iSelectionFactor = config.getPropertyDouble("StudentWeights.Selection", iSelectionFactor);
    116        iSameChoiceWeight = config.getPropertyDouble("StudentWeights.SameChoice", iSameChoiceWeight);
    117        iSameTimeWeight = config.getPropertyDouble("StudentWeights.SameTime", iSameTimeWeight);
    118        iSameConfigWeight = config.getPropertyDouble("StudentWeights.SameConfig", iSameConfigWeight);
    119        iGroupFactor = config.getPropertyDouble("StudentWeights.SameGroup", iGroupFactor);
    120        iGroupBestRatio = config.getPropertyDouble("StudentWeights.GroupBestRatio", iGroupBestRatio);
    121        iGroupFillRatio = config.getPropertyDouble("StudentWeights.GroupFillRatio", iGroupFillRatio);
    122        iNoTimeFactor = config.getPropertyDouble("StudentWeights.NoTimeFactor", iNoTimeFactor);
    123        iOnlineFactor = config.getPropertyDouble("StudentWeights.OnlineFactor", iOnlineFactor);
    124        iPastFactor = config.getPropertyDouble("StudentWeights.PastFactor", iPastFactor);
    125        iReservationNotFollowedFactor = config.getPropertyDouble("StudentWeights.ReservationNotFollowedFactor", iReservationNotFollowedFactor);
    126        iAdditiveWeights = config.getPropertyBoolean("StudentWeights.AdditiveWeights", iAdditiveWeights);
    127        iMaximizeAssignment = config.getPropertyBoolean("StudentWeights.MaximizeAssignment", iMaximizeAssignment);
    128        iPreciseComparison = config.getPropertyBoolean("StudentWeights.PreciseComparison", iPreciseComparison);
    129        iQalityWeights = new double[StudentQuality.Type.values().length];
    130        for (StudentQuality.Type type: StudentQuality.Type.values()) {
    131            iQalityWeights[type.ordinal()] = config.getPropertyDouble(type.getWeightName(), type.getWeightDefault());
    132        }
    133        iImprovedBound = config.getPropertyBoolean("StudentWeights.ImprovedBound", iImprovedBound);
    134        iPriortyBoost = config.getPropertyDouble("StudentWeights.PriortyBoost", 1.0);
    135        iCriticalBoost = config.getPropertyDouble("StudentWeights.CriticalBoost", 1.0);
    136    }
    137
    138    public double getWeight(Request request) {
    139        if (request.getStudent().isDummy() && iProjectedStudentWeight >= 0.0) {
    140            double weight = iProjectedStudentWeight;
    141            if (request.isAlternative())
    142                weight *= iAlternativeRequestFactor;
    143            return weight;
    144        }
    145        double total = 1000000.0;
    146        int nrReq = request.getStudent().nrRequests();
    147        double remain = (iLeftoverSpread ? Math.floor(1000000.0 * Math.pow(iPriorityFactor, nrReq) / nrReq) : 0.0);
    148        for (int idx = 0; idx < request.getStudent().getRequests().size(); idx++) {
    149            Request r = request.getStudent().getRequests().get(idx);
    150            boolean last = (idx + 1 == request.getStudent().getRequests().size());
    151            boolean lastNotAlt = !r.isAlternative() && (last || request.getStudent().getRequests().get(1 + idx).isAlternative());
    152            double w = Math.ceil(iPriorityFactor * total) + remain;
    153            if (!iLeftoverSpread && lastNotAlt) {
    154                w = total;
    155            } else {
    156                total -= w;
    157            }
    158            if (r.equals(request)) {
    159                return w / 1000000.0;
    160            }
    161        }
    162        return 0.0;
    163    }
    164
    165    public double getBoostedWeight(Request request) {
    166        double weight = getWeight(request);
    167        if (iPriortyBoost != 1.0) {
    168            Double boost = request.getStudent().getPriority().getBoost();
    169            if (boost != null)
    170                weight *= boost * iPriortyBoost;
    171        }
    172        if (iCriticalBoost != 1.0) {
    173            Double boost = request.getRequestPriority().getBoost();
    174            if (boost != null)
    175                weight *= boost * iCriticalBoost;
    176        }
    177        return weight;
    178    }
    179
    180    public double getCachedWeight(Request request) {
    181        double[] cache = (double[])request.getExtra();
    182        if (cache == null) {
    183            double base = getBoostedWeight(request);
    184            cache = new double[]{base, computeBound(base, request)};
    185            request.setExtra(cache);
    186        }
    187        return cache[0];
    188    }
    189
    190    /**
    191     * Return how much the given enrollment is different from the initial enrollment
    192     * @param enrollment given enrollment
    193     * @return 0.0 when all the sections are the same, 1.0 when all the section are different (including different times)
    194     */
    195    protected double getDifference(Enrollment enrollment) {
    196        if (enrollment.getStudent().isDummy() || !enrollment.isCourseRequest()) return 1.0;
    197        Enrollment other = enrollment.getRequest().getInitialAssignment();
    198        if (other != null) {
    199            double similarSections = 0.0;
    200            if (enrollment.getConfig().equals(other.getConfig())) {
    201                // same configurations -- compare sections of matching subpart
    202                for (Section section: enrollment.getSections()) {
    203                    for (Section initial: other.getSections()) {
    204                        if (section.getSubpart().equals(initial.getSubpart())) {
    205                            if (section.equals(initial)) {
    206                                similarSections += 1.0;
    207                            } else if (section.sameChoice(initial)) {
    208                                similarSections += iSameChoiceWeight;
    209                            } else if (section.sameTime(initial)) {
    210                                similarSections += iSameTimeWeight;
    211                            }
    212                            break;
    213                        }
    214                    }
    215                }
    216            } else {
    217                // different configurations -- compare sections of matching itype
    218                for (Section section: enrollment.getSections()) {
    219                    for (Section initial: other.getSections()) {
    220                        if (section.sameChoice(initial)) {
    221                            similarSections += iSameChoiceWeight;
    222                            break;
    223                        } else if (section.sameInstructionalType(initial) && section.sameTime(initial)) {
    224                            similarSections += iSameTimeWeight;
    225                            break;
    226                        }
    227                    }
    228                }
    229            }
    230            return 1.0 - similarSections / enrollment.getAssignments().size();
    231        }
    232        return 1.0;
    233    }
    234
    235    /**
    236     * Return how much the given enrollment is different from the selection (if any)
    237     * @param enrollment given enrollment
    238     * @return 0.0 when all the sections are the same, 1.0 when all the section are different (including different times)
    239     */
    240    public double getSelection(Enrollment enrollment) {
    241        if (enrollment.getStudent().isDummy()) return 1.0;
    242        if (enrollment.isCourseRequest()) {
    243            CourseRequest cr = (CourseRequest)enrollment.getRequest();
    244            if (!cr.getSelectedChoices().isEmpty()) {
    245                double similarSections = 0.0;
    246                for (Section section: enrollment.getSections()) {
    247                    double bestChoice = 0.0;
    248                    for (Choice ch: cr.getSelectedChoices()) {
    249                        if (bestChoice < 1.0 && ch.sameSection(section)) {
    250                            bestChoice = 1.0;
    251                        } else if (bestChoice < iSameChoiceWeight && ch.sameChoice(section)) {
    252                            bestChoice = iSameChoiceWeight;
    253                        } else if (bestChoice < iSameTimeWeight && ch.sameOffering(section) && ch.sameInstructionalType(section) && ch.sameTime(section)) {
    254                            bestChoice = iSameTimeWeight;
    255                        } else if (bestChoice < iSameConfigWeight && ch.sameConfiguration(section)) {
    256                            bestChoice = iSameConfigWeight;
    257                        }
    258                    }
    259                    similarSections += bestChoice;
    260                }
    261                return 1.0 - similarSections / enrollment.getAssignments().size();
    262            } else {
    263                return 1.0;
    264            }
    265        } else {
    266            return 1.0;
    267        }
    268    }
    269
    270
    271    @Override
    272    public double getBound(Request request) {
    273        double[] cache = (double[])request.getExtra();
    274        if (cache == null) {
    275            double base = getBoostedWeight(request);
    276            cache = new double[]{base, computeBound(base, request)};
    277            request.setExtra(cache);
    278        }
    279        return cache[1];
    280    }
    281
    282    protected double computeBound(double base, Request request) {
    283        if (!iImprovedBound) return base;
    284        if (iAdditiveWeights) {
    285            double weight = 0.0;
    286            if (request instanceof CourseRequest) {
    287                CourseRequest cr = (CourseRequest)request;
    288                if (iNoTimeFactor != 0.0 && !cr.getCourses().isEmpty()) {
    289                    weight += iNoTimeFactor * cr.getCourses().get(0).getArrHrsBound();
    290                }
    291                if (iOnlineFactor != 0.0 && !cr.getCourses().isEmpty()) {
    292                    weight += iOnlineFactor * cr.getCourses().get(0).getOnlineBound();
    293                }
    294                if (iPastFactor != 0.0 && !cr.getCourses().isEmpty()) {
    295                    weight += iPastFactor * cr.getCourses().get(0).getPastBound();
    296                }
    297                if (iMPP && cr.getInitialAssignment() == null) {
    298                    weight += iPerturbationFactor;
    299                }
    300                if (iSelectionFactor != 0.0 && cr.getSelectedChoices().isEmpty()) {
    301                    weight += iSelectionFactor;
    302                }
    303            }
    304            return round(base * (1.0 - weight));
    305        } else {
    306            double weight = base;
    307            if (request instanceof CourseRequest) {
    308                CourseRequest cr = (CourseRequest)request;
    309                if (iNoTimeFactor != 0.0 && !cr.getCourses().isEmpty()) {
    310                    weight *= (1.0 - iNoTimeFactor * cr.getCourses().get(0).getArrHrsBound());
    311                }
    312                if (iOnlineFactor != 0.0 && !cr.getCourses().isEmpty()) {
    313                    weight *= (1.0 - iOnlineFactor * cr.getCourses().get(0).getOnlineBound());
    314                }
    315                if (iPastFactor != 0.0 && !cr.getCourses().isEmpty()) {
    316                    weight *= (1.0 - iPastFactor * cr.getCourses().get(0).getPastBound());
    317                }
    318                if (iMPP && cr.getInitialAssignment() == null) {
    319                    weight *= (1.0 - iPerturbationFactor);
    320                }
    321                if (iSelectionFactor != 0.0 && cr.getSelectedChoices().isEmpty()) {
    322                    weight *= (1.0 - iSelectionFactor);
    323                }
    324            }
    325            return round(weight);
    326        }
    327    }
    328
    329    protected double round(double value) {
    330        return Math.ceil(1000000.0 * value) / 1000000.0;
    331    }
    332
    333    protected double getBaseWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    334        double weight = getCachedWeight(enrollment.getRequest());
    335        switch (enrollment.getTruePriority()) {
    336            case 0: break;
    337            case 1: weight *= iFirstAlternativeFactor; break;
    338            case 2: weight *= iSecondAlternativeFactor; break;
    339            default:
    340                weight *= Math.pow(iFirstAlternativeFactor, enrollment.getTruePriority());
    341        }
    342        return weight;
    343    }
    344
    345    @Override
    346    public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    347        if (iAdditiveWeights)
    348            return getWeightAdditive(assignment, enrollment);
    349        else
    350            return getWeightMultiplicative(assignment, enrollment);
    351    }
    352
    353    public double getWeightMultiplicative(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    354        double weight = getBaseWeight(assignment, enrollment);
    355        if (enrollment.isCourseRequest() && iNoTimeFactor != 0.0) {
    356            int noTimeSections = 0, total = 0;
    357            for (Section section: enrollment.getSections()) {
    358                if (!section.hasTime()) noTimeSections ++;
    359                total ++;
    360            }
    361            if (noTimeSections > 0)
    362                weight *= (1.0 - iNoTimeFactor * noTimeSections / total);
    363        }
    364        if (enrollment.isCourseRequest() && iOnlineFactor != 0.0) {
    365            int onlineSections = 0, total = 0;
    366            for (Section section: enrollment.getSections()) {
    367                if (section.isOnline()) onlineSections ++;
    368                total ++;
    369            }
    370            if (onlineSections > 0)
    371                weight *= (1.0 - iOnlineFactor * onlineSections / total);
    372        }
    373        if (enrollment.isCourseRequest() && iPastFactor != 0.0) {
    374            int pastSections = 0, total = 0;
    375            for (Section section: enrollment.getSections()) {
    376                if (section.isPast()) pastSections ++;
    377                total ++;
    378            }
    379            if (pastSections > 0)
    380                weight *= (1.0 - iPastFactor * pastSections / total);
    381        }
    382        if (enrollment.getTruePriority() < enrollment.getPriority()) {
    383            weight *= (1.0 - iReservationNotFollowedFactor);
    384        }
    385        if (enrollment.isCourseRequest() && iBalancingFactor != 0.0) {
    386            double configUsed = enrollment.getConfig().getEnrollmentTotalWeight(assignment, enrollment.getRequest()) + enrollment.getRequest().getWeight();
    387            double disbalanced = 0;
    388            double total = 0;
    389            for (Section section: enrollment.getSections()) {
    390                Subpart subpart = section.getSubpart();
    391                if (subpart.getSections().size() <= 1) continue;
    392                double used = section.getEnrollmentTotalWeight(assignment, enrollment.getRequest()) + enrollment.getRequest().getWeight();
    393                // sections have limits -> desired size is section limit x (total enrollment / total limit)
    394                // unlimited sections -> desired size is total enrollment / number of sections
    395                double desired = (subpart.getLimit() > 0
    396                        ? section.getLimit() * (configUsed / subpart.getLimit())
    397                        : configUsed / subpart.getSections().size());
    398                if (used > desired)
    399                    disbalanced += Math.min(enrollment.getRequest().getWeight(), used - desired) / enrollment.getRequest().getWeight();
    400                else
    401                    disbalanced -= Math.min(enrollment.getRequest().getWeight(), desired - used) / enrollment.getRequest().getWeight();
    402                total ++;
    403            }
    404            if (disbalanced > 0)
    405                weight *= (1.0 - disbalanced / total * iBalancingFactor);
    406        }
    407        if (iMPP) {
    408            double difference = getDifference(enrollment);
    409            if (difference > 0.0)
    410                weight *= (1.0 - difference * iPerturbationFactor);
    411        }
    412        if (iSelectionFactor != 0.0) {
    413            double selection = getSelection(enrollment);
    414            if (selection > 0.0)
    415                weight *= (1.0 - selection * iSelectionFactor);
    416        }
    417        if (enrollment.isCourseRequest() && iGroupFactor != 0.0) {
    418            double sameGroup = 0.0; int groupCount = 0;
    419            for (RequestGroup g: ((CourseRequest)enrollment.getRequest()).getRequestGroups()) {
    420                if (g.getCourse().equals(enrollment.getCourse())) {
    421                    sameGroup += g.getEnrollmentSpread(assignment, enrollment, iGroupBestRatio, iGroupFillRatio);
    422                    groupCount ++;
    423                }
    424            }
    425            if (groupCount > 0) {
    426                double difference = 1.0 - sameGroup / groupCount;
    427                weight *= (1.0 - difference * iGroupFactor);
    428            }
    429        }
    430        return round(weight);
    431    }
    432
    433    public double getWeightAdditive(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    434        double base = getBaseWeight(assignment, enrollment);
    435        double weight = 0.0;
    436        if (enrollment.isCourseRequest() && iNoTimeFactor != 0.0) {
    437            int noTimeSections = 0, total = 0;
    438            for (Section section: enrollment.getSections()) {
    439                if (!section.hasTime()) noTimeSections ++;
    440                total ++;
    441            }
    442            if (noTimeSections > 0)
    443                weight += iNoTimeFactor * noTimeSections / total;
    444        }
    445        if (enrollment.isCourseRequest() && iOnlineFactor != 0.0) {
    446            int onlineSections = 0, total = 0;
    447            for (Section section: enrollment.getSections()) {
    448                if (section.isOnline()) onlineSections ++;
    449                total ++;
    450            }
    451            if (onlineSections > 0)
    452                weight += iOnlineFactor * onlineSections / total;
    453        }
    454        if (enrollment.isCourseRequest() && iPastFactor != 0.0) {
    455            int pastSections = 0, total = 0;
    456            for (Section section: enrollment.getSections()) {
    457                if (section.isPast()) pastSections ++;
    458                total ++;
    459            }
    460            if (pastSections > 0)
    461                weight += iPastFactor * pastSections / total;
    462        }
    463        if (enrollment.getTruePriority() < enrollment.getPriority()) {
    464            weight += iReservationNotFollowedFactor;
    465        }
    466        if (enrollment.isCourseRequest() && iBalancingFactor != 0.0) {
    467            double configUsed = enrollment.getConfig().getEnrollmentTotalWeight(assignment, enrollment.getRequest()) + enrollment.getRequest().getWeight();
    468            double disbalanced = 0;
    469            double total = 0;
    470            for (Section section: enrollment.getSections()) {
    471                Subpart subpart = section.getSubpart();
    472                if (subpart.getSections().size() <= 1) continue;
    473                double used = section.getEnrollmentTotalWeight(assignment, enrollment.getRequest()) + enrollment.getRequest().getWeight();
    474                // sections have limits -> desired size is section limit x (total enrollment / total limit)
    475                // unlimited sections -> desired size is total enrollment / number of sections
    476                double desired = (subpart.getLimit() > 0
    477                        ? section.getLimit() * (configUsed / subpart.getLimit())
    478                        : configUsed / subpart.getSections().size());
    479                if (used > desired)
    480                    disbalanced += Math.min(enrollment.getRequest().getWeight(), used - desired) / enrollment.getRequest().getWeight();
    481                else
    482                    disbalanced -= Math.min(enrollment.getRequest().getWeight(), desired - used) / enrollment.getRequest().getWeight();
    483                total ++;
    484            }
    485            if (disbalanced > 0)
    486                weight += disbalanced / total * iBalancingFactor;
    487        }
    488        if (iMPP) {
    489            double difference = getDifference(enrollment);
    490            if (difference > 0.0)
    491                weight += difference * iPerturbationFactor;
    492        }
    493        if (iSelectionFactor != 0.0) {
    494            double selection = getSelection(enrollment);
    495            if (selection > 0.0)
    496                weight += selection * iSelectionFactor;
    497        }
    498        if (enrollment.isCourseRequest() && iGroupFactor != 0.0) {
    499            double sameGroup = 0.0; int groupCount = 0;
    500            for (RequestGroup g: ((CourseRequest)enrollment.getRequest()).getRequestGroups()) {
    501                if (g.getCourse().equals(enrollment.getCourse())) {
    502                    sameGroup += g.getEnrollmentSpread(assignment, enrollment, iGroupBestRatio, iGroupFillRatio);
    503                    groupCount ++;
    504                }
    505            }
    506            if (groupCount > 0) {
    507                double difference = 1.0 - sameGroup / groupCount;
    508                weight += difference * iGroupFactor;
    509            }
    510        }
    511        return round(base * (1.0 - weight));
    512    }
    513
    514    @Override
    515    public double getDistanceConflictWeight(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) {
    516        if (iAdditiveWeights) {
    517            if (c.getR1().getPriority() < c.getR2().getPriority()) {
    518                return round(getBaseWeight(assignment, c.getE2()) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict));
    519            } else {
    520                return round(getBaseWeight(assignment, c.getE1()) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict));
    521            }
    522        } else {
    523            if (c.getR1().getPriority() < c.getR2().getPriority()) {
    524                return round(getWeightMultiplicative(assignment, c.getE2()) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict));
    525            } else {
    526                return round(getWeightMultiplicative(assignment, c.getE1()) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict));
    527            }
    528        }
    529    }
    530
    531    @Override
    532    public double getTimeOverlapConflictWeight(Assignment<Request, Enrollment> assignment, Enrollment e, TimeOverlapsCounter.Conflict c) {
    533        if (e == null || e.getRequest() == null) return 0.0;
    534        double toc = Math.min(iTimeOverlapFactor * c.getShare() / e.getNrSlots(), iTimeOverlapMaxLimit);
    535        if (iAdditiveWeights) {
    536            return round(getBaseWeight(assignment, e) * toc);
    537        } else {
    538            return round(getWeightMultiplicative(assignment, e) * toc);
    539        }
    540    }
    541
    542    @Override
    543    public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<DistanceConflict.Conflict> distanceConflicts, Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts) {
    544        if (iAdditiveWeights) {
    545            double base = getBaseWeight(assignment, enrollment);
    546            double dc = 0.0;
    547            if (distanceConflicts != null) {
    548                for (DistanceConflict.Conflict c: distanceConflicts) {
    549                    Enrollment other = (c.getE1().equals(enrollment) ? c.getE2() : c.getE1());
    550                    if (other.getRequest().getPriority() <= enrollment.getRequest().getPriority())
    551                        dc += base * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict);
    552                    else
    553                        dc += getBaseWeight(assignment, other) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict);
    554                }
    555            }
    556            double toc = 0.0;
    557            if (timeOverlappingConflicts != null) {
    558                for (TimeOverlapsCounter.Conflict c: timeOverlappingConflicts) {
    559                    toc += base * Math.min(iTimeOverlapFactor * c.getShare() / enrollment.getNrSlots(), iTimeOverlapMaxLimit);
    560                    Enrollment other = (c.getE1().equals(enrollment) ? c.getE2() : c.getE1());
    561                    if (other.getRequest() != null)
    562                        toc += getBaseWeight(assignment, other) * Math.min(iTimeOverlapFactor * c.getShare() / other.getNrSlots(), iTimeOverlapMaxLimit);
    563                }
    564            }
    565            return round(getWeight(assignment, enrollment) - dc - toc);
    566        } else {
    567            double base = getWeightMultiplicative(assignment, enrollment);
    568            double dc = 0.0;
    569            if (distanceConflicts != null) {
    570                for (DistanceConflict.Conflict c: distanceConflicts) {
    571                    Enrollment other = (c.getE1().equals(enrollment) ? c.getE2() : c.getE1());
    572                    if (other.getRequest().getPriority() <= enrollment.getRequest().getPriority())
    573                        dc += base * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict);
    574                    else
    575                        dc += getWeightMultiplicative(assignment, other) * (c.getStudent().isNeedShortDistances() ? iShortDistanceConflict : iDistanceConflict);
    576                }
    577            }
    578            double toc = 0.0;
    579            if (timeOverlappingConflicts != null) {
    580                for (TimeOverlapsCounter.Conflict c: timeOverlappingConflicts) {
    581                    toc += base * Math.min(iTimeOverlapFactor * c.getShare() / enrollment.getNrSlots(), iTimeOverlapMaxLimit);
    582                    Enrollment other = (c.getE1().equals(enrollment) ? c.getE2() : c.getE1());
    583                    if (other.getRequest() != null)
    584                        toc += getWeightMultiplicative(assignment, other) * Math.min(iTimeOverlapFactor * c.getShare() / other.getNrSlots(), iTimeOverlapMaxLimit);
    585                }
    586            }
    587            return round(base - dc - toc);
    588        }
    589    }
    590
    591
    592    @Override
    593    public boolean isBetterThanBestSolution(Solution<Request, Enrollment> currentSolution) {
    594        if (currentSolution.getBestInfo() == null) return true;
    595        if (iMaximizeAssignment) {
    596            long acr = Math.round(((StudentSectioningModel)currentSolution.getModel()).getContext(currentSolution.getAssignment()).getAssignedCourseRequestWeight());
    597            long bcr = Math.round(((StudentSectioningModel)currentSolution.getModel()).getBestAssignedCourseRequestWeight());
    598            if (acr != bcr)
    599                return acr > bcr;
    600        }
    601        return ((StudentSectioningModel)currentSolution.getModel()).getTotalValue(currentSolution.getAssignment(), iPreciseComparison) < currentSolution.getBestValue();
    602    }
    603
    604    @Override
    605    public boolean isFreeTimeAllowOverlaps() {
    606        return false;
    607    }
    608
    609    /**
    610     * Test case -- run to see the weights for a few courses
    611     * @param args program arguments
    612     */
    613    public static void main(String[] args) {
    614        PriorityStudentWeights pw = new PriorityStudentWeights(new DataProperties());
    615        DecimalFormat df = new DecimalFormat("0.000000");
    616        Student s = new Student(0l);
    617        new CourseRequest(1l, 0, false, s, ToolBox.toList(
    618                new Course(1, "A", "1", new Offering(0, "A")),
    619                new Course(1, "A", "2", new Offering(0, "A")),
    620                new Course(1, "A", "3", new Offering(0, "A"))), false, null);
    621        new CourseRequest(2l, 1, false, s, ToolBox.toList(
    622                new Course(1, "B", "1", new Offering(0, "B")),
    623                new Course(1, "B", "2", new Offering(0, "B")),
    624                new Course(1, "B", "3", new Offering(0, "B"))), false, null);
    625        new CourseRequest(3l, 2, false, s, ToolBox.toList(
    626                new Course(1, "C", "1", new Offering(0, "C")),
    627                new Course(1, "C", "2", new Offering(0, "C")),
    628                new Course(1, "C", "3", new Offering(0, "C"))), false, null);
    629        new CourseRequest(4l, 3, false, s, ToolBox.toList(
    630                new Course(1, "D", "1", new Offering(0, "D")),
    631                new Course(1, "D", "2", new Offering(0, "D")),
    632                new Course(1, "D", "3", new Offering(0, "D"))), false, null);
    633        new CourseRequest(5l, 4, false, s, ToolBox.toList(
    634                new Course(1, "E", "1", new Offering(0, "E")),
    635                new Course(1, "E", "2", new Offering(0, "E")),
    636                new Course(1, "E", "3", new Offering(0, "E"))), false, null);
    637        new CourseRequest(6l, 5, true, s, ToolBox.toList(
    638                new Course(1, "F", "1", new Offering(0, "F")),
    639                new Course(1, "F", "2", new Offering(0, "F")),
    640                new Course(1, "F", "3", new Offering(0, "F"))), false, null);
    641        new CourseRequest(7l, 6, true, s, ToolBox.toList(
    642                new Course(1, "G", "1", new Offering(0, "G")),
    643                new Course(1, "G", "2", new Offering(0, "G")),
    644                new Course(1, "G", "3", new Offering(0, "G"))), false, null);
    645
    646        Assignment<Request, Enrollment> assignment = new DefaultSingleAssignment<Request, Enrollment>();
    647        Placement p = new Placement(null, new TimeLocation(1, 90, 12, 0, 0, null, null, new BitSet(), 10), new ArrayList<RoomLocation>());
    648        for (Request r: s.getRequests()) {
    649            CourseRequest cr = (CourseRequest)r;
    650            double[] w = new double[] {0.0, 0.0, 0.0};
    651            for (int i = 0; i < cr.getCourses().size(); i++) {
    652                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    653                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    654                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    655                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    656                w[i] = pw.getWeight(assignment, e, null, null);
    657            }
    658            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    659        }
    660
    661        System.out.println("With one distance conflict:");
    662        for (Request r: s.getRequests()) {
    663            CourseRequest cr = (CourseRequest)r;
    664            double[] w = new double[] {0.0, 0.0, 0.0};
    665            for (int i = 0; i < cr.getCourses().size(); i++) {
    666                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    667                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    668                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    669                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    670                Set<DistanceConflict.Conflict> dc = new HashSet<DistanceConflict.Conflict>();
    671                dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e, (Section)sections.iterator().next()));
    672                w[i] = pw.getWeight(assignment, e, dc, null);
    673            }
    674            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    675        }
    676
    677        System.out.println("With two distance conflicts:");
    678        for (Request r: s.getRequests()) {
    679            CourseRequest cr = (CourseRequest)r;
    680            double[] w = new double[] {0.0, 0.0, 0.0};
    681            for (int i = 0; i < cr.getCourses().size(); i++) {
    682                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    683                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    684                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    685                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    686                Set<DistanceConflict.Conflict> dc = new HashSet<DistanceConflict.Conflict>();
    687                dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e, (Section)sections.iterator().next()));
    688                dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e,
    689                        new Section(1, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)));
    690                w[i] = pw.getWeight(assignment, e, dc, null);
    691            }
    692            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    693        }
    694
    695        System.out.println("With 25% time overlapping conflict:");
    696        for (Request r: s.getRequests()) {
    697            CourseRequest cr = (CourseRequest)r;
    698            double[] w = new double[] {0.0, 0.0, 0.0};
    699            for (int i = 0; i < cr.getCourses().size(); i++) {
    700                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    701                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    702                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    703                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    704                Set<TimeOverlapsCounter.Conflict> toc = new HashSet<TimeOverlapsCounter.Conflict>();
    705                toc.add(new TimeOverlapsCounter.Conflict(s, 3, e, sections.iterator().next(), e, sections.iterator().next()));
    706                w[i] = pw.getWeight(assignment, e, null, toc);
    707            }
    708            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    709        }
    710
    711        System.out.println("Disbalanced sections (by 2 / 10 students):");
    712        for (Request r: s.getRequests()) {
    713            CourseRequest cr = (CourseRequest)r;
    714            double[] w = new double[] {0.0, 0.0, 0.0};
    715            for (int i = 0; i < cr.getCourses().size(); i++) {
    716                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    717                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    718                Subpart x = new Subpart(0, "Lec", "Lec", cfg, null);
    719                Section a = new Section(0, 10, "x", x, p, null);
    720                new Section(1, 10, "y", x, p, null);
    721                sections.add(a);
    722                a.assigned(assignment, new Enrollment(s.getRequests().get(0), i, cfg, sections, assignment));
    723                a.assigned(assignment, new Enrollment(s.getRequests().get(0), i, cfg, sections, assignment));
    724                cfg.getContext(assignment).assigned(assignment, new Enrollment(s.getRequests().get(0), i, cfg, sections, assignment));
    725                cfg.getContext(assignment).assigned(assignment, new Enrollment(s.getRequests().get(0), i, cfg, sections, assignment));
    726                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    727                w[i] = pw.getWeight(assignment, e, null, null);
    728            }
    729            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    730        }
    731
    732        System.out.println("Same choice sections:");
    733        pw.iMPP = true;
    734        for (Request r: s.getRequests()) {
    735            CourseRequest cr = (CourseRequest)r;
    736            double[] w = new double[] {0.0, 0.0, 0.0};
    737            for (int i = 0; i < cr.getCourses().size(); i++) {
    738                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    739                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    740                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    741                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    742                Set<SctAssignment> other = new HashSet<SctAssignment>();
    743                other.add(new Section(1, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    744                cr.setInitialAssignment(new Enrollment(cr, i, cfg, other, assignment));
    745                w[i] = pw.getWeight(assignment, e, null, null);
    746            }
    747            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    748        }
    749
    750        System.out.println("Same time sections:");
    751        for (Request r: s.getRequests()) {
    752            CourseRequest cr = (CourseRequest)r;
    753            double[] w = new double[] {0.0, 0.0, 0.0};
    754            for (int i = 0; i < cr.getCourses().size(); i++) {
    755                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    756                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    757                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    758                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    759                Set<SctAssignment> other = new HashSet<SctAssignment>();
    760                other.add(new Section(1, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null, new Instructor(1l, null, "Josef Novak", null)));
    761                cr.setInitialAssignment(new Enrollment(cr, i, cfg, other, assignment));
    762                w[i] = pw.getWeight(assignment, e, null, null);
    763            }
    764            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    765        }
    766
    767        System.out.println("Different time sections:");
    768        Placement q = new Placement(null, new TimeLocation(1, 102, 12, 0, 0, null, null, new BitSet(), 10), new ArrayList<RoomLocation>());
    769        for (Request r: s.getRequests()) {
    770            CourseRequest cr = (CourseRequest)r;
    771            double[] w = new double[] {0.0, 0.0, 0.0};
    772            for (int i = 0; i < cr.getCourses().size(); i++) {
    773                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    774                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    775                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    776                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    777                Set<SctAssignment> other = new HashSet<SctAssignment>();
    778                other.add(new Section(1, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), q, null));
    779                cr.setInitialAssignment(new Enrollment(cr, i, cfg, other, assignment));
    780                w[i] = pw.getWeight(assignment, e, null, null);
    781            }
    782            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    783        }
    784
    785        System.out.println("Two sections, one same choice, one same time:");
    786        for (Request r: s.getRequests()) {
    787            CourseRequest cr = (CourseRequest)r;
    788            double[] w = new double[] {0.0, 0.0, 0.0};
    789            for (int i = 0; i < cr.getCourses().size(); i++) {
    790                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    791                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    792                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    793                sections.add(new Section(1, 1, "y", new Subpart(1, "Rec", "Rec", cfg, null), p, null));
    794                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    795                Set<SctAssignment> other = new HashSet<SctAssignment>();
    796                other.add(new Section(2, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    797                other.add(new Section(3, 1, "y", new Subpart(1, "Rec", "Rec", cfg, null), p, null, new Instructor(1l, null, "Josef Novak", null)));
    798                cr.setInitialAssignment(new Enrollment(cr, i, cfg, other, assignment));
    799                w[i] = pw.getWeight(assignment, e, null, null);
    800            }
    801            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    802        }
    803
    804    }
    805
    806    @Override
    807    public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<StudentQuality.Conflict> qualityConflicts) {
    808        if (iAdditiveWeights) {
    809            double base = getBaseWeight(assignment, enrollment);
    810            double penalty = 0.0;
    811            if (qualityConflicts != null) {
    812                for (StudentQuality.Conflict c: qualityConflicts) {
    813                    switch (c.getType().getType()) {
    814                        case REQUEST:
    815                            if (enrollment.isCourseRequest())
    816                                penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    817                            break;
    818                        case BOTH:
    819                            Enrollment other = c.getOther(enrollment);
    820                            penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    821                            penalty += getBaseWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other);
    822                            break;
    823                        case LOWER:
    824                            other = c.getOther(enrollment);
    825                            if (other.getRequest().getPriority() <= enrollment.getRequest().getPriority())
    826                                penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    827                            else
    828                                penalty += getBaseWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other);
    829                            break;
    830                        case HIGHER:
    831                            other = c.getOther(enrollment);
    832                            if (other.getRequest().getPriority() >= enrollment.getRequest().getPriority())
    833                                penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    834                            else
    835                                penalty += getBaseWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other);
    836                    }
    837                }
    838            }
    839            return round(getWeight(assignment, enrollment) - penalty);
    840        } else {
    841            double base = getWeightMultiplicative(assignment, enrollment);
    842            double penalty = 0.0;
    843            if (qualityConflicts != null) {
    844                for (StudentQuality.Conflict c: qualityConflicts) {
    845                    Enrollment other = c.getOther(enrollment);
    846                    switch (c.getType().getType()) {
    847                        case REQUEST:
    848                            if (enrollment.isCourseRequest())
    849                                penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    850                            else if (other.isCourseRequest())
    851                                penalty += getWeightMultiplicative(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other);
    852                            break;
    853                        case BOTH:
    854                            penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    855                            if (other.getRequest() != null)
    856                                penalty += getWeightMultiplicative(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other);
    857                            break;
    858                        case LOWER:
    859                            other = c.getOther(enrollment);
    860                            if (other.getRequest().getPriority() <= enrollment.getRequest().getPriority())
    861                                penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    862                            else if (other.getRequest() != null)
    863                                penalty += getWeightMultiplicative(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other);
    864                            break;
    865                        case HIGHER:
    866                            other = c.getOther(enrollment);
    867                            if (other.getRequest().getPriority() >= enrollment.getRequest().getPriority())
    868                                penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    869                            else if (other.getRequest() != null)
    870                                penalty += getWeightMultiplicative(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other);
    871                    }
    872                }
    873            }
    874            return round(base - penalty);
    875        }
    876    }
    877
    878    @Override
    879    public double getStudentQualityConflictWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Conflict conflict) {
    880        switch (conflict.getType().getType()) {
    881            case BOTH:
    882                if (enrollment == null || enrollment.getRequest() == null) return 0.0;
    883                if (iAdditiveWeights) {
    884                    return round(getBaseWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment));
    885                } else {
    886                    return round(getWeightMultiplicative(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment));
    887                }
    888            case REQUEST:
    889                if (enrollment == null || enrollment.getRequest() == null || !enrollment.isCourseRequest()) return 0.0;
    890                if (iAdditiveWeights) {
    891                    return round(getBaseWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment));
    892                } else {
    893                    return round(getWeightMultiplicative(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment));
    894                }
    895            case LOWER:
    896                if (iAdditiveWeights) {
    897                    if (conflict.getR1().getPriority() < conflict.getR2().getPriority()) {
    898                        return round(getBaseWeight(assignment, conflict.getE2()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2()));
    899                    } else {
    900                        return round(getBaseWeight(assignment, conflict.getE1()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1()));
    901                    }
    902                } else {
    903                    if (conflict.getR1().getPriority() < conflict.getR2().getPriority()) {
    904                        return round(getWeightMultiplicative(assignment, conflict.getE2()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2()));
    905                    } else {
    906                        return round(getWeightMultiplicative(assignment, conflict.getE1()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1()));
    907                    }
    908                }
    909            case HIGHER:
    910                if (iAdditiveWeights) {
    911                    if (conflict.getR1().getPriority() > conflict.getR2().getPriority()) {
    912                        return round(getBaseWeight(assignment, conflict.getE2()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2()));
    913                    } else {
    914                        return round(getBaseWeight(assignment, conflict.getE1()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1()));
    915                    }
    916                } else {
    917                    if (conflict.getR1().getPriority() < conflict.getR2().getPriority()) {
    918                        return round(getWeightMultiplicative(assignment, conflict.getE2()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2()));
    919                    } else {
    920                        return round(getWeightMultiplicative(assignment, conflict.getE1()) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1()));
    921                    }
    922                }
    923            default:
    924                return 0.0;
    925        }
    926    }
    927}