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