Skip to content

Commit

Permalink
Add getBestMove to NeighbourhoodSearch. Add FirstBestAdmissibleTabuSe…
Browse files Browse the repository at this point in the history
…arch.java
  • Loading branch information
huanfachen committed May 31, 2017
1 parent f6bfd50 commit 1c9e3b8
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 0 deletions.
105 changes: 105 additions & 0 deletions src/main/java/org/jamesframework/core/search/NeighbourhoodSearch.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import java.util.Arrays;
import org.jamesframework.core.search.status.SearchStatus;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import org.jamesframework.core.exceptions.SearchException;
import org.jamesframework.core.problems.Problem;
Expand Down Expand Up @@ -353,6 +355,8 @@ protected final Move<? super SolutionType> getBestMove(Collection<? extends Move
bestMove = move;
bestMoveDelta = curMoveDelta;
bestMoveEvaluation = curMoveEvaluation;
// should update the bestMoveValidation
bestMoveValidation = curMoveValidation;
}
}
}
Expand All @@ -366,6 +370,107 @@ protected final Move<? super SolutionType> getBestMove(Collection<? extends Move
return bestMove;
}

/**
* <p>
* Get the best valid move among a collection of possible moves. The best valid move is the FIRST one yielding the positive delta
* or the one that is non-tabu and yield the largest delta(see {@link #computeDelta(Evaluation, Evaluation)}) when being applied to the current solution.
* </p>
* <p>
* If <code>requireImprovement</code> is set to <code>true</code>, only moves that improve the current solution
* are considered, i.e. moves that yield a positive delta (unless the current solution is invalid, then all
* valid moves are improvements). Any number of additional filters can be specified so that moves are only
* considered if they pass through all filters. Each filter is a predicate that should return <code>true</code>
* if a given move is to be considered. If any filter returns <code>false</code> for a specific move, this
* move is discarded.
* </p>
* <p>
* Returns <code>null</code> if no move is found that satisfies all conditions.
* </p>
* <p>
* Note that all computed evaluations and validations are cached.
* Before returning the selected best move, if any, its evaluation and validity are cached
* again to maximize the probability that these values will remain available in the cache.
* </p>
*
* @param moves collection of possible moves
* @param requireImprovement if set to <code>true</code>, only improving moves are considered
* @param acceptFirstImprovement if set to <code>true</code>, return the first improvement move
* @param filters additional move filters
* @return first improving move or the best valid move, may be <code>null</code>
*/
@SafeVarargs
protected final Move<? super SolutionType> getBestMove(List<? extends Move<? super SolutionType>> moves,
boolean requireImprovement,
boolean acceptFirstImprovement,
Predicate<? super Move<? super SolutionType>>... filters){
Collections.shuffle(moves);
// track first improvement move AND best valid move + corresponding evaluation, validation and delta
Move<? super SolutionType> bestMove = null, firstImprovementMove = null;
double bestMoveDelta = -Double.MAX_VALUE, curMoveDelta, firstImprovementMoveDelta = -Double.MAX_VALUE;
Evaluation curMoveEvaluation, bestMoveEvaluation = null, firstImprovementMoveEvaluation = null;
Validation curMoveValidation, bestMoveValidation = null, firstImprovementMoveValidation = null;
// go through all moves
for (Move<? super SolutionType> move : moves) {
// check filters
if(Arrays.stream(filters).allMatch(filter -> filter.test(move))){
// validate move
curMoveValidation = validate(move);
if (curMoveValidation.passed()) {
// evaluate move
curMoveEvaluation = evaluate(move);
// compute delta
curMoveDelta = computeDelta(curMoveEvaluation, getCurrentSolutionEvaluation());

// compare with current solution
if(curMoveDelta > 0 // better than curSolution
|| !getCurrentSolutionValidation().passed()){
firstImprovementMove = move;
firstImprovementMoveDelta = curMoveDelta;
firstImprovementMoveEvaluation = curMoveEvaluation;
firstImprovementMoveValidation = curMoveValidation;
break;
}
// if (curMoveDelta > bestMoveDelta // higher delta
// && (!requireImprovement // ensure improvement, if required
// || curMoveDelta > 0
// || !getCurrentSolutionValidation().passed())) {
// bestMove = move;
// bestMoveDelta = curMoveDelta;
// bestMoveEvaluation = curMoveEvaluation;
// bestMoveValidation = curMoveValidation;
// break;
// }
// compare with best move
if (curMoveDelta > bestMoveDelta){ // higher delta
bestMoveDelta = curMoveDelta;
bestMove = move;
bestMoveEvaluation = curMoveEvaluation;
bestMoveValidation = curMoveValidation;
}


}
}
}
// re-cache best move, if any
if(bestMove != null && cache != null){
cache.cacheMoveEvaluation(bestMove, bestMoveEvaluation);
cache.cacheMoveValidation(bestMove, bestMoveValidation);
}
// re-cache best non-tabu move, if any
if(firstImprovementMove != null && cache != null){
cache.cacheMoveEvaluation(firstImprovementMove, firstImprovementMoveEvaluation);
cache.cacheMoveValidation(firstImprovementMove, firstImprovementMoveValidation);
}
// If firstImprovementMove is not null, return firstImprovementMove.
// Otherwise, return bestMove (which should be no better than currentSolution but be better than any other move)
if(firstImprovementMove != null)
return firstImprovementMove;
else
return bestMove;

}

