    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' summary='Related Solver Parameters'>
    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 * @version StudentSct 1.3 (Student Sectioning)<br>
    069 *          Copyright (C) 2007 - 2014 Tomáš Müller<br>
    070 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    071 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    072 * <br>
    073 *          This library is free software; you can redistribute it and/or modify
    074 *          it under the terms of the GNU Lesser General Public License as
    075 *          published by the Free Software Foundation; either version 3 of the
    076 *          License, or (at your option) any later version. <br>
    077 * <br>
    078 *          This library is distributed in the hope that it will be useful, but
    079 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    080 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    081 *          Lesser General Public License for more details. <br>
    082 * <br>
    083 *          You should have received a copy of the GNU Lesser General Public
    084 *          License along with this library; if not see
    085 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    086 */
    087public class CourseLimitCheck {
    088    private static org.apache.log4j.Logger sLog = org.apache.log4j.Logger.getLogger(CourseLimitCheck.class);
    089    private static DecimalFormat sDF = new DecimalFormat("0.0");
    090    private StudentSectioningModel iModel;
    091    private CSVFile iCSVFile = null;
    092    private boolean iFixUnlimited = false;
    093    private boolean iUpZeroLimits = false;
    094    private boolean iUpNonZeroLimits = false;
    095
    096    /**
    097     * Constructor
    098     *
    099     * @param model
    100     *            student sectioning model
    101     */
    102    public CourseLimitCheck(StudentSectioningModel model) {
    103        iModel = model;
    104        iCSVFile = new CSVFile();
    105        iCSVFile.setHeader(new CSVFile.CSVField[] { new CSVFile.CSVField("Course"), new CSVFile.CSVField("Limit"),
    106                new CSVFile.CSVField("Students"), new CSVFile.CSVField("Real"), new CSVFile.CSVField("Last-like") });
    107        iFixUnlimited = model.getProperties().getPropertyBoolean("CourseLimitCheck.FixUnlimited", iFixUnlimited);
    108        iUpZeroLimits = model.getProperties().getPropertyBoolean("CourseLimitCheck.UpZeroLimits", iUpZeroLimits);
    109        iUpNonZeroLimits = model.getProperties().getPropertyBoolean("CourseLimitCheck.UpNonZeroLimits",
    110                iUpNonZeroLimits);
    111    }
    112
    113    /** Return student sectioning model
    114     * @return problem model
    115     **/
    116    public StudentSectioningModel getModel() {
    117        return iModel;
    118    }
    119
    120    /** Return report
    121     * @return generated report
    122     **/
    123    public CSVFile getCSVFile() {
    124        return iCSVFile;
    125    }
    126
    127    /**
    128     * Check for courses where the limit is below the number of students that
    129     * request the course
    130     *
    131     * @return false, if there is such a case
    132     */
    133    public boolean check() {
    134        sLog.info("Checking for course limits...");
    135        boolean ret = true;
    136        for (Offering offering : getModel().getOfferings()) {
    137            boolean hasUnlimitedSection = false;
    138            if (iFixUnlimited)
    139                for (Config config : offering.getConfigs()) {
    140                    for (Subpart subpart : config.getSubparts()) {
    141                        for (Section section : subpart.getSections()) {
    142                            if (section.getLimit() < 0)
    143                                hasUnlimitedSection = true;
    144                        }
    145                    }
    146                }
    147            int offeringLimit = 0;
    148            int nrStudents = 0;
    149            for (Course course : offering.getCourses()) {
    150                if (course.getLimit() < 0) {
    151                    offeringLimit = -1;
    152                    continue;
    153                }
    154                if (iFixUnlimited && hasUnlimitedSection) {
    155                    sLog.info("Course " + course + " made unlimited.");
    156                    course.setLimit(-1);
    157                    offeringLimit = -1;
    158                    continue;
    159                }
    160                double total = 0;
    161                double lastLike = 0, real = 0;
    162                for (Request request : getModel().variables()) {
    163                    if (request instanceof CourseRequest && ((CourseRequest) request).getCourses().contains(course)) {
    164                        total += request.getWeight();
    165                        if (request.getStudent().isDummy())
    166                            lastLike += request.getWeight();
    167                        else
    168                            real += request.getWeight();
    169                    }
    170                }
    171                nrStudents += Math.round(total);
    172                offeringLimit += course.getLimit();
    173                if (Math.round(total) > course.getLimit()) {
    174                    sLog.error("Course " + course + " is requested by " + sDF.format(total)
    175                            + " students, but its limit is only " + course.getLimit());
    176                    ret = false;
    177                    iCSVFile.addLine(new CSVFile.CSVField[] { new CSVFile.CSVField(course.getName()),
    178                            new CSVFile.CSVField(course.getLimit()), new CSVFile.CSVField(total),
    179                            new CSVFile.CSVField(real), new CSVFile.CSVField(lastLike) });
    180                    if (iUpZeroLimits && course.getLimit() == 0) {
    181                        int oldLimit = course.getLimit();
    182                        course.setLimit((int) Math.round(total));
    183                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
    184                                + oldLimit + ")");
    185                    } else if (iUpNonZeroLimits && course.getLimit() > 0) {
    186                        int oldLimit = course.getLimit();
    187                        course.setLimit((int) Math.round(total));
    188                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
    189                                + oldLimit + ")");
    190                    }
    191                }
    192            }
    193            if (iUpZeroLimits && offeringLimit == 0 && nrStudents > 0) {
    194                for (Config config : offering.getConfigs()) {
    195                    for (Subpart subpart : config.getSubparts()) {
    196                        for (Section section : subpart.getSections()) {
    197                            int oldLimit = section.getLimit();
    198                            section.setLimit(Math.max(section.getLimit(), (int) Math.ceil(nrStudents
    199                                    / subpart.getSections().size())));
    200                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
    201                                    + " (was " + oldLimit + ")");
    202                        }
    203                    }
    204                }
    205            } else if (iUpNonZeroLimits && offeringLimit >= 0 && nrStudents > offeringLimit) {
    206                double fact = ((double) nrStudents) / offeringLimit;
    207                for (Config config : offering.getConfigs()) {
    208                    for (Subpart subpart : config.getSubparts()) {
    209                        for (Section section : subpart.getSections()) {
    210                            int oldLimit = section.getLimit();
    211                            section.setLimit((int) Math.ceil(fact * section.getLimit()));
    212                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
    213                                    + " (was " + oldLimit + ")");
    214                        }
    215                    }
    216                }
    217            }
    218
    219            if (offeringLimit >= 0) {
    220                int totalSectionLimit = 0;
    221                for (Config config : offering.getConfigs()) {
    222                    int configLimit = -1;
    223                    for (Subpart subpart : config.getSubparts()) {
    224                        int subpartLimit = 0;
    225                        for (Section section : subpart.getSections()) {
    226                            subpartLimit += section.getLimit();
    227                        }
    228                        if (configLimit < 0)
    229                            configLimit = subpartLimit;
    230                        else
    231                            configLimit = Math.min(configLimit, subpartLimit);
    232                    }
    233                    totalSectionLimit += configLimit;
    234                }
    235                if (totalSectionLimit < offeringLimit) {
    236                    sLog.error("Offering limit of " + offering + " is " + offeringLimit
    237                            + ", but total section limit is only " + totalSectionLimit);
    238                    if (iUpZeroLimits && totalSectionLimit == 0) {
    239                        for (Config config : offering.getConfigs()) {
    240                            for (Subpart subpart : config.getSubparts()) {
    241                                for (Section section : subpart.getSections()) {
    242                                    int oldLimit = section.getLimit();
    243                                    section.setLimit(Math.max(section.getLimit(), (int) Math
    244                                            .ceil(((double) offeringLimit) / subpart.getSections().size())));
    245                                    sLog.info("    -- limit of section " + section + " increased to "
    246                                            + section.getLimit() + " (was " + oldLimit + ")");
    247                                }
    248                            }
    249                        }
    250                    } else if (iUpNonZeroLimits && totalSectionLimit > 0) {
    251                        double fact = ((double) offeringLimit) / totalSectionLimit;
    252                        for (Config config : offering.getConfigs()) {
    253                            for (Subpart subpart : config.getSubparts()) {
    254                                for (Section section : subpart.getSections()) {
    255                                    int oldLimit = section.getLimit();
    256                                    section.setLimit((int) Math.ceil(fact * section.getLimit()));
    257                                    sLog.info("    -- limit of section " + section + " increased to "
    258                                            + section.getLimit() + " (was " + oldLimit + ")");
    259                                }
    260                            }
    261                        }
    262                    }
    263                }
    264            }
    265        }
    266        return ret;
    267    }
    268
    269}