    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}