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 }