001package net.sf.cpsolver.studentsct.report; 002 003import java.text.DecimalFormat; 004import java.util.Comparator; 005import java.util.TreeSet; 006 007import net.sf.cpsolver.ifs.util.CSVFile; 008import net.sf.cpsolver.ifs.util.DataProperties; 009import net.sf.cpsolver.studentsct.StudentSectioningModel; 010import net.sf.cpsolver.studentsct.model.Config; 011import net.sf.cpsolver.studentsct.model.Enrollment; 012import net.sf.cpsolver.studentsct.model.Offering; 013import net.sf.cpsolver.studentsct.model.Section; 014import net.sf.cpsolver.studentsct.model.Student; 015import 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 Tomáš Müller<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 */ 050public class UnbalancedSectionsTable implements StudentSectioningReport { 051 private static DecimalFormat sDF1 = new DecimalFormat("0.####"); 052 private static DecimalFormat sDF2 = new DecimalFormat("0.0000"); 053 054 private StudentSectioningModel iModel = null; 055 056 /** 057 * Constructor 058 * 059 * @param model 060 * student sectioning model 061 */ 062 public UnbalancedSectionsTable(StudentSectioningModel model) { 063 iModel = model; 064 } 065 066 /** Return student sectioning model */ 067 public StudentSectioningModel getModel() { 068 return iModel; 069 } 070 071 /** 072 * Create report 073 * 074 * @param includeLastLikeStudents 075 * true, if last-like students should be included (i.e., 076 * {@link Student#isDummy()} is true) 077 * @param includeRealStudents 078 * true, if real students should be included (i.e., 079 * {@link Student#isDummy()} is false) 080 * @return report as comma separated text file 081 */ 082 public CSVFile createTable(boolean includeLastLikeStudents, boolean includeRealStudents) { 083 CSVFile csv = new CSVFile(); 084 csv.setHeader(new CSVFile.CSVField[] { new CSVFile.CSVField("Course"), new CSVFile.CSVField("Class"), 085 new CSVFile.CSVField("Meeting Time"), new CSVFile.CSVField("Enrollment"), 086 new CSVFile.CSVField("Target"), new CSVFile.CSVField("Limit"), new CSVFile.CSVField("Disbalance [%]") }); 087 088 TreeSet<Offering> offerings = new TreeSet<Offering>(new Comparator<Offering>() { 089 @Override 090 public int compare(Offering o1, Offering o2) { 091 int cmp = o1.getName().compareToIgnoreCase(o2.getName()); 092 if (cmp != 0) return cmp; 093 return o1.getId() < o2.getId() ? -1 : o2.getId() == o2.getId() ? 0 : 1; 094 } 095 }); 096 offerings.addAll(getModel().getOfferings()); 097 098 Offering last = null; 099 for (Offering offering: offerings) { 100 for (Config config: offering.getConfigs()) { 101 double configEnrl = 0; 102 for (Enrollment e: config.getEnrollments()) { 103 if (e.getStudent().isDummy() && !includeLastLikeStudents) continue; 104 if (!e.getStudent().isDummy() && !includeRealStudents) continue; 105 configEnrl += e.getRequest().getWeight(); 106 } 107 config.getEnrollmentWeight(null); 108 for (Subpart subpart: config.getSubparts()) { 109 if (subpart.getSections().size() <= 1) continue; 110 if (subpart.getLimit() > 0) { 111 // sections have limits -> desired size is section limit x (total enrollment / total limit) 112 double ratio = configEnrl / subpart.getLimit(); 113 for (Section section: subpart.getSections()) { 114 double enrl = 0.0; 115 for (Enrollment e: section.getEnrollments()) { 116 if (e.getStudent().isDummy() && !includeLastLikeStudents) continue; 117 if (!e.getStudent().isDummy() && !includeRealStudents) continue; 118 enrl += e.getRequest().getWeight(); 119 } 120 double desired = ratio * section.getLimit(); 121 if (Math.abs(desired - enrl) >= Math.max(1.0, 0.1 * section.getLimit())) { 122 if (last != null && !offering.equals(last)) csv.addLine(); 123 csv.addLine(new CSVFile.CSVField[] { 124 new CSVFile.CSVField(offering.equals(last) ? "" : offering.getName()), 125 new CSVFile.CSVField(section.getSubpart().getName() + " " + section.getName()), 126 new CSVFile.CSVField(section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader() + " - " + section.getTime().getEndTimeHeader()), 127 new CSVFile.CSVField(sDF1.format(enrl)), 128 new CSVFile.CSVField(sDF2.format(desired)), 129 new CSVFile.CSVField(sDF1.format(section.getLimit())), 130 new CSVFile.CSVField(sDF2.format(Math.min(1.0, Math.max(-1.0, (enrl - desired) / section.getLimit())))) 131 }); 132 last = offering; 133 } 134 } 135 } else { 136 // unlimited sections -> desired size is total enrollment / number of sections 137 for (Section section: subpart.getSections()) { 138 double enrl = 0.0; 139 for (Enrollment e: section.getEnrollments()) { 140 if (e.getStudent().isDummy() && !includeLastLikeStudents) continue; 141 if (!e.getStudent().isDummy() && !includeRealStudents) continue; 142 enrl += e.getRequest().getWeight(); 143 } 144 double desired = configEnrl / subpart.getSections().size(); 145 if (Math.abs(desired - enrl) >= Math.max(1.0, 0.1 * desired)) { 146 if (last != null && !offering.equals(last)) csv.addLine(); 147 csv.addLine(new CSVFile.CSVField[] { 148 new CSVFile.CSVField(offering.equals(last) ? "" : offering.getName()), 149 new CSVFile.CSVField(section.getSubpart().getName() + " " + section.getName()), 150 new CSVFile.CSVField(section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader() + " - " + section.getTime().getEndTimeHeader()), 151 new CSVFile.CSVField(sDF1.format(enrl)), 152 new CSVFile.CSVField(sDF2.format(desired)), 153 new CSVFile.CSVField(""), 154 new CSVFile.CSVField(sDF2.format(Math.min(1.0, Math.max(-1.0, (enrl - desired) / desired)))) 155 }); 156 last = offering; 157 } 158 } 159 } 160 } 161 } 162 } 163 return csv; 164 } 165 166 @Override 167 public CSVFile create(DataProperties properties) { 168 return createTable(properties.getPropertyBoolean("lastlike", false), properties.getPropertyBoolean("real", true)); 169 } 170 171}