001    package net.sf.cpsolver.coursett.heuristics;
002    
003    import java.util.Enumeration;
004    import java.util.HashSet;
005    import java.util.Iterator;
006    import java.util.Vector;
007    
008    import net.sf.cpsolver.coursett.constraint.DepartmentSpreadConstraint;
009    import net.sf.cpsolver.coursett.constraint.GroupConstraint;
010    import net.sf.cpsolver.coursett.constraint.InstructorConstraint;
011    import net.sf.cpsolver.coursett.constraint.JenrlConstraint;
012    import net.sf.cpsolver.coursett.constraint.RoomConstraint;
013    import net.sf.cpsolver.coursett.constraint.SpreadConstraint;
014    import net.sf.cpsolver.coursett.model.Lecture;
015    import net.sf.cpsolver.coursett.model.Placement;
016    import net.sf.cpsolver.coursett.model.TimetableModel;
017    import net.sf.cpsolver.ifs.model.Constraint;
018    import net.sf.cpsolver.ifs.perturbations.PerturbationsCounter;
019    import net.sf.cpsolver.ifs.solution.Solution;
020    import net.sf.cpsolver.ifs.solution.SolutionComparator;
021    import net.sf.cpsolver.ifs.util.DataProperties;
022    
023    /**
024     * Timetable (solution) comparator.
025     * <br><br>
026     * The quality of a solution is expressed as a weighted sum combining soft time and classroom preferences, satisfied soft 
027     * group constrains and the total number of student conflicts. This allows us to express the importance of different 
028     * types of soft constraints.
029     * <br><br>
030     * The solution comparator prefers a more complete solution (with a smaller number of unassigned variables) and a solution 
031     * with a smaller number of perturbations among solutions with the same number of unassigned variables. If both solutions 
032     * have the same number of unassigned variables and perturbations, the solution of better quality is selected. 
033     * <br><br>
034     * Parameters:
035     * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
036     * <tr><td>Comparator.HardStudentConflictWeight</td><td>{@link Double}</td><td>Weight of hard student conflict (conflict between single-section classes)</td></tr>
037     * <tr><td>Comparator.StudentConflictWeight</td><td>{@link Double}</td><td>Weight of student conflict</td></tr>
038     * <tr><td>Comparator.TimePreferenceWeight</td><td>{@link Double}</td><td>Time preferences weight</td></tr>
039     * <tr><td>Comparator.ContrPreferenceWeight</td><td>{@link Double}</td><td>Group constraint preferences weight</td></tr>
040     * <tr><td>Comparator.RoomPreferenceWeight</td><td>{@link Double}</td><td>Room preferences weight</td></tr>
041     * <tr><td>Comparator.UselessSlotWeight</td><td>{@link Double}</td><td>Useless slots weight</td></tr>
042     * <tr><td>Comparator.TooBigRoomWeight</td><td>{@link Double}</td><td>Too big room weight</td></tr>
043     * <tr><td>Comparator.DistanceInstructorPreferenceWeight</td><td>{@link Double}</td><td>Distance (of the rooms of the back-to-back classes) based instructor preferences weight</td></tr>
044     * <tr><td>Comparator.PerturbationPenaltyWeight</td><td>{@link Double}</td><td>Perturbation penalty (see {@link UniversalPerturbationsCounter})</td></tr>
045     * <tr><td>Comparator.DeptSpreadPenaltyWeight</td><td>{@link Double}</td><td>Department balancing penalty (see {@link net.sf.cpsolver.coursett.constraint.DepartmentSpreadConstraint})</td></tr>
046     * </table>
047     *
048     *
049     * @version
050     * CourseTT 1.1 (University Course Timetabling)<br>
051     * Copyright (C) 2006 Tomáš Müller<br>
052     * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
053     * Lazenska 391, 76314 Zlin, Czech Republic<br>
054     * <br>
055     * This library is free software; you can redistribute it and/or
056     * modify it under the terms of the GNU Lesser General Public
057     * License as published by the Free Software Foundation; either
058     * version 2.1 of the License, or (at your option) any later version.
059     * <br><br>
060     * This library is distributed in the hope that it will be useful,
061     * but WITHOUT ANY WARRANTY; without even the implied warranty of
062     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
063     * Lesser General Public License for more details.
064     * <br><br>
065     * You should have received a copy of the GNU Lesser General Public
066     * License along with this library; if not, write to the Free Software
067     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
068     */
069    
070    public class TimetableComparator implements SolutionComparator {
071        protected static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(TimetableComparator.class);
072        
073        private double iEmptySingleSlotWeight;
074        public static final String USELESS_SLOT_WEIGHT="Comparator.UselessSlotWeight";
075        private double iTimePreferencesWeight;
076        public static final String TIME_PREFERENCE_WEIGHT="Comparator.TimePreferenceWeight";
077        private double iStudentConflictWeight;
078        public static final String STUDENT_CONFLICT_WEIGHT="Comparator.StudentConflictWeight";
079        private double iRoomPreferencesWeight;
080        public static final String ROOM_PREFERENCE_WEIGHT="Comparator.RoomPreferenceWeight";
081        private double iConstrPreferencesWeight;
082        public static final String CONSTR_PREFERENCE_WEIGHT="Comparator.ContrPreferenceWeight";
083        private double iHardStudentConflictWeight;
084        public static final String HARD_STUDENT_CONFLICT_WEIGHT="Comparator.HardStudentConflictWeight";
085        private double iTooBigRoomWeight;
086        public static final String TOO_BIG_ROOM_WEIGHT = "Comparator.TooBigRoomWeight";
087        private boolean iCompareMpp;
088        public static final String DISTANCE_INSTRUCTOR_PREFERENCE_WEIGHT = "Comparator.DistanceInstructorPreferenceWeight";
089        private double iDistanceInstructorPreferenceWeight;
090        public static final String PERTURBATION_PENALTY_WEIGHT = "Comparator.PerturbationPenaltyWeight";
091        private double iPerturbationPenaltyWeight;
092        public static final String DEPT_SPREAD_PENALTY_WEIGHT = "Comparator.DeptSpreadPenaltyWeight";
093        private double iDeptSpreadPenaltyWeight;
094        public static final String SPREAD_PENALTY_WEIGHT = "Comparator.SpreadPenaltyWeight";
095        private double iSpreadPenaltyWeight;
096        public static final String COMMITED_STUDENT_CONFLICT_WEIGHT="Comparator.CommitedStudentConflictWeight";
097        private double iCommitedStudentConflictWeight;
098        
099        public TimetableComparator(DataProperties properties) {
100            iEmptySingleSlotWeight = properties.getPropertyDouble(USELESS_SLOT_WEIGHT,0.0);
101            iTimePreferencesWeight = properties.getPropertyDouble(TIME_PREFERENCE_WEIGHT,1.0);
102            iRoomPreferencesWeight = properties.getPropertyDouble(ROOM_PREFERENCE_WEIGHT,0.1);
103            iConstrPreferencesWeight = properties.getPropertyDouble(CONSTR_PREFERENCE_WEIGHT,1.0);
104            if (properties.getPropertyBoolean("General.SwitchStudents",true)) {
105                iHardStudentConflictWeight = properties.getPropertyDouble(HARD_STUDENT_CONFLICT_WEIGHT,1.0);
106                iStudentConflictWeight = properties.getPropertyDouble(STUDENT_CONFLICT_WEIGHT,0.2);
107            } else {
108                iHardStudentConflictWeight = 0.0;
109                iStudentConflictWeight = properties.getPropertyDouble(STUDENT_CONFLICT_WEIGHT,1.0);
110            }
111            iDistanceInstructorPreferenceWeight = properties.getPropertyDouble(DISTANCE_INSTRUCTOR_PREFERENCE_WEIGHT, 1.0);
112            iTooBigRoomWeight = properties.getPropertyDouble(TOO_BIG_ROOM_WEIGHT,0.0);
113            iCompareMpp = properties.getPropertyBoolean("General.MPP", false);
114            iPerturbationPenaltyWeight = properties.getPropertyDouble(PERTURBATION_PENALTY_WEIGHT,1.0);
115            iDeptSpreadPenaltyWeight = properties.getPropertyDouble(DEPT_SPREAD_PENALTY_WEIGHT,1.0);
116            iSpreadPenaltyWeight = properties.getPropertyDouble(SPREAD_PENALTY_WEIGHT,1.0);
117            iCommitedStudentConflictWeight = properties.getPropertyDouble(COMMITED_STUDENT_CONFLICT_WEIGHT,1.0);
118        }
119    
120        public boolean isBetterThanBestSolution(Solution currentSolution) {
121            if (currentSolution.getBestInfo()==null) return true;
122            TimetableModel tm = (TimetableModel)currentSolution.getModel();
123            int unassigned = tm.unassignedVariables().size();
124            if (tm.getBestUnassignedVariables()!=unassigned)
125                return tm.getBestUnassignedVariables()>unassigned;
126    
127            return (currentValue(currentSolution)<bestValue(currentSolution));
128        }
129        
130        public double currentValue(Solution currentSolution) {
131            return currentValue((TimetableModel)currentSolution.getModel(),currentSolution.getPerturbationsCounter());
132        }
133        
134        public double currentValue(TimetableModel tm, PerturbationsCounter cnt) {
135            int unassigned = tm.unassignedVariables().size();
136    
137            int tooBigCurr=0;
138            if (iTooBigRoomWeight!=0.0) tooBigCurr = tm.countTooBigRooms();
139            long uselessSlotsCur=0;
140            if (iEmptySingleSlotWeight!=0.0) 
141                uselessSlotsCur = tm.getUselessSlots();
142            long hardSCCurr = 0;
143            if (iHardStudentConflictWeight!=0.0) 
144                hardSCCurr = tm.getHardStudentConflicts();
145            double pertCurr=0.0;
146            if (iCompareMpp && iPerturbationPenaltyWeight!=0.0) 
147                pertCurr = cnt.getPerturbationPenalty(tm);
148            double deptSpread=0.0;
149            if (iDeptSpreadPenaltyWeight!=0.0)
150                deptSpread = tm.getDepartmentSpreadPenalty();
151            double spread=0.0;
152            if (iSpreadPenaltyWeight!=0.0)
153                    spread = tm.getSpreadPenalty();
154            int commitedStudentConflicts = 0;
155            if (iCommitedStudentConflictWeight!=0.0)
156                    commitedStudentConflicts = tm.getCommitedStudentConflicts();
157            
158            double prefCurr = (iEmptySingleSlotWeight*uselessSlotsCur)+
159                           (iTimePreferencesWeight*tm.getGlobalTimePreference())+
160                           (iRoomPreferencesWeight*tm.getGlobalRoomPreference())+
161                           (iConstrPreferencesWeight*tm.getGlobalGroupConstraintPreference())+
162                           (iStudentConflictWeight*tm.getViolatedStudentConflicts())+
163                           (iHardStudentConflictWeight*hardSCCurr)+
164                           (iTooBigRoomWeight*tooBigCurr)+
165                           (iDistanceInstructorPreferenceWeight*tm.getInstructorDistancePreference())+
166                           (iPerturbationPenaltyWeight*pertCurr)+
167                           (iDeptSpreadPenaltyWeight*deptSpread)+
168                           (iSpreadPenaltyWeight*spread)+
169                           (iCommitedStudentConflictWeight*commitedStudentConflicts)
170                           ;
171            return prefCurr;
172        }
173        
174        public double currentValue(TimetableModel tm, PerturbationsCounter cnt, Vector variables) {
175            int unassigned = 0;
176            
177            int roomPref = 0;
178            double timePref = 0;
179            double grPref = 0;
180            long allSC = 0, comSC = 0, hardSC = 0;
181            int instPref = 0;
182            int spreadPen = 0, deptSpreadPen = 0;
183            int tooBigRooms = 0;
184            int uselessSlots = 0;
185            double pertCurr = 0;
186            
187            if (iCompareMpp && iPerturbationPenaltyWeight!=0.0) 
188                pertCurr = cnt.getPerturbationPenalty(tm, variables);
189            
190            HashSet used = new HashSet();
191            
192            for (Enumeration e=variables.elements();e.hasMoreElements();) {
193                    Lecture lecture = (Lecture)e.nextElement();
194                    Placement placement = (Placement)lecture.getAssignment();
195                    
196                    if (placement!=null) {
197                            roomPref += placement.getRoomPreference();
198                            timePref += placement.getTimeLocation().getNormalizedPreference();
199                            comSC += lecture.getCommitedConflicts(placement);
200                            tooBigRooms += placement.getTooBigRoomPreference();
201                    } else {
202                            unassigned ++;
203                    }
204                    
205                    for (Enumeration f=lecture.constraints().elements();f.hasMoreElements();) {
206                            Constraint c = (Constraint)f.nextElement();
207                            if (!used.add(c)) continue;
208                            
209                            if (c instanceof InstructorConstraint) {
210                                    InstructorConstraint ic = (InstructorConstraint)c;
211                                    instPref += ic.getPreference();
212                            }
213                            
214                            if (c instanceof DepartmentSpreadConstraint) {
215                                    DepartmentSpreadConstraint dsc = (DepartmentSpreadConstraint)c;
216                                    deptSpreadPen += dsc.getPenalty();
217                            } else if (c instanceof SpreadConstraint) {
218                                    SpreadConstraint sc = (SpreadConstraint)c;
219                                    spreadPen += sc.getPenalty();
220                            }
221                            
222                            if (c instanceof GroupConstraint) {
223                                    GroupConstraint gc = (GroupConstraint)c;
224                            grPref += gc.getCurrentPreference();
225                            }
226                            
227                            if (c instanceof JenrlConstraint) {
228                                    JenrlConstraint jc = (JenrlConstraint)c;
229                                    if (!jc.isInConflict()) continue;
230                            Lecture l1 = (Lecture)jc.first();
231                            Lecture l2 = (Lecture)jc.second();
232                            allSC += jc.getJenrl();
233                            if (l1.areStudentConflictsHard(l2))
234                                    hardSC += jc.getJenrl();
235                            }
236                            
237                            if (c instanceof RoomConstraint) {
238                                    RoomConstraint rc = (RoomConstraint)c;
239                                    uselessSlots+=rc.countUselessSlots();
240                            }
241                    }
242            }
243    
244            double prefCurr = (iEmptySingleSlotWeight*uselessSlots)+
245                           (iTimePreferencesWeight*timePref)+
246                           (iRoomPreferencesWeight*roomPref)+
247                           (iConstrPreferencesWeight*grPref)+
248                           (iStudentConflictWeight*allSC)+
249                           (iHardStudentConflictWeight*hardSC)+
250                           (iTooBigRoomWeight*tooBigRooms)+
251                           (iDistanceInstructorPreferenceWeight*instPref)+
252                           (iPerturbationPenaltyWeight*pertCurr)+
253                           (iDeptSpreadPenaltyWeight*deptSpreadPen)+
254                           (iSpreadPenaltyWeight*spreadPen)+
255                           (iCommitedStudentConflictWeight*comSC)
256                           ;
257            return prefCurr;
258        }
259    
260        public double bestValue(Solution currentSolution) {
261            TimetableModel tm = (TimetableModel)currentSolution.getModel();
262            int unassigned = tm.unassignedVariables().size();
263    
264            int tooBigBest=0;
265            if (iTooBigRoomWeight!=0.0)
266                tooBigBest = tm.bestTooBigRooms();
267            long uselessSlotsBest=0;
268            if (iEmptySingleSlotWeight!=0.0)
269                uselessSlotsBest = tm.bestUselessSlots();
270            long hardSCBest = 0;
271            if (iHardStudentConflictWeight!=0.0)
272                hardSCBest = tm.bestHardStudentConflicts();
273            double pertBest=0.0;
274            if (iCompareMpp && iPerturbationPenaltyWeight!=0.0)
275                pertBest = currentSolution.getBestPerturbationsPenalty();
276            double deptSpreadBest=0.0;
277            if (iDeptSpreadPenaltyWeight!=0.0)
278                deptSpreadBest = tm.bestDepartmentSpreadPenalty();
279            double spread=0.0;
280            if (iSpreadPenaltyWeight!=0.0)
281                    spread = tm.bestSpreadPenalty();
282            int commitedStudentConflicts = 0;
283            if (iCommitedStudentConflictWeight!=0.0)
284                    commitedStudentConflicts = tm.bestCommitedStudentConflicts();
285            
286            double prefBest = (iEmptySingleSlotWeight*uselessSlotsBest)+
287                           (iTimePreferencesWeight*tm.bestGlobalTimePreference())+
288                           (iRoomPreferencesWeight*tm.bestGlobalRoomPreference())+
289                           (iConstrPreferencesWeight*tm.bestGlobalGroupConstraintPreference())+
290                           (iStudentConflictWeight*tm.bestViolatedStudentConflicts())+
291                           (iHardStudentConflictWeight*hardSCBest)+
292                           (iTooBigRoomWeight*tooBigBest)+
293                           (iDistanceInstructorPreferenceWeight*tm.bestInstructorDistancePreference())+
294                           (iPerturbationPenaltyWeight*pertBest)+
295                           (iDeptSpreadPenaltyWeight*deptSpreadBest)+
296                           (iSpreadPenaltyWeight*spread)+
297                           (iCommitedStudentConflictWeight*commitedStudentConflicts)
298                           ;
299            return prefBest;
300        }
301        
302        public double value(Placement placement, PerturbationsCounter cnt) {
303            Lecture lecture = (Lecture)placement.variable();
304            
305            int constrPreference = 0;
306            if (iConstrPreferencesWeight!=0.0) {
307                    for (Iterator i=lecture.groupConstraints().iterator();i.hasNext();) {
308                            GroupConstraint gc = (GroupConstraint)i.next();
309                            constrPreference += gc.getCurrentPreference(placement);
310                    }
311            }
312            
313            int distInstrPref = 0;
314            if (iDistanceInstructorPreferenceWeight!=0.0) {
315                    for (Enumeration e=lecture.constraints().elements();e.hasMoreElements();) {
316                            Constraint constraint = (Constraint)e.nextElement();
317                            if (constraint instanceof InstructorConstraint) {
318                                    distInstrPref += ((InstructorConstraint)constraint).getPreference(placement);
319                            }
320                    }
321            }
322            
323            return
324                    (iEmptySingleSlotWeight==0.0?0.0:iEmptySingleSlotWeight*placement.nrUselessHalfHours())+
325                    (iTimePreferencesWeight==0.0?0.0:iTimePreferencesWeight*placement.getTimeLocation().getNormalizedPreference())+
326                    (iRoomPreferencesWeight==0.0?0.0:iRoomPreferencesWeight*placement.sumRoomPreference())+
327                    (iConstrPreferencesWeight*constrPreference)+
328                    (iStudentConflictWeight==0.0?0.0:iStudentConflictWeight*lecture.countStudentConflicts(placement))+
329                    (iHardStudentConflictWeight==0.0?0.0:iHardStudentConflictWeight*lecture.countHardStudentConflicts(placement))+
330                    (iTooBigRoomWeight==0.0?0.0:iTooBigRoomWeight*placement.getTooBigRoomPreference())+
331                    (iDistanceInstructorPreferenceWeight*distInstrPref)+
332                    (iPerturbationPenaltyWeight==0.0 || cnt==null?0.0:iPerturbationPenaltyWeight*cnt.getPerturbationPenalty(lecture.getModel(),placement,new Vector(0)))+
333                    (iDeptSpreadPenaltyWeight==0.0 || lecture.getDeptSpreadConstraint()==null?0.0:iDeptSpreadPenaltyWeight*lecture.getDeptSpreadConstraint().getPenalty(placement))+
334                    (iSpreadPenaltyWeight==0.0?0.0:iSpreadPenaltyWeight*placement.getSpreadPenalty())+
335                    (iCommitedStudentConflictWeight==0.0?0.0:iCommitedStudentConflictWeight*lecture.getCommitedConflicts(placement))
336            ;
337        }
338    }