    001package org.cpsolver.studentsct.check;
    002
    003import java.text.DecimalFormat;
    004
    005import org.cpsolver.ifs.util.CSVFile;
    006import org.cpsolver.studentsct.StudentSectioningModel;
    007import org.cpsolver.studentsct.model.Config;
    008import org.cpsolver.studentsct.model.Course;
    009import org.cpsolver.studentsct.model.CourseRequest;
    010import org.cpsolver.studentsct.model.Offering;
    011import org.cpsolver.studentsct.model.Request;
    012import org.cpsolver.studentsct.model.Section;
    013import org.cpsolver.studentsct.model.Subpart;
    014
    015
    016/**
    017 * This class looks and reports cases when there are more students requesting a
    018 * course than the course limit.
    019 *
    020 * <br>
    021 * <br>
    022 *
    023 * Usage:
    024 * <pre><code>
    025 * &nbsp;&nbsp;&nbsp;&nbsp; CourseLimitCheck ch = new CourseLimitCheck(model);
    026 * &nbsp;&nbsp;&nbsp;&nbsp; if (!ch.check()) ch.getCSVFile().save(new File("limits.csv"));
    027 * </code></pre>
    028 *
    029 * <br>
    030 * Parameters:
    031 * <table border='1'><caption>Related Solver Parameters</caption>
    032 * <tr>
    033 * <th>Parameter</th>
    034 * <th>Type</th>
    035 * <th>Comment</th>
    036 * </tr>
    037 * <tr>
    038 * <td>CourseLimitCheck.FixUnlimited</td>
    039 * <td>{@link Boolean}</td>
    040 * <td>
    041 * If true, courses with zero or positive limit, but with unlimited sections,
    042 * are made unlimited (course limit is set to -1).</td>
    043 * </tr>
    044 * <tr>
    045 * <td>CourseLimitCheck.UpZeroLimits</td>
    046 * <td>{@link Boolean}</td>
    047 * <td>
    048 * If true, courses with zero limit, requested by one or more students are
    049 * increased in limit in order to accomodate all students that request the
    050 * course. Section limits are increased to ( total weight of all requests for
    051 * the offering / sections in subpart).</td>
    052 * </tr>
    053 * <tr>
    054 * <td>CourseLimitCheck.UpNonZeroLimits</td>
    055 * <td>{@link Boolean}</td>
    056 * <td>
    057 * If true, courses with positive limit, requested by more students than allowed
    058 * by the limit are increased in limit in order to accomodate all students that
    059 * requests the course. Section limits are increased proportionally by ( total
    060 * weight of all requests in the offering / current offering limit), where
    061 * offering limit is the sum of limits of courses of the offering.</td>
    062 * </tr>
    063 * </table>
    064 *
    065 * <br>
    066 * <br>
    067 *
    068 * @author  Tomáš Müller
    069 * @version StudentSct 1.3 (Student Sectioning)<br>
    070 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    071 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    072 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    073 * <br>
    074 *          This library is free software; you can redistribute it and/or modify
    075 *          it under the terms of the GNU Lesser General Public License as
    076 *          published by the Free Software Foundation; either version 3 of the
    077 *          License, or (at your option) any later version. <br>
    078 * <br>
    079 *          This library is distributed in the hope that it will be useful, but
    080 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    081 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    082 *          Lesser General Public License for more details. <br>
    083 * <br>
    084 *          You should have received a copy of the GNU Lesser General Public
    085 *          License along with this library; if not see
    086 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    087 */
    088public class CourseLimitCheck {
    089    private static org.apache.logging.log4j.Logger sLog = org.apache.logging.log4j.LogManager.getLogger(CourseLimitCheck.class);
    090    private static DecimalFormat sDF = new DecimalFormat("0.0");
    091    private StudentSectioningModel iModel;
    092    private CSVFile iCSVFile = null;
    093    private boolean iFixUnlimited = false;
    094    private boolean iUpZeroLimits = false;
    095    private boolean iUpNonZeroLimits = false;
    096
    097    /**
    098     * Constructor
    099     *
    100     * @param model
    101     *            student sectioning model
    102     */
    103    public CourseLimitCheck(StudentSectioningModel model) {
    104        iModel = model;
    105        iCSVFile = new CSVFile();
    106        iCSVFile.setHeader(new CSVFile.CSVField[] { new CSVFile.CSVField("Course"), new CSVFile.CSVField("Limit"),
    107                new CSVFile.CSVField("Students"), new CSVFile.CSVField("Real"), new CSVFile.CSVField("Last-like") });
    108        iFixUnlimited = model.getProperties().getPropertyBoolean("CourseLimitCheck.FixUnlimited", iFixUnlimited);
    109        iUpZeroLimits = model.getProperties().getPropertyBoolean("CourseLimitCheck.UpZeroLimits", iUpZeroLimits);
    110        iUpNonZeroLimits = model.getProperties().getPropertyBoolean("CourseLimitCheck.UpNonZeroLimits",
    111                iUpNonZeroLimits);
    112    }
    113
    114    /** Return student sectioning model
    115     * @return problem model
    116     **/
    117    public StudentSectioningModel getModel() {
    118        return iModel;
    119    }
    120
    121    /** Return report
    122     * @return generated report
    123     **/
    124    public CSVFile getCSVFile() {
    125        return iCSVFile;
    126    }
    127
    128    /**
    129     * Check for courses where the limit is below the number of students that
    130     * request the course
    131     *
    132     * @return false, if there is such a case
    133     */
    134    public boolean check() {
    135        sLog.info("Checking for course limits...");
    136        boolean ret = true;
    137        for (Offering offering : getModel().getOfferings()) {
    138            if (offering.isDummy()) continue;
    139            boolean hasUnlimitedSection = false;
    140            if (iFixUnlimited)
    141                for (Config config : offering.getConfigs()) {
    142                    for (Subpart subpart : config.getSubparts()) {
    143                        for (Section section : subpart.getSections()) {
    144                            if (section.getLimit() < 0)
    145                                hasUnlimitedSection = true;
    146                        }
    147                    }
    148                }
    149            int offeringLimit = 0;
    150            int nrStudents = 0;
    151            for (Course course : offering.getCourses()) {
    152                if (course.getLimit() < 0) {
    153                    offeringLimit = -1;
    154                    continue;
    155                }
    156                if (iFixUnlimited && hasUnlimitedSection) {
    157                    sLog.info("Course " + course + " made unlimited.");
    158                    course.setLimit(-1);
    159                    offeringLimit = -1;
    160                    continue;
    161                }
    162                double total = 0;
    163                double lastLike = 0, real = 0;
    164                for (Request request : getModel().variables()) {
    165                    if (request instanceof CourseRequest && ((CourseRequest) request).getCourses().contains(course)) {
    166                        total += request.getWeight();
    167                        if (request.getStudent().isDummy())
    168                            lastLike += request.getWeight();
    169                        else
    170                            real += request.getWeight();
    171                    }
    172                }
    173                nrStudents += Math.round(total);
    174                offeringLimit += course.getLimit();
    175                if (Math.round(total) > course.getLimit()) {
    176                    sLog.error("Course " + course + " is requested by " + sDF.format(total)
    177                            + " students, but its limit is only " + course.getLimit());
    178                    ret = false;
    179                    iCSVFile.addLine(new CSVFile.CSVField[] { new CSVFile.CSVField(course.getName()),
    180                            new CSVFile.CSVField(course.getLimit()), new CSVFile.CSVField(total),
    181                            new CSVFile.CSVField(real), new CSVFile.CSVField(lastLike) });
    182                    if (iUpZeroLimits && course.getLimit() == 0) {
    183                        int oldLimit = course.getLimit();
    184                        course.setLimit((int) Math.round(total));
    185                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
    186                                + oldLimit + ")");
    187                    } else if (iUpNonZeroLimits && course.getLimit() > 0) {
    188                        int oldLimit = course.getLimit();
    189                        course.setLimit((int) Math.round(total));
    190                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
    191                                + oldLimit + ")");
    192                    }
    193                }
    194            }
    195            if (iUpZeroLimits && offeringLimit == 0 && nrStudents > 0) {
    196                for (Config config : offering.getConfigs()) {
    197                    for (Subpart subpart : config.getSubparts()) {
    198                        for (Section section : subpart.getSections()) {
    199                            int oldLimit = section.getLimit();
    200                            section.setLimit(Math.max(section.getLimit(), (int) Math.ceil(nrStudents
    201                                    / subpart.getSections().size())));
    202                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
    203                                    + " (was " + oldLimit + ")");
    204                        }
    205                    }
    206                }
    207            } else if (iUpNonZeroLimits && offeringLimit >= 0 && nrStudents > offeringLimit) {
    208                double fact = ((double) nrStudents) / offeringLimit;
    209                for (Config config : offering.getConfigs()) {
    210                    for (Subpart subpart : config.getSubparts()) {
    211                        for (Section section : subpart.getSections()) {
    212                            int oldLimit = section.getLimit();
    213                            section.setLimit((int) Math.ceil(fact * section.getLimit()));
    214                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
    215                                    + " (was " + oldLimit + ")");
    216                        }
    217                    }
    218                }
    219            }
    220
    221            if (offeringLimit >= 0) {
    222                int totalSectionLimit = 0;
    223                for (Config config : offering.getConfigs()) {
    224                    int configLimit = -1;
    225                    for (Subpart subpart : config.getSubparts()) {
    226                        int subpartLimit = 0;
    227                        for (Section section : subpart.getSections()) {
    228                            subpartLimit += section.getLimit();
    229                        }
    230                        if (configLimit < 0)
    231                            configLimit = subpartLimit;
    232                        else
    233                            configLimit = Math.min(configLimit, subpartLimit);
    234                    }
    235                    totalSectionLimit += configLimit;
    236                }
    237                if (totalSectionLimit < offeringLimit) {
    238                    sLog.error("Offering limit of " + offering + " is " + offeringLimit
    239                            + ", but total section limit is only " + totalSectionLimit);
    240                    if (iUpZeroLimits && totalSectionLimit == 0) {
    241                        for (Config config : offering.getConfigs()) {
    242                            for (Subpart subpart : config.getSubparts()) {
    243                                for (Section section : subpart.getSections()) {
    244                                    int oldLimit = section.getLimit();
    245                                    section.setLimit(Math.max(section.getLimit(), (int) Math
    246                                            .ceil(((double) offeringLimit) / subpart.getSections().size())));
    247                                    sLog.info("    -- limit of section " + section + " increased to "
    248                                            + section.getLimit() + " (was " + oldLimit + ")");
    249                                }
    250                            }
    251                        }
    252                    } else if (iUpNonZeroLimits && totalSectionLimit > 0) {
    253                        double fact = ((double) offeringLimit) / totalSectionLimit;
    254                        for (Config config : offering.getConfigs()) {
    255                            for (Subpart subpart : config.getSubparts()) {
    256                                for (Section section : subpart.getSections()) {
    257                                    int oldLimit = section.getLimit();
    258                                    section.setLimit((int) Math.ceil(fact * section.getLimit()));
    259                                    sLog.info("    -- limit of section " + section + " increased to "
    260                                            + section.getLimit() + " (was " + oldLimit + ")");
    261                                }
    262                            }
    263                        }
    264                    }
    265                }
    266            }
    267        }
    268        return ret;
    269    }
    270
    271}