001    package ttsolver;
002    
003    import ifs.model.*;
004    import ifs.util.*;
005    
006    import java.util.*;
007    
008    import ttsolver.constraint.*;
009    import ttsolver.model.*;
010    
011    /**
012     * Timetable model.
013     * 
014     * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomáš Müller</a>
015     * @version 1.0
016     */
017    
018    public class TimetableModel extends Model {
019        private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(TimetableModel.class);
020        private static java.text.DecimalFormat sDoubleFormat = new java.text.DecimalFormat("0.00",new java.text.DecimalFormatSymbols(Locale.US));
021        private static java.text.SimpleDateFormat sDateFormat = new java.text.SimpleDateFormat("dd-MMM-yy_HHmmss",java.util.Locale.US);
022        private long iGlobalRoomPreference = 0;
023        private double iGlobalTimePreference = 0;
024        private long iBestRoomPreference = 0;
025        private long iBestInstructorDistancePreference = 0;
026        private double iBestTimePreference = 0;
027        private int iBestDepartmentSpreadPenalty = 0;
028        private Counter iGlobalGroupConstraintPreference = new Counter();
029        private Counter iViolatedStudentConflicts = new Counter();
030        private FastVector iInstructorConstraints = new FastVector();
031        private FastVector iJenrlConstraints = new FastVector();
032        private FastVector iRoomConstraints = new FastVector();
033        private FastVector iDepartmentSpreadConstraints = new FastVector();
034        private FastVector iGroupConstraints = new FastVector();
035        private FastVector iIgnoredClasses  = new FastVector();
036        private boolean iSwitchStudents = false;
037        private DataProperties iProperties = null;
038        
039        /** For MPP generator */
040        public Vector ignoredClasses() { return iIgnoredClasses; }
041        
042        public TimetableModel(DataProperties properties) {
043            super();
044            iSwitchStudents            = properties.getPropertyBoolean("General.SwitchStudents",true);
045            iProperties = properties;
046            InstructorConstraint.sNoPreferenceLimit = iProperties.getPropertyDouble("General.Distance.NoPreferenceLimit",0.0);
047            InstructorConstraint.sDiscouragedLimit = iProperties.getPropertyDouble("General.Distance.DiscouragedLimit",5.0);
048            InstructorConstraint.sProhibitedLimit = iProperties.getPropertyDouble("General.Distance.ProhibitedLimit",20.0);
049        }
050        
051        public DataProperties getProperties() { return iProperties; }
052    
053        /** Check validity of JENRL constraints from student enrollments */
054        public void checkJenrl() {
055            try {
056                System.err.println("Checking jenrl ...");
057                for (Enumeration i1=variables().elements();i1.hasMoreElements();) {
058                    Lecture l1 = (Lecture)i1.nextElement();
059                    System.err.println("Checking "+l1.getName()+" ...");
060                    for (Enumeration i2=variables().elements();i2.hasMoreElements();) {
061                        Lecture l2 = (Lecture)i2.nextElement();
062                        if (l1.getId()<l2.getId()) {
063                            int jenrl = 0;
064                            Vector jenrlStudents = new FastVector();
065                            for (Enumeration i3=l1.students().elements(); i3.hasMoreElements(); ) {
066                                String student = (String)i3.nextElement();
067                                if (l2.students().contains(student)) {
068                                    jenrl++;
069                                    jenrlStudents.addElement(student);
070                                }
071                            }
072                            boolean found = false;
073                            for (Enumeration i3=iJenrlConstraints.elements();i3.hasMoreElements(); ) {
074                                JenrlConstraint j = (JenrlConstraint)i3.nextElement();
075                                Lecture a=(Lecture)j.first();
076                                Lecture b=(Lecture)j.second();
077                                if ((a.equals(l1) && b.equals(l2)) || (a.equals(l2) && b.equals(l1))) {
078                                    found = true;
079                                    if (j.getJenrl()!=jenrl) {
080                                        sLogger.error("ERROR: Wrong jenrl between "+l1.getName()+" and "+l2.getName()+" (constraint="+j.getJenrl()+" != computed="+jenrl+").");
081                                        sLogger.error("       "+l1.getName()+" has students: "+l1.students());
082                                        sLogger.error("       "+l2.getName()+" has students: "+l2.students());
083                                        sLogger.error("       intersection: "+jenrlStudents);
084                                    }
085                                }
086                            }
087                            if (!found && jenrl>0) {
088                                System.err.println("ERROR: Missing jenrl between "+l1.getName()+" and "+l2.getName()+" (computed="+jenrl+").");
089                                sLogger.error("ERROR: Missing jenrl between "+l1.getName()+" and "+l2.getName()+" (computed="+jenrl+").");
090                                sLogger.error("       "+l1.getName()+" has students: "+l1.students());
091                                sLogger.error("       "+l2.getName()+" has students: "+l2.students());
092                                sLogger.error("       intersection: "+jenrlStudents);
093                            }
094                        }
095                    }
096                }
097                System.err.println("Check done.");
098            } catch (Exception e) {
099                            edu.purdue.smas.timetable.util.Debug.error(e);
100                e.printStackTrace();
101            }
102        }
103        
104        /** Overall room preference */
105        public long getGlobalRoomPreference() { return iGlobalRoomPreference; }
106        /** Overall time preference */
107        public double getGlobalTimePreference() { return iGlobalTimePreference; }
108        /** Number of student conflicts */
109        public long getViolatedStudentConflicts() { return iViolatedStudentConflicts.get(); }
110        /** Number of student conflicts */
111        public long countViolatedStudentConflicts() {
112            long studentConflicts = 0;
113            for (Enumeration it1=iJenrlConstraints.elements();it1.hasMoreElements();) {
114                JenrlConstraint jenrl = (JenrlConstraint)it1.nextElement();
115                Lecture a = (Lecture)jenrl.first();
116                Lecture b = (Lecture)jenrl.second();
117                if (a.getAssignment()!=null && b.getAssignment()!=null && JenrlConstraint.isInConflict((Placement)a.getAssignment(),(Placement)b.getAssignment()))
118                    studentConflicts+=jenrl.getJenrl();
119            }
120            return studentConflicts;
121        }
122        /** Number of student conflicts */
123        public Counter getViolatedStudentConflictsCounter() { return iViolatedStudentConflicts; }
124        /** Overall group constraint preference */
125        public long getGlobalGroupConstraintPreference() { return iGlobalGroupConstraintPreference.get(); }
126        /** Overall group constraint preference */
127        public Counter getGlobalGroupConstraintPreferenceCounter() { return iGlobalGroupConstraintPreference; }
128        /** Overall room preference of the best solution ever found */
129        public long getBestRoomPreference() { return iBestRoomPreference; }
130        /** Overall time preference of the best solution ever found */
131        public double getBestTimePreference() { return iBestTimePreference; }
132        /** Sets overall room preference of the best solution ever found */
133        public void setBestRoomPreference(long bestRoomPreference) { iBestRoomPreference = bestRoomPreference; }
134        /** Sets overall time preference of the best solution ever found */
135        public void setBestTimePreference(double bestTimePreference) { iBestTimePreference = bestTimePreference; }
136        /** Overall instructor distance (back-to-back) preference */
137        public long getInstructorDistancePreference() {
138            long pref = 0;
139            for (Enumeration it1=iInstructorConstraints.elements();it1.hasMoreElements();) {
140                InstructorConstraint constraint = (InstructorConstraint)it1.nextElement();
141                pref+=constraint.getPreference();
142            }
143            return pref;
144        }
145        /** The worst instructor distance (back-to-back) preference */
146        public long getInstructorWorstDistancePreference() {
147            long pref = 0;
148            for (Enumeration it1=iInstructorConstraints.elements();it1.hasMoreElements();) {
149                InstructorConstraint constraint = (InstructorConstraint)it1.nextElement();
150                pref+=constraint.getWorstPreference();
151            }
152            return pref;
153        }
154        /** Overall number of useless time slots */
155        public long getUselessSlots() {
156            long uselessSlots = 0;
157            for (Enumeration it1=iRoomConstraints.elements();it1.hasMoreElements();) {
158                RoomConstraint constraint = (RoomConstraint)it1.nextElement();
159                uselessSlots+=((RoomConstraint)constraint).countUselessSlots();
160            }
161            return uselessSlots;
162        }
163        /** Overall number of student conflicts caused by distancies (back-to-back classes are too far)*/
164        public long getStudentDistanceConflicts() {
165            long nrConflicts = 0;
166            for (Enumeration it1=iJenrlConstraints.elements();it1.hasMoreElements();) {
167                JenrlConstraint jenrl = (JenrlConstraint)it1.nextElement();
168                if (jenrl.isInConflict() && 
169                        !((Placement)jenrl.first().getAssignment()).getTimeLocation().hasIntersection(((Placement)jenrl.second().getAssignment()).getTimeLocation()))
170                        nrConflicts += jenrl.getJenrl();
171            }
172            return nrConflicts;
173        }
174        /** Overall hard student conflicts (student conflict between single section classes) */ 
175        public long getHardStudentConflicts() {
176            if (!iSwitchStudents) return 0;
177            long hardStudentConflicts = 0;
178            for (Enumeration it1=iJenrlConstraints.elements();it1.hasMoreElements();) {
179                JenrlConstraint jenrl = (JenrlConstraint)it1.nextElement();
180                if (jenrl.isInConflict()) {
181                    Lecture l1 = (Lecture)jenrl.first();
182                    Lecture l2 = (Lecture)jenrl.second();
183                    if (l1.sameLectures()!=null && l1.sameLectures().size()==1 &&
184                    l2.sameLectures()!=null && l2.sameLectures().size()==1)
185                        hardStudentConflicts+=jenrl.getJenrl();
186                }
187            }
188            return hardStudentConflicts;
189        }
190        
191        /** When a value is assigned to a variable -- update gloval preferences */
192        public void afterAssigned(long iteration, Value value) {
193            super.afterAssigned(iteration, value);
194            if (value==null) return;
195            Placement placement = (Placement)value;
196            iGlobalRoomPreference += placement.getRoomLocation().getPreference();
197            iGlobalTimePreference += placement.getTimeLocation().getNormalizedPreference();
198        }
199        /** When a value is unassigned from a variable -- update gloval preferences */
200        public void afterUnassigned(long iteration, Value value) {
201            super.afterUnassigned(iteration, value);
202            if (value==null) return;
203            Placement placement = (Placement)value;
204            iGlobalRoomPreference -= placement.getRoomLocation().getPreference();
205            iGlobalTimePreference -= placement.getTimeLocation().getNormalizedPreference();
206        }
207    
208        /** Student final sectioning (switching students between sections of the same class in order to minimize overall number of student conflicts) */
209        public void switchStudents() {
210            if (iSwitchStudents) {
211                long before;
212                Progress.getInstance().save();
213                Collection variables = variables();
214                do {
215                    //sLogger.debug("Shifting students ...");
216                    Progress.getInstance().setPhase("Shifting students ...",variables().size());
217                    before = getViolatedStudentConflictsCounter().get();
218                    HashSet lecturesToRecompute = new HashSet(variables.size());
219                    for (Iterator i=variables.iterator();i.hasNext();) {
220                        Lecture lecture = (Lecture)i.next();
221                        //sLogger.debug("Shifting students for "+lecture);
222                        Set lects = lecture.swapStudents();
223                        //sLogger.debug("Lectures to recompute: "+lects);
224                        if (lects!=null) lecturesToRecompute.addAll(lects);
225                        Progress.getInstance().incProgress();
226                    }
227                    //sLogger.debug("Shifting done, "+getViolatedStudentConflictsCounter().get()+" conflicts.");
228                    variables = lecturesToRecompute;
229                } while (!variables.isEmpty() && before>getViolatedStudentConflictsCounter().get());
230                Progress.getInstance().restore();
231            }
232        }
233    
234        public String toString() {
235            return "TimetableModel{"+
236            "\n  super="+super.toString()+
237            "\n  studentConflicts="+iViolatedStudentConflicts.get()+
238    //        "\n  studentConflicts(violated room distance)="+iViolatedRoomDistanceStudentConflicts.get()+
239    //        "\n  studentPreferences="+iRoomDistanceStudentPreference.get()+
240            "\n  roomPreferences="+iGlobalRoomPreference+"/"+iBestRoomPreference+
241            "\n  timePreferences="+iGlobalTimePreference+"/"+iBestTimePreference+
242            "\n  groupConstraintPreferences="+iGlobalGroupConstraintPreference.get()+
243            "\n}";
244        }
245        
246        /** Overall number of too big rooms (rooms with more than 3/2 seats than needed) */
247        public int countTooBigRooms() {
248            int tooBigRooms=0;
249            for (Enumeration it1=assignedVariables().elements();it1.hasMoreElements();) {
250                Lecture lecture = (Lecture)it1.nextElement();
251                if (lecture.getAssignment()==null) continue;
252                Placement placement = (Placement)lecture.getAssignment();
253                long roomSize = placement.getRoomLocation().getRoomSize();
254                long students = lecture.countStudents();
255                if (roomSize>TimetableModel.getMaxCapacity(students)) tooBigRooms++;
256            }
257            return tooBigRooms;
258        }
259    
260        /** Overall departmental spread penalty */
261        public int getDepartmentSpreadPenalty() {
262            if (iDepartmentSpreadConstraints.isEmpty()) return 0;
263            int penalty = 0;
264            for (Enumeration e=iDepartmentSpreadConstraints.elements();e.hasMoreElements();) {
265                DepartmentSpreadConstraint c = (DepartmentSpreadConstraint)e.nextElement();
266                penalty += ((DepartmentSpreadConstraint)c).getPenalty();
267            }
268            return penalty;
269        }
270        
271        /** Global info */
272        public java.util.Hashtable getInfo() {
273            Hashtable ret = super.getInfo();
274            ret.put("Memory usage", edu.purdue.smas.timetable.util.Debug.getMem());
275            ret.put("Room preferences", iGlobalRoomPreference+" / "+iBestRoomPreference);
276            ret.put("Time preferences", sDoubleFormat.format(iGlobalTimePreference)+" / "+sDoubleFormat.format(iBestTimePreference));
277            ret.put("Group constraint preferences", new Long(iGlobalGroupConstraintPreference.get()));
278            if (getProperties().getPropertyBoolean("General.UseDistanceConstraints",false)) {
279                ret.put("Distance student conflicts", new Long(getStudentDistanceConflicts()));
280                ret.put("Distance instructor preferences", getInstructorDistancePreference()+" / "+getInstructorWorstDistancePreference());
281            }
282            if (getProperties().getPropertyBoolean("General.UseDepartmentSpreadConstraints", false)) {
283                ret.put("Department balancing penalty", new Integer(getDepartmentSpreadPenalty()));
284            }
285            ret.put("Student conflicts", new Long(getViolatedStudentConflicts()));
286            ret.put("Too big rooms", new Integer(countTooBigRooms()));
287            ret.put("Hard student conflicts", new Long(getHardStudentConflicts()));
288            ret.put("Useless half-hours", new Long(getUselessSlots()));
289            return ret;
290        }
291        
292        private int iBestTooBigRooms;
293        private long iBestUselessSlots;
294        private double iBestGlobalTimePreference;
295        private long iBestGlobalRoomPreference;
296        private long iBestGlobalGroupConstraintPreference;
297        private long iBestViolatedStudentConflicts;
298        private long iBestHardStudentConflicts;
299    
300        /** Overall number of too big rooms of the best solution ever found */
301        public int bestTooBigRooms() { return iBestTooBigRooms; }
302        /** Overall number of useless slots of the best solution ever found */
303        public long bestUselessSlots() { return iBestUselessSlots;}
304        /** Overall time preference of the best solution ever found */
305        public double bestGlobalTimePreference() { return iBestGlobalTimePreference;}
306        /** Overall room preference of the best solution ever found */
307        public long bestGlobalRoomPreference() { return iBestGlobalRoomPreference;}
308        /** Overall group constraint preference of the best solution ever found */
309        public long bestGlobalGroupConstraintPreference() { return iBestGlobalGroupConstraintPreference;}
310        /** Overall number of student conflicts of the best solution ever found */
311        public long bestViolatedStudentConflicts() { return iBestViolatedStudentConflicts;}
312        /** Overall number of student conflicts between single section classes of the best solution ever found */
313        public long bestHardStudentConflicts() { return iBestHardStudentConflicts;}
314        /** Overall instructor distance preference of the best solution ever found */
315        public long bestInstructorDistancePreference() { return iBestInstructorDistancePreference; }
316        /** Overall departmental spread penalty of the best solution ever found */
317        public int bestDepartmentSpreadPenalty() { return iBestDepartmentSpreadPenalty; }
318        
319        public void saveBest() {
320            super.saveBest();
321            iBestTooBigRooms = countTooBigRooms();
322            iBestUselessSlots = getUselessSlots();
323            iBestGlobalTimePreference = getGlobalTimePreference();
324            iBestGlobalRoomPreference = getGlobalRoomPreference();
325            iBestGlobalGroupConstraintPreference = getGlobalGroupConstraintPreference();
326            iBestViolatedStudentConflicts = getViolatedStudentConflicts();
327            iBestHardStudentConflicts = getHardStudentConflicts();
328            iBestInstructorDistancePreference = getInstructorDistancePreference();
329            iBestDepartmentSpreadPenalty = getDepartmentSpreadPenalty();
330        }
331    
332        public void addConstraint(Constraint constraint) {
333            super.addConstraint(constraint);
334            if (constraint instanceof InstructorConstraint) {
335                iInstructorConstraints.addElement(constraint);
336            } else if (constraint instanceof JenrlConstraint) {
337                iJenrlConstraints.addElement(constraint);
338            } else if (constraint instanceof RoomConstraint) {
339                iRoomConstraints.addElement(constraint);
340            } else if (constraint instanceof DepartmentSpreadConstraint) {
341                iDepartmentSpreadConstraints.addElement(constraint);
342            } else if (constraint instanceof GroupConstraint) {
343                iGroupConstraints.addElement(constraint);
344            }
345        }
346        public void removeConstraint(Constraint constraint) {
347            super.removeConstraint(constraint);
348            if (constraint instanceof InstructorConstraint) {
349                iInstructorConstraints.removeElement(constraint);
350            } else if (constraint instanceof JenrlConstraint) {
351                iJenrlConstraints.removeElement(constraint);
352            } else if (constraint instanceof RoomConstraint) {
353                iRoomConstraints.removeElement(constraint);
354            } else if (constraint instanceof DepartmentSpreadConstraint) {
355                iDepartmentSpreadConstraints.removeElement(constraint);
356            } else if (constraint instanceof GroupConstraint) {
357                iGroupConstraints.removeElement(constraint);
358            }
359        }
360    
361        /** The list of all instructor constraints */
362        public Vector getInstructorConstraints() { return iInstructorConstraints; }
363        /** The list of all group constraints */
364        public Vector getGroupConstraints() { return iGroupConstraints; }
365        /** The list of all jenrl constraints */
366        public Vector getJenrlConstraints() { return iJenrlConstraints; }
367        /** The list of all room constraints */
368        public Vector getRoomConstraints() { return iRoomConstraints; }
369        /** The list of all departmental spread constraints */
370        public Vector getDepartmentSpreadConstraints() { return iDepartmentSpreadConstraints;  }
371        /** Max capacity for too big rooms (3/2 of the number of students) */
372        public static long getMaxCapacity(long nrStudents) {
373            return (3*nrStudents)/2;
374        }
375    }