001 package net.sf.cpsolver.studentsct.report; 002 003 import java.text.DecimalFormat; 004 import java.util.Comparator; 005 import java.util.TreeSet; 006 007 import net.sf.cpsolver.ifs.util.CSVFile; 008 import net.sf.cpsolver.ifs.util.DataProperties; 009 import net.sf.cpsolver.studentsct.StudentSectioningModel; 010 import net.sf.cpsolver.studentsct.model.Config; 011 import net.sf.cpsolver.studentsct.model.Enrollment; 012 import net.sf.cpsolver.studentsct.model.Offering; 013 import net.sf.cpsolver.studentsct.model.Section; 014 import net.sf.cpsolver.studentsct.model.Student; 015 import net.sf.cpsolver.studentsct.model.Subpart; 016 017 /** 018 * This class lists all unbalanced sections. Each line includes the class, its meeting time, 019 * number of enrolled students, desired section size, and the limit. The Target column show 020 * the ideal number of students the section (if all the sections were filled equally) and the 021 * Disbalance shows the % between the target and the current enrollment. 022 * 023 * <br> 024 * <br> 025 * 026 * Usage: new UnbalancedSectionsTable(model),createTable(true, true).save(aFile); 027 * 028 * <br> 029 * <br> 030 * 031 * @version StudentSct 1.2 (Student Sectioning)<br> 032 * Copyright (C) 2007 - 2010 Tomas Muller<br> 033 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 034 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 035 * <br> 036 * This library is free software; you can redistribute it and/or modify 037 * it under the terms of the GNU Lesser General Public License as 038 * published by the Free Software Foundation; either version 3 of the 039 * License, or (at your option) any later version. <br> 040 * <br> 041 * This library is distributed in the hope that it will be useful, but 042 * WITHOUT ANY WARRANTY; without even the implied warranty of 043 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 044 * Lesser General Public License for more details. <br> 045 * <br> 046 * You should have received a copy of the GNU Lesser General Public 047 * License along with this library; if not see 048 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 049 */ 050 public class UnbalancedSectionsTable implements StudentSectioningReport { 051 private static DecimalFormat sDF = new DecimalFormat("0.###"); 052 053 private StudentSectioningModel iModel = null; 054 055 /** 056 * Constructor 057 * 058 * @param model 059 * student sectioning model 060 */ 061 public UnbalancedSectionsTable(StudentSectioningModel model) { 062 iModel = model; 063 } 064 065 /** Return student sectioning model */ 066 public StudentSectioningModel getModel() { 067 return iModel; 068 } 069 070 /** 071 * Create report 072 * 073 * @param includeLastLikeStudents 074 * true, if last-like students should be included (i.e., 075 * {@link Student#isDummy()} is true) 076 * @param includeRealStudents 077 * true, if real students should be included (i.e., 078 * {@link Student#isDummy()} is false) 079 * @return report as comma separated text file 080 */ 081 public CSVFile createTable(boolean includeLastLikeStudents, boolean includeRealStudents) { 082 CSVFile csv = new CSVFile(); 083 csv.setHeader(new CSVFile.CSVField[] { new CSVFile.CSVField("Course"), new CSVFile.CSVField("Class"), 084 new CSVFile.CSVField("Meeting Time"), new CSVFile.CSVField("Enrollment"), 085 new CSVFile.CSVField("Target"), new CSVFile.CSVField("Limit"), new CSVFile.CSVField("Disbalance [%]") }); 086 087 TreeSet<Offering> offerings = new TreeSet<Offering>(new Comparator<Offering>() { 088 @Override 089 public int compare(Offering o1, Offering o2) { 090 int cmp = o1.getName().compareToIgnoreCase(o2.getName()); 091 if (cmp != 0) return cmp; 092 return o1.getId() < o2.getId() ? -1 : o2.getId() == o2.getId() ? 0 : 1; 093 } 094 }); 095 offerings.addAll(getModel().getOfferings()); 096 097 Offering last = null; 098 for (Offering offering: offerings) { 099 for (Config config: offering.getConfigs()) { 100 double configEnrl = 0; 101 for (Enrollment e: config.getEnrollments()) { 102 if (e.getStudent().isDummy() && !includeLastLikeStudents) continue; 103 if (!e.getStudent().isDummy() && !includeRealStudents) continue; 104 configEnrl += e.getRequest().getWeight(); 105 } 106 config.getEnrollmentWeight(null); 107 for (Subpart subpart: config.getSubparts()) { 108 if (subpart.getSections().size() <= 1) continue; 109 if (subpart.getLimit() > 0) { 110 // sections have limits -> desired size is section limit x (total enrollment / total limit) 111 double ratio = configEnrl / subpart.getLimit(); 112 for (Section section: subpart.getSections()) { 113 double enrl = 0.0; 114 for (Enrollment e: section.getEnrollments()) { 115 if (e.getStudent().isDummy() && !includeLastLikeStudents) continue; 116 if (!e.getStudent().isDummy() && !includeRealStudents) continue; 117 enrl += e.getRequest().getWeight(); 118 } 119 double desired = ratio * section.getLimit(); 120 if (Math.abs(desired - enrl) >= Math.max(1.0, 0.1 * section.getLimit())) { 121 if (last != null && !offering.equals(last)) csv.addLine(); 122 csv.addLine(new CSVFile.CSVField[] { 123 new CSVFile.CSVField(offering.equals(last) ? "" : offering.getName()), 124 new CSVFile.CSVField(section.getSubpart().getName() + " " + section.getName()), 125 new CSVFile.CSVField(section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader() + " - " + section.getTime().getEndTimeHeader()), 126 new CSVFile.CSVField(sDF.format(enrl)), 127 new CSVFile.CSVField(sDF.format(desired)), 128 new CSVFile.CSVField(sDF.format(section.getLimit())), 129 new CSVFile.CSVField(sDF.format(Math.min(1.0, Math.max(-1.0, (enrl - desired) / section.getLimit())))) 130 }); 131 last = offering; 132 } 133 } 134 } else { 135 // unlimited sections -> desired size is total enrollment / number of sections 136 for (Section section: subpart.getSections()) { 137 double enrl = 0.0; 138 for (Enrollment e: section.getEnrollments()) { 139 if (e.getStudent().isDummy() && !includeLastLikeStudents) continue; 140 if (!e.getStudent().isDummy() && !includeRealStudents) continue; 141 enrl += e.getRequest().getWeight(); 142 } 143 double desired = configEnrl / subpart.getSections().size(); 144 if (Math.abs(desired - enrl) >= Math.max(1.0, 0.1 * desired)) { 145 if (last != null && !offering.equals(last)) csv.addLine(); 146 csv.addLine(new CSVFile.CSVField[] { 147 new CSVFile.CSVField(offering.equals(last) ? "" : offering.getName()), 148 new CSVFile.CSVField(section.getSubpart().getName() + " " + section.getName()), 149 new CSVFile.CSVField(section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader() + " - " + section.getTime().getEndTimeHeader()), 150 new CSVFile.CSVField(sDF.format(enrl)), 151 new CSVFile.CSVField(sDF.format(desired)), 152 new CSVFile.CSVField(""), 153 new CSVFile.CSVField(sDF.format(Math.min(1.0, Math.max(-1.0, (enrl - desired) / desired)))) 154 }); 155 last = offering; 156 } 157 } 158 } 159 } 160 } 161 } 162 return csv; 163 } 164 165 @Override 166 public CSVFile create(DataProperties properties) { 167 return createTable(properties.getPropertyBoolean("lastlike", false), properties.getPropertyBoolean("real", true)); 168 } 169 170 }