    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.extension.DistanceConflict;
    018import org.cpsolver.studentsct.extension.StudentQuality;
    019import org.cpsolver.studentsct.extension.TimeOverlapsCounter;
    020import org.cpsolver.studentsct.model.Config;
    021import org.cpsolver.studentsct.model.Course;
    022import org.cpsolver.studentsct.model.CourseRequest;
    023import org.cpsolver.studentsct.model.Enrollment;
    024import org.cpsolver.studentsct.model.Offering;
    025import org.cpsolver.studentsct.model.Request;
    026import org.cpsolver.studentsct.model.SctAssignment;
    027import org.cpsolver.studentsct.model.Section;
    028import org.cpsolver.studentsct.model.Student;
    029import org.cpsolver.studentsct.model.Subpart;
    030
    031
    032/**
    033 * Original weighting that was used before this student weightings model was introduced
    034 *
    035 * @version StudentSct 1.3 (Student Sectioning)<br>
    036 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    037 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    038 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    039 * <br>
    040 *          This library is free software; you can redistribute it and/or modify
    041 *          it under the terms of the GNU Lesser General Public License as
    042 *          published by the Free Software Foundation; either version 3 of the
    043 *          License, or (at your option) any later version. <br>
    044 * <br>
    045 *          This library is distributed in the hope that it will be useful, but
    046 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    047 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    048 *          Lesser General Public License for more details. <br>
    049 * <br>
    050 *          You should have received a copy of the GNU Lesser General Public
    051 *          License along with this library; if not see
    052 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    053 */
    054
    055public class OriginalStudentWeights implements StudentWeights {
    056    private double iPriorityWeight = 0.90;
    057    private double iAlterativeWeight = 1.0;
    058    private double iInitialWeight = 1.2;
    059    private double iSelectedWeight = 1.1;
    060    private double iWaitlistedWeight = 1.01;
    061    private double iDistConfWeight = 0.95;
    062    protected double[] iQalityWeights;
    063    /**
    064     * Enrollment value: value * sAltValue ^ index, where index is zero for the
    065     * first course, one for the second course etc.
    066     */
    067    private double iAltValue = 0.5;
    068    private double iDummyStudentWeight = 0.5;
    069    private double iNormPenalty = 5.0;
    070
    071    public OriginalStudentWeights(DataProperties config) {
    072        iDummyStudentWeight = config.getPropertyDouble("Student.DummyStudentWeight", iDummyStudentWeight);
    073        iQalityWeights = new double[StudentQuality.Type.values().length];
    074        for (StudentQuality.Type type: StudentQuality.Type.values()) {
    075            iQalityWeights[type.ordinal()] = config.getPropertyDouble(type.getWeightName(), type.getWeightDefault());
    076        }
    077    }
    078
    079    /**
    080     * Normalized enrollment penalty -- to be used in
    081     * {@link Enrollment#toDouble(Assignment)}
    082     * @param penalty given penalty
    083     * @return normalized penalty
    084     */
    085    public double normalizePenalty(double penalty) {
    086        return iNormPenalty / (iNormPenalty + penalty);
    087    }
    088
    089
    090    public double getWeight(Request request) {
    091        return   Math.pow(iPriorityWeight, request.getPriority())
    092               * (request.isAlternative() ? iAlterativeWeight : 1.0)
    093               * (request.getStudent().isDummy() ? iDummyStudentWeight : 1.0);
    094    }
    095
    096    @Override
    097    public double getBound(Request request) {
    098        double w = getWeight(request) * Math.pow(iInitialWeight, (request.getInitialAssignment() == null ? 0 : 1));
    099        if (request instanceof CourseRequest) {
    100            CourseRequest cr = (CourseRequest)request;
    101            w *= Math.pow(iSelectedWeight, cr.getSelectedChoices().isEmpty() ? 0 : 1);
    102            w *= Math.pow(iWaitlistedWeight, cr.getWaitlistedChoices().isEmpty() ? 0 : 1);
    103            w *= normalizePenalty(cr.getMinPenalty());
    104        }
    105        return w;
    106    }
    107
    108    @Override
    109    public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment) {
    110        return  getWeight(enrollment.getRequest())
    111                * Math.pow(iAltValue, enrollment.getPriority())
    112                * Math.pow(iInitialWeight, enrollment.percentInitial())
    113                * Math.pow(iSelectedWeight, enrollment.percentSelected())
    114                * Math.pow(iWaitlistedWeight, enrollment.percentWaitlisted())
    115                * normalizePenalty(enrollment.getPenalty());
    116    }
    117
    118    @Override
    119    public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<DistanceConflict.Conflict> distanceConflicts, Set<TimeOverlapsCounter.Conflict> timeOverlappingConflicts) {
    120        int share = 0;
    121        if (timeOverlappingConflicts != null)
    122            for (TimeOverlapsCounter.Conflict c: timeOverlappingConflicts)
    123                share += c.getShare();
    124        return getWeight(assignment, enrollment)
    125               * (distanceConflicts == null || distanceConflicts.isEmpty() ? 1.0 : Math.pow(iDistConfWeight, distanceConflicts.size()))
    126               * Math.max(share == 0 ? 1.0 : 1.0 - (((double)share) / enrollment.getNrSlots()) / 2.0, 0.5);
    127    }
    128
    129    @Override
    130    public double getDistanceConflictWeight(Assignment<Request, Enrollment> assignment, DistanceConflict.Conflict c) {
    131        if (c.getR1().getPriority() < c.getR2().getPriority()) {
    132            return (1.0 - iDistConfWeight) * getWeight(assignment, c.getE1());
    133        } else {
    134            return (1.0 - iDistConfWeight) * getWeight(assignment, c.getE2());
    135        }
    136    }
    137
    138    @Override
    139    public double getTimeOverlapConflictWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, TimeOverlapsCounter.Conflict timeOverlap) {
    140        return Math.min(0.5 * timeOverlap.getShare() / enrollment.getNrSlots(), 0.5) * getWeight(assignment, enrollment);
    141    }
    142
    143    @Override
    144    public boolean isBetterThanBestSolution(Solution<Request, Enrollment> currentSolution) {
    145        if (currentSolution.getBestInfo() == null) return true;
    146        int unassigned = currentSolution.getModel().nrUnassignedVariables(currentSolution.getAssignment());
    147        if (currentSolution.getModel().getBestUnassignedVariables() != unassigned)
    148            return currentSolution.getModel().getBestUnassignedVariables() > unassigned;
    149        return currentSolution.getModel().getTotalValue(currentSolution.getAssignment()) < currentSolution.getBestValue();
    150    }
    151
    152    /**
    153     * Test case -- run to see the weights for a few courses
    154     * @param args program arguments
    155     */
    156    public static void main(String[] args) {
    157        OriginalStudentWeights pw = new OriginalStudentWeights(new DataProperties());
    158        DecimalFormat df = new DecimalFormat("0.000");
    159        Student s = new Student(0l);
    160        new CourseRequest(1l, 0, false, s, ToolBox.toList(
    161                new Course(1, "A", "1", new Offering(0, "A")),
    162                new Course(1, "A", "2", new Offering(0, "A")),
    163                new Course(1, "A", "3", new Offering(0, "A"))), false, null);
    164        new CourseRequest(2l, 1, false, s, ToolBox.toList(
    165                new Course(1, "B", "1", new Offering(0, "B")),
    166                new Course(1, "B", "2", new Offering(0, "B")),
    167                new Course(1, "B", "3", new Offering(0, "B"))), false, null);
    168        new CourseRequest(3l, 2, false, s, ToolBox.toList(
    169                new Course(1, "C", "1", new Offering(0, "C")),
    170                new Course(1, "C", "2", new Offering(0, "C")),
    171                new Course(1, "C", "3", new Offering(0, "C"))), false, null);
    172        new CourseRequest(4l, 3, false, s, ToolBox.toList(
    173                new Course(1, "D", "1", new Offering(0, "D")),
    174                new Course(1, "D", "2", new Offering(0, "D")),
    175                new Course(1, "D", "3", new Offering(0, "D"))), false, null);
    176        new CourseRequest(5l, 4, false, s, ToolBox.toList(
    177                new Course(1, "E", "1", new Offering(0, "E")),
    178                new Course(1, "E", "2", new Offering(0, "E")),
    179                new Course(1, "E", "3", new Offering(0, "E"))), false, null);
    180        new CourseRequest(6l, 5, true, s, ToolBox.toList(
    181                new Course(1, "F", "1", new Offering(0, "F")),
    182                new Course(1, "F", "2", new Offering(0, "F")),
    183                new Course(1, "F", "3", new Offering(0, "F"))), false, null);
    184        new CourseRequest(7l, 6, true, s, ToolBox.toList(
    185                new Course(1, "G", "1", new Offering(0, "G")),
    186                new Course(1, "G", "2", new Offering(0, "G")),
    187                new Course(1, "G", "3", new Offering(0, "G"))), false, null);
    188
    189        Assignment<Request, Enrollment> assignment = new DefaultSingleAssignment<Request, Enrollment>();
    190        Placement p = new Placement(null, new TimeLocation(1, 90, 12, 0, 0, null, null, new BitSet(), 10), new ArrayList<RoomLocation>());
    191        for (Request r: s.getRequests()) {
    192            CourseRequest cr = (CourseRequest)r;
    193            double[] w = new double[] {0.0, 0.0, 0.0};
    194            for (int i = 0; i < cr.getCourses().size(); i++) {
    195                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    196                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    197                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    198                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    199                w[i] = pw.getWeight(assignment, e, null, null);
    200            }
    201            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    202        }
    203
    204        System.out.println("With one distance conflict:");
    205        for (Request r: s.getRequests()) {
    206            CourseRequest cr = (CourseRequest)r;
    207            double[] w = new double[] {0.0, 0.0, 0.0};
    208            for (int i = 0; i < cr.getCourses().size(); i++) {
    209                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    210                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    211                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    212                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    213                Set<DistanceConflict.Conflict> dc = new HashSet<DistanceConflict.Conflict>();
    214                dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e, (Section)sections.iterator().next()));
    215                w[i] = pw.getWeight(assignment, e, dc, null);
    216            }
    217            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    218        }
    219
    220        System.out.println("With two distance conflicts:");
    221        for (Request r: s.getRequests()) {
    222            CourseRequest cr = (CourseRequest)r;
    223            double[] w = new double[] {0.0, 0.0, 0.0};
    224            for (int i = 0; i < cr.getCourses().size(); i++) {
    225                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    226                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    227                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    228                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    229                Set<DistanceConflict.Conflict> dc = new HashSet<DistanceConflict.Conflict>();
    230                dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e, (Section)sections.iterator().next()));
    231                dc.add(new DistanceConflict.Conflict(s, e, (Section)sections.iterator().next(), e,
    232                        new Section(1, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null)));
    233                w[i] = pw.getWeight(assignment, e, dc, null);
    234            }
    235            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    236        }
    237
    238        System.out.println("With 25% time overlapping conflicts:");
    239        for (Request r: s.getRequests()) {
    240            CourseRequest cr = (CourseRequest)r;
    241            double[] w = new double[] {0.0, 0.0, 0.0};
    242            for (int i = 0; i < cr.getCourses().size(); i++) {
    243                Config cfg = new Config(0l, -1, "", cr.getCourses().get(i).getOffering());
    244                Set<SctAssignment> sections = new HashSet<SctAssignment>();
    245                sections.add(new Section(0, 1, "x", new Subpart(0, "Lec", "Lec", cfg, null), p, null));
    246                Enrollment e = new Enrollment(cr, i, cfg, sections, assignment);
    247                Set<TimeOverlapsCounter.Conflict> toc = new HashSet<TimeOverlapsCounter.Conflict>();
    248                toc.add(new TimeOverlapsCounter.Conflict(s, 3, e, sections.iterator().next(), e, sections.iterator().next()));
    249                w[i] = pw.getWeight(assignment, e, null, toc);
    250            }
    251            System.out.println(cr + ": " + df.format(w[0]) + "  " + df.format(w[1]) + "  " + df.format(w[2]));
    252        }
    253    }
    254
    255    @Override
    256    public boolean isFreeTimeAllowOverlaps() {
    257        return true;
    258    }
    259
    260    @Override
    261    public double getWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, Set<StudentQuality.Conflict> qualityConflicts) {
    262        double base = getWeight(assignment, enrollment);
    263        double penalty = 0.0;
    264        if (qualityConflicts != null) {
    265            for (StudentQuality.Conflict c: qualityConflicts) {
    266                Enrollment other = c.getOther(enrollment);
    267                switch (c.getType().getType()) {
    268                    case REQUEST:
    269                        if (enrollment.isCourseRequest())
    270                            penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    271                        else if (other.isCourseRequest())
    272                            penalty += getWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other);
    273                        break;
    274                    case BOTH:
    275                        penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    276                        if (other.getRequest() != null)
    277                            penalty += getWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other);
    278                        break;
    279                    case LOWER:
    280                        other = c.getOther(enrollment);
    281                        if (other.getRequest().getPriority() <= enrollment.getRequest().getPriority())
    282                            penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    283                        else if (other.getRequest() != null)
    284                            penalty += getWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other);
    285                        break;
    286                    case HIGHER:
    287                        other = c.getOther(enrollment);
    288                        if (other.getRequest().getPriority() >= enrollment.getRequest().getPriority())
    289                            penalty += base * iQalityWeights[c.getType().ordinal()] * c.getWeight(enrollment);
    290                        else if (other.getRequest() != null)
    291                            penalty += getWeight(assignment, other) * iQalityWeights[c.getType().ordinal()] * c.getWeight(other);
    292                }
    293            }
    294        }
    295        return base - penalty;
    296    }
    297
    298    @Override
    299    public double getStudentQualityConflictWeight(Assignment<Request, Enrollment> assignment, Enrollment enrollment, StudentQuality.Conflict conflict) {
    300        switch (conflict.getType().getType()) {
    301            case BOTH:
    302                if (enrollment == null || enrollment.getRequest() == null) return 0.0;
    303                return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment);
    304            case REQUEST:
    305                if (enrollment == null || enrollment.getRequest() == null || !enrollment.isCourseRequest()) return 0.0;
    306                return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(enrollment);
    307            case LOWER:
    308                if (conflict.getR1().getPriority() < conflict.getR2().getPriority()) {
    309                    return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2());
    310                } else {
    311                    return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1());
    312                }
    313            case HIGHER:
    314                if (conflict.getR1().getPriority() > conflict.getR2().getPriority()) {
    315                    return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE2());
    316                } else {
    317                    return getWeight(assignment, enrollment) * iQalityWeights[conflict.getType().ordinal()] * conflict.getWeight(conflict.getE1());
    318                }
    319            default:
    320                return 0.0;
    321        }
    322    }
    323}