Skip to content

Commit

Permalink
clean up tabu search tests
Browse files Browse the repository at this point in the history
  • Loading branch information
hdbeukel committed Jun 20, 2017
1 parent b9f89e7 commit c9047dd
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@

package org.jamesframework.core.search;

import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.jamesframework.core.exceptions.SearchException;
import org.jamesframework.core.problems.Problem;
import org.jamesframework.core.problems.solutions.SolutionTest;
import org.jamesframework.core.subset.SubsetProblem;
import org.jamesframework.core.problems.sol.Solution;
import org.jamesframework.core.problems.constraints.Constraint;
Expand All @@ -32,19 +34,25 @@
import org.jamesframework.core.search.neigh.Move;
import org.jamesframework.core.search.stopcriteria.MaxSteps;
import org.jamesframework.core.subset.neigh.SingleDeletionNeighbourhood;
import org.jamesframework.core.subset.neigh.moves.*;
import org.jamesframework.core.subset.neigh.moves.AdditionMove;
import org.jamesframework.core.subset.neigh.moves.DeletionMove;
import org.jamesframework.core.subset.neigh.moves.GeneralSubsetMove;
import org.jamesframework.core.subset.neigh.moves.SubsetMove;
import org.jamesframework.core.subset.neigh.moves.SwapMove;
import org.jamesframework.core.util.JamesConstants;
import org.jamesframework.core.util.SetUtilities;
import org.jamesframework.test.util.DoubleComparatorWithPrecision;
import org.jamesframework.test.stubs.NeverSatisfiedConstraintStub;
import org.jamesframework.test.util.TestConstants;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.Before;

import static org.junit.Assert.*;

/**
* Test abstract neighbourhood search behaviour.
*
Expand Down Expand Up @@ -72,8 +80,6 @@ public static void tearDownClass() {
System.out.println("# Done testing NeighbourhoodSearch!");
}



/**
* Create search and components to work with in each test method.
*/
Expand Down Expand Up @@ -307,7 +313,9 @@ public void testIsImprovement() {
initial.selectAll(SetUtilities.getRandomSubset(initial.getUnselectedIDs(), SUBSET_SIZE, RG));
neighSearch.setCurrentSolution(initial);
// pick any addition move
AdditionMove m = new AdditionMove(SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getUnselectedIDs(), RG));
AdditionMove m = new AdditionMove(
SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getUnselectedIDs(), RG)
);
// verify: addition should increase score
assertTrue(neighSearch.isImprovement(m));
// apply move
Expand All @@ -324,7 +332,9 @@ public void testIsImprovement() {
initial.selectAll(SetUtilities.getRandomSubset(initial.getUnselectedIDs(), SUBSET_SIZE, RG));
neighSearch.setCurrentSolution(initial);
// verify: addition is no improvement
m = new AdditionMove(SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getUnselectedIDs(), RG));
m = new AdditionMove(
SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getUnselectedIDs(), RG)
);
assertFalse(neighSearch.isImprovement(m));
// apply addition
neighSearch.accept(m);
Expand All @@ -339,10 +349,16 @@ public void testIsImprovement() {
problem.addMandatoryConstraint(c);
neighSearch.setCurrentSolution(problem.createRandomSolution());
// create random addition, deletion and swap move
m = new AdditionMove(SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getUnselectedIDs(), RG));
m2 = new DeletionMove(SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getSelectedIDs(), RG));
SwapMove m3 = new SwapMove(SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getUnselectedIDs(), RG),
SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getSelectedIDs(), RG));
m = new AdditionMove(
SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getUnselectedIDs(), RG)
);
m2 = new DeletionMove(
SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getSelectedIDs(), RG)
);
SwapMove m3 = new SwapMove(
SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getUnselectedIDs(), RG),
SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getSelectedIDs(), RG)
);
// verify (no moves are considered improvements because of mandatory constraint)
assertFalse(neighSearch.isImprovement(m));
assertFalse(neighSearch.isImprovement(m2));
Expand All @@ -359,10 +375,16 @@ public void testIsImprovement() {
// set initial solution
neighSearch.setCurrentSolution(initial);
// create random addition, deletion and swap move
m = new AdditionMove(SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getUnselectedIDs(), RG));
m2 = new DeletionMove(SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getSelectedIDs(), RG));
m3 = new SwapMove(SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getUnselectedIDs(), RG),
SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getSelectedIDs(), RG));
m = new AdditionMove(SetUtilities.getRandomElement(
neighSearch.getCurrentSolution().getUnselectedIDs(), RG)
);
m2 = new DeletionMove(SetUtilities.getRandomElement(
neighSearch.getCurrentSolution().getSelectedIDs(), RG)
);
m3 = new SwapMove(
SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getUnselectedIDs(), RG),
SetUtilities.getRandomElement(neighSearch.getCurrentSolution().getSelectedIDs(), RG)
);
// verify that all moves are considered improvements, regardless of their evaluation,
// as yield valid neighbours of an invalid current solution
assertTrue(neighSearch.isImprovement(m));
Expand All @@ -375,7 +397,7 @@ public void testIsImprovement() {

}

