Skip to content

Commit

Permalink
Local search implementations simplified. Success indication now confo…
Browse files Browse the repository at this point in the history
…rms to standard search interface.
  • Loading branch information
RuedigerLunde committed Jun 2, 2019
1 parent a257480 commit 0259484
Show file tree
Hide file tree
Showing 8 changed files with 40 additions and 124 deletions.
Expand Up @@ -37,16 +37,11 @@
*/ */
public class HillClimbingSearch<S, A> implements SearchForActions<S, A>, SearchForStates<S, A>, Informed<S, A> { public class HillClimbingSearch<S, A> implements SearchForActions<S, A>, SearchForStates<S, A>, Informed<S, A> {


public enum SearchOutcome {
FAILURE, SOLUTION_FOUND
}

public static final String METRIC_NODES_EXPANDED = "nodesExpanded"; public static final String METRIC_NODES_EXPANDED = "nodesExpanded";
public static final String METRIC_NODE_VALUE = "nodeValue"; public static final String METRIC_NODE_VALUE = "nodeValue";


private ToDoubleFunction<Node<S, A>> h = null; private ToDoubleFunction<Node<S, A>> h = null;
private final NodeExpander<S, A> nodeExpander; private final NodeExpander<S, A> nodeExpander;
private SearchOutcome outcome = SearchOutcome.FAILURE;
private S lastState = null; private S lastState = null;
private Metrics metrics = new Metrics(); private Metrics metrics = new Metrics();


Expand Down Expand Up @@ -83,58 +78,41 @@ public Optional<S> findState(Problem<S, A> p) {
} }


/** /**
* Returns a node corresponding to a local maximum or empty if the search was * Returns a node corresponding to a goal state or empty. Method {@link #getLastState()}
* cancelled by the user. * provides the local maximum if result is empty.
* *
* @param p the search problem * @param p the search problem
* @return a node or empty * @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<Node<S, A>> findNode(Problem<S, A> p) { public Optional<Node<S, A>> findNode(Problem<S, A> p) {
clearMetrics(); clearMetrics();
outcome = SearchOutcome.FAILURE; /// current <- MAKE-NODE(problem.INITIAL-STATE)
// current <- MAKE-NODE(problem.INITIAL-STATE)
Node<S, A> current = nodeExpander.createRootNode(p.getInitialState()); Node<S, A> current = nodeExpander.createRootNode(p.getInitialState());
Node<S, A> neighbor; Node<S, A> neighbor;
// loop do /// loop do
while (!Tasks.currIsCancelled()) { while (!Tasks.currIsCancelled()) {
lastState = current.getState();
metrics.set(METRIC_NODE_VALUE, getValue(current)); metrics.set(METRIC_NODE_VALUE, getValue(current));
List<Node<S, A>> children = nodeExpander.expand(current, p); List<Node<S, A>> children = nodeExpander.expand(current, p);
// neighbor <- a highest-valued successor of current /// neighbor <- a highest-valued successor of current
neighbor = getHighestValuedNodeFrom(children); 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 (neighbor == null || getValue(neighbor) <= getValue(current)) {
if (p.testSolution(current)) lastState = current.getState();
outcome = SearchOutcome.SOLUTION_FOUND; return Optional.ofNullable(p.testSolution(current) ? current : null);
return Optional.of(current);
} }
// current <- neighbor /// current <- neighbor
current = neighbor; current = neighbor;
} }
lastState = current.getState();
return Optional.empty(); return Optional.empty();
} }


/** /**
* Returns SOLUTION_FOUND if the local maximum is a goal state, or FAILURE * Returns the last explored state which is at least a local maximum if search was not cancelled by the user.
* 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.
*/ */
public S getLastSearchState() { public S getLastState() {
return lastState; return lastState;
} }


Expand Down
Expand Up @@ -7,7 +7,6 @@


import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Random;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.ToDoubleFunction; import java.util.function.ToDoubleFunction;


Expand Down Expand Up @@ -41,19 +40,14 @@
*/ */
public class SimulatedAnnealingSearch<S, A> implements SearchForActions<S, A>, SearchForStates<S, A> { public class SimulatedAnnealingSearch<S, A> implements SearchForActions<S, A>, SearchForStates<S, A> {


public enum SearchOutcome {
FAILURE, SOLUTION_FOUND
}

public static final String METRIC_NODES_EXPANDED = "nodesExpanded"; public static final String METRIC_NODES_EXPANDED = "nodesExpanded";
public static final String METRIC_TEMPERATURE = "temp"; public static final String METRIC_TEMPERATURE = "temp";
public static final String METRIC_NODE_VALUE = "nodeValue"; public static final String METRIC_NODE_VALUE = "nodeValue";


private final ToDoubleFunction<Node<S, A>> h; private final ToDoubleFunction<Node<S, A>> h;
private final Scheduler scheduler; private final Scheduler scheduler;
private final NodeExpander<S, A> nodeExpander; private final NodeExpander<S, A> nodeExpander;


private SearchOutcome outcome = SearchOutcome.FAILURE;
private S lastState; private S lastState;
private Metrics metrics = new Metrics(); private Metrics metrics = new Metrics();


Expand Down Expand Up @@ -100,69 +94,49 @@ public Optional<S> findState(Problem<S, A> p) {
return SearchUtils.toState(findNode(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<Node<S, A>> findNode(Problem<S, A> p) { public Optional<Node<S, A>> findNode(Problem<S, A> p) {
clearMetrics(); clearMetrics();
outcome = SearchOutcome.FAILURE; /// current <- MAKE-NODE(problem.INITIAL-STATE)
lastState = null;
// current <- MAKE-NODE(problem.INITIAL-STATE)
Node<S, A> current = nodeExpander.createRootNode(p.getInitialState()); Node<S, A> current = nodeExpander.createRootNode(p.getInitialState());
// for t = 1 to INFINITY do /// for t = 1 to INFINITY do
int timeStep = 0; int timeStep = 0;
while (!Tasks.currIsCancelled()) { while (!Tasks.currIsCancelled()) {
// temperature <- schedule(t) /// temperature <- schedule(t)
double temperature = scheduler.getTemp(timeStep); double temperature = scheduler.getTemp(timeStep);
timeStep++; timeStep++;
lastState = current.getState(); lastState = current.getState();
// if temperature = 0 then return current /// if temperature = 0 then return current
if (temperature == 0.0) { if (temperature == 0.0) {
if (p.testSolution(current)) lastState = current.getState();
outcome = SearchOutcome.SOLUTION_FOUND; return Optional.ofNullable(p.testSolution(current) ? current : null);
return Optional.of(current);
} }


updateMetrics(temperature, getValue(current)); updateMetrics(temperature, getValue(current));
List<Node<S, A>> children = nodeExpander.expand(current, p); List<Node<S, A>> children = nodeExpander.expand(current, p);
if (children.size() > 0) { if (children.size() > 0) {
// next <- a randomly selected successor of current /// next <- a randomly selected successor of current
Node<S, A> next = Util.selectRandomlyFromList(children); Node<S, A> next = Util.selectRandomlyFromList(children);
// /\E <- next.VALUE - current.value /// /\E <- next.VALUE - current.value
double deltaE = getValue(next) - getValue(current); double deltaE = getValue(next) - getValue(current);

/// if /\E &gt; 0 then current &lt;- next
if (shouldAccept(temperature, deltaE)) { /// else current &lt;- next only with probability e&circ;(/\E/T)
if (deltaE > 0.0 || Math.random() <= Math.exp(deltaE / temperature))
current = next; current = next;
}
} }
} }
lastState = current.getState();
return Optional.empty(); return Optional.empty();
} }


/** /**
* Returns <em>e</em><sup>&delta<em>E / T</em></sup> * Returns the last explored state.
*
* @param temperature
* <em>T</em>, a "temperature" controlling the probability of
* downward steps
* @param deltaE
* VALUE[<em>next</em>] - VALUE[<em>current</em>]
* @return <em>e</em><sup>&delta<em>E / T</em></sup>
*/ */
public double probabilityOfAcceptance(double temperature, double deltaE) { public Object getLastState() {
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() {
return lastState; return lastState;
} }


Expand Down Expand Up @@ -202,13 +176,6 @@ public boolean removeNodeListener(Consumer<Node<S, A>> listener) {
// PRIVATE METHODS // 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<S, A> n) { private double getValue(Node<S, A> n) {
// assumption greater heuristic value => // assumption greater heuristic value =>
// HIGHER on hill; 0 == goal state; // HIGHER on hill; 0 == goal state;
Expand Down
Expand Up @@ -10,7 +10,6 @@
import aima.test.core.unit.search.informed.AStarSearchTest; import aima.test.core.unit.search.informed.AStarSearchTest;
import aima.test.core.unit.search.informed.GreedyBestFirstSearchTest; import aima.test.core.unit.search.informed.GreedyBestFirstSearchTest;
import aima.test.core.unit.search.informed.RecursiveBestFirstSearchTest; 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.nondeterministic.AndOrSearchTest;
import aima.test.core.unit.search.online.LRTAStarAgentTest; import aima.test.core.unit.search.online.LRTAStarAgentTest;
import aima.test.core.unit.search.online.OnlineDFSAgentTest; import aima.test.core.unit.search.online.OnlineDFSAgentTest;
Expand All @@ -21,7 +20,7 @@
@RunWith(Suite.class) @RunWith(Suite.class)
@Suite.SuiteClasses({ AssignmentTest.class, CSPTest.class, MapCSPTest.class, MetricsTest.class, TreeCspSolverTest.class, @Suite.SuiteClasses({ AssignmentTest.class, CSPTest.class, MapCSPTest.class, MetricsTest.class, TreeCspSolverTest.class,
AStarSearchTest.class, GreedyBestFirstSearchTest.class, RecursiveBestFirstSearchTest.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, BidirectionalSearchTest.class, BreadthFirstSearchTest.class, DepthFirstSearchTest.class,
DepthLimitedSearchTest.class, IterativeDeepeningSearchTest.class, UniformCostSearchTest.class, NodeTest.class, DepthLimitedSearchTest.class, IterativeDeepeningSearchTest.class, UniformCostSearchTest.class, NodeTest.class,
SolutionTesterTest.class }) SolutionTesterTest.class })
Expand Down

This file was deleted.

Expand Up @@ -119,8 +119,7 @@ private static void eightPuzzleSimulatedAnnealingDemo() {
(EightPuzzleFunctions::getManhattanDistance); (EightPuzzleFunctions::getManhattanDistance);
SearchAgent<Object, EightPuzzleBoard, Action> agent = new SearchAgent<>(problem, search); SearchAgent<Object, EightPuzzleBoard, Action> agent = new SearchAgent<>(problem, search);
printActions(agent.getActions()); printActions(agent.getActions());
System.out.println("Search Outcome=" + search.getOutcome()); System.out.println("Final State:\n" + search.getLastState());
System.out.println("Final State:\n" + search.getLastSearchState());
printInstrumentation(agent.getInstrumentation()); printInstrumentation(agent.getInstrumentation());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
Expand Down
6 changes: 2 additions & 4 deletions aima-gui/src/main/java/aima/gui/demo/search/NQueensDemo.java
Expand Up @@ -101,8 +101,7 @@ private static void solveNQueensWithSimulatedAnnealingSearch() {


actions.ifPresent(qActions -> qActions.forEach(System.out::println)); actions.ifPresent(qActions -> qActions.forEach(System.out::println));
System.out.println(search.getMetrics()); System.out.println(search.getMetrics());
System.out.println("Search Outcome=" + search.getOutcome()); System.out.println("Final State:\n" + search.getLastState());
System.out.println("Final State:\n" + search.getLastSearchState());
} }


private static void solveNQueensWithHillClimbingSearch() { private static void solveNQueensWithHillClimbingSearch() {
Expand All @@ -116,8 +115,7 @@ private static void solveNQueensWithHillClimbingSearch() {


actions.ifPresent(qActions -> qActions.forEach(System.out::println)); actions.ifPresent(qActions -> qActions.forEach(System.out::println));
System.out.println(search.getMetrics()); System.out.println(search.getMetrics());
System.out.println("Search Outcome=" + search.getOutcome()); System.out.println("Final State:\n" + search.getLastState());
System.out.println("Final State:\n" + search.getLastSearchState());
} }


private static void solveNQueensWithGeneticAlgorithmSearch() { private static void solveNQueensWithGeneticAlgorithmSearch() {
Expand Down
Expand Up @@ -105,7 +105,7 @@ public void startHillClimbingExperiment() {
search.addNodeListener(n -> notifyProgressTrackers(n.getState(), search.getMetrics())); search.addNodeListener(n -> notifyProgressTrackers(n.getState(), search.getMetrics()));
search.findActions(problem); search.findActions(problem);


board = (NQueensBoard) ((HillClimbingSearch) search).getLastSearchState(); board = (NQueensBoard) ((HillClimbingSearch) search).getLastState();
notifyProgressTrackers(board, search.getMetrics()); notifyProgressTrackers(board, search.getMetrics());
} }


Expand All @@ -117,7 +117,7 @@ public void startSimulatedAnnealingExperiment() {
search.addNodeListener(n -> notifyProgressTrackers(n.getState(), search.getMetrics())); search.addNodeListener(n -> notifyProgressTrackers(n.getState(), search.getMetrics()));
search.findActions(problem); search.findActions(problem);


board = (NQueensBoard) ((SimulatedAnnealingSearch) search).getLastSearchState(); board = (NQueensBoard) ((SimulatedAnnealingSearch) search).getLastState();
notifyProgressTrackers(board, search.getMetrics()); notifyProgressTrackers(board, search.getMetrics());
} }


Expand Down
Expand Up @@ -114,7 +114,7 @@ public void startExperiment() {
search = new SimulatedAnnealingSearch<>(n -> 1 - func.apply(n.getState()), scheduler); search = new SimulatedAnnealingSearch<>(n -> 1 - func.apply(n.getState()), scheduler);
search.addNodeListener(n -> updateStateView(n.getState())); search.addNodeListener(n -> updateStateView(n.getState()));
search.findActions(problem); search.findActions(problem);
updateStateView(search.getLastSearchState()); updateStateView(search.getLastState());
} }


/** Creates a random initial state for the maximum search problem. */ /** Creates a random initial state for the maximum search problem. */
Expand Down

0 comments on commit 0259484

Please sign in to comment.