001    package net.sf.cpsolver.studentsct.report;
002    
003    import java.text.DecimalFormat;
004    import java.util.Collections;
005    import java.util.Comparator;
006    import java.util.Enumeration;
007    import java.util.HashSet;
008    import java.util.Hashtable;
009    import java.util.Iterator;
010    import java.util.Map;
011    import java.util.TreeSet;
012    
013    import net.sf.cpsolver.ifs.util.CSVFile;
014    import net.sf.cpsolver.studentsct.StudentSectioningModel;
015    import net.sf.cpsolver.studentsct.extension.DistanceConflict;
016    import net.sf.cpsolver.studentsct.extension.DistanceConflict.Conflict;
017    import net.sf.cpsolver.studentsct.model.Course;
018    import net.sf.cpsolver.studentsct.model.Enrollment;
019    import net.sf.cpsolver.studentsct.model.Request;
020    import net.sf.cpsolver.studentsct.model.Section;
021    import net.sf.cpsolver.studentsct.model.Student;
022    
023    /**
024     * This class lists distance student conflicts in a {@link CSVFile} comma separated text file.
025     * Two sections that are attended by the same student are considered in a 
026     * distance conflict if they are back-to-back taught in locations
027     * that are two far away. See {@link DistanceConflict} for more details.
028     * <br><br>
029     *  
030     * Each line represent a pair if courses that have one or more distance conflicts
031     * in between (columns Course1, Course2), column NrStud displays the number
032     * of student distance conflicts (weighted by requests weights), and column
033     * AvgDist displays the average distance for all the distance conflicts between
034     * these two courses. The column NoAlt is Y when every possible enrollment of the 
035     * first course is either overlapping or there is a distance conflict with every 
036     * possible enrollment of the second course (it is N otherwise) and a column 
037     * Reason which lists the sections that are involved in a distance conflict. 
038     * 
039     * <br><br>
040     * 
041     * Usage: new DistanceConflictTable(model),createTable(true, true).save(aFile);
042     * 
043     * <br><br>
044     * 
045     * @version
046     * StudentSct 1.1 (Student Sectioning)<br>
047     * Copyright (C) 2007 Tomáš Müller<br>
048     * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
049     * Lazenska 391, 76314 Zlin, Czech Republic<br>
050     * <br>
051     * This library is free software; you can redistribute it and/or
052     * modify it under the terms of the GNU Lesser General Public
053     * License as published by the Free Software Foundation; either
054     * version 2.1 of the License, or (at your option) any later version.
055     * <br><br>
056     * This library is distributed in the hope that it will be useful,
057     * but 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.
060     * <br><br>
061     * You should have received a copy of the GNU Lesser General Public
062     * License along with this library; if not, write to the Free Software
063     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
064     */
065    public class DistanceConflictTable {
066        private static org.apache.log4j.Logger sLog = org.apache.log4j.Logger.getLogger(DistanceConflictTable.class);
067        private static DecimalFormat sDF = new DecimalFormat("0.000");
068        
069        private StudentSectioningModel iModel = null;
070        private DistanceConflict iDC = null;
071        
072        /**
073         * Constructor
074         * @param model student sectioning model
075         */
076        public DistanceConflictTable(StudentSectioningModel model) {
077            iModel = model;
078            iDC = model.getDistanceConflict();
079            if (iDC==null)
080                iDC = new DistanceConflict(null, model.getProperties());
081        }
082        
083        /** Return student sectioning model */
084        public StudentSectioningModel getModel() {
085            return iModel;
086        }
087    
088        /** 
089         * True, if there is no pair of enrollments of r1 and r2 that is not in a hard conflict and without a distance conflict 
090         */
091        private boolean areInHardConfict(Request r1, Request r2) {
092            for (Enumeration e=r1.values().elements();e.hasMoreElements();) {
093                Enrollment e1 = (Enrollment)e.nextElement();
094                for (Enumeration f=r2.values().elements();f.hasMoreElements();) {
095                    Enrollment e2 = (Enrollment)f.nextElement();
096                    if (!e1.isOverlapping(e2) && iDC.nrConflicts(e1, e2)==0) return false;
097                }
098            }
099            return true;
100        }
101        
102        /**
103         * Create report
104         * @param includeLastLikeStudents true, if last-like students should be included (i.e., {@link Student#isDummy()} is true) 
105         * @param includeRealStudents true, if real students should be included (i.e., {@link Student#isDummy()} is false)
106         * @return report as comma separated text file
107         */
108        public CSVFile createTable(boolean includeLastLikeStudents, boolean includeRealStudents) {
109            CSVFile csv = new CSVFile();
110            csv.setHeader(new CSVFile.CSVField[] {
111                    new CSVFile.CSVField("Course1"),
112                    new CSVFile.CSVField("Course2"),
113                    new CSVFile.CSVField("NrStud"),
114                    new CSVFile.CSVField("StudWeight"),
115                    new CSVFile.CSVField("AvgDist"),
116                    new CSVFile.CSVField("NoAlt"),
117                    new CSVFile.CSVField("Reason")
118            });
119            HashSet confs = iDC.computeAllConflicts();
120            Hashtable distConfTable = new Hashtable();
121            for (Iterator i=confs.iterator();i.hasNext();) {
122                Conflict conflict = (Conflict)i.next();
123                if (conflict.getStudent().isDummy() && !includeLastLikeStudents) continue;
124                if (!conflict.getStudent().isDummy() && !includeRealStudents) continue;
125                Section s1=conflict.getS1(), s2=conflict.getS2();
126                Course c1 = null, c2=null;
127                Request r1 = null, r2=null;
128                for (Enumeration e=conflict.getStudent().getRequests().elements();e.hasMoreElements();) {
129                    Request request = (Request)e.nextElement();
130                    Enrollment enrollment = (Enrollment)request.getAssignment();
131                    if (enrollment==null || !enrollment.isCourseRequest()) continue;
132                    if (c1==null && enrollment.getAssignments().contains(s1)) {
133                        c1 = enrollment.getConfig().getOffering().getCourse(conflict.getStudent());
134                        r1 = request;
135                    }
136                    if (c2==null && enrollment.getAssignments().contains(s2)) {
137                        c2 = enrollment.getConfig().getOffering().getCourse(conflict.getStudent());
138                        r2 = request;
139                    }
140                }
141                if (c1==null) {
142                    sLog.error("Unable to find a course for "+s1); continue;
143                }
144                if (c2==null) {
145                    sLog.error("Unable to find a course for "+s2); continue;
146                }
147                if (c1.getName().compareTo(c2.getName())>0) {
148                    Course x = c1; c1 = c2; c2 = x;
149                    Section y = s1; s1 = s2; s2 = y;
150                }
151                if (c1.equals(c2) && s1.getName().compareTo(s2.getName())>0) {
152                    Section y = s1; s1 = s2; s2 = y;
153                }
154                Hashtable firstCourseTable = (Hashtable)distConfTable.get(c1);
155                if (firstCourseTable==null) {
156                    firstCourseTable = new Hashtable();
157                    distConfTable.put(c1, firstCourseTable);
158                }
159                Object[] secondCourseTable = (Object[])firstCourseTable.get(c2);
160                double nrStud = (secondCourseTable==null?0.0:((Double)secondCourseTable[0]).doubleValue()) + 1.0;
161                double nrStudW = (secondCourseTable==null?0.0:((Double)secondCourseTable[1]).doubleValue()) + conflict.getWeight();
162                double dist = (secondCourseTable==null?0.0:((Double)secondCourseTable[2]).doubleValue()) + (conflict.getDistance() * conflict.getWeight());
163                boolean hard = (secondCourseTable==null?areInHardConfict(r1,r2):((Boolean)secondCourseTable[3]).booleanValue());
164                HashSet expl = (HashSet)(secondCourseTable==null?null:secondCourseTable[4]);
165                if (expl==null) expl = new HashSet();
166                expl.add(s1.getSubpart().getName()+" "+s1.getTime().getLongName()+" "+s1.getPlacement().getRoomName(",")+
167                        " vs "+
168                        s2.getSubpart().getName()+" "+s2.getTime().getLongName()+" "+s2.getPlacement().getRoomName(","));
169                firstCourseTable.put(c2, new Object[] {new Double(nrStud), new Double(nrStudW), new Double(dist), new Boolean(hard), expl});
170            }
171            for (Iterator i=distConfTable.entrySet().iterator();i.hasNext();) {
172                Map.Entry entry = (Map.Entry)i.next();
173                Course c1 = (Course)entry.getKey();
174                Hashtable firstCourseTable = (Hashtable)entry.getValue();
175                for (Iterator j=firstCourseTable.entrySet().iterator();j.hasNext();) {
176                    Map.Entry entry2 = (Map.Entry)j.next();
177                    Course c2 = (Course)entry2.getKey();
178                    Object[] secondCourseTable = (Object[])entry2.getValue();
179                    HashSet expl = (HashSet)secondCourseTable[4];
180                    String explStr = "";
181                    for (Iterator k=new TreeSet(expl).iterator();k.hasNext();)
182                        explStr += k.next() + (k.hasNext()?"\n":"");
183                    double nrStud = ((Double)secondCourseTable[0]).doubleValue();
184                    double nrStudW = ((Double)secondCourseTable[1]).doubleValue();
185                    double dist = ((Double)secondCourseTable[2]).doubleValue()/nrStud;
186                    csv.addLine(new CSVFile.CSVField[] {
187                       new CSVFile.CSVField(c1.getName()),
188                       new CSVFile.CSVField(c2.getName()),
189                       new CSVFile.CSVField(sDF.format(nrStud)),
190                       new CSVFile.CSVField(sDF.format(nrStudW)),
191                       new CSVFile.CSVField(sDF.format(dist)),
192                       new CSVFile.CSVField(((Boolean)secondCourseTable[3]).booleanValue()?"Y":"N"),
193                       new CSVFile.CSVField(explStr)
194                    });
195                 }
196            }
197            if (csv.getLines()!=null)
198                Collections.sort(csv.getLines(), new Comparator() {
199                    public int compare(Object o1, Object o2) {
200                        CSVFile.CSVLine l1 = (CSVFile.CSVLine)o1;
201                        CSVFile.CSVLine l2 = (CSVFile.CSVLine)o2;
202                        //int cmp = l2.getField(4).toString().compareTo(l1.getField(4).toString());
203                        //if (cmp!=0) return cmp;
204                        int cmp = Double.compare(l2.getField(2).toDouble(),l1.getField(2).toDouble());
205                        if (cmp!=0) return cmp;
206                        cmp = l1.getField(0).toString().compareTo(l2.getField(0).toString());
207                        if (cmp!=0) return cmp;
208                        return l1.getField(1).toString().compareTo(l2.getField(1).toString());
209                    }
210                 });
211            return csv;
212        }
213    }