001    package net.sf.cpsolver.studentsct.check;
002    
003    import java.text.DecimalFormat;
004    
005    import net.sf.cpsolver.ifs.util.CSVFile;
006    import net.sf.cpsolver.studentsct.StudentSectioningModel;
007    import net.sf.cpsolver.studentsct.model.Config;
008    import net.sf.cpsolver.studentsct.model.Course;
009    import net.sf.cpsolver.studentsct.model.CourseRequest;
010    import net.sf.cpsolver.studentsct.model.Offering;
011    import net.sf.cpsolver.studentsct.model.Request;
012    import net.sf.cpsolver.studentsct.model.Section;
013    import 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 Tomas Muller<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     */
087    public 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    }