    001package net.sf.cpsolver.studentsct.check;
    002
    003import java.text.DecimalFormat;
    004
    005import net.sf.cpsolver.ifs.util.CSVFile;
    006import net.sf.cpsolver.studentsct.StudentSectioningModel;
    007import net.sf.cpsolver.studentsct.model.Config;
    008import net.sf.cpsolver.studentsct.model.Course;
    009import net.sf.cpsolver.studentsct.model.CourseRequest;
    010import net.sf.cpsolver.studentsct.model.Offering;
    011import net.sf.cpsolver.studentsct.model.Request;
    012import net.sf.cpsolver.studentsct.model.Section;
    013import net.sf.cpsolver.studentsct.model.Subpart;
    014
    015/**
    016 * This class looks and reports cases when there are more students requesting a
    017 * course than the course limit.
    018 *
    019 * <br>
    020 * <br>
    021 *
    022 * Usage:<br>
    023 * <code>
    024 * &nbsp;&nbsp;&nbsp;&nbsp; CourseLimitCheck ch = new CourseLimitCheck(model);<br>
    025 * &nbsp;&nbsp;&nbsp;&nbsp; if (!ch.check()) ch.getCSVFile().save(new File("limits.csv"));
    026 * </code>
    027 *
    028 * <br>
    029 * <br>
    030 * Parameters:
    031 * <table border='1'>
    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.2 (Student Sectioning)<br>
    069 *          Copyright (C) 2007 - 2010 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    public StudentSectioningModel getModel() {
    115        return iModel;
    116    }
    117
    118    /** Return report */
    119    public CSVFile getCSVFile() {
    120        return iCSVFile;
    121    }
    122
    123    /**
    124     * Check for courses where the limit is below the number of students that
    125     * request the course
    126     *
    127     * @return false, if there is such a case
    128     */
    129    public boolean check() {
    130        sLog.info("Checking for course limits...");
    131        boolean ret = true;
    132        for (Offering offering : getModel().getOfferings()) {
    133            boolean hasUnlimitedSection = false;
    134            if (iFixUnlimited)
    135                for (Config config : offering.getConfigs()) {
    136                    for (Subpart subpart : config.getSubparts()) {
    137                        for (Section section : subpart.getSections()) {
    138                            if (section.getLimit() < 0)
    139                                hasUnlimitedSection = true;
    140                        }
    141                    }
    142                }
    143            int offeringLimit = 0;
    144            int nrStudents = 0;
    145            for (Course course : offering.getCourses()) {
    146                if (course.getLimit() < 0) {
    147                    offeringLimit = -1;
    148                    continue;
    149                }
    150                if (iFixUnlimited && hasUnlimitedSection) {
    151                    sLog.info("Course " + course + " made unlimited.");
    152                    course.setLimit(-1);
    153                    offeringLimit = -1;
    154                    continue;
    155                }
    156                double total = 0;
    157                double lastLike = 0, real = 0;
    158                for (Request request : getModel().variables()) {
    159                    if (request instanceof CourseRequest && ((CourseRequest) request).getCourses().contains(course)) {
    160                        total += request.getWeight();
    161                        if (request.getStudent().isDummy())
    162                            lastLike += request.getWeight();
    163                        else
    164                            real += request.getWeight();
    165                    }
    166                }
    167                nrStudents += Math.round(total);
    168                offeringLimit += course.getLimit();
    169                if (Math.round(total) > course.getLimit()) {
    170                    sLog.error("Course " + course + " is requested by " + sDF.format(total)
    171                            + " students, but its limit is only " + course.getLimit());
    172                    ret = false;
    173                    iCSVFile.addLine(new CSVFile.CSVField[] { new CSVFile.CSVField(course.getName()),
    174                            new CSVFile.CSVField(course.getLimit()), new CSVFile.CSVField(total),
    175                            new CSVFile.CSVField(real), new CSVFile.CSVField(lastLike) });
    176                    if (iUpZeroLimits && course.getLimit() == 0) {
    177                        int oldLimit = course.getLimit();
    178                        course.setLimit((int) Math.round(total));
    179                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
    180                                + oldLimit + ")");
    181                    } else if (iUpNonZeroLimits && course.getLimit() > 0) {
    182                        int oldLimit = course.getLimit();
    183                        course.setLimit((int) Math.round(total));
    184                        sLog.info("  -- limit of course " + course + " increased to " + course.getLimit() + " (was "
    185                                + oldLimit + ")");
    186                    }
    187                }
    188            }
    189            if (iUpZeroLimits && offeringLimit == 0 && nrStudents > 0) {
    190                for (Config config : offering.getConfigs()) {
    191                    for (Subpart subpart : config.getSubparts()) {
    192                        for (Section section : subpart.getSections()) {
    193                            int oldLimit = section.getLimit();
    194                            section.setLimit(Math.max(section.getLimit(), (int) Math.ceil(nrStudents
    195                                    / subpart.getSections().size())));
    196                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
    197                                    + " (was " + oldLimit + ")");
    198                        }
    199                    }
    200                }
    201            } else if (iUpNonZeroLimits && offeringLimit >= 0 && nrStudents > offeringLimit) {
    202                double fact = ((double) nrStudents) / offeringLimit;
    203                for (Config config : offering.getConfigs()) {
    204                    for (Subpart subpart : config.getSubparts()) {
    205                        for (Section section : subpart.getSections()) {
    206                            int oldLimit = section.getLimit();
    207                            section.setLimit((int) Math.ceil(fact * section.getLimit()));
    208                            sLog.info("    -- limit of section " + section + " increased to " + section.getLimit()
    209                                    + " (was " + oldLimit + ")");
    210                        }
    211                    }
    212                }
    213            }
    214
    215            if (offeringLimit >= 0) {
    216                int totalSectionLimit = 0;
    217                for (Config config : offering.getConfigs()) {
    218                    int configLimit = -1;
    219                    for (Subpart subpart : config.getSubparts()) {
    220                        int subpartLimit = 0;
    221                        for (Section section : subpart.getSections()) {
    222                            subpartLimit += section.getLimit();
    223                        }
    224                        if (configLimit < 0)
    225                            configLimit = subpartLimit;
    226                        else
    227                            configLimit = Math.min(configLimit, subpartLimit);
    228                    }
    229                    totalSectionLimit += configLimit;
    230                }
    231                if (totalSectionLimit < offeringLimit) {
    232                    sLog.error("Offering limit of " + offering + " is " + offeringLimit
    233                            + ", but total section limit is only " + totalSectionLimit);
    234                    if (iUpZeroLimits && totalSectionLimit == 0) {
    235                        for (Config config : offering.getConfigs()) {
    236                            for (Subpart subpart : config.getSubparts()) {
    237                                for (Section section : subpart.getSections()) {
    238                                    int oldLimit = section.getLimit();
    239                                    section.setLimit(Math.max(section.getLimit(), (int) Math
    240                                            .ceil(((double) offeringLimit) / subpart.getSections().size())));
    241                                    sLog.info("    -- limit of section " + section + " increased to "
    242                                            + section.getLimit() + " (was " + oldLimit + ")");
    243                                }
    244                            }
    245                        }
    246                    } else if (iUpNonZeroLimits && totalSectionLimit > 0) {
    247                        double fact = ((double) offeringLimit) / totalSectionLimit;
    248                        for (Config config : offering.getConfigs()) {
    249                            for (Subpart subpart : config.getSubparts()) {
    250                                for (Section section : subpart.getSections()) {
    251                                    int oldLimit = section.getLimit();
    252                                    section.setLimit((int) Math.ceil(fact * section.getLimit()));
    253                                    sLog.info("    -- limit of section " + section + " increased to "
    254                                            + section.getLimit() + " (was " + oldLimit + ")");
    255                                }
    256                            }
    257                        }
    258                    }
    259                }
    260            }
    261        }
    262        return ret;
    263    }
    264
    265}