Skip to content
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

Merged
merged 9 commits into from
Jun 14, 2017
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,11 @@
*.log
*.jar
*.zip
*.iml
.classpath
.project
**/.settings/**
**/.idea/**


/target/
Empty file modified .travis/add-sonatype-server.py
100755 → 100644
Empty file.
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<groupId>org.jamesframework</groupId>
<artifactId>james</artifactId>
<version>1.3-SNAPSHOT</version>
<relativePath>../james/james</relativePath>
Copy link
Owner

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.

Copy link
Contributor Author

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.

Copy link
Owner

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.

</parent>
<!-- james core specifications -->
<artifactId>james-core</artifactId>
Expand Down
171 changes: 138 additions & 33 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,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;
Expand Down Expand Up @@ -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,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once this method's implementation is finished, the other getBestMove method should relay to this one with acceptFirstImprovement set to false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a problem with JAMES but with Eclipse. See #41 and #31.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem solved by switching to IntelliJ IDEA

boolean requireImprovement,
boolean acceptFirstImprovement,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the parameter acceptFirstImprovement is never checked.

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;
}

/**
Expand Down
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>{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also test this new Tabu search.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test added in the new pull request.

Copy link
Owner

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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?

Copy link
Owner

@hdbeukel hdbeukel Jun 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't specify an explicit branch when pushing, git will push to the remote branch that has been associated with your local branch. So yes, these commits will end up in the same branch each time. In your case tabuSearch/add_variant.

Are you using git on command line or a desktop client?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ protected void searchStep() {
false,
// filter tabu moves (with aspiration criterion)
m -> !tabuMemory.isTabu(m, getCurrentSolution())
|| computeDelta(evaluate(m), getBestSolutionEvaluation()) > 0
|| (validate(m).passed() &&
computeDelta(evaluate(m), getBestSolutionEvaluation()) > 0)
);
if(move != null){
// accept move (also updates tabu memory by overriding move acceptance)
Expand Down
Loading