001package org.cpsolver.ifs.heuristics; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.HashMap; 006import java.util.Iterator; 007import java.util.List; 008import java.util.Map; 009import java.util.Set; 010import java.util.concurrent.locks.Lock; 011 012 013import org.apache.logging.log4j.Logger; 014import org.cpsolver.ifs.assignment.Assignment; 015import org.cpsolver.ifs.assignment.context.AssignmentContext; 016import org.cpsolver.ifs.constant.ConstantVariable; 017import org.cpsolver.ifs.extension.ConflictStatistics; 018import org.cpsolver.ifs.extension.Extension; 019import org.cpsolver.ifs.model.Model; 020import org.cpsolver.ifs.model.Neighbour; 021import org.cpsolver.ifs.model.Value; 022import org.cpsolver.ifs.model.Variable; 023import org.cpsolver.ifs.solution.Solution; 024import org.cpsolver.ifs.solver.Solver; 025import org.cpsolver.ifs.util.DataProperties; 026import org.cpsolver.ifs.util.JProf; 027 028/** 029 * Backtracking-based neighbour selection. A best neighbour that is found by a 030 * backtracking-based algorithm within a limited depth from a selected variable 031 * is returned. <br> 032 * <br> 033 * Parameters: <br> 034 * <table border='1'><caption>Related Solver Parameters</caption> 035 * <tr> 036 * <th>Parameter</th> 037 * <th>Type</th> 038 * <th>Comment</th> 039 * </tr> 040 * <tr> 041 * <td>Neighbour.BackTrackTimeout</td> 042 * <td>{@link Integer}</td> 043 * <td>Timeout for each neighbour selection (in milliseconds).</td> 044 * </tr> 045 * <tr> 046 * <td>Neighbour.BackTrackDepth</td> 047 * <td>{@link Integer}</td> 048 * <td>Limit of search depth.</td> 049 * </tr> 050 * </table> 051 * 052 * @author Tomáš Müller 053 * @version StudentSct 1.3 (Student Sectioning)<br> 054 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 055 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 056 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 057 * <br> 058 * This library is free software; you can redistribute it and/or modify 059 * it under the terms of the GNU Lesser General Public License as 060 * published by the Free Software Foundation; either version 3 of the 061 * License, or (at your option) any later version. <br> 062 * <br> 063 * This library is distributed in the hope that it will be useful, but 064 * WITHOUT ANY WARRANTY; without even the implied warranty of 065 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 066 * Lesser General Public License for more details. <br> 067 * <br> 068 * You should have received a copy of the GNU Lesser General Public 069 * License along with this library; if not see 070 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 071 * 072 * @param <V> Variable 073 * @param <T> Value 074 */ 075public class BacktrackNeighbourSelection<V extends Variable<V, T>, T extends Value<V, T>> extends StandardNeighbourSelection<V, T> { 076 private ConflictStatistics<V, T> iStat = null; 077 private static Logger sLog = org.apache.logging.log4j.LogManager.getLogger(BacktrackNeighbourSelection.class); 078 private int iTimeout = 5000; 079 private int iDepth = 4; 080 private int iMaxIters = -1; 081 protected BacktrackNeighbourSelectionContext iContext; 082 083 /** 084 * Constructor 085 * 086 * @param properties 087 * configuration 088 * @throws Exception thrown when initialization fails 089 */ 090 public BacktrackNeighbourSelection(DataProperties properties) throws Exception { 091 super(properties); 092 iTimeout = properties.getPropertyInt("Neighbour.BackTrackTimeout", iTimeout); 093 iDepth = properties.getPropertyInt("Neighbour.BackTrackDepth", iDepth); 094 iMaxIters = properties.getPropertyInt("Neighbour.BackTrackMaxIters", iMaxIters); 095 } 096 097 /** Solver initialization */ 098 @Override 099 public void init(Solver<V, T> solver) { 100 super.init(solver); 101 for (Extension<V, T> extension : solver.getExtensions()) { 102 if (ConflictStatistics.class.isInstance(extension)) 103 iStat = (ConflictStatistics<V, T>) extension; 104 } 105 } 106 107 /** 108 * Select neighbour. The standard variable selection (see 109 * {@link StandardNeighbourSelection#getVariableSelection()}) is used to 110 * select a variable. A backtracking of a limited depth is than employed 111 * from this variable. The best assignment found is returned (see 112 * {@link BackTrackNeighbour}). 113 **/ 114 @Override 115 public Neighbour<V, T> selectNeighbour(Solution<V, T> solution) { 116 return selectNeighbour(solution, getVariableSelection().selectVariable(solution)); 117 } 118 119 /** 120 * Select neighbour -- starts from the provided variable. A backtracking of 121 * a limited depth is employed from the given variable. The best assignment 122 * found is returned (see {@link BackTrackNeighbour}). 123 * @param solution current solution 124 * @param variable selected variable 125 * @return a neighbour, null if not found 126 **/ 127 public Neighbour<V, T> selectNeighbour(Solution<V, T> solution, V variable) { 128 if (variable == null) 129 return null; 130 131 BacktrackNeighbourSelectionContext context = new BacktrackNeighbourSelectionContext(solution); 132 selectNeighbour(solution, variable, context); 133 return context.getBackTrackNeighbour(); 134 } 135 136 protected void selectNeighbour(Solution<V, T> solution, V variable, BacktrackNeighbourSelectionContext context) { 137 iContext = context; 138 Lock lock = solution.getLock().writeLock(); 139 lock.lock(); 140 try { 141 if (sLog.isDebugEnabled()) 142 sLog.debug("-- before BT (" + variable.getName() + "): nrAssigned=" + solution.getAssignment().nrAssignedVariables() + ", value=" + solution.getModel().getTotalValue(solution.getAssignment())); 143 144 List<V> variables2resolve = new ArrayList<V>(1); 145 variables2resolve.add(variable); 146 backtrack(context, variables2resolve, 0, iDepth); 147 148 if (sLog.isDebugEnabled()) 149 sLog.debug("-- after BT (" + variable.getName() + "): nrAssigned=" + solution.getAssignment().nrAssignedVariables() + ", value=" + solution.getModel().getTotalValue(solution.getAssignment())); 150 } finally { 151 lock.unlock(); 152 } 153 154 if (sLog.isDebugEnabled()) 155 sLog.debug("-- selected neighbour: " + context.getBackTrackNeighbour()); 156 } 157 158 public BacktrackNeighbourSelectionContext getContext() { 159 return iContext; 160 } 161 162 private boolean containsConstantValues(Collection<T> values) { 163 for (T value : values) { 164 if (value.variable() instanceof ConstantVariable && ((ConstantVariable<?>) value.variable()).isConstant()) 165 return true; 166 } 167 return false; 168 } 169 170 /** List of values of the given variable that will be considered 171 * @param context assignment context 172 * @param variable given variable 173 * @return values of the given variable that will be considered 174 **/ 175 protected Iterator<T> values(BacktrackNeighbourSelectionContext context, V variable) { 176 return variable.values(context.getAssignment()).iterator(); 177 } 178 179 /** Check bound 180 * @param variables2resolve unassigned variables that are in conflict with the current solution 181 * @param idx position in variables2resolve 182 * @param depth current depth 183 * @param value value to check 184 * @param conflicts conflicting values 185 * @return bound (best possible improvement) 186 **/ 187 protected boolean checkBound(List<V> variables2resolve, int idx, int depth, T value, Set<T> conflicts) { 188 int nrUnassigned = variables2resolve.size() - idx; 189 if ((nrUnassigned + conflicts.size() > depth)) { 190 if (sLog.isDebugEnabled()) 191 sLog.debug(" -- too deap"); 192 return false; 193 } 194 if (containsConstantValues(conflicts)) { 195 if (sLog.isDebugEnabled()) 196 sLog.debug(" -- contains constants values"); 197 return false; 198 } 199 boolean containAssigned = false; 200 for (Iterator<T> i = conflicts.iterator(); !containAssigned && i.hasNext();) { 201 T conflict = i.next(); 202 int confIdx = variables2resolve.indexOf(conflict.variable()); 203 if (confIdx >= 0 && confIdx <= idx) { 204 if (sLog.isDebugEnabled()) 205 sLog.debug(" -- contains resolved variable " + conflict.variable()); 206 containAssigned = true; 207 } 208 } 209 if (containAssigned) 210 return false; 211 return true; 212 } 213 214 /** Check whether backtrack can continue 215 * @param context assignment context 216 * @param variables2resolve unassigned variables that are in conflict with the current solution 217 * @param idx position in variables2resolve 218 * @param depth current depth 219 * @return true if the search can continue 220 **/ 221 protected boolean canContinue(BacktrackNeighbourSelectionContext context, List<V> variables2resolve, int idx, int depth) { 222 if (depth <= 0) { 223 if (sLog.isDebugEnabled()) 224 sLog.debug(" -- depth reached"); 225 return false; 226 } 227 if (context.isTimeoutReached()) { 228 if (sLog.isDebugEnabled()) 229 sLog.debug(" -- timeout reached"); 230 return false; 231 } 232 if (context.isMaxItersReached()) { 233 if (sLog.isDebugEnabled()) 234 sLog.debug(" -- max number of iterations reached"); 235 return false; 236 } 237 return true; 238 } 239 240 protected boolean canContinueEvaluation(BacktrackNeighbourSelectionContext context) { 241 return !context.isMaxItersReached() && !context.isTimeoutReached(); 242 } 243 244 /** Backtracking 245 * @param context assignment context 246 * @param variables2resolve unassigned variables that are in conflict with the current solution 247 * @param idx position in variables2resolve 248 * @param depth current depth 249 **/ 250 protected void backtrack(BacktrackNeighbourSelectionContext context, List<V> variables2resolve, int idx, int depth) { 251 if (sLog.isDebugEnabled()) 252 sLog.debug(" -- bt[" + depth + "]: " + idx + " of " + variables2resolve.size() + " " + variables2resolve); 253 context.incIteration(); 254 int nrUnassigned = variables2resolve.size() - idx; 255 if (nrUnassigned == 0) { 256 context.saveBest(variables2resolve); 257 return; 258 } 259 if (!canContinue(context, variables2resolve, idx, depth)) 260 return; 261 V variable = variables2resolve.get(idx); 262 if (sLog.isDebugEnabled()) 263 sLog.debug(" -- variable " + variable); 264 for (Iterator<T> e = values(context, variable); canContinueEvaluation(context) && e.hasNext();) { 265 T value = e.next(); 266 T current = context.getAssignment().getValue(variable); 267 if (value.equals(current)) 268 continue; 269 if (sLog.isDebugEnabled()) 270 sLog.debug(" -- value " + value); 271 Set<T> conflicts = context.getModel().conflictValues(context.getAssignment(), value); 272 if (sLog.isDebugEnabled()) 273 sLog.debug(" -- conflicts " + conflicts); 274 if (!checkBound(variables2resolve, idx, depth, value, conflicts)) 275 continue; 276 List<V> newVariables2resolve = new ArrayList<V>(variables2resolve); 277 for (Iterator<T> i = conflicts.iterator(); i.hasNext();) { 278 T conflict = i.next(); 279 context.getAssignment().unassign(0, conflict.variable()); 280 if (!newVariables2resolve.contains(conflict.variable())) 281 newVariables2resolve.add(conflict.variable()); 282 } 283 if (current != null) 284 context.getAssignment().unassign(0, current.variable()); 285 context.getAssignment().assign(0, value); 286 backtrack(context, newVariables2resolve, idx + 1, depth - 1); 287 if (current == null) 288 context.getAssignment().unassign(0, variable); 289 else 290 context.getAssignment().assign(0, current); 291 for (Iterator<T> i = conflicts.iterator(); i.hasNext();) { 292 T conflict = i.next(); 293 context.getAssignment().assign(0, conflict); 294 } 295 } 296 } 297 298 /** Backtracking neighbour */ 299 public class BackTrackNeighbour implements Neighbour<V, T> { 300 private double iTotalValue = 0; 301 private double iValue = 0; 302 private List<T> iDifferentAssignments = null; 303 private Model<V, T> iModel = null; 304 305 /** 306 * Constructor 307 * 308 * @param context assignment context 309 * @param resolvedVariables 310 * variables that has been changed 311 */ 312 public BackTrackNeighbour(BacktrackNeighbourSelectionContext context, List<V> resolvedVariables) { 313 iTotalValue = context.getModel().getTotalValue(context.getAssignment()); 314 iDifferentAssignments = new ArrayList<T>(); 315 for (V variable : resolvedVariables) { 316 T value = variable.getAssignment(context.getAssignment()); 317 iDifferentAssignments.add(value); 318 } 319 iValue = iTotalValue - context.iValue; 320 if (sLog.isDebugEnabled()) 321 iModel = context.getModel(); 322 } 323 324 /** 325 * Constructor 326 * 327 * @param context assignment context 328 * @param resolvedVariables 329 * variables that has been changed 330 */ 331 public BackTrackNeighbour(BacktrackNeighbourSelectionContext context, 332 @SuppressWarnings("unchecked") V... resolvedVariables) { 333 iTotalValue = context.getModel().getTotalValue(context.getAssignment()); 334 iDifferentAssignments = new ArrayList<T>(); 335 for (V variable : resolvedVariables) { 336 T value = variable.getAssignment(context.getAssignment()); 337 iDifferentAssignments.add(value); 338 } 339 iValue = iTotalValue - context.iValue; 340 if (sLog.isDebugEnabled()) 341 iModel = context.getModel(); 342 } 343 344 /** Neighbour value (solution total value if the neighbour is applied). 345 * @return value of the new solution 346 **/ 347 public double getTotalValue() { 348 return iTotalValue; 349 } 350 351 /** 352 * Sum of values of variables from the neighbour that change their 353 * values 354 */ 355 @Override 356 public double value(Assignment<V, T> assignment) { 357 return iValue; 358 } 359 360 /** Neighbour assignments 361 * @return list of assignments in this neighbour 362 **/ 363 public List<T> getAssignments() { 364 return iDifferentAssignments; 365 } 366 367 /** 368 * Assign the neighbour 369 */ 370 @Override 371 public void assign(Assignment<V, T> assignment, long iteration) { 372 if (sLog.isDebugEnabled()) 373 sLog.debug("-- before assignment: nrAssigned=" + assignment.nrAssignedVariables() + ", value=" + iModel.getTotalValue(assignment)); 374 if (sLog.isDebugEnabled()) 375 sLog.debug(" " + this); 376 int idx = 0; 377 for (Iterator<T> e = iDifferentAssignments.iterator(); e.hasNext(); idx++) { 378 T p = e.next(); 379 T o = assignment.getValue(p.variable()); 380 if (o != null) { 381 if (idx > 0 && iStat != null) 382 iStat.variableUnassigned(iteration, o, iDifferentAssignments.get(0)); 383 assignment.unassign(iteration, p.variable()); 384 } 385 } 386 for (T p : iDifferentAssignments) { 387 assignment.assign(iteration, p); 388 } 389 if (sLog.isDebugEnabled()) 390 sLog.debug("-- after assignment: nrAssigned=" + assignment.nrAssignedVariables() + ", value=" + iModel.getTotalValue(assignment)); 391 } 392 393 /** 394 * Compare two neighbours 395 * @param solution current solution 396 * @return comparison 397 */ 398 public int compareTo(Solution<V, T> solution) { 399 return Double.compare(iTotalValue, solution.getModel().getTotalValue(solution.getAssignment())); 400 } 401 402 @Override 403 public String toString() { 404 StringBuffer sb = new StringBuffer("BT{value=" + iTotalValue + ": "); 405 for (Iterator<T> e = iDifferentAssignments.iterator(); e.hasNext();) { 406 T p = e.next(); 407 sb.append("\n " + p.variable().getName() + " " + p.getName() + (e.hasNext() ? "," : "")); 408 } 409 sb.append("}"); 410 return sb.toString(); 411 } 412 413 @Override 414 public Map<V, T> assignments() { 415 Map<V, T> ret = new HashMap<V, T>(); 416 for (T p : iDifferentAssignments) 417 ret.put(p.variable(), p); 418 return ret; 419 } 420 } 421 422 /** Return maximal depth 423 * @return maximal search depth 424 **/ 425 public int getDepth() { 426 return iDepth; 427 } 428 429 /** Set maximal depth 430 * @param depth maximal search depth 431 **/ 432 public void setDepth(int depth) { 433 iDepth = depth; 434 } 435 436 /** Return time limit 437 * @return time limit 438 **/ 439 public int getTimeout() { 440 return iTimeout; 441 } 442 443 /** Set time limit 444 * @param timeout time limit 445 **/ 446 public void setTimeout(int timeout) { 447 iTimeout = timeout; 448 } 449 450 /** Return maximal number of iterations 451 * @return maximal number of iterations 452 **/ 453 public int getMaxIters() { 454 return iMaxIters; 455 } 456 457 /** Set maximal number of iterations 458 * @param maxIters maximal number of iterations 459 **/ 460 public void setMaxIters(int maxIters) { 461 iMaxIters = maxIters; 462 } 463 464 public class BacktrackNeighbourSelectionContext implements AssignmentContext { 465 private long iT0, iT1 = 0; 466 private boolean iTimeoutReached = false; 467 private int iMaxIters = -1, iNrIters = 0; 468 protected Solution<V, T> iSolution = null; 469 protected BackTrackNeighbour iBackTrackNeighbour = null; 470 protected double iValue = 0; 471 private int iNrAssigned = 0; 472 private boolean iMaxItersReached = false; 473 474 public BacktrackNeighbourSelectionContext(Solution<V, T> solution) { 475 iSolution = solution; 476 iBackTrackNeighbour = null; 477 iValue = solution.getModel().getTotalValue(iSolution.getAssignment()); 478 iNrAssigned = iSolution.getAssignment().nrAssignedVariables(); 479 iT0 = JProf.currentTimeMillis(); 480 iNrIters = 0; 481 iTimeoutReached = false; 482 iMaxItersReached = false; 483 } 484 485 /** Time needed to find a neighbour (last call of selectNeighbour method) 486 * @return search time 487 **/ 488 public long getTime() { 489 if (iT1 == 0) return JProf.currentTimeMillis() - iT0; 490 return iT1 - iT0; 491 } 492 493 /** 494 * True, if timeout was reached during the last call of selectNeighbour 495 * method 496 * @return true if the timeout was reached 497 */ 498 public boolean isTimeoutReached() { 499 return iTimeoutReached; 500 } 501 502 /** 503 * True, if the maximum number of iterations was reached by the last call of 504 * selectNeighbour method 505 * @return true if the maximum number of iterations was reached 506 */ 507 public boolean isMaxItersReached() { 508 return iMaxItersReached; 509 } 510 511 public BackTrackNeighbour getBackTrackNeighbour() { return iBackTrackNeighbour; } 512 513 public void incIteration() { 514 iT1 = JProf.currentTimeMillis(); 515 if (!iTimeoutReached && iTimeout > 0 && iT1 - iT0 > iTimeout) 516 iTimeoutReached = true; 517 if (!iMaxItersReached && iMaxIters > 0 && iNrIters++ > iMaxIters) 518 iMaxItersReached = true; 519 } 520 521 public void saveBest(List<V> variables2resolve) { 522 if (sLog.isDebugEnabled()) 523 sLog.debug(" -- all assigned"); 524 if (iSolution.getAssignment().nrAssignedVariables() > iNrAssigned || (iSolution.getAssignment().nrAssignedVariables() == iNrAssigned && iValue > iSolution.getModel().getTotalValue(iSolution.getAssignment()))) { 525 if (sLog.isDebugEnabled()) 526 sLog.debug(" -- better than current"); 527 if (iBackTrackNeighbour == null || iBackTrackNeighbour.compareTo(iSolution) >= 0) { 528 if (sLog.isDebugEnabled()) 529 sLog.debug(" -- better than best"); 530 iBackTrackNeighbour = new BackTrackNeighbour(this, variables2resolve); 531 } 532 } 533 } 534 535 public void saveBest(@SuppressWarnings("unchecked") V... variables2resolve) { 536 if (sLog.isDebugEnabled()) 537 sLog.debug(" -- all assigned"); 538 if (iSolution.getAssignment().nrAssignedVariables() > iNrAssigned || (iSolution.getAssignment().nrAssignedVariables() == iNrAssigned && iValue > iSolution.getModel().getTotalValue(iSolution.getAssignment()))) { 539 if (sLog.isDebugEnabled()) 540 sLog.debug(" -- better than current"); 541 if (iBackTrackNeighbour == null || iBackTrackNeighbour.compareTo(iSolution) >= 0) { 542 if (sLog.isDebugEnabled()) 543 sLog.debug(" -- better than best"); 544 iBackTrackNeighbour = new BackTrackNeighbour(this, variables2resolve); 545 } 546 } 547 } 548 549 public Model<V, T> getModel() { return iSolution.getModel();} 550 551 public Assignment<V, T> getAssignment() { return iSolution.getAssignment(); } 552 } 553}