private class InvalidateSelectedSolutionsConstraint implements Constraint<SubsetSolution, Object>{
private class InvalidateSelectedSolutionsConstraint implements Constraint<SubsetSolution, Object> {

// invalid solutions
private final Set<SubsetSolution> invalid;
Expand Down Expand Up @@ -445,89 +467,111 @@ public void testGetBestMove() {
bestMove = neighSearch.getBestMove(moves, true);
// verify: move with negative delta selected although improvement was required
assertNotNull(bestMove);
assertTrue(neighSearch.computeDelta(neighSearch.evaluate(bestMove), neighSearch.getCurrentSolutionEvaluation()) < 0);
assertTrue(
neighSearch.computeDelta(neighSearch.evaluate(bestMove), neighSearch.getCurrentSolutionEvaluation()) < 0
);
assertTrue(neighSearch.validate(bestMove).passed());
// remove constraint
assertTrue(problem.removeMandatoryConstraint(c));

}

/**
* Test of getBestMove method with acceptFirstImprovement as true, of class NeighbourhoodSearch.
* Test getBestMove method with acceptFirstImprovement enabled.
*/
@Test
public void testGetBestMoveAcceptFirstImprovement() throws Exception {
System.out.println(" - test getBestMove with acceptFirstImprovement as true");

System.out.println(" - test getBestMove with acceptFirstImprovement enabled");

// set random initial solution
neighSearch.setCurrentSolution(problem.createRandomSolution());

List<? extends Move<? super SubsetSolution>> moves = neigh.getAllMoves(neighSearch.getCurrentSolution());
Move<? super SubsetSolution> bestMoveFirstImprovement = neighSearch.getBestMove(moves, true, true);
Move<? super SubsetSolution> bestMove = neighSearch.getBestMove(moves, true, false);

// Evaluation prevSolutionEvaluation = neighSearch.getCurrentSolutionEvaluation();
// Solution curSolution = neighSearch.getCurrentSolution();
// apply best move until no more improvements found (important: only positive deltas allowed)
while(bestMove != null){
// apply move

// verify: bestMove should surpass bestMoveFirstImprovement?
assertTrue(neighSearch.evaluate(bestMove).getValue() >= neighSearch.
evaluate(bestMoveFirstImprovement).getValue());
// apply
neighSearch.accept(bestMoveFirstImprovement);
// retrieve all moves
List<SubsetMove> moves = neigh.getAllMoves(neighSearch.getCurrentSolution());
// find first and max improvement
Move<? super SubsetSolution> firstImprovement = neighSearch.getBestMove(moves, true, true);
Move<? super SubsetSolution> maxImprovement = neighSearch.getBestMove(moves, true, false);

// apply first improvement until no more improvements found (important: restrict to positive deltas)
while(firstImprovement != null){
// verify: best move should be at least as good as first improvement
assertTrue(
neighSearch.computeDelta(
neighSearch.evaluate(maxImprovement),
neighSearch.evaluate(firstImprovement)
)
>= 0
);
// apply first improvement
neighSearch.accept(firstImprovement);
// get new moves
moves = neigh.getAllMoves(neighSearch.getCurrentSolution());
// get move with largest positive delta
bestMove = neighSearch.getBestMove(moves, true, false);
bestMoveFirstImprovement = neighSearch.getBestMove(moves, true,true);
// find first and max improvement
maxImprovement = neighSearch.getBestMove(moves, true, false);
firstImprovement = neighSearch.getBestMove(moves, true,true);
}

// when curSolution is best, bestMove and bestMoveFirstImprovement should be the same
neighSearch.setCurrentSolution(neighSearch.getBestSolution());
// we are now in a local optimum, so the best move has a negative delta (or zero) and
// should be the same regardless of whether the first improvement is accepted
// (note: allow negative deltas here!)
moves = neigh.getAllMoves(neighSearch.getCurrentSolution());
assertTrue(moves != null);
bestMove = neighSearch.getBestMove(moves, false, false);
bestMoveFirstImprovement = neighSearch.getBestMove(moves, false,true);
assertTrue(bestMove != null);
assertTrue(bestMoveFirstImprovement != null);
assertTrue(bestMove == bestMoveFirstImprovement);

// when curSolution is a neighbour of the best solution, bestMove and bestMoveFirstImprovement should be different
neighSearch.setCurrentSolution(neighSearch.getBestSolution());
Evaluation bestSolutionEvaluation = neighSearch.getCurrentSolutionEvaluation();
Move<? super SubsetSolution> randomMove = neigh.getRandomMove(neighSearch.getCurrentSolution());
Move<? super SubsetSolution> bestMove = neighSearch.getBestMove(moves, false, false);
Move<? super SubsetSolution> bestMoveFirstImprovement = neighSearch.getBestMove(moves, false,true);
assertSame(bestMove, bestMoveFirstImprovement);
assertTrue(
neighSearch.computeDelta(neighSearch.evaluate(bestMove), neighSearch.getCurrentSolutionEvaluation()) <= 0
);
assertFalse(neighSearch.isImprovement(bestMove));

// find an inferior neighbour of the current local optimum
Evaluation bestEval = neighSearch.getCurrentSolutionEvaluation();
Move<? super SubsetSolution> move = null;
// obtain a randomMove that is worse than bestSolution
while(neighSearch.evaluate(randomMove).getValue() >= bestSolutionEvaluation.getValue())
randomMove = neigh.getRandomMove(neighSearch.getCurrentSolution());

neighSearch.accept(randomMove);

// should have more than 2 neighbours
assertTrue(neigh.getAllMoves(neighSearch.getCurrentSolution()).size() > 2);

// find a move that results to a solution whose quality is between bestSolution and curSolution
randomMove = neigh.getRandomMove(neighSearch.getCurrentSolution());
while(neighSearch.evaluate(randomMove).getValue() >= bestSolutionEvaluation.getValue())
randomMove = neigh.getRandomMove(neighSearch.getCurrentSolution());

// listMove: randomMove + allMoves
Collection<SubsetMove> listMove = new ArrayList<SubsetMove>();
listMove.add((SubsetMove)randomMove);
listMove.addAll(neigh.getAllMoves(neighSearch.getCurrentSolution()));
while(move == null || neighSearch.computeDelta(neighSearch.evaluate(move), bestEval) >= 0){
move = neigh.getRandomMove(neighSearch.getCurrentSolution());
}

bestMove = neighSearch.getBestMove(listMove, false, false);
bestMoveFirstImprovement = neighSearch.getBestMove(listMove, false,true);
// accept move towards inferior neighbour
neighSearch.accept(move);

assertTrue(bestMove != null);
assertTrue(bestMoveFirstImprovement != null);
// find an intermediate neighbour (between current and best solution)
Evaluation curEval = neighSearch.getCurrentSolutionEvaluation();
move = null;
while(move == null
|| neighSearch.computeDelta(neighSearch.evaluate(move), curEval) <= 0
|| neighSearch.computeDelta(neighSearch.evaluate(move), bestEval) >= 0){
move = neigh.getRandomMove(neighSearch.getCurrentSolution());
}

assertEquals(neighSearch.evaluate(bestMove).getValue(), bestSolutionEvaluation.getValue(), 1e-8);
assertTrue(bestMove != bestMoveFirstImprovement);
assertTrue(neighSearch.evaluate(bestMove).getValue() >
neighSearch.evaluate(bestMoveFirstImprovement).getValue());
// retrieve list with all possible moves
List<Move<? super SubsetSolution>> modifiedMoveList = new ArrayList<>(
neigh.getAllMoves(neighSearch.getCurrentSolution())
);
// prepend move that leads to intermediate neighbour
modifiedMoveList.add(0, move);

// find first and max improvement
firstImprovement = neighSearch.getBestMove(modifiedMoveList, true,true);
maxImprovement = neighSearch.getBestMove(modifiedMoveList, true, false);

// check both not null
assertNotNull(firstImprovement);
assertNotNull(maxImprovement);

// first and best improvement should be different!
assertNotSame(firstImprovement, maxImprovement);
// therefore, max improvement outperforms first improvement
assertTrue(
neighSearch.computeDelta(neighSearch.evaluate(maxImprovement), neighSearch.evaluate(firstImprovement)) >= 0
);
// first improvement = first move from list
assertSame(modifiedMoveList.get(0), firstImprovement);
// max improvement is as least as good as previously obtained local optimum
// (included in the current solution's neighbourhood due to symmetry)
assertTrue(neighSearch.computeDelta(neighSearch.evaluate(maxImprovement), bestEval) >= 0);
}

/**
* Test of accept method, of class NeighbourhoodSearch
*/
Expand All @@ -554,9 +598,17 @@ public void testAccept() {
copyEval = problem.evaluate(copy);
// verify
assertEquals(copy, neighSearch.getCurrentSolution());
assertEquals(copyEval.getValue(), neighSearch.getCurrentSolutionEvaluation().getValue(), TestConstants.DOUBLE_COMPARISON_PRECISION);
assertTrue(DoubleComparatorWithPrecision.greaterThanOrEqual(
neighSearch.getBestSolutionEvaluation().getValue(), copyEval.getValue(), TestConstants.DOUBLE_COMPARISON_PRECISION));
assertEquals(
copyEval.getValue(),
neighSearch.getCurrentSolutionEvaluation().getValue(),
TestConstants.DOUBLE_COMPARISON_PRECISION
);
assertTrue(
DoubleComparatorWithPrecision.greaterThanOrEqual(
neighSearch.getBestSolutionEvaluation().getValue(),
copyEval.getValue(),
TestConstants.DOUBLE_COMPARISON_PRECISION)
);
}

// try to accept an invalid move
Expand Down Expand Up @@ -594,9 +646,17 @@ public void testAcceptMinimizing() {
copyEval = problem.evaluate(copy);
// verify
assertEquals(copy, neighSearch.getCurrentSolution());
assertEquals(copyEval.getValue(), neighSearch.getCurrentSolutionEvaluation().getValue(), TestConstants.DOUBLE_COMPARISON_PRECISION);
assertTrue(DoubleComparatorWithPrecision.smallerThanOrEqual(
neighSearch.getBestSolutionEvaluation().getValue(), copyEval.getValue(), TestConstants.DOUBLE_COMPARISON_PRECISION));
assertEquals(
copyEval.getValue(),
neighSearch.getCurrentSolutionEvaluation().getValue(),
TestConstants.DOUBLE_COMPARISON_PRECISION
);
assertTrue(
DoubleComparatorWithPrecision.smallerThanOrEqual(
neighSearch.getBestSolutionEvaluation().getValue(),
copyEval.getValue(),
TestConstants.DOUBLE_COMPARISON_PRECISION)
);
}

}
Expand Down Expand Up @@ -631,7 +691,7 @@ protected void searchStep() {
*
* @param <SolutionType> solution type
*/
private class SearchListenerStub<SolutionType extends Solution> implements SearchListener<SolutionType>{
private class SearchListenerStub<SolutionType extends Solution> implements SearchListener<SolutionType> {

// number of calls of callback
private int numCalls = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,12 @@ protected void multiRunWithMaximumRuntime(Search<SubsetSolution> search, long ma
}

// listener that verifies whether every new best solution is indeed an improvement over the previous best solution
private class BestSolutionListener implements SearchListener<SubsetSolution>{
private class BestSolutionListener implements SearchListener<SubsetSolution> {

private Evaluation prevBestEval = null;
private double delta = 1e-12;
private boolean ok = true;

@Override
public void newBestSolution(Search<? extends SubsetSolution> search,
SubsetSolution newBestSolution,
Expand All @@ -240,9 +242,11 @@ public void newBestSolution(Search<? extends SubsetSolution> search,
}
prevBestEval = newBestSolutionEvaluation;
}

public boolean isOK(){
return ok;
}

}

}
Loading

0 comments on commit c9047dd

Please sign in to comment.