001    package ttsolver.heuristics;
002    
003    import ifs.extension.*;
004    import ifs.heuristics.*;
005    import ifs.model.*;
006    import ifs.solution.*;
007    import ifs.solver.*;
008    import ifs.util.*;
009    
010    import java.util.*;
011    
012    import ttsolver.constraint.*;
013    import ttsolver.model.*;
014    import ttsolver.*;
015    import edu.purdue.smas.timetable.serverfwk.ParameterDefinition;
016    
017    /**
018     * Lecture (variable) selection.
019     * <br><br>
020     * If there are one or more variables unassigned, the variable selection criterion picks one of them randomly. We have 
021     * tried several approaches using domain sizes, number of previous assignments, numbers of constraints in which the 
022     * variable participates, etc., but there was no significant improvement in this timetabling problem towards the random 
023     * selection of an unassigned variable. The reason is, that it is easy to go back when a wrong variable is picked - 
024     * such a variable is unassigned when there is a conflict with it in some of the subsequent iterations. 
025     * <br><br>
026     * When all variables are assigned, an evaluation is made for each variable according to the above described weights. The 
027     * variable with the worst evaluation is selected. This variable promises the best improvement in optimization.
028     * <br><br>
029     * Parameters (selection among unassigned lectures):
030     * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
031     * <tr><td>Lecture.RouletteWheelSelection</td><td>{@link Boolean}</td><td>Roulette wheel selection</td></tr>
032     * <tr><td>Lecture.RandomWalkProb</td><td>{@link Double}</td><td>Random walk probability</td></tr>
033     * <tr><td>Lecture.DomainSizeWeight</td><td>{@link Double}</td><td>Domain size weight</td></tr>
034     * <tr><td>Lecture.NrAssignmentsWeight</td><td>{@link Double}</td><td>Number of assignments weight</td></tr>
035     * <tr><td>Lecture.InitialAssignmentWeight</td><td>{@link Double}</td><td>Initial assignment weight</td></tr>
036     * <tr><td>Lecture.NrConstraintsWeight</td><td>{@link Double}</td><td>Number of constraint weight</td></tr>
037     * </table>
038     * <br>
039     * Parameters (selection among assigned lectures, when the solution is complete):
040     * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
041     * <tr><td>Lecture.HardStudentConflictWeight</td><td>{@link Double}</td><td>Hard student conflict weight</td></tr>
042     * <tr><td>Lecture.StudentConflictWeight</td><td>{@link Double}</td><td>Student conflict weight</td></tr>
043     * <tr><td>Lecture.TimePreferenceWeight</td><td>{@link Double}</td><td>Time preference weight</td></tr>
044     * <tr><td>Lecture.ContrPreferenceWeight</td><td>{@link Double}</td><td>Group constraint preference weight</td></tr>
045     * <tr><td>Lecture.RoomPreferenceWeight</td><td>{@link Double}</td><td>Room preference weight</td></tr>
046     * <tr><td>Lecture.UselessSlotWeight</td><td>{@link Double}</td><td>Useless slot weight</td></tr>
047     * <tr><td>Lecture.TooBigRoomWeight</td><td>{@link Double}</td><td>Too big room weight</td></tr>
048     * <tr><td>Lecture.DistanceInstructorPreferenceWeight</td><td>{@link Double}</td><td>Distance (of the rooms of the back-to-back classes) based instructor preferences weight</td></tr>
049     * <tr><td>Lecture.DeptSpreadPenaltyWeight</td><td>{@link Double}</td><td>Department balancing penalty (see {@link ttsolver.constraint.DepartmentSpreadConstraint})</td></tr>
050     * </table>
051     * <br>
052     * Parameters (selection among subset of lectures (faster)):
053     * <table border='1'><tr><th>Parameter</th><th>Type</th><th>Comment</th></tr>
054     * <tr><td>Lecture.SelectionSubSet</td><td>{@link Boolean}</td><td>Selection among subset of lectures (faster)</td></tr>
055     * <tr><td>Lecture.SelectionSubSetMinSize</td><td>{@link Double}</td><td>Minimal subset size</td></tr>
056     * <tr><td>Lecture.SelectionSubSetPart</td><td>{@link Double}</td><td>Subset size in percentage of all lectures available for selection</td></tr>
057     * </table>
058     * 
059     * @see PlacementSelection
060     * @author <a href="mailto:muller@ktiml.mff.cuni.cz">Tomáš Müller</a>
061     * @version 1.0
062     */
063    public class LectureSelection implements VariableSelection {
064        private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(LectureSelection.class);
065        private double iRandomWalkProb;
066        private double iDomainSizeWeight;
067        private double iGoodValuesWeight;
068        private double iNrAssignmentsWeight;
069        private double iConstraintsWeight;
070        private double iInitialAssignmentWeight;
071        private boolean iRouletteWheelSelection;
072        private boolean iUnassignWhenNotGood;
073        
074        private double iEmptySingleSlotWeight;
075        private double iTooBigRoomWeight;
076        private double iTimePreferencesWeight;
077        private double iStudentConflictWeight;
078        private double iRoomPreferencesWeight;
079        private double iConstrPreferencesWeight;
080        private double iHardStudentConflictWeight;
081        private double iDistanceInstructorPreferenceWeight;
082        private double iDeptSpreadPenaltyWeight;
083        
084        private boolean iSubSetSelection;
085        private double iSelectionSubSetPart;
086        private int iSelectionSubSetMinSize;
087        
088        private boolean iRW = false;
089        private boolean iMPP = false;
090        private boolean iSwitchStudents = false;
091        
092        private ConflictStatistics iStat = null;
093        private MacPropagation iProp = null;
094        private ViolatedInitials iViolatedInitials = null;
095    
096        public LectureSelection(DataProperties properties) {
097            iRouletteWheelSelection  = properties.getPropertyBoolean("Lecture.RouletteWheelSelection",true);
098            iUnassignWhenNotGood     = properties.getPropertyBoolean("Lecture.UnassignWhenNotGood",false);
099            iRW                      = properties.getPropertyBoolean("General.RandomWalk", true);
100            iRandomWalkProb          = (!iRW?0.0:properties.getPropertyDouble("Lecture.RandomWalkProb",1.00));
101            iGoodValuesWeight        = properties.getPropertyDouble("Lecture.GoodValueProb",1.0);
102            iDomainSizeWeight        = properties.getPropertyDouble("Lecture.DomainSizeWeight",30.0);
103            
104            iNrAssignmentsWeight     = properties.getPropertyDouble("Lecture.NrAssignmentsWeight",10.0);
105            iConstraintsWeight       = properties.getPropertyDouble("Lecture.NrConstraintsWeight",0.0);
106            iMPP                     = properties.getPropertyBoolean("General.MPP", false);
107            iInitialAssignmentWeight = (!iMPP?0.0:properties.getPropertyDouble("Lecture.InitialAssignmentWeight",20.0));
108    
109            iEmptySingleSlotWeight   = properties.getPropertyDouble("Lecture.UselessSlotWeight",properties.getPropertyDouble("Comparator.UselessSlotWeight",0.0));
110            iTooBigRoomWeight        = properties.getPropertyDouble("Lecture.TooBigRoomWeight",properties.getPropertyDouble("Comparator.TooBigRoomWeight",0.0));
111            iTimePreferencesWeight   = properties.getPropertyDouble("Lecture.TimePreferenceWeight",properties.getPropertyDouble("Comparator.TimePreferenceWeight",1.0));
112            iStudentConflictWeight   = properties.getPropertyDouble("Lecture.StudentConflictWeight",properties.getPropertyDouble("Comparator.StudentConflictWeight",0.2));
113            iRoomPreferencesWeight   = properties.getPropertyDouble("Lecture.RoomPreferenceWeight",properties.getPropertyDouble("Comparator.RoomPreferenceWeight",0.1));
114            iConstrPreferencesWeight = properties.getPropertyDouble("Lecture.ContrPreferenceWeight",properties.getPropertyDouble("Comparator.ContrPreferenceWeight",1.0));
115            
116            iSwitchStudents            = properties.getPropertyBoolean("General.SwitchStudents",true);
117            iHardStudentConflictWeight = (!iSwitchStudents?0.0:properties.getPropertyDouble("Lecture.HardStudentConflictWeight",properties.getPropertyDouble("Comparator.HardStudentConflictWeight",1.0)));
118            iDistanceInstructorPreferenceWeight = properties.getPropertyDouble("Lecture.DistanceInstructorPreferenceWeight",properties.getPropertyDouble("Comparator.DistanceInstructorPreferenceWeight",1.0));
119            iDeptSpreadPenaltyWeight = properties.getPropertyDouble("Lecture.DeptSpreadPenaltyWeight",properties.getPropertyDouble("Comparator.DeptSpreadPenaltyWeight",1.0));
120            
121            iSubSetSelection           = properties.getPropertyBoolean("Lecture.SelectionSubSet",true);
122            iSelectionSubSetMinSize    = properties.getPropertyInt("Lecture.SelectionSubSetMinSize",10);
123            iSelectionSubSetPart       = properties.getPropertyDouble("Lecture.SelectionSubSetPart", 0.2);
124        }
125        
126        public static Collection parameters() {
127            Vector ret = new FastVector();
128            
129            //ParameterDefinition.Dependency mppDep = new ParameterDefinition.Dependency("General.MPP","true");
130            //ParameterDefinition.Dependency rwDep = new ParameterDefinition.Dependency("General.RandomWalk","true");
131            //ParameterDefinition.Dependency propDep = new ParameterDefinition.Dependency("General.MAC","true");
132            //ParameterDefinition.Dependency swDep = new ParameterDefinition.Dependency("General.SwitchStudents","true");
133            //ParameterDefinition.Dependency deptSpreadDep = new ParameterDefinition.Dependency("General.UseDepartmentSpreadConstraints","false");
134            
135            //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.RouletteWheelSelection", "Roulette-wheel selection", ParameterDefinition.TYPE_BOOLEAN, "true"));
136            //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.UnassignWhenNotGood", "Unassign when no good", ParameterDefinition.TYPE_BOOLEAN, "false").addDependency(propDep));
137            //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.RandomWalkProb", "Random walk probability", ParameterDefinition.TYPE_DOUBLE, "0.02").addDependency(rwDep));
138            //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.GoodValueProb", "Good value selection", ParameterDefinition.TYPE_DOUBLE, "1.00").addDependency(propDep));
139    
140            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights","Lecture.DomainSizeWeight", "Domain size", ParameterDefinition.TYPE_DOUBLE, "30.0"));
141            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights","Lecture.NrAssignmentsWeight", "Number of assignments", ParameterDefinition.TYPE_DOUBLE, "10.0"));
142            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights","Lecture.NrConstraintsWeight", "Number of constraints", ParameterDefinition.TYPE_DOUBLE, "0.0"));
143            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights","Lecture.InitialAssignmentWeight", "Initial assignment", ParameterDefinition.TYPE_DOUBLE, "20.0").addDependency(mppDep));
144            
145            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.UselessSlotWeight", "Useless slots", ParameterDefinition.TYPE_DOUBLE, "0.00"));
146            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.TimePreferenceWeight", "Time preferences", ParameterDefinition.TYPE_DOUBLE, "1.00"));
147            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.StudentConflictWeight", "Student conflicts", ParameterDefinition.TYPE_DOUBLE, "0.10"));
148            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.RoomPreferenceWeight", "Room preferences", ParameterDefinition.TYPE_DOUBLE, "0.10"));
149            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.ContrPreferenceWeight", "Group constraint preferences", ParameterDefinition.TYPE_DOUBLE, "1.00"));
150            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.HardStudentConflictWeight", "Hard student conflicts", ParameterDefinition.TYPE_DOUBLE, "1.00"));
151            
152            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.DistanceInstructorPreferenceWeight", "Distance Instructor Preference", ParameterDefinition.TYPE_DOUBLE, "0.10"));
153            //ret.addElement(new ParameterDefinition("Lecture Selection - Weights (a complete solution found)","Lecture.DeptSpreadPenaltyWeight", "Deparment balancing -- penalty a slot over initial allowance", ParameterDefinition.TYPE_DOUBLE, "0.0").addDependency(deptSpreadDep));
154    
155            //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.SelectionSubSet", "Subset selection", ParameterDefinition.TYPE_BOOLEAN, "true"));
156            //ParameterDefinition.Dependency subSetDep = new ParameterDefinition.Dependency("Lecture.SelectionSubSet", "true");
157            //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.SelectionSubSetMinSize", "Subset selection -- subset minimum size", ParameterDefinition.TYPE_INTEGER, "10").addDependency(subSetDep));
158            //ret.addElement(new ParameterDefinition("Lecture Selection","Lecture.SelectionSubSetPart", "Subset selection -- part", ParameterDefinition.TYPE_DOUBLE, "0.20").addDependency(subSetDep));
159    
160            return ret;
161        }
162    
163        public void init(Solver solver) {
164            for (Enumeration i=solver.getExtensions().elements();i.hasMoreElements();) {
165                Extension extension = (Extension)i.nextElement();
166                if (extension instanceof ConflictStatistics) 
167                    iStat = (ConflictStatistics) extension;
168                if (extension instanceof MacPropagation)
169                    iProp = (MacPropagation)extension;
170                if (extension instanceof ViolatedInitials)
171                    iViolatedInitials = (ViolatedInitials)extension;
172            }
173        }
174    
175        public Variable selectVariable(Solution solution) {
176            if (solution.getModel().unassignedVariables().isEmpty()) {
177                Vector variables = solution.getModel().perturbVariables();
178                if (variables.isEmpty()) variables = solution.getModel().variables();
179                
180                //if (iRW && ToolBox.random()<=iRandomWalkProb) return (Variable)ToolBox.random(variables);
181                
182                Vector selectionVariables = null;
183                int worstTimePreference = 0;
184                int worstRoomConstrPreference = 0;
185                
186                for (Iterator i1=(iSubSetSelection?ToolBox.subSet(variables,iSelectionSubSetPart,iSelectionSubSetMinSize):variables).iterator();i1.hasNext();) {
187                    Variable selectedVariable = (Variable) i1.next();
188                    Value value = (Value)selectedVariable.getAssignment();
189                    
190                    int sumStudentConflicts = (iStudentConflictWeight!=0.0?((Lecture)selectedVariable).countStudentConflicts(value):0);
191                    boolean haveAlternative = (((Lecture)selectedVariable).sameLectures()==null?false:((Lecture)selectedVariable).sameLectures().size()>1);
192                    int constrPreference = 0;
193                    int emptySingleHalfHours = 0;
194                    int sumHardStudentConflicts = 0;
195                    int distanceInstructorPreferences = 0;
196                    boolean tooBig = ((Placement)value).getRoomLocation().getRoomSize()>TimetableModel.getMaxCapacity(((Lecture)selectedVariable).countStudents());
197                    
198                    for (Enumeration i2=selectedVariable.constraints().elements();i2.hasMoreElements();) {
199                        Constraint constraint = (Constraint)i2.nextElement();
200                        if (iDistanceInstructorPreferenceWeight!=0.0 && constraint instanceof InstructorConstraint) {
201                            distanceInstructorPreferences += ((InstructorConstraint)constraint).getPreference(value);
202                        }
203                        if (iHardStudentConflictWeight!=0.0 && !haveAlternative && constraint instanceof JenrlConstraint) {
204                            JenrlConstraint jenrl = (JenrlConstraint)constraint;
205                            Collection anotherSameLectures = ((Lecture)jenrl.another(selectedVariable)).sameLectures();
206                            if (anotherSameLectures==null || anotherSameLectures.size()==1)
207                                sumHardStudentConflicts += ((JenrlConstraint)constraint).jenrl(selectedVariable, value);
208                        } else if (iConstrPreferencesWeight!=0.0 && constraint instanceof GroupConstraint) {
209                            GroupConstraint gc = (GroupConstraint)constraint;
210                            constrPreference += gc.getCurrentPreference();
211                        } else if (iEmptySingleSlotWeight!=0.0 && constraint instanceof RoomConstraint) {
212                            RoomConstraint rc = (RoomConstraint)constraint;
213                            for (int i=0; i<((Placement)value).getTimeLocation().getStartSlots().length; i++) {
214                                int startSlot = ((Placement)value).getTimeLocation().getStartSlots()[i];
215                                int endSlot = startSlot + ((Placement)value).getTimeLocation().getLength() - 1;
216                                if (((startSlot%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)>=2) && rc.getResource()[startSlot-1]==null && rc.getResource()[startSlot-2]!=null)
217                                    emptySingleHalfHours++;
218                                if (((endSlot%edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY)<edu.purdue.smas.timetable.util.Constants.SLOTS_PER_DAY-2) && rc.getResource()[startSlot+1]==null && rc.getResource()[startSlot+2]!=null)
219                                    emptySingleHalfHours++;
220                            }
221                        }
222                    }
223                    
224                    int roomPreference = ((Placement)value).getRoomLocation().getPreference();
225                    double deptSpreadPenalty = (iDeptSpreadPenaltyWeight==0.0 || ((Lecture)selectedVariable).getDeptSpreadConstraint()==null?0:((Lecture)selectedVariable).getDeptSpreadConstraint().getPenalty());
226                    int timePreference = (int)(100.0*(
227                        (iStudentConflictWeight*sumStudentConflicts)+
228                        (iHardStudentConflictWeight*sumHardStudentConflicts)+
229                        (iTimePreferencesWeight*((Placement)value).getTimeLocation().getNormalizedPreference())+
230                        (iEmptySingleSlotWeight*emptySingleHalfHours)+
231                        (iRoomPreferencesWeight*roomPreference)+
232                        (tooBig?iTooBigRoomWeight:0.0)+
233                        (iConstrPreferencesWeight*constrPreference)+
234                        (iDistanceInstructorPreferenceWeight*distanceInstructorPreferences)+
235                        (iDeptSpreadPenaltyWeight*deptSpreadPenalty)
236                    ));
237    
238                    if (selectionVariables==null || timePreference>worstTimePreference || (timePreference==worstTimePreference && roomPreference+constrPreference>worstRoomConstrPreference)) {
239                        if (selectionVariables==null) selectionVariables=new FastVector(); else selectionVariables.clear();
240                        selectionVariables.addElement(selectedVariable);
241                        worstTimePreference=timePreference;
242                        worstRoomConstrPreference=roomPreference+constrPreference;
243                    } else if (timePreference==worstTimePreference && roomPreference+constrPreference==worstRoomConstrPreference) {
244                        selectionVariables.addElement(selectedVariable);
245                    }
246                }
247                
248                return (Variable)ToolBox.random(selectionVariables);
249            } else {
250                if (ToolBox.random()<=iRandomWalkProb) return (Variable)ToolBox.random(solution.getModel().unassignedVariables());
251                
252                if (iProp!=null && iUnassignWhenNotGood) {
253                    int totalPoints = 0;
254                    Vector noGoodVariables = new FastVector();
255                    for (Iterator i1=ToolBox.subSet(solution.getModel().unassignedVariables(),iSelectionSubSetPart,iSelectionSubSetMinSize).iterator();i1.hasNext(); ){
256                        Variable variable = (Variable) i1.next();
257                        if (iProp.goodValues(variable).isEmpty())
258                            noGoodVariables.addElement(variable);
259                    }
260                    if (!noGoodVariables.isEmpty()) {
261                        if (ToolBox.random()<0.02) return (Variable)ToolBox.random(solution.getModel().assignedVariables());
262                        for (int attempt=0;attempt<10;attempt++) {
263                            Variable noGoodVariable = (Variable)ToolBox.random(noGoodVariables);
264                            Value noGoodValue = (Value)ToolBox.random(noGoodVariable.values());
265                            if (!iProp.noGood(noGoodValue).isEmpty()) return ((Value)ToolBox.random(iProp.noGood(noGoodValue))).variable();
266                        }
267                    }
268                }
269                    
270                if (iRouletteWheelSelection) {
271                    int iMaxDomainSize=0;
272                    int iMaxGoodDomainSize=0;
273                    int iMaxConstraints=0;
274                    long iMaxNrAssignments=0;
275                    Collection variables = (iSubSetSelection?ToolBox.subSet(solution.getModel().unassignedVariables(),iSelectionSubSetPart,iSelectionSubSetMinSize):solution.getModel().unassignedVariables());
276                    for (Iterator i=variables.iterator();i.hasNext(); ){
277                        Variable variable = (Variable) i.next();
278                        iMaxDomainSize=Math.max(iMaxDomainSize,variable.values().size());
279                        iMaxGoodDomainSize=(iProp==null?0:Math.max(iMaxGoodDomainSize,iProp.goodValues(variable).size()));
280                        iMaxConstraints=Math.max(iMaxConstraints,variable.constraints().size());
281                        iMaxNrAssignments=Math.max(iMaxNrAssignments,variable.countAssignments());
282                    }
283    
284                    Vector points = new FastVector();
285                    int totalPoints = 0;
286                    
287                    for (Iterator i=variables.iterator();i.hasNext(); ){
288                        Variable variable = (Variable) i.next();
289                        
290                        long pointsThisVariable = Math.round(
291                            iDomainSizeWeight*(((double)(iMaxDomainSize-variable.values().size()))/((double)iMaxDomainSize))+
292                            (iProp==null?0.0:iGoodValuesWeight*(((double)(iMaxGoodDomainSize-iProp.goodValues(variable).size()))/((double)iMaxGoodDomainSize)))+
293                            iNrAssignmentsWeight*(((double)variable.countAssignments())/((double)iMaxNrAssignments))+
294                            iConstraintsWeight*(((double)(iMaxConstraints-variable.constraints().size()))/((double)iMaxConstraints))+
295                            iInitialAssignmentWeight*(variable.getInitialAssignment()!=null?solution.getModel().conflictValues(variable.getInitialAssignment()).size():0.0)
296                            );
297                        if (pointsThisVariable>0) {
298                            totalPoints += pointsThisVariable;
299                            points.addElement(new Integer(totalPoints));
300                        }
301                    }
302                    
303                    if (totalPoints>0) {
304                        int rndPoints = ToolBox.random(totalPoints);
305                        Iterator x=variables.iterator();
306                        for (int i=0;x.hasNext() && i<points.size();i++){
307                            Variable variable = (Variable)x.next();
308                            int tp = ((Integer)points.elementAt(i)).intValue();
309                            if (tp>rndPoints) return variable;
310                        }
311                    }
312                    
313                } else {
314                    
315                    Vector selectionVariables = null;
316                    long bestGood = 0;
317                    for (Iterator i=ToolBox.subSet(solution.getModel().unassignedVariables(),iSelectionSubSetPart,iSelectionSubSetMinSize).iterator();i.hasNext(); ){
318                        Variable variable = (Variable) i.next();
319                        
320                        long good = (long)(
321                        iDomainSizeWeight*variable.values().size()
322                        + iGoodValuesWeight*(iProp==null?0:iProp.goodValues(variable).size())
323                        + iNrAssignmentsWeight*variable.countAssignments()
324                        + iConstraintsWeight*variable.constraints().size()
325                        + iInitialAssignmentWeight*(variable.getInitialAssignment()!=null?solution.getModel().conflictValues(variable.getInitialAssignment()).size():0.0)
326                        );
327                        if (selectionVariables==null || bestGood>good) {
328                            if (selectionVariables==null) selectionVariables = new FastVector(); else selectionVariables.clear();
329                            bestGood=good;
330                            selectionVariables.addElement(variable);
331                        } else if (good==bestGood) {
332                            selectionVariables.addElement(variable);
333                        }
334                    }
335                    
336                    if (!selectionVariables.isEmpty())
337                        return (Variable)ToolBox.random(selectionVariables);
338                }
339                return (Variable)ToolBox.random(solution.getModel().unassignedVariables());
340            }
341        }
342        
343    }