001package org.cpsolver.studentsct.report; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.Comparator; 006import java.util.List; 007import java.util.Set; 008import java.util.TreeSet; 009 010import org.cpsolver.coursett.Constants; 011import org.cpsolver.coursett.model.RoomLocation; 012import org.cpsolver.coursett.model.TimeLocation; 013import org.cpsolver.ifs.assignment.Assignment; 014import org.cpsolver.ifs.util.CSVFile; 015import org.cpsolver.ifs.util.DataProperties; 016import org.cpsolver.studentsct.StudentSectioningModel; 017import org.cpsolver.studentsct.extension.StudentQuality; 018import org.cpsolver.studentsct.extension.StudentQuality.Conflict; 019import org.cpsolver.studentsct.extension.StudentQuality.Type; 020import org.cpsolver.studentsct.model.AreaClassificationMajor; 021import org.cpsolver.studentsct.model.CourseRequest; 022import org.cpsolver.studentsct.model.Enrollment; 023import org.cpsolver.studentsct.model.Instructor; 024import org.cpsolver.studentsct.model.Request; 025import org.cpsolver.studentsct.model.SctAssignment; 026import org.cpsolver.studentsct.model.Section; 027import org.cpsolver.studentsct.model.Student; 028import org.cpsolver.studentsct.model.StudentGroup; 029 030 031/** 032 * This class lists student accommodation conflicts in a {@link CSVFile} comma 033 * separated text file. See {@link StudentQuality} for more 034 * details. <br> 035 * <br> 036 * 037 * Each line represent a pair if classes that are in a distance conflict and have 038 * one or more students in common. 039 * 040 * <br> 041 * <br> 042 * 043 * Usage: new DistanceConflictTable(model),createTable(true, true).save(aFile); 044 * 045 * <br> 046 * <br> 047 * 048 * @version StudentSct 1.3 (Student Sectioning)<br> 049 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 050 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 051 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 052 * <br> 053 * This library is free software; you can redistribute it and/or modify 054 * it under the terms of the GNU Lesser General Public License as 055 * published by the Free Software Foundation; either version 3 of the 056 * License, or (at your option) any later version. <br> 057 * <br> 058 * This library is distributed in the hope that it will be useful, but 059 * WITHOUT ANY WARRANTY; without even the implied warranty of 060 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 061 * Lesser General Public License for more details. <br> 062 * <br> 063 * You should have received a copy of the GNU Lesser General Public 064 * License along with this library; if not see 065 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 066 */ 067public class AccommodationConflictsTable implements StudentSectioningReport { 068 private StudentSectioningModel iModel = null; 069 private StudentQuality iSQ = null; 070 private Type[] iTypes = new Type[] { 071 Type.ShortDistance, Type.AccBackToBack, Type.AccBreaksBetweenClasses, Type.AccFreeTimeOverlap 072 }; 073 074 /** 075 * Constructor 076 * 077 * @param model 078 * student sectioning model 079 */ 080 public AccommodationConflictsTable(StudentSectioningModel model) { 081 iModel = model; 082 iSQ = iModel.getStudentQuality(); 083 } 084 085 /** Return student sectioning model 086 * @return problem model 087 **/ 088 public StudentSectioningModel getModel() { 089 return iModel; 090 } 091 092 protected String rooms(SctAssignment section) { 093 if (section.getNrRooms() == 0) return ""; 094 String ret = ""; 095 for (RoomLocation r: section.getRooms()) 096 ret += (ret.isEmpty() ? "" : ", ") + r.getName(); 097 return ret; 098 } 099 100 protected String curriculum(Student student) { 101 String curriculum = ""; 102 for (AreaClassificationMajor acm: student.getAreaClassificationMajors()) 103 curriculum += (curriculum.isEmpty() ? "" : ", ") + acm.toString(); 104 return curriculum; 105 } 106 107 protected String group(Student student) { 108 String group = ""; 109 Set<String> groups = new TreeSet<String>(); 110 for (StudentGroup g: student.getGroups()) 111 groups.add(g.getReference()); 112 for (String g: groups) 113 group += (group.isEmpty() ? "" : ", ") + g; 114 return group; 115 } 116 117 protected String advisor(Student student) { 118 String advisors = ""; 119 for (Instructor instructor: student.getAdvisors()) 120 advisors += (advisors.isEmpty() ? "" : ", ") + instructor.getName(); 121 return advisors; 122 } 123 124 /** 125 * Create report 126 * 127 * @param assignment current assignment 128 * @param includeLastLikeStudents 129 * true, if last-like students should be included (i.e., 130 * {@link Student#isDummy()} is true) 131 * @param includeRealStudents 132 * true, if real students should be included (i.e., 133 * {@link Student#isDummy()} is false) 134 * @param useAmPm use 12-hour format 135 * @return report as comma separated text file 136 */ 137 public CSVFile createTable(final Assignment<Request, Enrollment> assignment, boolean includeLastLikeStudents, boolean includeRealStudents, boolean useAmPm) { 138 if (iSQ == null) throw new IllegalArgumentException("Student Schedule Quality is not enabled."); 139 140 CSVFile csv = new CSVFile(); 141 csv.setHeader(new CSVFile.CSVField[] { 142 new CSVFile.CSVField("__Student"), 143 new CSVFile.CSVField("External Id"), new CSVFile.CSVField("Student Name"), 144 new CSVFile.CSVField("Curriculum"), new CSVFile.CSVField("Group"), new CSVFile.CSVField("Advisor"), 145 new CSVFile.CSVField("Type"), 146 new CSVFile.CSVField("Course"), new CSVFile.CSVField("Class"), new CSVFile.CSVField("Meeting Time"), new CSVFile.CSVField("Room"), 147 new CSVFile.CSVField("Conflicting\nCourse"), new CSVFile.CSVField("Conflicting\nClass"), new CSVFile.CSVField("Conflicting\nMeeting Time"), new CSVFile.CSVField("Conflicting\nRoom"), 148 new CSVFile.CSVField("Penalty\nMinutes") 149 }); 150 151 List<Conflict> confs = new ArrayList<Conflict>(); 152 for (Request r1 : getModel().variables()) { 153 Enrollment e1 = assignment.getValue(r1); 154 if (e1 == null || !(r1 instanceof CourseRequest)) 155 continue; 156 for (StudentQuality.Type t: iTypes) 157 confs.addAll(iSQ.conflicts(t, e1)); 158 for (Request r2 : r1.getStudent().getRequests()) { 159 Enrollment e2 = assignment.getValue(r2); 160 if (e2 == null || r1.getId() >= r2.getId() || !(r2 instanceof CourseRequest)) 161 continue; 162 for (StudentQuality.Type t: iTypes) 163 confs.addAll(iSQ.conflicts(t, e1, e2)); 164 } 165 } 166 Collections.sort(confs, new Comparator<Conflict>() { 167 @Override 168 public int compare(Conflict c1, Conflict c2) { 169 int cmp = (c1.getStudent().getExternalId() == null ? "" : c1.getStudent().getExternalId()).compareTo(c2.getStudent().getExternalId() == null ? "" : c2.getStudent().getExternalId()); 170 if (cmp != 0) return cmp; 171 cmp = c1.getStudent().compareTo(c2.getStudent()); 172 if (cmp != 0) return cmp; 173 if (c1.getType() != c2.getType()) 174 return Integer.compare(c1.getType().ordinal(), c2.getType().ordinal()); 175 cmp = c1.getE1().getCourse().getName().toString().compareTo(c2.getE1().getCourse().getName()); 176 if (cmp != 0) return cmp; 177 return ((Section)c1.getS1()).getName(c1.getE1().getCourse().getId()).compareTo(((Section)c2.getS1()).getName(c2.getE1().getCourse().getId())); 178 } 179 }); 180 181 for (Conflict conflict : confs) { 182 if (conflict.getStudent().isDummy() && !includeLastLikeStudents) continue; 183 if (!conflict.getStudent().isDummy() && !includeRealStudents) continue; 184 if (conflict.getType() == Type.AccBackToBack) { 185 boolean trueConflict = false; 186 for (int i = 0; i < Constants.DAY_CODES.length; i++) { 187 if ((conflict.getS1().getTime().getDayCode() & Constants.DAY_CODES[i]) == 0 || (conflict.getS2().getTime().getDayCode() & Constants.DAY_CODES[i]) == 0) continue; 188 boolean inBetween = false; 189 for (Request r: conflict.getStudent().getRequests()) { 190 Enrollment e = r.getAssignment(assignment); 191 if (e == null) continue; 192 for (SctAssignment s: e.getAssignments()) { 193 if (s.getTime() == null) continue; 194 if ((s.getTime().getDayCode() & Constants.DAY_CODES[i]) == 0) continue; 195 if (!s.getTime().shareWeeks(conflict.getS1().getTime()) || !s.getTime().shareWeeks(conflict.getS2().getTime())) continue; 196 if (conflict.getS1().getTime().getStartSlot() + conflict.getS1().getTime().getLength() <= s.getTime().getStartSlot() && 197 s.getTime().getStartSlot() + s.getTime().getLength() <= conflict.getS2().getTime().getStartSlot()) { 198 inBetween = true; break; 199 } 200 if (conflict.getS2().getTime().getStartSlot() + conflict.getS2().getTime().getLength() <= s.getTime().getStartSlot() && 201 s.getTime().getStartSlot() + s.getTime().getLength() <= conflict.getS1().getTime().getStartSlot()) { 202 inBetween = true; break; 203 } 204 } 205 } 206 if (!inBetween) { trueConflict = true; break; } 207 } 208 if (!trueConflict) continue; 209 } 210 List<CSVFile.CSVField> line = new ArrayList<CSVFile.CSVField>(); 211 212 line.add(new CSVFile.CSVField(conflict.getStudent().getId())); 213 line.add(new CSVFile.CSVField(conflict.getStudent().getExternalId())); 214 line.add(new CSVFile.CSVField(conflict.getStudent().getName())); 215 line.add(new CSVFile.CSVField(curriculum(conflict.getStudent()))); 216 line.add(new CSVFile.CSVField(group(conflict.getStudent()))); 217 line.add(new CSVFile.CSVField(advisor(conflict.getStudent()))); 218 switch (conflict.getType()) { 219 case ShortDistance: 220 line.add(new CSVFile.CSVField(iSQ.getDistanceMetric().getShortDistanceAccommodationReference())); 221 break; 222 case AccBackToBack: 223 line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getBackToBackAccommodation())); 224 break; 225 case AccBreaksBetweenClasses: 226 line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getBreakBetweenClassesAccommodation())); 227 break; 228 case AccFreeTimeOverlap: 229 line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getFreeTimeAccommodation())); 230 break; 231 default: 232 line.add(new CSVFile.CSVField(conflict.getType().getName())); 233 break; 234 } 235 line.add(new CSVFile.CSVField(conflict.getE1().getCourse().getName())); 236 line.add(new CSVFile.CSVField(conflict.getS1() instanceof Section ? ((Section)conflict.getS1()).getName(conflict.getE1().getCourse().getId()) : "")); 237 line.add(new CSVFile.CSVField(conflict.getS1().getTime() == null ? "" : conflict.getS1().getTime().getLongName(useAmPm))); 238 line.add(new CSVFile.CSVField(rooms(conflict.getS1()))); 239 line.add(new CSVFile.CSVField(conflict.getE2().isCourseRequest() ? conflict.getE2().getCourse().getName() : "Free Time")); 240 line.add(new CSVFile.CSVField(conflict.getS2() instanceof Section ? ((Section)conflict.getS2()).getName(conflict.getE2().getCourse().getId()) : "")); 241 line.add(new CSVFile.CSVField(conflict.getS2().getTime() == null ? "" : conflict.getS2().getTime().getLongName(useAmPm))); 242 line.add(new CSVFile.CSVField(rooms(conflict.getS2()))); 243 switch (conflict.getType()) { 244 case AccFreeTimeOverlap: 245 line.add(new CSVFile.CSVField(5 * conflict.getPenalty())); 246 break; 247 case ShortDistance: 248 line.add(new CSVFile.CSVField(iSQ.getStudentQualityContext().getDistanceInMinutes(((Section)conflict.getS1()).getPlacement(), ((Section)conflict.getS2()).getPlacement()))); 249 break; 250 case AccBackToBack: 251 case AccBreaksBetweenClasses: 252 TimeLocation t1 = conflict.getS1().getTime(); 253 TimeLocation t2 = conflict.getS2().getTime(); 254 if (t1.getStartSlot() + t1.getNrSlotsPerMeeting() <= t2.getStartSlot()) { 255 int dist = t2.getStartSlot() - (t1.getStartSlot() + t1.getNrSlotsPerMeeting()); 256 line.add(new CSVFile.CSVField(5 * dist + t1.getBreakTime())); 257 } else if (t2.getStartSlot() + t2.getNrSlotsPerMeeting() <= t1.getStartSlot()) { 258 int dist = t1.getStartSlot() - (t2.getStartSlot() + t2.getNrSlotsPerMeeting()); 259 line.add(new CSVFile.CSVField(5 * dist + t2.getBreakTime())); 260 } else { 261 line.add(new CSVFile.CSVField(null)); 262 } 263 break; 264 default: 265 line.add(new CSVFile.CSVField(conflict.getPenalty())); 266 break; 267 } 268 csv.addLine(line); 269 } 270 271 return csv; 272 } 273 274 @Override 275 public CSVFile create(Assignment<Request, Enrollment> assignment, DataProperties properties) { 276 return createTable(assignment, properties.getPropertyBoolean("lastlike", false), properties.getPropertyBoolean("real", true), properties.getPropertyBoolean("useAmPm", true)); 277 } 278}