001 package aima.search.informed; 002 003 import java.util.ArrayList; 004 import java.util.List; 005 006 import aima.search.framework.EvaluationFunction; 007 import aima.search.framework.Node; 008 import aima.search.framework.NodeExpander; 009 import aima.search.framework.Problem; 010 import aima.search.framework.Search; 011 import aima.search.framework.SearchUtils; 012 013 /** 014 * Artificial Intelligence A Modern Approach (2nd Edition): Figure 4.5, page 102. 015 * 016 * <code> 017 * function RECURSIVE-BEST-FIRST-SEARCH(problem) returns a solution, or failure 018 * RBFS(problem, MAKE-NODE(INITIAL-STATE[problem]), infinity) 019 * 020 * function RBFS(problem, node, f_limit) returns a solution, or failure and a new f-cost limit 021 * if GOAL-TEST[problem](STATE[node]) then return node 022 * successors <- EXPAND(node, problem) 023 * if successors is empty then return failure, infinity 024 * for each s in successors do 025 * f[s] <- max(g(s) + h(s), f[node]) 026 * repeat 027 * best <- the lowest f-value node in successors 028 * if f[best] > f_limit then return failure, f[best] 029 * alternative <- the second-lowest f-value among successors 030 * result, f[best] <- RBFS(problem, best, min(f_limit, alternative)) 031 * if result <> failure then return result 032 * </code> 033 * Figure 4.5 The algorithm for recursive best-first search. 034 */ 035 036 /** 037 * @author Ciaran O'Reilly 038 * 039 */ 040 public class RecursiveBestFirstSearch extends NodeExpander implements Search { 041 042 private final EvaluationFunction evaluationFunction; 043 044 private static final String MAX_RECURSIVE_DEPTH = "maxRecursiveDepth"; 045 046 private static final String PATH_COST = "pathCost"; 047 048 private static final Double INFINITY = Double.MAX_VALUE; 049 050 public RecursiveBestFirstSearch(EvaluationFunction ef) { 051 evaluationFunction = ef; 052 } 053 054 // function RECURSIVE-BEST-FIRST-SEARCH(problem) returns a solution, or 055 // failure 056 public List<String> search(Problem p) throws Exception { 057 List<String> actions = new ArrayList<String>(); 058 059 clearInstrumentation(); 060 061 // RBFS(problem, MAKE-NODE(INITIAL-STATE[problem]), infinity) 062 Node n = new Node(p.getInitialState()); 063 SearchResult sr = rbfs(p, n, evaluationFunction.getValue(p, n), 064 INFINITY, 0); 065 if (sr.getOutcome() == SearchResult.SearchOutcome.SOLUTION_FOUND) { 066 Node s = sr.getSolution(); 067 actions = SearchUtils.actionsFromNodes(s.getPathFromRoot()); 068 setPathCost(s.getPathCost()); 069 } 070 071 // Empty List can indicate already at Goal 072 // or unable to find valid set of actions 073 return actions; 074 } 075 076 @Override 077 public void clearInstrumentation() { 078 super.clearInstrumentation(); 079 metrics.set(MAX_RECURSIVE_DEPTH, 0); 080 metrics.set(PATH_COST, 0.0); 081 } 082 083 public void setMaxRecursiveDepth(int recursiveDepth) { 084 int maxRdepth = metrics.getInt(MAX_RECURSIVE_DEPTH); 085 if (recursiveDepth > maxRdepth) { 086 metrics.set(MAX_RECURSIVE_DEPTH, recursiveDepth); 087 } 088 } 089 090 public int getMaxRecursiveDepth() { 091 return metrics.getInt(MAX_RECURSIVE_DEPTH); 092 } 093 094 public double getPathCost() { 095 return metrics.getDouble(PATH_COST); 096 } 097 098 public void setPathCost(Double pathCost) { 099 metrics.set(PATH_COST, pathCost); 100 } 101 102 // 103 // PRIVATE METHODS 104 // 105 // function RBFS(problem, node, f_limit) returns a solution, or failure and 106 // a new f-cost limit 107 private SearchResult rbfs(Problem p, Node n, Double fNode, Double fLimit, 108 int recursiveDepth) { 109 110 setMaxRecursiveDepth(recursiveDepth); 111 112 // if GOAL-TEST[problem](STATE[node]) then return node 113 if (p.isGoalState(n.getState())) { 114 return new SearchResult(n, fLimit); 115 } 116 117 // successors <- EXPAND(node, problem) 118 List<Node> successors = expandNode(n, p); 119 // if successors is empty then return failure, infinity 120 if (0 == successors.size()) { 121 return new SearchResult(null, INFINITY); 122 } 123 double[] f = new double[successors.size()]; 124 // for each s in successors do 125 int size = successors.size(); 126 for (int s = 0; s < size; s++) { 127 // f[s] <- max(g(s) + h(s), f[node]) 128 f[s] = Math.max(evaluationFunction.getValue(p, successors.get(s)), 129 fNode); 130 } 131 132 // repeat 133 while (true) { 134 // best <- the lowest f-value node in successors 135 int bestIndex = getBestFValueIndex(f); 136 // if f[best] > f_limit then return failure, f[best] 137 if (f[bestIndex] > fLimit) { 138 return new SearchResult(null, f[bestIndex]); 139 } 140 // alternative <- the second-lowest f-value among successors 141 int altIndex = getNextBestFValueIndex(f, bestIndex); 142 // result, f[best] <- RBFS(problem, best, min(f_limit, alternative)) 143 SearchResult sr = rbfs(p, successors.get(bestIndex), f[bestIndex], 144 Math.min(fLimit, f[altIndex]), recursiveDepth + 1); 145 f[bestIndex] = sr.getFCostLimit(); 146 // if result <> failure then return result 147 if (sr.getOutcome() == SearchResult.SearchOutcome.SOLUTION_FOUND) { 148 return sr; 149 } 150 } 151 } 152 153 // the lowest f-value node 154 private int getBestFValueIndex(double[] f) { 155 int lidx = 0; 156 Double lowestSoFar = INFINITY; 157 158 for (int i = 0; i < f.length; i++) { 159 if (f[i] < lowestSoFar) { 160 lowestSoFar = f[i]; 161 lidx = i; 162 } 163 } 164 165 return lidx; 166 } 167 168 // the second-lowest f-value 169 private int getNextBestFValueIndex(double[] f, int bestIndex) { 170 // Array may only contain 1 item (i.e. no alternative), 171 // therefore default to bestIndex initially 172 int lidx = bestIndex; 173 Double lowestSoFar = INFINITY; 174 175 for (int i = 0; i < f.length; i++) { 176 if (i != bestIndex && f[i] < lowestSoFar) { 177 lowestSoFar = f[i]; 178 lidx = i; 179 } 180 } 181 182 return lidx; 183 } 184 } 185 186 class SearchResult { 187 public enum SearchOutcome { 188 FAILURE, SOLUTION_FOUND 189 }; 190 191 private Node solution; 192 193 private SearchOutcome outcome; 194 195 private final Double fCostLimit; 196 197 public SearchResult(Node solution, Double fCostLimit) { 198 if (null == solution) { 199 this.outcome = SearchOutcome.FAILURE; 200 } else { 201 this.outcome = SearchOutcome.SOLUTION_FOUND; 202 this.solution = solution; 203 } 204 this.fCostLimit = fCostLimit; 205 } 206 207 public SearchOutcome getOutcome() { 208 return outcome; 209 } 210 211 public Node getSolution() { 212 return solution; 213 } 214 215 public Double getFCostLimit() { 216 return fCostLimit; 217 } 218 }