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.Student; 017import org.cpsolver.studentsct.model.Subpart; 018 019 020/** 021 * This class lists all unbalanced sections. Each line includes the class, its meeting time, 022 * number of enrolled students, desired section size, and the limit. The Target column show 023 * the ideal number of students the section (if all the sections were filled equally) and the 024 * Disbalance shows the % between the target and the current enrollment. 025 * 026 * <br> 027 * <br> 028 * 029 * Usage: new UnbalancedSectionsTable(model),createTable(true, true).save(aFile); 030 * 031 * <br> 032 * <br> 033 * 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 implements StudentSectioningReport { 054 private static DecimalFormat sDF1 = new DecimalFormat("0.####"); 055 private static DecimalFormat sDF2 = new DecimalFormat("0.0000"); 056 057 private StudentSectioningModel iModel = null; 058 059 /** 060 * Constructor 061 * 062 * @param model 063 * student sectioning model 064 */ 065 public UnbalancedSectionsTable(StudentSectioningModel model) { 066 iModel = model; 067 } 068 069 /** Return student sectioning model 070 * @return problem model 071 **/ 072 public StudentSectioningModel getModel() { 073 return iModel; 074 } 075 076 /** 077 * Create report 078 * 079 * @param assignment current assignment 080 * @param includeLastLikeStudents 081 * true, if last-like students should be included (i.e., 082 * {@link Student#isDummy()} is true) 083 * @param includeRealStudents 084 * true, if real students should be included (i.e., 085 * {@link Student#isDummy()} is false) 086 * @param useAmPm use 12-hour format 087 * @return report as comma separated text file 088 */ 089 public CSVFile createTable(Assignment<Request, Enrollment> assignment, boolean includeLastLikeStudents, boolean includeRealStudents, boolean useAmPm) { 090 CSVFile csv = new CSVFile(); 091 csv.setHeader(new CSVFile.CSVField[] { new CSVFile.CSVField("Course"), new CSVFile.CSVField("Class"), 092 new CSVFile.CSVField("Meeting Time"), new CSVFile.CSVField("Enrollment"), 093 new CSVFile.CSVField("Target"), new CSVFile.CSVField("Limit"), new CSVFile.CSVField("Disbalance [%]") }); 094 095 TreeSet<Offering> offerings = new TreeSet<Offering>(new Comparator<Offering>() { 096 @Override 097 public int compare(Offering o1, Offering o2) { 098 int cmp = o1.getName().compareToIgnoreCase(o2.getName()); 099 if (cmp != 0) return cmp; 100 return o1.getId() < o2.getId() ? -1 : o2.getId() == o2.getId() ? 0 : 1; 101 } 102 }); 103 offerings.addAll(getModel().getOfferings()); 104 105 Offering last = null; 106 for (Offering offering: offerings) { 107 for (Config config: offering.getConfigs()) { 108 double configEnrl = 0; 109 for (Enrollment e: config.getEnrollments(assignment)) { 110 if (e.getStudent().isDummy() && !includeLastLikeStudents) continue; 111 if (!e.getStudent().isDummy() && !includeRealStudents) continue; 112 configEnrl += e.getRequest().getWeight(); 113 } 114 for (Subpart subpart: config.getSubparts()) { 115 if (subpart.getSections().size() <= 1) continue; 116 if (subpart.getLimit() > 0) { 117 // sections have limits -> desired size is section limit x (total enrollment / total limit) 118 double ratio = configEnrl / subpart.getLimit(); 119 for (Section section: subpart.getSections()) { 120 double enrl = 0.0; 121 for (Enrollment e: section.getEnrollments(assignment)) { 122 if (e.getStudent().isDummy() && !includeLastLikeStudents) continue; 123 if (!e.getStudent().isDummy() && !includeRealStudents) continue; 124 enrl += e.getRequest().getWeight(); 125 } 126 double desired = ratio * section.getLimit(); 127 if (Math.abs(desired - enrl) >= Math.max(1.0, 0.1 * section.getLimit())) { 128 if (last != null && !offering.equals(last)) csv.addLine(); 129 csv.addLine(new CSVFile.CSVField[] { 130 new CSVFile.CSVField(offering.equals(last) ? "" : offering.getName()), 131 new CSVFile.CSVField(section.getSubpart().getName() + " " + section.getName()), 132 new CSVFile.CSVField(section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader(useAmPm) + " - " + section.getTime().getEndTimeHeader(useAmPm)), 133 new CSVFile.CSVField(sDF1.format(enrl)), 134 new CSVFile.CSVField(sDF2.format(desired)), 135 new CSVFile.CSVField(sDF1.format(section.getLimit())), 136 new CSVFile.CSVField(sDF2.format(Math.min(1.0, Math.max(-1.0, (enrl - desired) / section.getLimit())))) 137 }); 138 last = offering; 139 } 140 } 141 } else { 142 // unlimited sections -> desired size is total enrollment / number of sections 143 for (Section section: subpart.getSections()) { 144 double enrl = 0.0; 145 for (Enrollment e: section.getEnrollments(assignment)) { 146 if (e.getStudent().isDummy() && !includeLastLikeStudents) continue; 147 if (!e.getStudent().isDummy() && !includeRealStudents) continue; 148 enrl += e.getRequest().getWeight(); 149 } 150 double desired = configEnrl / subpart.getSections().size(); 151 if (Math.abs(desired - enrl) >= Math.max(1.0, 0.1 * desired)) { 152 if (last != null && !offering.equals(last)) csv.addLine(); 153 csv.addLine(new CSVFile.CSVField[] { 154 new CSVFile.CSVField(offering.equals(last) ? "" : offering.getName()), 155 new CSVFile.CSVField(section.getSubpart().getName() + " " + section.getName()), 156 new CSVFile.CSVField(section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader(useAmPm) + " - " + section.getTime().getEndTimeHeader(useAmPm)), 157 new CSVFile.CSVField(sDF1.format(enrl)), 158 new CSVFile.CSVField(sDF2.format(desired)), 159 new CSVFile.CSVField(""), 160 new CSVFile.CSVField(sDF2.format(Math.min(1.0, Math.max(-1.0, (enrl - desired) / desired)))) 161 }); 162 last = offering; 163 } 164 } 165 } 166 } 167 } 168 } 169 return csv; 170 } 171 172 @Override 173 public CSVFile create(Assignment<Request, Enrollment> assignment, DataProperties properties) { 174 return createTable(assignment, properties.getPropertyBoolean("lastlike", false), properties.getPropertyBoolean("real", true), properties.getPropertyBoolean("useAmPm", true)); 175 } 176 177}