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