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