001    package aima.search.online;
002    
003    import java.util.ArrayList;
004    import java.util.Hashtable;
005    import java.util.List;
006    
007    import aima.basic.Agent;
008    import aima.basic.Percept;
009    import aima.search.framework.Problem;
010    import aima.search.framework.Successor;
011    
012    /**
013     * Artificial Intelligence A Modern Approach (2nd Edition): Figure 4.23, page
014     * 128.<br>
015     * <code>
016     * function LRTA*AGENT(s') returns an action
017     *   inputs: s', a percept that identifies the current state
018     *   static: result, a table, indexed by action and state, initially empty
019     *           H, a table of cost estimates indexed by state, initially empty
020     *           s, a, the previous state and action, initially null
021     *           
022     *   if GOAL-TEST(s') then return stop
023     *   if s' is new state (not in H) then H[s'] <- h(s')
024     *   unless s is null
025     *     result[a, s] <- s'
026     *     H[s] <- min LRTA*-COST(s, b, result[b, s], H)
027     *             b (element of) ACTIONS(s)
028     *   a <- an action b in ACTIONS(s') that minimizes LRTA*-COST(s', b, result[b, s'], H)
029     *   s <- s'
030     *   return a
031     *   
032     * 
033     * function LRTA*-COST(s, a, s', H) returns a cost estimate
034     *   if s' is undefined then return h(s)
035     *   else return c(s, a, s') + H[s']
036     * </code>
037     * 
038     * Figure 4.23 LRTA*-AGENT selects an action according to the value of
039     * neighboring states, which are updated as the agent moves about the state
040     * space.<br>
041     * Note: This algorithm fails to exit if the goal does not exist (e.g. A<->B Goal=X),
042     * this could be an issue with the implementation. Comments welcome.
043     */
044    
045    /**
046     * @author Ciaran O'Reilly
047     * 
048     */
049    public class LRTAStarAgent extends Agent {
050    
051            private Problem problem;
052            // static: result, a table, indexed by action and state, initially empty
053            private final Hashtable<ActionState, Percept> result = new Hashtable<ActionState, Percept>();
054            // H, a table of cost estimates indexed by state, initially empty
055            private final Hashtable<Percept, Double> H = new Hashtable<Percept, Double>();
056            // s, a, the previous state and action, initially null
057            private Percept s = null;
058            private Object a = null;
059    
060            public LRTAStarAgent(Problem problem) {
061                    setProblem(problem);
062            }
063    
064            public Problem getProblem() {
065                    return problem;
066            }
067    
068            public void setProblem(Problem problem) {
069                    this.problem = problem;
070                    init();
071            }
072    
073            // function LRTA*AGENT(s') returns an action
074            // inputs: s', a percept that identifies the current state
075            @Override
076            public String execute(Percept sComma) {
077    
078                    // if GOAL-TEST(s') then return stop
079                    if (!goalTest(sComma)) {
080                            // if s' is new state (not in H) then H[s'] <- h(s')
081                            if (!H.containsKey(sComma)) {
082                                    H.put(sComma, getProblem().getHeuristicFunction()
083                                                    .getHeuristicValue(sComma));
084                            }
085                            // unless s is null
086                            if (null != s) {
087                                    // result[a, s] <- s'
088                                    result.put(new ActionState(a, s), sComma);
089    
090                                    // H[s] <- min LRTA*-COST(s, b, result[b, s], H)
091                                    // b (element of) ACTIONS(s)
092                                    double min = Double.MAX_VALUE;
093                                    for (Object b : actions(s)) {
094                                            double cost = lrtaCost(s, b, result.get(new ActionState(b,
095                                                            s)));
096                                            if (cost < min) {
097                                                    min = cost;
098                                            }
099                                    }
100                                    H.put(s, min);
101                            }
102                            // a <- an action b in ACTIONS(s') that minimizes LRTA*-COST(s', b,
103                            // result[b, s'], H)
104                            double min = Double.MAX_VALUE;
105                            // Just in case no actions
106                            a = Agent.NO_OP;
107                            for (Object b : actions(sComma)) {
108                                    double cost = lrtaCost(sComma, b, result.get(new ActionState(b,
109                                                    sComma)));
110                                    if (cost < min) {
111                                            min = cost;
112                                            a = b;
113                                    }
114                            }
115                    } else {
116                            a = Agent.NO_OP;
117                    }
118    
119                    // s <- s'
120                    s = sComma;
121    
122                    if (Agent.NO_OP.equals(a)) {
123                            // I'm either at the Goal or can't get to it,
124                            // which in either case I'm finished so just die.
125                            die();
126                    }
127                    // return a
128                    return a.toString();
129            }
130    
131            //
132            // PRIVATE METHODS
133            //
134            private void init() {
135                    live();
136                    result.clear();
137                    H.clear();
138                    s = null;
139                    a = null;
140            }
141    
142            private boolean goalTest(Percept state) {
143                    return getProblem().isGoalState(state);
144            }
145    
146            // function LRTA*-COST(s, a, s', H) returns a cost estimate
147            private double lrtaCost(Percept s, Object action, Percept sComma) {
148                    // if s' is undefined then return h(s)
149                    if (null == sComma) {
150                            return getProblem().getHeuristicFunction().getHeuristicValue(s);
151                    }
152                    // else return c(s, a, s') + H[s']
153                    return getProblem().getStepCostFunction().calculateStepCost(s, sComma,
154                                    action.toString())
155                                    + H.get(sComma);
156            }
157    
158            private List<Object> actions(Percept state) {
159                    List<Object> actions = new ArrayList<Object>();
160    
161                    List<Successor> successors = getProblem().getSuccessorFunction()
162                                    .getSuccessors(state);
163    
164                    for (Successor s : successors) {
165                            actions.add(s.getAction());
166                    }
167    
168                    return actions;
169            }
170    }