-
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 all commits
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 |
---|---|---|
|
@@ -6,4 +6,11 @@ | |
*.log | ||
*.jar | ||
*.zip | ||
*.iml | ||
.classpath | ||
.project | ||
**/.settings/** | ||
**/.idea/** | ||
|
||
|
||
/target/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,9 @@ | |
import java.util.Arrays; | ||
import org.jamesframework.core.search.status.SearchStatus; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
import java.util.function.Predicate; | ||
import org.jamesframework.core.exceptions.SearchException; | ||
import org.jamesframework.core.problems.Problem; | ||
|
@@ -329,41 +332,143 @@ && validate(move).passed() | |
protected final Move<? super SolutionType> getBestMove(Collection<? extends Move<? super SolutionType>> moves, | ||
boolean requireImprovement, | ||
Predicate<? super Move<? super SolutionType>>... filters){ | ||
// track best valid move + corresponding evaluation, validation and delta | ||
Move<? super SolutionType> bestMove = null; | ||
double bestMoveDelta = -Double.MAX_VALUE, curMoveDelta; | ||
Evaluation curMoveEvaluation, bestMoveEvaluation = null; | ||
Validation curMoveValidation, bestMoveValidation = 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 best move | ||
if (curMoveDelta > bestMoveDelta // higher delta | ||
&& (!requireImprovement // ensure improvement, if required | ||
|| curMoveDelta > 0 | ||
|| !getCurrentSolutionValidation().passed())) { | ||
bestMove = move; | ||
bestMoveDelta = curMoveDelta; | ||
bestMoveEvaluation = curMoveEvaluation; | ||
} | ||
} | ||
} | ||
return this.getBestMove(moves, requireImprovement, false, filters); | ||
} | ||
|
||
/** | ||
* <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(Collection<? extends Move<? super SolutionType>> 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. Once this method's implementation is finished, the other 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. Agree with the suggestion. Before adding tests, I found a problem in Line 445 of GenericProblem.java, which prevents the running of NeighbourhoodSearchTest. Could you have a look at it? 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. 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. Problem solved by switching to IntelliJ IDEA |
||
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){ | ||
// 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; | ||
|
||
// track the chosen move + corresponding evaluation, validation and delta | ||
Move<? super SolutionType> chosenMove = null; | ||
double chosenMoveDelta = -Double.MAX_VALUE, curMoveDelta = -Double.MAX_VALUE; | ||
Evaluation chosenMoveEvaluation = null, curMoveEvaluation = null; | ||
Validation chosenMoveValidation = null, curMoveValidation = null; | ||
Iterator<? extends Move<? super SolutionType>> iteMove = moves.iterator(); | ||
while(iteMove.hasNext() && | ||
!(acceptFirstImprovement && // if acceptFirstImprovement=false, should check every move | ||
chosenMoveDelta>0) ){ // if chosenMoveDelta<=0, should check every move | ||
Move<? super SolutionType> curMove = iteMove.next(); | ||
if(Arrays.stream(filters).allMatch(filter -> filter.test(curMove))){ | ||
curMoveValidation = validate(curMove); | ||
if(curMoveValidation.passed()){ | ||
curMoveEvaluation = evaluate(curMove); | ||
curMoveDelta = computeDelta(curMoveEvaluation, getCurrentSolutionEvaluation()); | ||
if(curMoveDelta > chosenMoveDelta && | ||
(!requireImprovement // ensure improvement, if required | ||
|| curMoveDelta > 0 | ||
|| !getCurrentSolutionValidation().passed())){ | ||
chosenMove = curMove; | ||
chosenMoveDelta = curMoveDelta; | ||
chosenMoveEvaluation = curMoveEvaluation; | ||
chosenMoveValidation = curMoveValidation; | ||
} | ||
} | ||
} | ||
} | ||
// re-cache best move, if any | ||
if(bestMove != null && cache != null){ | ||
cache.cacheMoveEvaluation(bestMove, bestMoveEvaluation); | ||
cache.cacheMoveValidation(bestMove, bestMoveValidation); | ||
// re-cache the choseMove, if any | ||
if(chosenMove != null && cache != null){ | ||
cache.cacheMoveEvaluation(chosenMove, chosenMoveEvaluation); | ||
cache.cacheMoveValidation(chosenMove, chosenMoveValidation); | ||
} | ||
// return best move | ||
return bestMove; | ||
|
||
return chosenMove; | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package org.jamesframework.core.search.algo.tabu; | ||
|
||
import java.util.List; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
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 | ||
List<? extends Move<? super SolutionType>> listMove = getNeighbourhood().getAllMoves(getCurrentSolution()); | ||
Collections.shuffle(listMove); | ||
Move<? super SolutionType> move = getBestMove( | ||
// inspect all moves | ||
listMove, | ||
// not necessarily an improvement | ||
false, | ||
// return first improvement move | ||
true, | ||
// filter tabu moves (with aspiration criterion) | ||
m -> !getTabuMemory().isTabu(m, getCurrentSolution()) | ||
|| (validate(m).passed() && | ||
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.
Why has this line been added? Should not be necessary.
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.
It is added to make the codes work on my computer, as I cloned James and James-core separately. I have added pom.xml to .gitignore. Please revise the pom.xml file accordingly.
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.
I see. I will remove this line when merging the pull request.