From 0259484c90c3174d67c3d3a6c5ae15a6a6d61d07 Mon Sep 17 00:00:00 2001 From: Ruediger Lunde Date: Sun, 2 Jun 2019 13:52:18 +0200 Subject: [PATCH] Local search implementations simplified. Success indication now conforms to standard search interface. --- .../core/search/local/HillClimbingSearch.java | 48 ++++-------- .../local/SimulatedAnnealingSearch.java | 73 +++++-------------- .../core/unit/search/SearchTestSuite.java | 3 +- .../local/SimulatedAnnealingSearchTest.java | 25 ------- .../aima/gui/demo/search/EightPuzzleDemo.java | 3 +- .../aima/gui/demo/search/NQueensDemo.java | 6 +- .../search/NQueensSearchDemo.java | 4 +- .../SimulatedAnnealingMaximumFinderApp.java | 2 +- 8 files changed, 40 insertions(+), 124 deletions(-) delete mode 100644 aima-core/src/test/java/aima/test/core/unit/search/local/SimulatedAnnealingSearchTest.java diff --git a/aima-core/src/main/java/aima/core/search/local/HillClimbingSearch.java b/aima-core/src/main/java/aima/core/search/local/HillClimbingSearch.java index a2d72c2426..9e2287b9c7 100644 --- a/aima-core/src/main/java/aima/core/search/local/HillClimbingSearch.java +++ b/aima-core/src/main/java/aima/core/search/local/HillClimbingSearch.java @@ -37,16 +37,11 @@ */ public class HillClimbingSearch implements SearchForActions, SearchForStates, Informed { - public enum SearchOutcome { - FAILURE, SOLUTION_FOUND - } - public static final String METRIC_NODES_EXPANDED = "nodesExpanded"; public static final String METRIC_NODE_VALUE = "nodeValue"; private ToDoubleFunction> h = null; private final NodeExpander nodeExpander; - private SearchOutcome outcome = SearchOutcome.FAILURE; private S lastState = null; private Metrics metrics = new Metrics(); @@ -83,58 +78,41 @@ public Optional findState(Problem p) { } /** - * Returns a node corresponding to a local maximum or empty if the search was - * cancelled by the user. + * Returns a node corresponding to a goal state or empty. Method {@link #getLastState()} + * provides the local maximum if result is empty. * * @param p the search problem * @return a node or empty */ - // function HILL-CLIMBING(problem) returns a state that is a local maximum + /// function HILL-CLIMBING(problem) returns a state that is a local maximum public Optional> findNode(Problem p) { clearMetrics(); - outcome = SearchOutcome.FAILURE; - // current <- MAKE-NODE(problem.INITIAL-STATE) + /// current <- MAKE-NODE(problem.INITIAL-STATE) Node current = nodeExpander.createRootNode(p.getInitialState()); Node neighbor; - // loop do + /// loop do while (!Tasks.currIsCancelled()) { - lastState = current.getState(); metrics.set(METRIC_NODE_VALUE, getValue(current)); List> children = nodeExpander.expand(current, p); - // neighbor <- a highest-valued successor of current + /// neighbor <- a highest-valued successor of current neighbor = getHighestValuedNodeFrom(children); - // if neighbor.VALUE <= current.VALUE then return current.STATE + /// if neighbor.VALUE <= current.VALUE then return current.STATE if (neighbor == null || getValue(neighbor) <= getValue(current)) { - if (p.testSolution(current)) - outcome = SearchOutcome.SOLUTION_FOUND; - return Optional.of(current); + lastState = current.getState(); + return Optional.ofNullable(p.testSolution(current) ? current : null); } - // current <- neighbor + /// current <- neighbor current = neighbor; } + lastState = current.getState(); return Optional.empty(); } /** - * Returns SOLUTION_FOUND if the local maximum is a goal state, or FAILURE - * if the local maximum is not a goal state. - * - * @return SOLUTION_FOUND if the local maximum is a goal state, or FAILURE - * if the local maximum is not a goal state. - */ - public SearchOutcome getOutcome() { - return outcome; - } - - /** - * Returns the last state from which the hill climbing search found the - * local maximum. - * - * @return the last state from which the hill climbing search found the - * local maximum. + * Returns the last explored state which is at least a local maximum if search was not cancelled by the user. */ - public S getLastSearchState() { + public S getLastState() { return lastState; } diff --git a/aima-core/src/main/java/aima/core/search/local/SimulatedAnnealingSearch.java b/aima-core/src/main/java/aima/core/search/local/SimulatedAnnealingSearch.java index c4daf8fa82..990f5a185b 100644 --- a/aima-core/src/main/java/aima/core/search/local/SimulatedAnnealingSearch.java +++ b/aima-core/src/main/java/aima/core/search/local/SimulatedAnnealingSearch.java @@ -7,7 +7,6 @@ import java.util.List; import java.util.Optional; -import java.util.Random; import java.util.function.Consumer; import java.util.function.ToDoubleFunction; @@ -41,10 +40,6 @@ */ public class SimulatedAnnealingSearch implements SearchForActions, SearchForStates { - public enum SearchOutcome { - FAILURE, SOLUTION_FOUND - } - public static final String METRIC_NODES_EXPANDED = "nodesExpanded"; public static final String METRIC_TEMPERATURE = "temp"; public static final String METRIC_NODE_VALUE = "nodeValue"; @@ -52,8 +47,7 @@ public enum SearchOutcome { private final ToDoubleFunction> h; private final Scheduler scheduler; private final NodeExpander nodeExpander; - - private SearchOutcome outcome = SearchOutcome.FAILURE; + private S lastState; private Metrics metrics = new Metrics(); @@ -100,69 +94,49 @@ public Optional findState(Problem p) { return SearchUtils.toState(findNode(p)); } - // function SIMULATED-ANNEALING(problem, schedule) returns a solution state + /** + * Returns a node corresponding to a goal state or empty. Method {@link #getLastState()} + * provides the last explored state if result is empty. + */ + /// function SIMULATED-ANNEALING(problem, schedule) returns a solution state public Optional> findNode(Problem p) { clearMetrics(); - outcome = SearchOutcome.FAILURE; - lastState = null; - // current <- MAKE-NODE(problem.INITIAL-STATE) + /// current <- MAKE-NODE(problem.INITIAL-STATE) Node current = nodeExpander.createRootNode(p.getInitialState()); - // for t = 1 to INFINITY do + /// for t = 1 to INFINITY do int timeStep = 0; while (!Tasks.currIsCancelled()) { - // temperature <- schedule(t) + /// temperature <- schedule(t) double temperature = scheduler.getTemp(timeStep); timeStep++; lastState = current.getState(); - // if temperature = 0 then return current + /// if temperature = 0 then return current if (temperature == 0.0) { - if (p.testSolution(current)) - outcome = SearchOutcome.SOLUTION_FOUND; - return Optional.of(current); + lastState = current.getState(); + return Optional.ofNullable(p.testSolution(current) ? current : null); } updateMetrics(temperature, getValue(current)); List> children = nodeExpander.expand(current, p); if (children.size() > 0) { - // next <- a randomly selected successor of current + /// next <- a randomly selected successor of current Node next = Util.selectRandomlyFromList(children); - // /\E <- next.VALUE - current.value + /// /\E <- next.VALUE - current.value double deltaE = getValue(next) - getValue(current); - - if (shouldAccept(temperature, deltaE)) { + /// if /\E > 0 then current <- next + /// else current <- next only with probability eˆ(/\E/T) + if (deltaE > 0.0 || Math.random() <= Math.exp(deltaE / temperature)) current = next; - } } } + lastState = current.getState(); return Optional.empty(); } /** - * Returns e&deltaE / T - * - * @param temperature - * T, a "temperature" controlling the probability of - * downward steps - * @param deltaE - * VALUE[next] - VALUE[current] - * @return e&deltaE / T + * Returns the last explored state. */ - public double probabilityOfAcceptance(double temperature, double deltaE) { - return Math.exp(deltaE / temperature); - } - - public SearchOutcome getOutcome() { - return outcome; - } - - /** - * Returns the last state from which the simulated annealing search found a - * solution state. - * - * @return the last state from which the simulated annealing search found a - * solution state. - */ - public Object getLastSearchState() { + public Object getLastState() { return lastState; } @@ -202,13 +176,6 @@ public boolean removeNodeListener(Consumer> listener) { // PRIVATE METHODS // - // if /\E > 0 then current <- next - // else current <- next only with probability e^(/\E/T) - private boolean shouldAccept(double temperature, double deltaE) { - return (deltaE > 0.0) - || (Math.random() <= probabilityOfAcceptance(temperature, deltaE)); - } - private double getValue(Node n) { // assumption greater heuristic value => // HIGHER on hill; 0 == goal state; diff --git a/aima-core/src/test/java/aima/test/core/unit/search/SearchTestSuite.java b/aima-core/src/test/java/aima/test/core/unit/search/SearchTestSuite.java index afc717fd36..46db8ca402 100644 --- a/aima-core/src/test/java/aima/test/core/unit/search/SearchTestSuite.java +++ b/aima-core/src/test/java/aima/test/core/unit/search/SearchTestSuite.java @@ -10,7 +10,6 @@ import aima.test.core.unit.search.informed.AStarSearchTest; import aima.test.core.unit.search.informed.GreedyBestFirstSearchTest; import aima.test.core.unit.search.informed.RecursiveBestFirstSearchTest; -import aima.test.core.unit.search.local.SimulatedAnnealingSearchTest; import aima.test.core.unit.search.nondeterministic.AndOrSearchTest; import aima.test.core.unit.search.online.LRTAStarAgentTest; import aima.test.core.unit.search.online.OnlineDFSAgentTest; @@ -21,7 +20,7 @@ @RunWith(Suite.class) @Suite.SuiteClasses({ AssignmentTest.class, CSPTest.class, MapCSPTest.class, MetricsTest.class, TreeCspSolverTest.class, AStarSearchTest.class, GreedyBestFirstSearchTest.class, RecursiveBestFirstSearchTest.class, - SimulatedAnnealingSearchTest.class, AndOrSearchTest.class, LRTAStarAgentTest.class, OnlineDFSAgentTest.class, + AndOrSearchTest.class, LRTAStarAgentTest.class, OnlineDFSAgentTest.class, BidirectionalSearchTest.class, BreadthFirstSearchTest.class, DepthFirstSearchTest.class, DepthLimitedSearchTest.class, IterativeDeepeningSearchTest.class, UniformCostSearchTest.class, NodeTest.class, SolutionTesterTest.class }) diff --git a/aima-core/src/test/java/aima/test/core/unit/search/local/SimulatedAnnealingSearchTest.java b/aima-core/src/test/java/aima/test/core/unit/search/local/SimulatedAnnealingSearchTest.java deleted file mode 100644 index b282bb7ce6..0000000000 --- a/aima-core/src/test/java/aima/test/core/unit/search/local/SimulatedAnnealingSearchTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package aima.test.core.unit.search.local; - -import aima.core.agent.Action; -import org.junit.Assert; -import org.junit.Test; - -import aima.core.search.local.SimulatedAnnealingSearch; - -public class SimulatedAnnealingSearchTest { - - @Test - public void testForGivenNegativeDeltaEProbabilityOfAcceptanceDecreasesWithDecreasingTemperature() { - // this isn't very nice. the object's state is uninitialized but is ok - // for this test. - SimulatedAnnealingSearch search = new SimulatedAnnealingSearch<>(null); - int deltaE = -1; - double higherTemperature = 30.0; - double lowerTemperature = 29.5; - - Assert.assertTrue(search.probabilityOfAcceptance(lowerTemperature, - deltaE) < search.probabilityOfAcceptance(higherTemperature, - deltaE)); - } - -} diff --git a/aima-gui/src/main/java/aima/gui/demo/search/EightPuzzleDemo.java b/aima-gui/src/main/java/aima/gui/demo/search/EightPuzzleDemo.java index 25a7d83dc6..30373e990d 100644 --- a/aima-gui/src/main/java/aima/gui/demo/search/EightPuzzleDemo.java +++ b/aima-gui/src/main/java/aima/gui/demo/search/EightPuzzleDemo.java @@ -119,8 +119,7 @@ private static void eightPuzzleSimulatedAnnealingDemo() { (EightPuzzleFunctions::getManhattanDistance); SearchAgent agent = new SearchAgent<>(problem, search); printActions(agent.getActions()); - System.out.println("Search Outcome=" + search.getOutcome()); - System.out.println("Final State:\n" + search.getLastSearchState()); + System.out.println("Final State:\n" + search.getLastState()); printInstrumentation(agent.getInstrumentation()); } catch (Exception e) { e.printStackTrace(); diff --git a/aima-gui/src/main/java/aima/gui/demo/search/NQueensDemo.java b/aima-gui/src/main/java/aima/gui/demo/search/NQueensDemo.java index 1edf785030..f736d980c1 100644 --- a/aima-gui/src/main/java/aima/gui/demo/search/NQueensDemo.java +++ b/aima-gui/src/main/java/aima/gui/demo/search/NQueensDemo.java @@ -101,8 +101,7 @@ private static void solveNQueensWithSimulatedAnnealingSearch() { actions.ifPresent(qActions -> qActions.forEach(System.out::println)); System.out.println(search.getMetrics()); - System.out.println("Search Outcome=" + search.getOutcome()); - System.out.println("Final State:\n" + search.getLastSearchState()); + System.out.println("Final State:\n" + search.getLastState()); } private static void solveNQueensWithHillClimbingSearch() { @@ -116,8 +115,7 @@ private static void solveNQueensWithHillClimbingSearch() { actions.ifPresent(qActions -> qActions.forEach(System.out::println)); System.out.println(search.getMetrics()); - System.out.println("Search Outcome=" + search.getOutcome()); - System.out.println("Final State:\n" + search.getLastSearchState()); + System.out.println("Final State:\n" + search.getLastState()); } private static void solveNQueensWithGeneticAlgorithmSearch() { diff --git a/aima-gui/src/main/java/aima/gui/fx/applications/search/NQueensSearchDemo.java b/aima-gui/src/main/java/aima/gui/fx/applications/search/NQueensSearchDemo.java index 1503d3e4a5..20ca6931f4 100644 --- a/aima-gui/src/main/java/aima/gui/fx/applications/search/NQueensSearchDemo.java +++ b/aima-gui/src/main/java/aima/gui/fx/applications/search/NQueensSearchDemo.java @@ -105,7 +105,7 @@ public void startHillClimbingExperiment() { search.addNodeListener(n -> notifyProgressTrackers(n.getState(), search.getMetrics())); search.findActions(problem); - board = (NQueensBoard) ((HillClimbingSearch) search).getLastSearchState(); + board = (NQueensBoard) ((HillClimbingSearch) search).getLastState(); notifyProgressTrackers(board, search.getMetrics()); } @@ -117,7 +117,7 @@ public void startSimulatedAnnealingExperiment() { search.addNodeListener(n -> notifyProgressTrackers(n.getState(), search.getMetrics())); search.findActions(problem); - board = (NQueensBoard) ((SimulatedAnnealingSearch) search).getLastSearchState(); + board = (NQueensBoard) ((SimulatedAnnealingSearch) search).getLastState(); notifyProgressTrackers(board, search.getMetrics()); } diff --git a/aima-gui/src/main/java/aima/gui/fx/applications/search/local/SimulatedAnnealingMaximumFinderApp.java b/aima-gui/src/main/java/aima/gui/fx/applications/search/local/SimulatedAnnealingMaximumFinderApp.java index bda1ecc512..d96feceb92 100644 --- a/aima-gui/src/main/java/aima/gui/fx/applications/search/local/SimulatedAnnealingMaximumFinderApp.java +++ b/aima-gui/src/main/java/aima/gui/fx/applications/search/local/SimulatedAnnealingMaximumFinderApp.java @@ -114,7 +114,7 @@ public void startExperiment() { search = new SimulatedAnnealingSearch<>(n -> 1 - func.apply(n.getState()), scheduler); search.addNodeListener(n -> updateStateView(n.getState())); search.findActions(problem); - updateStateView(search.getLastSearchState()); + updateStateView(search.getLastState()); } /** Creates a random initial state for the maximum search problem. */