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 Tomas Muller<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                config.getEnrollmentWeight(assignment, null);
115                for (Subpart subpart: config.getSubparts()) {
116                    if (subpart.getSections().size() <= 1) continue;
117                    if (subpart.getLimit() > 0) {
118                        // sections have limits -> desired size is section limit x (total enrollment / total limit)
119                        double ratio = configEnrl / subpart.getLimit();
120                        for (Section section: subpart.getSections()) {
121                            double enrl = 0.0;
122                            for (Enrollment e: section.getEnrollments(assignment)) {
123                                if (e.getStudent().isDummy() && !includeLastLikeStudents) continue;
124                                if (!e.getStudent().isDummy() && !includeRealStudents) continue;
125                                enrl += e.getRequest().getWeight();
126                            }
127                            double desired = ratio * section.getLimit();
128                            if (Math.abs(desired - enrl) >= Math.max(1.0, 0.1 * section.getLimit())) {
129                                if (last != null && !offering.equals(last)) csv.addLine();
130                                csv.addLine(new CSVFile.CSVField[] {
131                                        new CSVFile.CSVField(offering.equals(last) ? "" : offering.getName()),
132                                        new CSVFile.CSVField(section.getSubpart().getName() + " " + section.getName()),
133                                        new CSVFile.CSVField(section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader(useAmPm) + " - " + section.getTime().getEndTimeHeader(useAmPm)),
134                                        new CSVFile.CSVField(sDF1.format(enrl)),
135                                        new CSVFile.CSVField(sDF2.format(desired)),
136                                        new CSVFile.CSVField(sDF1.format(section.getLimit())),
137                                        new CSVFile.CSVField(sDF2.format(Math.min(1.0, Math.max(-1.0, (enrl - desired) / section.getLimit()))))
138                                });
139                                last = offering;
140                            }
141                        }
142                    } else {
143                        // unlimited sections -> desired size is total enrollment / number of sections
144                        for (Section section: subpart.getSections()) {
145                            double enrl = 0.0;
146                            for (Enrollment e: section.getEnrollments(assignment)) {
147                                if (e.getStudent().isDummy() && !includeLastLikeStudents) continue;
148                                if (!e.getStudent().isDummy() && !includeRealStudents) continue;
149                                enrl += e.getRequest().getWeight();
150                            }
151                            double desired = configEnrl / subpart.getSections().size();
152                            if (Math.abs(desired - enrl) >= Math.max(1.0, 0.1 * desired)) {
153                                if (last != null && !offering.equals(last)) csv.addLine();
154                                csv.addLine(new CSVFile.CSVField[] {
155                                        new CSVFile.CSVField(offering.equals(last) ? "" : offering.getName()),
156                                        new CSVFile.CSVField(section.getSubpart().getName() + " " + section.getName()),
157                                        new CSVFile.CSVField(section.getTime() == null ? "" : section.getTime().getDayHeader() + " " + section.getTime().getStartTimeHeader(useAmPm) + " - " + section.getTime().getEndTimeHeader(useAmPm)),
158                                        new CSVFile.CSVField(sDF1.format(enrl)),
159                                        new CSVFile.CSVField(sDF2.format(desired)),
160                                        new CSVFile.CSVField(""),
161                                        new CSVFile.CSVField(sDF2.format(Math.min(1.0, Math.max(-1.0, (enrl - desired) / desired))))
162                                });
163                                last = offering;
164                            }
165                        }
166                    }
167                }
168            }
169        }
170        return csv;
171    }
172    
173    @Override
174    public CSVFile create(Assignment<Request, Enrollment> assignment, DataProperties properties) {
175        return createTable(assignment, properties.getPropertyBoolean("lastlike", false), properties.getPropertyBoolean("real", true), properties.getPropertyBoolean("useAmPm", true));
176    }
177
178}