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}