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