001    package net.sf.cpsolver.studentsct.report;
002    
003    import java.text.DecimalFormat;
004    import java.util.ArrayList;
005    import java.util.Comparator;
006    import java.util.HashMap;
007    import java.util.HashSet;
008    import java.util.List;
009    import java.util.Map;
010    import java.util.Set;
011    import java.util.TreeSet;
012    
013    import net.sf.cpsolver.ifs.util.CSVFile;
014    import net.sf.cpsolver.ifs.util.DataProperties;
015    import net.sf.cpsolver.studentsct.StudentSectioningModel;
016    import net.sf.cpsolver.studentsct.extension.TimeOverlapsCounter;
017    import net.sf.cpsolver.studentsct.extension.TimeOverlapsCounter.Conflict;
018    import net.sf.cpsolver.studentsct.model.Assignment;
019    import net.sf.cpsolver.studentsct.model.Course;
020    import net.sf.cpsolver.studentsct.model.FreeTimeRequest;
021    import net.sf.cpsolver.studentsct.model.Request;
022    import net.sf.cpsolver.studentsct.model.Section;
023    import 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 Tomas Muller<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     */
062    public class TimeOverlapConflictTable implements StudentSectioningReport {
063        private static DecimalFormat sDF = new DecimalFormat("0.000");
064    
065        private StudentSectioningModel iModel = null;
066        private TimeOverlapsCounter iTOC = null;
067    
068        /**
069         * Constructor
070         * 
071         * @param model
072         *            student sectioning model
073         */
074        public TimeOverlapConflictTable(StudentSectioningModel model) {
075            iModel = model;
076            iTOC = model.getTimeOverlaps();
077            if (iTOC == null) {
078                iTOC = new TimeOverlapsCounter(null, model.getProperties());
079            }
080        }
081    
082        /** Return student sectioning model */
083        public StudentSectioningModel getModel() {
084            return iModel;
085        }
086    
087        /**
088         * Create report
089         * 
090         * @param includeLastLikeStudents
091         *            true, if last-like students should be included (i.e.,
092         *            {@link Student#isDummy()} is true)
093         * @param includeRealStudents
094         *            true, if real students should be included (i.e.,
095         *            {@link Student#isDummy()} is false)
096         * @return report as comma separated text file
097         */
098        public CSVFile createTable(boolean includeLastLikeStudents, boolean includeRealStudents) {
099            CSVFile csv = new CSVFile();
100            csv.setHeader(new CSVFile.CSVField[] { new CSVFile.CSVField("Course"), new CSVFile.CSVField("Total\nConflicts"),
101                    new CSVFile.CSVField("Class"), new CSVFile.CSVField("Meeting Time"),
102                    new CSVFile.CSVField("Time\nConflicts"), new CSVFile.CSVField("% of Total\nConflicts"),
103                    new CSVFile.CSVField("Conflicting\nClass"), new CSVFile.CSVField("Conflicting\nMeeting Time"),
104                    new CSVFile.CSVField("Overlap [min]"), new CSVFile.CSVField("Joined\nConflicts"), new CSVFile.CSVField("% of Total\nConflicts")
105                    });
106            Set<Conflict> confs = iTOC.computeAllConflicts();
107            
108            HashMap<Course, Set<Long>> totals = new HashMap<Course, Set<Long>>();
109            HashMap<CourseSection, Map<CourseSection, Double>> conflictingPairs = new HashMap<CourseSection, Map<CourseSection,Double>>();
110            HashMap<CourseSection, Set<Long>> sectionOverlaps = new HashMap<CourseSection, Set<Long>>();        
111            
112            for (Conflict conflict : confs) {
113                if (conflict.getStudent().isDummy() && !includeLastLikeStudents) continue;
114                if (!conflict.getStudent().isDummy() && !includeRealStudents) continue;
115                if (conflict.getR1() instanceof FreeTimeRequest || conflict.getR2() instanceof FreeTimeRequest) continue;
116                Section s1 = (Section)conflict.getS1(), s2 = (Section)conflict.getS2();
117                Request r1 = conflict.getR1();
118                Course c1 = conflict.getR1().getAssignment().getCourse();
119                Request r2 = conflict.getR2();
120                Course c2 = conflict.getR2().getAssignment().getCourse();
121                CourseSection a = new CourseSection(c1, s1);
122                CourseSection b = new CourseSection(c2, s2);
123                
124                Set<Long> students = totals.get(c1);
125                if (students == null) {
126                    students = new HashSet<Long>();
127                    totals.put(c1, students);
128                }
129                students.add(r1.getStudent().getId());
130                students = totals.get(c2);
131                if (students == null) {
132                    students = new HashSet<Long>();
133                    totals.put(c2, students);
134                }
135                students.add(r2.getStudent().getId());
136                
137                Set<Long> total = sectionOverlaps.get(a);
138                if (total == null) {
139                    total = new HashSet<Long>();
140                    sectionOverlaps.put(a, total);
141                }
142                total.add(r1.getStudent().getId());
143                Map<CourseSection, Double> pair = conflictingPairs.get(a);
144                if (pair == null) {
145                    pair = new HashMap<CourseSection, Double>();
146                    conflictingPairs.put(a, pair);
147                }
148                Double prev = pair.get(b);
149                pair.put(b, r2.getWeight() + (prev == null ? 0.0 : prev.doubleValue()));
150                
151                total = sectionOverlaps.get(b);
152                if (total == null) {
153                    total = new HashSet<Long>();
154                    sectionOverlaps.put(b, total);
155                }
156                total.add(r2.getStudent().getId());
157                pair = conflictingPairs.get(b);
158                if (pair == null) {
159                    pair = new HashMap<CourseSection, Double>();
160                    conflictingPairs.put(b, pair);
161                }
162                prev = pair.get(a);
163                pair.put(a, r1.getWeight() + (prev == null ? 0.0 : prev.doubleValue()));
164            }
165            
166            Comparator<Course> courseComparator = new Comparator<Course>() {
167                @Override
168                public int compare(Course a, Course b) {
169                    int cmp = a.getName().compareTo(b.getName());
170                    if (cmp != 0) return cmp;
171                    return a.getId() < b.getId() ? -1 : a.getId() == b.getId() ? 0 : 1;
172                }
173            };
174            Comparator<Section> sectionComparator = new Comparator<Section>() {
175                @Override
176                public int compare(Section a, Section b) {
177                    int cmp = a.getSubpart().getConfig().getOffering().getName().compareTo(b.getSubpart().getConfig().getOffering().getName());
178                    if (cmp != 0) return cmp;
179                    cmp = a.getSubpart().getInstructionalType().compareTo(b.getSubpart().getInstructionalType());
180                    // if (cmp != 0) return cmp;
181                    // cmp = a.getName().compareTo(b.getName());
182                    if (cmp != 0) return cmp;
183                    return a.getId() < b.getId() ? -1 : a.getId() == b.getId() ? 0 : 1;
184                }
185            };
186            
187            TreeSet<Course> courses = new TreeSet<Course>(courseComparator);
188            courses.addAll(totals.keySet());
189            for (Course course: courses) {
190                Set<Long> total = totals.get(course);
191                
192                TreeSet<Section> sections = new TreeSet<Section>(sectionComparator);
193                for (Map.Entry<CourseSection, Set<Long>> entry: sectionOverlaps.entrySet())
194                    if (course.equals(entry.getKey().getCourse()))
195                        sections.add(entry.getKey().getSection());
196                
197                boolean firstCourse = true;
198                for (Section section: sections) {
199                    Set<Long> sectionOverlap = sectionOverlaps.get(new CourseSection(course, section));
200                    Map<CourseSection, Double> pair = conflictingPairs.get(new CourseSection(course, section));
201                    boolean firstClass = true;
202                    
203                    for (CourseSection other: new TreeSet<CourseSection>(pair.keySet())) {
204                        List<CSVFile.CSVField> line = new ArrayList<CSVFile.CSVField>();
205                        line.add(new CSVFile.CSVField(firstCourse && firstClass ? course.getName() : ""));
206                        line.add(new CSVFile.CSVField(firstCourse && firstClass ? total.size() : ""));
207                        
208                        line.add(new CSVFile.CSVField(firstClass ? section.getSubpart().getName() + " " + section.getName(course.getId()): ""));
209                        line.add(new CSVFile.CSVField(firstClass ? section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader() + " - " + section.getTime().getEndTimeHeader(): ""));
210                            
211                        line.add(new CSVFile.CSVField(firstClass && sectionOverlap != null ? sDF.format(sectionOverlap.size()): ""));
212                        line.add(new CSVFile.CSVField(firstClass && sectionOverlap != null ? sDF.format(((double)sectionOverlap.size()) / total.size()) : ""));
213    
214                        line.add(new CSVFile.CSVField(other.getCourse().getName() + " " + other.getSection().getSubpart().getName() + " " + other.getSection().getName(other.getCourse().getId())));
215                        line.add(new CSVFile.CSVField(other.getSection().getTime().getDayHeader() + " " + other.getSection().getTime().getStartTimeHeader() + " - " + other.getSection().getTime().getEndTimeHeader()));
216                        
217                        line.add(new CSVFile.CSVField(sDF.format(5 * iTOC.share(section, other.getSection()))));
218                        line.add(new CSVFile.CSVField(sDF.format(pair.get(other))));
219                        line.add(new CSVFile.CSVField(sDF.format(pair.get(other) / total.size())));
220                        
221                        csv.addLine(line);
222                        firstClass = false;
223                    }                    
224                    firstCourse = false;
225                }
226                
227                csv.addLine();
228            }
229            
230            
231            return csv;
232        }
233    
234        @Override
235        public CSVFile create(DataProperties properties) {
236            return createTable(properties.getPropertyBoolean("lastlike", false), properties.getPropertyBoolean("real", true));
237        }
238    }