package aima.core.search.informed;
import java.util.ArrayList;
import java.util.List;
import aima.core.agent.Action;
import aima.core.search.framework.EvaluationFunction;
import aima.core.search.framework.Node;
import aima.core.search.framework.NodeExpander;
import aima.core.search.framework.Problem;
import aima.core.search.framework.Search;
import aima.core.search.framework.SearchUtils;
/**
* Artificial Intelligence A Modern Approach (3rd Edition): Figure 3.26, page
* 99.<br>
* <br>
*
* <pre>
* 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
* </pre>
*
* Figure 3.26 The algorithm for recursive best-first search.
*
* @author Ciaran O'Reilly
* @author Mike Stampone
*/
public class RecursiveBestFirstSearch extends NodeExpander implements Search {
private final EvaluationFunction evaluationFunction;
private static final String MAX_RECURSIVE_DEPTH = "maxRecursiveDepth";
private static final String PATH_COST = "pathCost";
private static final Double INFINITY = Double.MAX_VALUE;
public RecursiveBestFirstSearch(EvaluationFunction ef) {
evaluationFunction = ef;
}
// function RECURSIVE-BEST-FIRST-SEARCH(problem) returns a solution, or
// failure
public List<Action> search(Problem p) throws Exception {
List<Action> actions = new ArrayList<Action>();
clearInstrumentation();
// RBFS(problem, MAKE-NODE(INITIAL-STATE[problem]), infinity)
Node n = new Node(p.getInitialState());
SearchResult sr = rbfs(p, n, evaluationFunction.f(n), INFINITY, 0);
if (sr.getOutcome() == SearchResult.SearchOutcome.SOLUTION_FOUND) {
Node s = sr.getSolution();
actions = SearchUtils.actionsFromNodes(s.getPathFromRoot());
setPathCost(s.getPathCost());
}
// Empty List can indicate already at Goal
// or unable to find valid set of actions
return actions;
}
/**
* Sets all metrics to zero.
*/
@Override
public void clearInstrumentation() {
super.clearInstrumentation();
metrics.set(MAX_RECURSIVE_DEPTH, 0);
metrics.set(PATH_COST, 0.0);
}
/**
* Increases the maximum recursive depth if the specified depth is greater
* than the current maximum.
*
* @param recursiveDepth
* the depth of the current path
*/
public void setMaxRecursiveDepth(int recursiveDepth) {
int maxRdepth = metrics.getInt(MAX_RECURSIVE_DEPTH);
if (recursiveDepth > maxRdepth) {
metrics.set(MAX_RECURSIVE_DEPTH, recursiveDepth);
}
}
/**
* Returns the maximum recursive depth.
*
* @return the maximum recursive depth.
*/
public int getMaxRecursiveDepth() {
return metrics.getInt(MAX_RECURSIVE_DEPTH);
}
/**
* Returns the path cost.
*
* @return the path cost
*/
public double getPathCost() {
return metrics.getDouble(PATH_COST);
}
/**
* Sets the path cost.
*
* @param pathCost
* the cost of the path
*/
public void setPathCost(Double pathCost) {
metrics.set(PATH_COST, pathCost);
}
//
// 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 n, double node_f, double fLimit,
int recursiveDepth) {
setMaxRecursiveDepth(recursiveDepth);
// if problem.GOAL-TEST(node.STATE) then return SOLUTION(node)
if (SearchUtils.isGoalState(p, n)) {
return new SearchResult(n, fLimit);
}
// successors <- []
// for each action in problem.ACTION(node.STATE) do
// add CHILD-NODE(problem, node, action) into successors
List<Node> successors = expandNode(n, p);
// if successors is empty then return failure, infinity
if (0 == successors.size()) {
return new SearchResult(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 new SearchResult(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.getOutcome() == SearchResult.SearchOutcome.SOLUTION_FOUND) {
return sr;
}
}
}
// 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;
}
}
class SearchResult {
public enum SearchOutcome {
FAILURE, SOLUTION_FOUND
};
private Node solution;
private SearchOutcome outcome;
private final Double fCostLimit;
public SearchResult(Node solution, Double fCostLimit) {
if (null == solution) {
this.outcome = SearchOutcome.FAILURE;
} else {
this.outcome = SearchOutcome.SOLUTION_FOUND;
this.solution = solution;
}
this.fCostLimit = fCostLimit;
}
public SearchOutcome getOutcome() {
return outcome;
}
public Node getSolution() {
return solution;
}
public Double getFCostLimit() {
return fCostLimit;
}
}