    001package org.cpsolver.studentsct.report;
    002
    003import java.text.DecimalFormat;
    004import java.util.ArrayList;
    005import java.util.Comparator;
    006import java.util.HashMap;
    007import java.util.HashSet;
    008import java.util.List;
    009import java.util.Map;
    010import java.util.Set;
    011import java.util.TreeSet;
    012
    013import org.cpsolver.ifs.assignment.Assignment;
    014import org.cpsolver.ifs.util.CSVFile;
    015import org.cpsolver.ifs.util.DataProperties;
    016import org.cpsolver.studentsct.StudentSectioningModel;
    017import org.cpsolver.studentsct.extension.TimeOverlapsCounter;
    018import org.cpsolver.studentsct.extension.TimeOverlapsCounter.Conflict;
    019import org.cpsolver.studentsct.model.Course;
    020import org.cpsolver.studentsct.model.Enrollment;
    021import org.cpsolver.studentsct.model.FreeTimeRequest;
    022import org.cpsolver.studentsct.model.Request;
    023import org.cpsolver.studentsct.model.SctAssignment;
    024import org.cpsolver.studentsct.model.Section;
    025
    026
    027/**
    028 * This class lists time overlapping conflicts in a {@link CSVFile} comma
    029 * separated text file. Only sections that allow overlaps
    030 * (see {@link SctAssignment#isAllowOverlap()}) can overlap. See {@link TimeOverlapsCounter} for more
    031 * details. <br>
    032 * <br>
    033 *
    034 * Each line represent a pair if classes that overlap in time and have one
    035 * or more students in common.
    036 *
    037 * <br>
    038 * <br>
    039 *
    040 * Usage: new DistanceConflictTable(model),createTable(true, true).save(aFile);
    041 *
    042 * <br>
    043 * <br>
    044 *
    045 * @author  Tomáš Müller
    046 * @version StudentSct 1.3 (Student Sectioning)<br>
    047 *          Copyright (C) 2013 - 2014 Tomáš Müller<br>
    048 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    049 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    050 * <br>
    051 *          This library is free software; you can redistribute it and/or modify
    052 *          it under the terms of the GNU Lesser General Public License as
    053 *          published by the Free Software Foundation; either version 3 of the
    054 *          License, or (at your option) any later version. <br>
    055 * <br>
    056 *          This library is distributed in the hope that it will be useful, but
    057 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    058 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    059 *          Lesser General Public License for more details. <br>
    060 * <br>
    061 *          You should have received a copy of the GNU Lesser General Public
    062 *          License along with this library; if not see
    063 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    064 */
    065public class TimeOverlapConflictTable extends AbstractStudentSectioningReport {
    066    private static DecimalFormat sDF1 = new DecimalFormat("0.####");
    067    private static DecimalFormat sDF2 = new DecimalFormat("0.0000");
    068
    069    private TimeOverlapsCounter iTOC = null;
    070
    071    /**
    072     * Constructor
    073     *
    074     * @param model
    075     *            student sectioning model
    076     */
    077    public TimeOverlapConflictTable(StudentSectioningModel model) {
    078        super(model);
    079        iTOC = model.getTimeOverlaps();
    080        if (iTOC == null) {
    081            iTOC = new TimeOverlapsCounter(null, model.getProperties());
    082        }
    083    }
    084
    085    /**
    086     * Create report
    087     *
    088     * @param assignment current assignment
    089     * @return report as comma separated text file
    090     */
    091    @Override
    092    public CSVFile createTable(Assignment<Request, Enrollment> assignment, DataProperties properties) {
    093        CSVFile csv = new CSVFile();
    094        csv.setHeader(new CSVFile.CSVField[] { new CSVFile.CSVField("Course"), new CSVFile.CSVField("Total\nConflicts"),
    095                new CSVFile.CSVField("Class"), new CSVFile.CSVField("Meeting Time"),
    096                new CSVFile.CSVField("Time\nConflicts"), new CSVFile.CSVField("% of Total\nConflicts"),
    097                new CSVFile.CSVField("Conflicting\nClass"), new CSVFile.CSVField("Conflicting\nMeeting Time"),
    098                new CSVFile.CSVField("Overlap [min]"), new CSVFile.CSVField("Joined\nConflicts"), new CSVFile.CSVField("% of Total\nConflicts")
    099                });
    100
    101        Set<Conflict> confs = new HashSet<Conflict>();
    102        for (Request r1 : getModel().variables()) {
    103            Enrollment e1 = assignment.getValue(r1);
    104            if (e1 == null || r1 instanceof FreeTimeRequest)
    105                continue;
    106            for (Request r2 : r1.getStudent().getRequests()) {
    107                Enrollment e2 = assignment.getValue(r2);
    108                if (r2 instanceof FreeTimeRequest) {
    109                    FreeTimeRequest ft = (FreeTimeRequest)r2;
    110                    confs.addAll(iTOC.conflicts(e1, ft.createEnrollment()));
    111                } else if (e2 != null && r1.getId() < r2.getId()) {
    112                    confs.addAll(iTOC.conflicts(e1, e2));
    113                }
    114            }
    115        }
    116
    117        HashMap<Course, Set<Long>> totals = new HashMap<Course, Set<Long>>();
    118        HashMap<CourseSection, Map<CourseSection, Double>> conflictingPairs = new HashMap<CourseSection, Map<CourseSection,Double>>();
    119        HashMap<CourseSection, Set<Long>> sectionOverlaps = new HashMap<CourseSection, Set<Long>>();
    120
    121        for (Conflict conflict : confs) {
    122            if (!matches(conflict.getR1(), conflict.getE1())) continue;
    123            if (conflict.getR1() == null || conflict.getR1() instanceof FreeTimeRequest || conflict.getR2() == null || conflict.getR2() instanceof FreeTimeRequest) continue;
    124            Section s1 = (Section)conflict.getS1(), s2 = (Section)conflict.getS2();
    125            Request r1 = conflict.getR1();
    126            Course c1 = assignment.getValue(conflict.getR1()).getCourse();
    127            Request r2 = conflict.getR2();
    128            Course c2 = assignment.getValue(conflict.getR2()).getCourse();
    129            CourseSection a = new CourseSection(c1, s1);
    130            CourseSection b = new CourseSection(c2, s2);
    131
    132            Set<Long> students = totals.get(c1);
    133            if (students == null) {
    134                students = new HashSet<Long>();
    135                totals.put(c1, students);
    136            }
    137            students.add(r1.getStudent().getId());
    138            students = totals.get(c2);
    139            if (students == null) {
    140                students = new HashSet<Long>();
    141                totals.put(c2, students);
    142            }
    143            students.add(r2.getStudent().getId());
    144
    145            Set<Long> total = sectionOverlaps.get(a);
    146            if (total == null) {
    147                total = new HashSet<Long>();
    148                sectionOverlaps.put(a, total);
    149            }
    150            total.add(r1.getStudent().getId());
    151            Map<CourseSection, Double> pair = conflictingPairs.get(a);
    152            if (pair == null) {
    153                pair = new HashMap<CourseSection, Double>();
    154                conflictingPairs.put(a, pair);
    155            }
    156            Double prev = pair.get(b);
    157            pair.put(b, r2.getWeight() + (prev == null ? 0.0 : prev.doubleValue()));
    158
    159            total = sectionOverlaps.get(b);
    160            if (total == null) {
    161                total = new HashSet<Long>();
    162                sectionOverlaps.put(b, total);
    163            }
    164            total.add(r2.getStudent().getId());
    165            pair = conflictingPairs.get(b);
    166            if (pair == null) {
    167                pair = new HashMap<CourseSection, Double>();
    168                conflictingPairs.put(b, pair);
    169            }
    170            prev = pair.get(a);
    171            pair.put(a, r1.getWeight() + (prev == null ? 0.0 : prev.doubleValue()));
    172        }
    173
    174        Comparator<Course> courseComparator = new Comparator<Course>() {
    175            @Override
    176            public int compare(Course a, Course b) {
    177                int cmp = a.getName().compareTo(b.getName());
    178                if (cmp != 0) return cmp;
    179                return a.getId() < b.getId() ? -1 : a.getId() == b.getId() ? 0 : 1;
    180            }
    181        };
    182        Comparator<Section> sectionComparator = new Comparator<Section>() {
    183            @Override
    184            public int compare(Section a, Section b) {
    185                int cmp = a.getSubpart().getConfig().getOffering().getName().compareTo(b.getSubpart().getConfig().getOffering().getName());
    186                if (cmp != 0) return cmp;
    187                cmp = a.getSubpart().getInstructionalType().compareTo(b.getSubpart().getInstructionalType());
    188                // if (cmp != 0) return cmp;
    189                // cmp = a.getName().compareTo(b.getName());
    190                if (cmp != 0) return cmp;
    191                return a.getId() < b.getId() ? -1 : a.getId() == b.getId() ? 0 : 1;
    192            }
    193        };
    194
    195        TreeSet<Course> courses = new TreeSet<Course>(courseComparator);
    196        courses.addAll(totals.keySet());
    197        for (Course course: courses) {
    198            Set<Long> total = totals.get(course);
    199
    200            TreeSet<Section> sections = new TreeSet<Section>(sectionComparator);
    201            for (Map.Entry<CourseSection, Set<Long>> entry: sectionOverlaps.entrySet())
    202                if (course.equals(entry.getKey().getCourse()))
    203                    sections.add(entry.getKey().getSection());
    204
    205            boolean firstCourse = true;
    206            for (Section section: sections) {
    207                Set<Long> sectionOverlap = sectionOverlaps.get(new CourseSection(course, section));
    208                Map<CourseSection, Double> pair = conflictingPairs.get(new CourseSection(course, section));
    209                boolean firstClass = true;
    210
    211                for (CourseSection other: new TreeSet<CourseSection>(pair.keySet())) {
    212                    List<CSVFile.CSVField> line = new ArrayList<CSVFile.CSVField>();
    213                    line.add(new CSVFile.CSVField(firstCourse && firstClass ? course.getName() : ""));
    214                    line.add(new CSVFile.CSVField(firstCourse && firstClass ? total.size() : ""));
    215
    216                    line.add(new CSVFile.CSVField(firstClass ? section.getSubpart().getName() + " " + section.getName(course.getId()): ""));
    217                    line.add(new CSVFile.CSVField(firstClass ? section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader(isUseAmPm()) + " - " + section.getTime().getEndTimeHeader(isUseAmPm()): ""));
    218
    219                    line.add(new CSVFile.CSVField(firstClass && sectionOverlap != null ? String.valueOf(sectionOverlap.size()): ""));
    220                    line.add(new CSVFile.CSVField(firstClass && sectionOverlap != null ? sDF2.format(((double)sectionOverlap.size()) / total.size()) : ""));
    221
    222                    line.add(new CSVFile.CSVField(other.getCourse().getName() + " " + other.getSection().getSubpart().getName() + " " + other.getSection().getName(other.getCourse().getId())));
    223                    line.add(new CSVFile.CSVField(other.getSection().getTime().getDayHeader() + " " + other.getSection().getTime().getStartTimeHeader(isUseAmPm()) + " - " + other.getSection().getTime().getEndTimeHeader(isUseAmPm())));
    224
    225                    line.add(new CSVFile.CSVField(sDF1.format(5 * iTOC.share(section, other.getSection()))));
    226                    line.add(new CSVFile.CSVField(sDF1.format(pair.get(other))));
    227                    line.add(new CSVFile.CSVField(sDF2.format(pair.get(other) / total.size())));
    228
    229                    csv.addLine(line);
    230                    firstClass = false;
    231                }
    232                firstCourse = false;
    233            }
    234
    235            csv.addLine();
    236        }
    237
    238
    239        return csv;
    240    }
    241}