Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
aima.core.search.informed.RecursiveBestFirstSearch Maven / Gradle / Ivy
Go to download
AIMA-Java Core Algorithms from the book Artificial Intelligence a Modern Approach 3rd Ed.
package aima.core.search.informed;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import aima.core.agent.Action;
import aima.core.search.framework.Metrics;
import aima.core.search.framework.Node;
import aima.core.search.framework.NodeExpander;
import aima.core.search.framework.SearchUtils;
import aima.core.search.framework.evalfunc.EvaluationFunction;
import aima.core.search.framework.SearchForActions;
import aima.core.search.framework.problem.Problem;
/**
* Artificial Intelligence A Modern Approach (3rd Edition): Figure 3.26, page
* 99.
*
*
*
* function RECURSIVE-BEST-FIRST-SEARCH(problem) returns a solution, or failure
* return RBFS(problem, MAKE-NODE(problem.INITIAL-STATE), infinity)
*
* function RBFS(problem, node, f_limit) returns a solution, or failure and a new f-cost limit
* if problem.GOAL-TEST(node.STATE) then return SOLUTION(node)
* successors <- []
* for each action in problem.ACTION(node.STATE) do
* add CHILD-NODE(problem, node, action) into successors
* if successors is empty then return failure, infinity
* for each s in successors do // update f with value from previous search, if any
* s.f <- max(s.g + s.h, node.f)
* repeat
* best <- the lowest f-value node in successors
* if best.f > f_limit then return failure, best.f
* alternative <- the second-lowest f-value among successors
* result, best.f <- RBFS(problem, best, min(f_limit, alternative))
* if result != failure then return result
*
*
* Figure 3.26 The algorithm for recursive best-first search.
*
*
* This version additionally provides an option to avoid loops. States on the
* current path are stored in a hash set if the loop avoidance option is enabled.
*
* @author Ciaran O'Reilly
* @author Mike Stampone
* @author Ruediger Lunde
*/
public class RecursiveBestFirstSearch implements SearchForActions {
public static final String METRIC_NODES_EXPANDED = "nodesExpanded";
public static final String METRIC_MAX_RECURSIVE_DEPTH = "maxRecursiveDepth";
public static final String METRIC_PATH_COST = "pathCost";
private static final Double INFINITY = Double.MAX_VALUE;
private final EvaluationFunction evaluationFunction;
private boolean avoidLoops;
private final NodeExpander nodeExpander;
// stores the states on the current path if avoidLoops is true.
Set explored = new HashSet();
private Metrics metrics;
public RecursiveBestFirstSearch(EvaluationFunction ef) {
this(ef, false);
}
/** Constructor which allows to enable the loop avoidance strategy. */
public RecursiveBestFirstSearch(EvaluationFunction ef, boolean avoidLoops) {
this(ef, avoidLoops, new NodeExpander());
}
public RecursiveBestFirstSearch(EvaluationFunction ef, boolean avoidLoops, NodeExpander nodeExpander) {
evaluationFunction = ef;
this.avoidLoops = avoidLoops;
this.nodeExpander = nodeExpander;
metrics = new Metrics();
}
// function RECURSIVE-BEST-FIRST-SEARCH(problem) returns a solution, or
// failure
@Override
public List findActions(Problem p) {
List actions = new ArrayList();
explored.clear();
clearInstrumentation();
// RBFS(problem, MAKE-NODE(INITIAL-STATE[problem]), infinity)
Node n = nodeExpander.createRootNode(p.getInitialState());
SearchResult sr = rbfs(p, n, evaluationFunction.f(n), INFINITY, 0);
if (sr.hasSolution()) {
Node s = sr.getSolutionNode();
actions = SearchUtils.getSequenceOfActions(s);
metrics.set(METRIC_PATH_COST, s.getPathCost());
}
// Empty List can indicate already at Goal
// or unable to find valid set of actions
return actions;
}
public EvaluationFunction getEvaluationFunction() {
return evaluationFunction;
}
@Override
public NodeExpander getNodeExpander() {
return nodeExpander;
}
/**
* Returns all the search metrics.
*/
@Override
public Metrics getMetrics() {
metrics.set(METRIC_NODES_EXPANDED, nodeExpander.getNumOfExpandCalls());
return metrics;
}
/**
* Sets all metrics to zero.
*/
private void clearInstrumentation() {
nodeExpander.resetCounter();
metrics.set(METRIC_NODES_EXPANDED, 0);
metrics.set(METRIC_MAX_RECURSIVE_DEPTH, 0);
metrics.set(METRIC_PATH_COST, 0.0);
}
//
// PRIVATE METHODS
//
// function RBFS(problem, node, f_limit) returns a solution, or failure and
// a new f-cost limit
private SearchResult rbfs(Problem p, Node node, double node_f, double fLimit, int recursiveDepth) {
updateMetrics(recursiveDepth);
// if problem.GOAL-TEST(node.STATE) then return SOLUTION(node)
if (SearchUtils.isGoalState(p, node))
return getResult(null, node, fLimit);
// successors <- []
// for each action in problem.ACTION(node.STATE) do
// add CHILD-NODE(problem, node, action) into successors
List successors = expandNode(node, p);
// if successors is empty then return failure, infinity
if (successors.isEmpty())
return getResult(node, null, INFINITY);
double[] f = new double[successors.size()];
// for each s in successors do
// update f with value from previous search, if any
int size = successors.size();
for (int s = 0; s < size; s++) {
// s.f <- max(s.g + s.h, node.f)
f[s] = Math.max(evaluationFunction.f(successors.get(s)), node_f);
}
// repeat
while (true) {
// best <- the lowest f-value node in successors
int bestIndex = getBestFValueIndex(f);
// if best.f > f_limit then return failure, best.f
if (f[bestIndex] > fLimit) {
return getResult(node, null, f[bestIndex]);
}
// if best.f > f_limit then return failure, best.f
int altIndex = getNextBestFValueIndex(f, bestIndex);
// result, best.f <- RBFS(problem, best, min(f_limit, alternative))
SearchResult sr = rbfs(p, successors.get(bestIndex), f[bestIndex], Math.min(fLimit, f[altIndex]),
recursiveDepth + 1);
f[bestIndex] = sr.getFCostLimit();
// if result != failure then return result
if (sr.hasSolution()) {
return getResult(node, sr.getSolutionNode(), sr.getFCostLimit());
}
}
}
// the lowest f-value node
private int getBestFValueIndex(double[] f) {
int lidx = 0;
Double lowestSoFar = INFINITY;
for (int i = 0; i < f.length; i++) {
if (f[i] < lowestSoFar) {
lowestSoFar = f[i];
lidx = i;
}
}
return lidx;
}
// the second-lowest f-value
private int getNextBestFValueIndex(double[] f, int bestIndex) {
// Array may only contain 1 item (i.e. no alternative),
// therefore default to bestIndex initially
int lidx = bestIndex;
Double lowestSoFar = INFINITY;
for (int i = 0; i < f.length; i++) {
if (i != bestIndex && f[i] < lowestSoFar) {
lowestSoFar = f[i];
lidx = i;
}
}
return lidx;
}
private List expandNode(Node node, Problem problem) {
List result = nodeExpander.expand(node, problem);
if (avoidLoops) {
explored.add(node.getState());
for (Iterator ni = result.iterator(); ni.hasNext();)
if (explored.contains(ni.next().getState())) {
ni.remove();
}
}
return result;
}
private SearchResult getResult(Node currNode, Node solutionNode, double fCostLimit) {
if (avoidLoops && currNode != null)
explored.remove(currNode.getState());
return new SearchResult(solutionNode, fCostLimit);
}
/**
* Increases the maximum recursive depth if the specified depth is greater
* than the current maximum.
*
* @param recursiveDepth
* the depth of the current path
*/
private void updateMetrics(int recursiveDepth) {
int maxRdepth = metrics.getInt(METRIC_MAX_RECURSIVE_DEPTH);
if (recursiveDepth > maxRdepth) {
metrics.set(METRIC_MAX_RECURSIVE_DEPTH, recursiveDepth);
}
}
static class SearchResult {
private Node solNode;
private final double fCostLimit;
public SearchResult(Node solutionNode, double fCostLimit) {
this.solNode = solutionNode;
this.fCostLimit = fCostLimit;
}
public boolean hasSolution() {
return solNode != null;
}
public Node getSolutionNode() {
return solNode;
}
public Double getFCostLimit() {
return fCostLimit;
}
}
}