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    }