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