001    package net.sf.cpsolver.studentsct;
002    
003    import java.text.DecimalFormat;
004    import java.util.Enumeration;
005    import java.util.Hashtable;
006    import java.util.Iterator;
007    import java.util.Vector;
008    
009    import net.sf.cpsolver.coursett.Constants;
010    import net.sf.cpsolver.coursett.model.TimeLocation;
011    import net.sf.cpsolver.coursett.model.TimeLocation.IntEnumeration;
012    import net.sf.cpsolver.ifs.heuristics.RouletteWheelSelection;
013    import net.sf.cpsolver.studentsct.heuristics.selection.BranchBoundSelection;
014    import net.sf.cpsolver.studentsct.model.Assignment;
015    import net.sf.cpsolver.studentsct.model.Config;
016    import net.sf.cpsolver.studentsct.model.Course;
017    import net.sf.cpsolver.studentsct.model.CourseRequest;
018    import net.sf.cpsolver.studentsct.model.Enrollment;
019    import net.sf.cpsolver.studentsct.model.FreeTimeRequest;
020    import net.sf.cpsolver.studentsct.model.Offering;
021    import net.sf.cpsolver.studentsct.model.Request;
022    import net.sf.cpsolver.studentsct.model.Section;
023    import net.sf.cpsolver.studentsct.model.Student;
024    import net.sf.cpsolver.studentsct.model.Subpart;
025    
026    /**
027     * An attempt to empirically test the case when students can choose their sections (section times).
028     * <br><br>
029     * Each student has his/her own order of possible times of the week 
030     * (selection of a day and an hour starting 7:30, 8:30, etc.) -- 
031     * this order is computed using roulette wheel selection with the distribution of 
032     * possible times defined in {@link StudentPreferencePenalties#sStudentRequestDistribution}.
033     * <br><br>
034     * A penalty for each section is computed proportionally based on this order 
035     * (and the number of slots that falls into each time frame), 
036     * the existing branch&bound selection is used to section each student one by one (in a random order).
037     * <br><br>
038     * Usage:<br>
039     * <code>
040     * for (Enumeration e=students.elements();e.hasMoreElements();) {<br>
041     * &nbsp;&nbsp;// take a student (one by one)<br>
042     * &nbsp;&nbsp;Student student = (Student)e.nextElement();<br>
043     * <br>
044     * &nbsp;&nbsp;// compute and apply penalties using this class<br>
045     * &nbsp;&nbsp;new StudentPreferencePenalties().setPenalties(student);<br>
046     * <br>
047     * &nbsp;&nbsp;// section a student<br>
048     * &nbsp;&nbsp;// for instance, {@link BranchBoundSelection} can be used (with Neighbour.BranchAndBoundMinimizePenalty set to true)<br>
049     * &nbsp;&nbsp;Neighbour neighbour = new BranchBoundSelection(config).getSelection(student).select();<br>
050     * &nbsp;&nbsp;if (neighbour!=null) neighbour.assign(iteration++);<br>
051     * };
052     * </code>
053     * <br><br>
054     * 
055     * @version
056     * StudentSct 1.1 (Student Sectioning)<br>
057     * Copyright (C) 2007 Tomáš Müller<br>
058     * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
059     * Lazenska 391, 76314 Zlin, Czech Republic<br>
060     * <br>
061     * This library is free software; you can redistribute it and/or
062     * modify it under the terms of the GNU Lesser General Public
063     * License as published by the Free Software Foundation; either
064     * version 2.1 of the License, or (at your option) any later version.
065     * <br><br>
066     * This library is distributed in the hope that it will be useful,
067     * but WITHOUT ANY WARRANTY; without even the implied warranty of
068     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
069     * Lesser General Public License for more details.
070     * <br><br>
071     * You should have received a copy of the GNU Lesser General Public
072     * License along with this library; if not, write to the Free Software
073     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
074     */
075    public class StudentPreferencePenalties {
076        private static org.apache.log4j.Logger sLog = org.apache.log4j.Logger.getLogger(StudentPreferencePenalties.class);
077        private static DecimalFormat sDF = new DecimalFormat("0.000");
078        private static boolean sDebug = false;
079        public static int sDistTypeUniform = 0;
080        public static int sDistTypePreference = 1;
081        public static int sDistTypePreferenceQuadratic = 2;
082        public static int sDistTypePreferenceReverse = 3;
083        
084        public static int[][] sStudentRequestDistribution = new int[][] {
085            //morning, 7:30a, 8:30a, 9:30a, 10:30a, 11:30a, 12:30p, 1:30p, 2:30p, 3:30p, 4:30p, evening
086            {       1,     1,     4,     7,     10,     10,      5,     8,     8,     6,     3,      1 }, //Monday
087            {       1,     2,     4,     7,     10,     10,      5,     8,     8,     6,     3,      1 }, //Tuesday
088            {       1,     2,     4,     7,     10,     10,      5,     8,     8,     6,     3,      1 }, //Wednesday
089            {       1,     2,     4,     7,     10,     10,      5,     8,     8,     6,     3,      1 }, //Thursday
090            {       1,     2,     4,     7,     10,     10,      5,     4,     3,     2,     1,      1 }, //Friday
091            {       1,     1,     1,     1,      1,      1,      1,     1,     1,     1,     1,      1 }, //Saturday
092            {       1,     1,     1,     1,      1,      1,      1,     1,     1,     1,     1,      1 }  //Sunday
093        };
094        private Hashtable iWeight = new Hashtable();
095        
096        /** 
097         * Constructor. 
098         * All possible times are ordered based on the distribution defined by
099         * {@link StudentPreferencePenalties#sStudentRequestDistribution}.
100         * The first time gets zero penalty, the second 1/nrTimes, the third 2/nrTimes etc. where 
101         * nrTimes is the number of times in {@link StudentPreferencePenalties#sStudentRequestDistribution}.
102         */
103        public StudentPreferencePenalties(int disributionType) {
104            RouletteWheelSelection roulette = new RouletteWheelSelection();
105            for (int d=0;d<sStudentRequestDistribution.length;d++)
106                for (int t=0;t<sStudentRequestDistribution[d].length;t++) {
107                    if (disributionType==sDistTypeUniform) {
108                        roulette.add(new int[]{d,t}, 1);
109                    } else if (disributionType==sDistTypePreference) {
110                        roulette.add(new int[]{d,t}, sStudentRequestDistribution[d][t]);
111                    } else if (disributionType==sDistTypePreferenceQuadratic) {
112                        roulette.add(new int[]{d,t}, sStudentRequestDistribution[d][t]*sStudentRequestDistribution[d][t]);
113                    } else if (disributionType==sDistTypePreferenceReverse) {
114                        roulette.add(new int[]{d,t}, 11-sStudentRequestDistribution[d][t]);
115                    } else {
116                        roulette.add(new int[]{d,t}, 1);
117                    }
118                }
119            int idx = 0;
120            while (roulette.hasMoreElements()) {
121                int[] dt = (int[])roulette.nextElement();
122                iWeight.put(dt[0]+"."+dt[1], new Double(((double)idx)/(roulette.size()-1)));
123                if (sDebug) sLog.debug("  -- "+(idx+1)+". preference is "+toString(dt[0],dt[1])+" (P:"+sDF.format(((double)idx)/(roulette.size()-1))+")");
124                idx++;
125            }
126        }
127        
128        /** Return day index in {@link StudentPreferencePenalties#sStudentRequestDistribution} for the given slot. */ 
129        public static int day(int slot) {
130            return slot / Constants.SLOTS_PER_DAY;
131        }
132        
133        /** Return time index in {@link StudentPreferencePenalties#sStudentRequestDistribution} for the given slot. */
134        public static int time(int slot) {
135            int s = slot % Constants.SLOTS_PER_DAY;
136            int min =  (s * Constants.SLOT_LENGTH_MIN + Constants.FIRST_SLOT_TIME_MIN);
137            if (min<450) return 0; //morning
138            int idx = 1+(min-450)/60;
139            return (idx>11?11:idx); //11+ is evening
140        }
141        
142        /** Return time of the given day and time index of {@link StudentPreferencePenalties#sStudentRequestDistribution}. */
143        public String toString(int day, int time) {
144            if (time==0) return Constants.DAY_NAMES_SHORT[day]+" morning";
145            if (time==11) return Constants.DAY_NAMES_SHORT[day]+" evening";
146            return Constants.DAY_NAMES_SHORT[day]+" "+(6+time)+":30";
147        }
148        
149        /** 
150         * Return penalty of the given time. It is comuted as average of the penalty for each time slot of the time. 
151         **/
152        public double getPenalty(TimeLocation time) {
153            int nrSlots = 0;
154            double penalty = 0.0;
155            for (IntEnumeration e=time.getSlots();e.hasMoreElements();) {
156                int slot = e.nextInt();
157                nrSlots++;
158                penalty += ((Double)iWeight.get(day(slot)+"."+time(slot))).doubleValue();
159            }
160            return penalty/nrSlots;
161        }
162        
163        /**
164         * Return penalty of an assignment. It is a penalty of its time (see {@link Assignment#getTime()}) or zero
165         * if the time is null.
166         */
167        public double getPenalty(Assignment assignment) {
168            return (assignment.getTime()==null?0.0:getPenalty(assignment.getTime()));
169        }
170        
171        /**
172         * Return penalty of an enrollment. It is an average penalty of all its assignments {@link Enrollment#getAssignments()}. 
173         */
174        public double getPenalty(Enrollment enrollment) {
175            double penalty = 0;
176            for (Iterator i=enrollment.getAssignments().iterator();i.hasNext();) {
177                Section section = (Section)i.next();
178                penalty += getPenalty(section);
179            }
180            return penalty/enrollment.getAssignments().size();
181        }
182        
183        /** Minimal penalty of a course request */ 
184        public double getMinPenalty(Request request) {
185            if (request instanceof CourseRequest)
186                return getMinPenalty((CourseRequest)request);
187            else if (request instanceof FreeTimeRequest)
188                return getPenalty(((FreeTimeRequest)request).getTime());
189            return 0;
190        }
191    
192        /** Minimal penalty of a course request */
193        public double getMinPenalty(CourseRequest request) {
194            double min = Double.MAX_VALUE;
195            for (Enumeration e=request.getCourses().elements();e.hasMoreElements();) {
196                Course course = (Course)e.nextElement();
197                min = Math.min(min, getMinPenalty(course.getOffering()));
198            }
199            return (min==Double.MAX_VALUE?0.0:min);
200        }
201    
202        /** Minimal penalty of an offering */
203        public double getMinPenalty(Offering offering) {
204            double min = Double.MAX_VALUE;
205            for (Enumeration e=offering.getConfigs().elements();e.hasMoreElements();) {
206                Config config = (Config)e.nextElement();
207                min = Math.min(min, getMinPenalty(config));
208            }
209            return (min==Double.MAX_VALUE?0.0:min);
210        }
211    
212        /** Minimal penalty of a config */
213        public double getMinPenalty(Config config) {
214            double min = 0;
215            for (Enumeration e=config.getSubparts().elements();e.hasMoreElements();) {
216                Subpart subpart = (Subpart)e.nextElement();
217                min += getMinPenalty(subpart);
218            }
219            return min/config.getSubparts().size();
220        }
221        
222        /** Minimal penalty of a subpart */
223        public double getMinPenalty(Subpart subpart) {
224            double min = Double.MAX_VALUE;
225            for (Enumeration e=subpart.getSections().elements();e.hasMoreElements();) {
226                Section section = (Section)e.nextElement();
227                min = Math.min(min, getPenalty(section));
228            }
229            return (min==Double.MAX_VALUE?0.0:min);
230        }
231        
232        /** Maximal penalty of a course request */ 
233        public double getMaxPenalty(Request request) {
234            if (request instanceof CourseRequest)
235                return getMaxPenalty((CourseRequest)request);
236            else if (request instanceof FreeTimeRequest)
237                return getPenalty(((FreeTimeRequest)request).getTime());
238            return 0;
239        }
240    
241        /** Maximal penalty of a course request */ 
242        public double getMaxPenalty(CourseRequest request) {
243            double max = Double.MIN_VALUE;
244            for (Enumeration e=request.getCourses().elements();e.hasMoreElements();) {
245                Course course = (Course)e.nextElement();
246                max = Math.max(max, getMaxPenalty(course.getOffering()));
247            }
248            return (max==Double.MIN_VALUE?0.0:max);
249        }
250    
251        /** Maximal penalty of an offering */
252        public double getMaxPenalty(Offering offering) {
253            double max = Double.MIN_VALUE;
254            for (Enumeration e=offering.getConfigs().elements();e.hasMoreElements();) {
255                Config config = (Config)e.nextElement();
256                max = Math.max(max, getMaxPenalty(config));
257            }
258            return (max==Double.MIN_VALUE?0.0:max);
259        }
260    
261        /** Maximal penalty of a config */
262        public double getMaxPenalty(Config config) {
263            double max = 0;
264            for (Enumeration e=config.getSubparts().elements();e.hasMoreElements();) {
265                Subpart subpart = (Subpart)e.nextElement();
266                max += getMaxPenalty(subpart);
267            }
268            return max/config.getSubparts().size();
269        }
270        
271        /** Maximal penalty of a subpart */
272        public double getMaxPenalty(Subpart subpart) {
273            double max = Double.MIN_VALUE;
274            for (Enumeration e=subpart.getSections().elements();e.hasMoreElements();) {
275                Section section = (Section)e.nextElement();
276                max = Math.max(max, getPenalty(section));
277            }
278            return (max==Double.MIN_VALUE?0.0:max);
279        }
280        
281        /** Minimal and maximal available enrollment penalty of a request */ 
282        public double[] getMinMaxAvailableEnrollmentPenalty(Request request) {
283            if (request instanceof CourseRequest) {
284                return getMinMaxAvailableEnrollmentPenalty((CourseRequest)request);
285            } else {
286                double pen = getPenalty(((FreeTimeRequest)request).getTime());
287                return new double[] {pen,pen};
288            }
289        }
290    
291        /** Minimal and maximal available enrollment penalty of a request */
292        public double[] getMinMaxAvailableEnrollmentPenalty(CourseRequest request) {
293            Vector enrollments = request.getAvaiableEnrollments();
294            if (enrollments.isEmpty()) return new double[] {0,0};
295            double min = Double.MAX_VALUE, max = Double.MIN_VALUE;
296            for (Iterator i=enrollments.iterator();i.hasNext();) {
297                Enrollment enrollment = (Enrollment)i.next();
298                double penalty = getPenalty(enrollment);
299                min = Math.min(min, penalty);
300                max = Math.max(max, penalty);
301            }
302            return new double[] {min, max};
303        }
304    
305        /** Minimal and maximal available enrollment penalty of a request */ 
306        public double[] getMinMaxEnrollmentPenalty(Request request) {
307            if (request instanceof CourseRequest) {
308                return getMinMaxEnrollmentPenalty((CourseRequest)request);
309            } else {
310                double pen = getPenalty(((FreeTimeRequest)request).getTime());
311                return new double[] {pen,pen};
312            }
313        }
314    
315        /** Minimal and maximal available enrollment penalty of a request */
316        public double[] getMinMaxEnrollmentPenalty(CourseRequest request) {
317            Vector enrollments = request.values();
318            if (enrollments.isEmpty()) return new double[] {0,0};
319            double min = Double.MAX_VALUE, max = Double.MIN_VALUE;
320            for (Enumeration e=enrollments.elements();e.hasMoreElements();) {
321                Enrollment enrollment = (Enrollment)e.nextElement();
322                double penalty = getPenalty(enrollment);
323                min = Math.min(min, penalty);
324                max = Math.max(max, penalty);
325            }
326            return new double[] {min, max};
327        }
328    
329        /**
330         * Set the computed penalties to all sections of all requests of the given student
331         */
332        public static void setPenalties(Student student, int distributionType) {
333            if (sDebug) sLog.debug("Setting penalties for "+student);
334            StudentPreferencePenalties penalties = new StudentPreferencePenalties(distributionType);
335            for (Enumeration e=student.getRequests().elements();e.hasMoreElements();) {
336                Request request = (Request)e.nextElement();
337                if (!(request instanceof CourseRequest)) continue;
338                CourseRequest courseRequest = (CourseRequest)request;
339                if (sDebug) sLog.debug("-- "+courseRequest);
340                for (Enumeration f=courseRequest.getCourses().elements();f.hasMoreElements();) {
341                    Course course = (Course)f.nextElement();
342                    if (sDebug) sLog.debug("  -- "+course.getName());
343                    for (Enumeration g=course.getOffering().getConfigs().elements();g.hasMoreElements();) {
344                        Config config = (Config)g.nextElement();
345                        if (sDebug) sLog.debug("    -- "+config.getName());
346                        for (Enumeration h=config.getSubparts().elements();h.hasMoreElements();) {
347                            Subpart subpart = (Subpart)h.nextElement();
348                            if (sDebug) sLog.debug("      -- "+subpart.getName());
349                            for (Enumeration i=subpart.getSections().elements();i.hasMoreElements();) {
350                                Section section = (Section)i.nextElement();
351                                section.setPenalty(penalties.getPenalty(section));
352                                if (sDebug) sLog.debug("        -- "+section);
353                            }
354                        }
355                    }
356                }
357                courseRequest.clearCache();
358            }
359        }
360        
361    }