001package org.cpsolver.coursett.heuristics; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.Iterator; 006import java.util.List; 007 008import org.cpsolver.coursett.model.Lecture; 009import org.cpsolver.coursett.model.Placement; 010import org.cpsolver.coursett.model.TimetableModel; 011import org.cpsolver.ifs.assignment.Assignment; 012import org.cpsolver.ifs.extension.Extension; 013import org.cpsolver.ifs.extension.MacPropagation; 014import org.cpsolver.ifs.heuristics.VariableSelection; 015import org.cpsolver.ifs.solution.Solution; 016import org.cpsolver.ifs.solver.Solver; 017import org.cpsolver.ifs.util.DataProperties; 018import org.cpsolver.ifs.util.ToolBox; 019 020 021/** 022 * Lecture (variable) selection. <br> 023 * <br> 024 * If there are one or more variables unassigned, the variable selection 025 * criterion picks one of them randomly. We have tried several approaches using 026 * domain sizes, number of previous assignments, numbers of constraints in which 027 * the variable participates, etc., but there was no significant improvement in 028 * this timetabling problem towards the random selection of an unassigned 029 * variable. The reason is, that it is easy to go back when a wrong variable is 030 * picked - such a variable is unassigned when there is a conflict with it in 031 * some of the subsequent iterations. <br> 032 * <br> 033 * When all variables are assigned, an evaluation is made for each variable 034 * according to the above described weights. The variable with the worst 035 * evaluation is selected. This variable promises the best improvement in 036 * optimization. <br> 037 * <br> 038 * Parameters (selection among unassigned lectures): 039 * <table border='1'><caption>Related Solver Parameters</caption> 040 * <tr> 041 * <th>Parameter</th> 042 * <th>Type</th> 043 * <th>Comment</th> 044 * </tr> 045 * <tr> 046 * <td>Lecture.RouletteWheelSelection</td> 047 * <td>{@link Boolean}</td> 048 * <td>Roulette wheel selection</td> 049 * </tr> 050 * <tr> 051 * <td>Lecture.RandomWalkProb</td> 052 * <td>{@link Double}</td> 053 * <td>Random walk probability</td> 054 * </tr> 055 * <tr> 056 * <td>Lecture.DomainSizeWeight</td> 057 * <td>{@link Double}</td> 058 * <td>Domain size weight</td> 059 * </tr> 060 * <tr> 061 * <td>Lecture.NrAssignmentsWeight</td> 062 * <td>{@link Double}</td> 063 * <td>Number of assignments weight</td> 064 * </tr> 065 * <tr> 066 * <td>Lecture.InitialAssignmentWeight</td> 067 * <td>{@link Double}</td> 068 * <td>Initial assignment weight</td> 069 * </tr> 070 * <tr> 071 * <td>Lecture.NrConstraintsWeight</td> 072 * <td>{@link Double}</td> 073 * <td>Number of constraint weight</td> 074 * </tr> 075 * </table> 076 * <br> 077 * Parameters (selection among assigned lectures, when the solution is 078 * complete): 079 * <table border='1'><caption>Related Solver Parameters</caption> 080 * <tr> 081 * <th>Parameter</th> 082 * <th>Type</th> 083 * <th>Comment</th> 084 * </tr> 085 * <tr> 086 * <td>Comparator.HardStudentConflictWeight</td> 087 * <td>{@link Double}</td> 088 * <td>Hard student conflict weight</td> 089 * </tr> 090 * <tr> 091 * <td>Comparator.StudentConflictWeight</td> 092 * <td>{@link Double}</td> 093 * <td>Student conflict weight</td> 094 * </tr> 095 * <tr> 096 * <td>Comparator.TimePreferenceWeight</td> 097 * <td>{@link Double}</td> 098 * <td>Time preference weight</td> 099 * </tr> 100 * <tr> 101 * <td>Comparator.ContrPreferenceWeight</td> 102 * <td>{@link Double}</td> 103 * <td>Group constraint preference weight</td> 104 * </tr> 105 * <tr> 106 * <td>Comparator.RoomPreferenceWeight</td> 107 * <td>{@link Double}</td> 108 * <td>Room preference weight</td> 109 * </tr> 110 * <tr> 111 * <td>Comparator.UselessSlotWeight</td> 112 * <td>{@link Double}</td> 113 * <td>Useless slot weight</td> 114 * </tr> 115 * <tr> 116 * <td>Comparator.TooBigRoomWeight</td> 117 * <td>{@link Double}</td> 118 * <td>Too big room weight</td> 119 * </tr> 120 * <tr> 121 * <td>Comparator.DistanceInstructorPreferenceWeight</td> 122 * <td>{@link Double}</td> 123 * <td>Distance (of the rooms of the back-to-back classes) based instructor 124 * preferences weight</td> 125 * </tr> 126 * <tr> 127 * <td>Comparator.DeptSpreadPenaltyWeight</td> 128 * <td>{@link Double}</td> 129 * <td>Department balancing penalty (see 130 * {@link org.cpsolver.coursett.constraint.DepartmentSpreadConstraint})</td> 131 * </tr> 132 * </table> 133 * <br> 134 * Parameters (selection among subset of lectures (faster)): 135 * <table border='1'><caption>Related Solver Parameters</caption> 136 * <tr> 137 * <th>Parameter</th> 138 * <th>Type</th> 139 * <th>Comment</th> 140 * </tr> 141 * <tr> 142 * <td>Lecture.SelectionSubSet</td> 143 * <td>{@link Boolean}</td> 144 * <td>Selection among subset of lectures (faster)</td> 145 * </tr> 146 * <tr> 147 * <td>Lecture.SelectionSubSetMinSize</td> 148 * <td>{@link Double}</td> 149 * <td>Minimal subset size</td> 150 * </tr> 151 * <tr> 152 * <td>Lecture.SelectionSubSetPart</td> 153 * <td>{@link Double}</td> 154 * <td>Subset size in percentage of all lectures available for selection</td> 155 * </tr> 156 * </table> 157 * 158 * @see PlacementSelection 159 * @version CourseTT 1.3 (University Course Timetabling)<br> 160 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 161 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 162 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 163 * <br> 164 * This library is free software; you can redistribute it and/or modify 165 * it under the terms of the GNU Lesser General Public License as 166 * published by the Free Software Foundation; either version 3 of the 167 * License, or (at your option) any later version. <br> 168 * <br> 169 * This library is distributed in the hope that it will be useful, but 170 * WITHOUT ANY WARRANTY; without even the implied warranty of 171 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 172 * Lesser General Public License for more details. <br> 173 * <br> 174 * You should have received a copy of the GNU Lesser General Public 175 * License along with this library; if not see 176 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 177 */ 178public class LectureSelection implements VariableSelection<Lecture, Placement> { 179 private double iRandomWalkProb; 180 private double iDomainSizeWeight; 181 private double iGoodValuesWeight; 182 private double iConstraintsWeight; 183 private double iInitialAssignmentWeight; 184 private boolean iRouletteWheelSelection; 185 private boolean iUnassignWhenNotGood; 186 187 private boolean iSubSetSelection; 188 private double iSelectionSubSetPart; 189 private int iSelectionSubSetMinSize; 190 private boolean iInteractiveMode; 191 192 private boolean iRW = false; 193 private boolean iMPP = false; 194 195 private MacPropagation<Lecture, Placement> iProp = null; 196 197 private int iTabuSize = 0; 198 private ArrayList<Lecture> iTabu = null; 199 private int iTabuPos = 0; 200 201 public LectureSelection(DataProperties properties) { 202 iRouletteWheelSelection = properties.getPropertyBoolean("Lecture.RouletteWheelSelection", true); 203 iUnassignWhenNotGood = properties.getPropertyBoolean("Lecture.UnassignWhenNotGood", false); 204 iRW = properties.getPropertyBoolean("General.RandomWalk", true); 205 iRandomWalkProb = (!iRW ? 0.0 : properties.getPropertyDouble("Lecture.RandomWalkProb", 1.00)); 206 iGoodValuesWeight = properties.getPropertyDouble("Lecture.GoodValueProb", 1.0); 207 iDomainSizeWeight = properties.getPropertyDouble("Lecture.DomainSizeWeight", 30.0); 208 209 iInteractiveMode = properties.getPropertyBoolean("General.InteractiveMode", false); 210 211 iConstraintsWeight = properties.getPropertyDouble("Lecture.NrConstraintsWeight", 0.0); 212 iMPP = properties.getPropertyBoolean("General.MPP", false); 213 iInitialAssignmentWeight = (!iMPP ? 0.0 : properties.getPropertyDouble("Lecture.InitialAssignmentWeight", 20.0)); 214 215 iSubSetSelection = properties.getPropertyBoolean("Lecture.SelectionSubSet", true); 216 iSelectionSubSetMinSize = properties.getPropertyInt("Lecture.SelectionSubSetMinSize", 10); 217 iSelectionSubSetPart = properties.getPropertyDouble("Lecture.SelectionSubSetPart", 0.2); 218 219 iTabuSize = properties.getPropertyInt("Lecture.TabuSize", 20); 220 if (iTabuSize > 0) 221 iTabu = new ArrayList<Lecture>(iTabuSize); 222 } 223 224 @Override 225 public void init(Solver<Lecture, Placement> solver) { 226 for (Extension<Lecture, Placement> extension : solver.getExtensions()) { 227 if (MacPropagation.class.isInstance(extension)) 228 iProp = (MacPropagation<Lecture, Placement>) extension; 229 } 230 } 231 232 @Override 233 public Lecture selectVariable(Solution<Lecture, Placement> solution) { 234 TimetableModel model = (TimetableModel) solution.getModel(); 235 Assignment<Lecture, Placement> assignment = solution.getAssignment(); 236 Collection<Lecture> unassignedVariables = model.unassignedVariables(assignment); 237 if (iInteractiveMode) { 238 // remove variables that have no values 239 unassignedVariables = new ArrayList<Lecture>(unassignedVariables.size()); 240 for (Lecture variable : model.unassignedVariables(assignment)) { 241 if (!variable.values(solution.getAssignment()).isEmpty()) 242 unassignedVariables.add(variable); 243 } 244 } 245 246 if (unassignedVariables.isEmpty()) { 247 Collection<Lecture> variables = model.perturbVariables(assignment); 248 if (variables.isEmpty()) 249 variables = model.assignedVariables(assignment); 250 251 if (iRW && ToolBox.random() <= iRandomWalkProb) 252 return ToolBox.random(variables); 253 254 List<Lecture> selectionVariables = new ArrayList<Lecture>(); 255 double worstValue = 0.0; 256 257 for (Iterator<Lecture> i1 = (iSubSetSelection ? ToolBox.subSet(variables, iSelectionSubSetPart, iSelectionSubSetMinSize) : variables).iterator(); i1.hasNext();) { 258 Lecture selectedVariable = i1.next(); 259 260 if (iTabu != null && iTabu.contains(selectedVariable)) 261 continue; 262 263 double value = assignment.getValue(selectedVariable).toDouble(assignment); 264 265 if (selectionVariables.isEmpty() || value > worstValue) { 266 selectionVariables.clear(); 267 selectionVariables.add(selectedVariable); 268 worstValue = value; 269 } else if (worstValue == value) { 270 selectionVariables.add(selectedVariable); 271 } 272 } 273 274 Lecture selectedVariable = ToolBox.random(selectionVariables); 275 276 if (selectedVariable == null) 277 selectedVariable = ToolBox.random(variables); 278 279 if (selectedVariable != null && iTabu != null) { 280 if (iTabu.size() == iTabuPos) 281 iTabu.add(selectedVariable); 282 else 283 iTabu.set(iTabuPos, selectedVariable); 284 iTabuPos = (iTabuPos + 1) % iTabuSize; 285 } 286 287 return selectedVariable; 288 } else { 289 if (ToolBox.random() <= iRandomWalkProb) 290 return ToolBox.random(unassignedVariables); 291 292 if (iProp != null && iUnassignWhenNotGood) { 293 List<Lecture> noGoodVariables = new ArrayList<Lecture>(); 294 for (Iterator<Lecture> i1 = ToolBox.subSet(unassignedVariables, iSelectionSubSetPart, iSelectionSubSetMinSize).iterator(); i1.hasNext();) { 295 Lecture variable = i1.next(); 296 if (iProp.goodValues(assignment, variable).isEmpty()) 297 noGoodVariables.add(variable); 298 } 299 if (!noGoodVariables.isEmpty()) { 300 if (ToolBox.random() < 0.02) 301 return ToolBox.random(assignment.assignedVariables()); 302 for (int attempt = 0; attempt < 10; attempt++) { 303 Lecture noGoodVariable = ToolBox.random(noGoodVariables); 304 Placement noGoodValue = ToolBox.random(noGoodVariable.values(solution.getAssignment())); 305 if (!iProp.noGood(assignment, noGoodValue).isEmpty()) 306 return ToolBox.random(iProp.noGood(assignment, noGoodValue)).variable(); 307 } 308 } 309 } 310 311 if (iRouletteWheelSelection) { 312 int iMaxDomainSize = 0; 313 int iMaxGoodDomainSize = 0; 314 int iMaxConstraints = 0; 315 Collection<Lecture> variables = (iSubSetSelection ? ToolBox.subSet(unassignedVariables, iSelectionSubSetPart, iSelectionSubSetMinSize) : unassignedVariables); 316 for (Lecture variable : variables) { 317 318 if (iTabu != null && iTabu.contains(variable)) 319 continue; 320 321 iMaxDomainSize = Math.max(iMaxDomainSize, variable.values(solution.getAssignment()).size()); 322 iMaxGoodDomainSize = (iProp == null ? 0 : Math.max(iMaxGoodDomainSize, iProp.goodValues(assignment, variable).size())); 323 iMaxConstraints = Math.max(iMaxConstraints, variable.constraints().size()); 324 } 325 326 List<Integer> points = new ArrayList<Integer>(); 327 int totalPoints = 0; 328 329 for (Lecture variable : variables) { 330 331 long pointsThisVariable = Math.round( 332 iDomainSizeWeight * (((double) (iMaxDomainSize - variable.values(solution.getAssignment()).size())) / ((double) iMaxDomainSize)) + 333 (iProp == null ? 0.0 : iGoodValuesWeight * (((double) (iMaxGoodDomainSize - iProp.goodValues(assignment, variable).size())) / ((double) iMaxGoodDomainSize))) + 334 iConstraintsWeight * (((double) (iMaxConstraints - variable.constraints().size())) / ((double) iMaxConstraints)) + 335 iInitialAssignmentWeight * (variable.getInitialAssignment() != null ? model.conflictValues(assignment, variable.getInitialAssignment()).size() : 0.0)); 336 if (pointsThisVariable > 0) { 337 totalPoints += pointsThisVariable; 338 points.add(totalPoints); 339 } 340 } 341 342 if (totalPoints > 0) { 343 int rndPoints = ToolBox.random(totalPoints); 344 Iterator<Lecture> x = variables.iterator(); 345 for (int i = 0; x.hasNext() && i < points.size(); i++) { 346 Lecture variable = x.next(); 347 int tp = points.get(i); 348 if (tp > rndPoints) { 349 if (variable != null && iTabu != null) { 350 if (iTabu.size() == iTabuPos) 351 iTabu.add(variable); 352 else 353 iTabu.set(iTabuPos, variable); 354 iTabuPos = (iTabuPos + 1) % iTabuSize; 355 } 356 return variable; 357 } 358 } 359 } 360 361 } else { 362 363 List<Lecture> selectionVariables = null; 364 long bestGood = 0; 365 for (Iterator<Lecture> i = ToolBox.subSet(unassignedVariables, iSelectionSubSetPart, 366 iSelectionSubSetMinSize).iterator(); i.hasNext();) { 367 Lecture variable = i.next(); 368 369 if (iTabu != null && iTabu.contains(variable)) 370 continue; 371 372 long good = (long) (iDomainSizeWeight * variable.values(solution.getAssignment()).size() + 373 iGoodValuesWeight * (iProp == null ? 0 : iProp.goodValues(assignment, variable).size()) + 374 iConstraintsWeight * variable.constraints().size() + 375 iInitialAssignmentWeight * (variable.getInitialAssignment() != null ? model.conflictValues(assignment, variable.getInitialAssignment()).size() : 0.0)); 376 if (selectionVariables == null || bestGood > good) { 377 if (selectionVariables == null) 378 selectionVariables = new ArrayList<Lecture>(); 379 else 380 selectionVariables.clear(); 381 bestGood = good; 382 selectionVariables.add(variable); 383 } else if (good == bestGood) { 384 selectionVariables.add(variable); 385 } 386 } 387 388 if (!selectionVariables.isEmpty()) { 389 Lecture selectedVariable = ToolBox.random(selectionVariables); 390 391 if (selectedVariable != null && iTabu != null) { 392 if (iTabu.size() == iTabuPos) 393 iTabu.add(selectedVariable); 394 else 395 iTabu.set(iTabuPos, selectedVariable); 396 iTabuPos = (iTabuPos + 1) % iTabuSize; 397 } 398 399 return selectedVariable; 400 } 401 } 402 403 Lecture selectedVariable = ToolBox.random(unassignedVariables); 404 405 if (selectedVariable != null && iTabu != null) { 406 if (iTabu.size() == iTabuPos) 407 iTabu.add(selectedVariable); 408 else 409 iTabu.set(iTabuPos, selectedVariable); 410 iTabuPos = (iTabuPos + 1) % iTabuSize; 411 } 412 413 return selectedVariable; 414 } 415 } 416 417}