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