-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add getBestMove and FirstBestAdmissibleTabuSearch #39
Changes from 1 commit
1c9e3b8
e06489b
a25b183
df1fe55
80184ef
5d564cb
9eaf5c4
d687bc9
37e899e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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; | ||
} | ||
} | ||
} | ||
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the parameter |
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need separate variables for first and best improvement. One variable, say There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need to store first and best move (and their metadata separately). You can have one series of variables for the chosen move, say |
||
Validation curMoveValidation, bestMoveValidation = null, firstImprovementMoveValidation = null; | ||
// go through all moves | ||
for (Move<? super SolutionType> move : moves) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use an |
||
// 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 | ||
|
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>{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please also test this new Tabu search. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test added in the new pull request. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't create a new pull request for each commit. Just keep committing to the same branch, all changes will show up here. I closed the other pull request #43. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not very familiar with Github. Say, I add and commit the changes, and push them to the remote repo. Will it be automatically committed to the same branch? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you don't specify an explicit branch when pushing, Are you using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am using git bash on Windows. Just add a new push. |
||
|
||
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(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameter
acceptFirstImprovement
is never used. You implementation returns the first improvement, even ifacceptFirstImprovement
isfalse
. See comments below to fix.