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 }