/**
* Accept the given move by applying it to the current solution. Updates the evaluation and validation of
* the current solution and checks whether a new best solution has been found. The updates only take place
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.jamesframework.core.search.algo.tabu;

import java.util.Arrays;
import java.util.Collection;
import java.util.function.Predicate;

import org.jamesframework.core.problems.Problem;
import org.jamesframework.core.problems.constraints.validations.Validation;
import org.jamesframework.core.problems.objectives.evaluations.Evaluation;
import org.jamesframework.core.problems.sol.Solution;
import org.jamesframework.core.search.SingleNeighbourhoodSearch;
import org.jamesframework.core.search.neigh.Move;
import org.jamesframework.core.search.neigh.Neighbourhood;
/**
* Tabu search algorithm using first-best-admissible move strategy.
* In every search step, shuffle the list of moves and then iterate over all moves. If a valid neighbour that is better
* than the current solution is found, it is adopted as the new solution. Otherwise, the best valid and non-tabu neighbour
* is adopted, even if it is no improvement over the current solution, which is the same as the ordinary tabu search.
* To avoid repeatedly revisiting the same solutions, moves might be declared tabu based on a tabu memory.
* This memory dynamically tracks (a limited number of) recently visited solutions, features of these solutions and/or
* recently applied moves (i.e. recently modified features). If a move is tabu, it is not considered, unless it yields
* a solution which is better than the best solution found so far (aspiration criterion).
* <p>
* If all valid neighbours of the current solution are tabu and no valid neighbours are better than the current solution,
* the search stops. Note that this may never happen so that a stop criterion should preferably be set to ensure termination.
*
* @param <SolutionType> solution type of the problems that may be solved using this search, required to extend {@link Solution}
* @author <a href="mailto:chenhuanfa@gmail.com">Huanfa Chen</a>
*/
public class FirstBestAdmissibleTabuSearch<SolutionType extends Solution> extends TabuSearch<SolutionType>{

public FirstBestAdmissibleTabuSearch(Problem<SolutionType> problem, Neighbourhood<? super SolutionType> neighbourhood,
TabuMemory<SolutionType> tabuMemory) {
super(problem, neighbourhood, tabuMemory);
// TODO Auto-generated constructor stub
}

@Override
protected void searchStep() {
// get best valid, non tabu move
Move<? super SolutionType> move = getBestMove(
// inspect all moves
getNeighbourhood().getAllMoves(getCurrentSolution()),
// not necessarily an improvement
false,
// return first improvement move
true,
// filter tabu moves (with aspiration criterion)
m -> !getTabuMemory().isTabu(m, getCurrentSolution())
|| computeDelta(evaluate(m), getBestSolutionEvaluation()) > 0
);
if(move != null){
// accept move (also updates tabu memory by overriding move acceptance)
accept(move);
} else {
// no valid, non tabu neighbour found: stop search
stop();
}
}
}

0 comments on commit 1c9e3b8

Please sign in to comment.