001    /*
002     * Created on Sep 14, 2004
003     *
004     */
005    package aima.search.informed;
006    
007    import java.util.ArrayList;
008    import java.util.List;
009    import java.util.Random;
010    
011    import aima.search.framework.Node;
012    import aima.search.framework.NodeExpander;
013    import aima.search.framework.Problem;
014    import aima.search.framework.Search;
015    import aima.search.framework.SearchUtils;
016    import aima.util.Util;
017    
018    /**
019     * Artificial Intelligence A Modern Approach (2nd Edition): Figure 4.14, page
020     * 116.
021     * 
022     * <code>
023     * function SIMULATED-ANNEALING(problem, schedule) returns a solution state
024     *   inputs: problem, a problem
025     *           schedule, a mapping from time to "temperature"
026     *   local variables: current, a node
027     *                    next, a node
028     *                    T, a "temperature" controlling the probability of downward steps
029     *                    
030     *   current <- MAKE-NODE(INITIAL-STATE[problem])
031     *   for t <- 1 to INFINITY do
032     *     T <- schedule[t]
033     *     if T = 0 then return current
034     *     next <- a randomly selected successor of current
035     *     /\E <- VALUE[next] - VALUE[current]
036     *     if /\E > 0 then current <- next
037     *     else current <- next only with probablity e^(/\E/T)
038     * </code>
039     * Figure 4.14 The simulated annealing search algorithm, a version of the
040     * stochastic hill climbing where some downhill moves are allowed. Downhill
041     * moves are accepted readily early in the annealing schedule and then less
042     * often as time goes on. The schedule input determines the value of T as a
043     * function of time.
044     */
045    
046    /**
047     * @author Ravi Mohan
048     * 
049     */
050    public class SimulatedAnnealingSearch extends NodeExpander implements Search {
051    
052            public enum SearchOutcome {
053                    FAILURE, SOLUTION_FOUND
054            };
055    
056            private final Scheduler scheduler;
057    
058            private SearchOutcome outcome = SearchOutcome.FAILURE;
059    
060            private Object lastState = null;
061    
062            public SimulatedAnnealingSearch() {
063                    this.scheduler = new Scheduler();
064            }
065    
066            // function SIMULATED-ANNEALING(problem, schedule) returns a solution state
067            // inputs: problem, a problem
068            // schedule, a mapping from time to "temperature"
069            public List<String> search(Problem p) throws Exception {
070                    // local variables: current, a node
071                    // next, a node
072                    // T, a "temperature" controlling the probability of downward steps
073                    clearInstrumentation();
074                    outcome = SearchOutcome.FAILURE;
075                    lastState = null;
076                    // current <- MAKE-NODE(INITIAL-STATE[problem])
077                    Node current = new Node(p.getInitialState());
078                    Node next = null;
079                    List<String> ret = new ArrayList<String>();
080                    // for t <- 1 to INFINITY do
081                    int timeStep = 0;
082                    while (true) {
083                            // temperature <- schedule[t]
084                            double temperature = scheduler.getTemp(timeStep);
085                            timeStep++;
086                            // if temperature = 0 then return current
087                            if (temperature == 0.0) {
088                                    if (p.isGoalState(current.getState())) {
089                                            outcome = SearchOutcome.SOLUTION_FOUND;
090                                    }
091                                    ret = SearchUtils.actionsFromNodes(current.getPathFromRoot());
092                                    lastState = current.getState();
093                                    break;
094                            }
095    
096                            List<Node> children = expandNode(current, p);
097                            if (children.size() > 0) {
098                                    // next <- a randomly selected successor of current
099                                    next = Util.selectRandomlyFromList(children);
100                                    // /\E <- VALUE[next] - VALUE[current]
101                                    double deltaE = getValue(p, next) - getValue(p, current);
102    
103                                    if (shouldAccept(temperature, deltaE)) {
104                                            current = next;
105                                    }
106                            }
107                    }
108    
109                    return ret;
110            }
111    
112            // if /\E > 0 then current <- next
113            // else current <- next only with probablity e^(/\E/T)
114            private boolean shouldAccept(double temperature, double deltaE) {
115                    return (deltaE > 0.0)
116                                    || (new Random().nextDouble() <= probabilityOfAcceptance(
117                                                    temperature, deltaE));
118            }
119    
120            public double probabilityOfAcceptance(double temperature, double deltaE) {
121                    return Math.exp(deltaE / temperature);
122            }
123    
124            public SearchOutcome getOutcome() {
125                    return outcome;
126            }
127    
128            public Object getLastSearchState() {
129                    return lastState;
130            }
131    
132            private double getValue(Problem p, Node n) {
133                    return -1 * getHeuristic(p, n); // assumption greater heuristic value =>
134                    // HIGHER on hill; 0 == goal state;
135                    // SA deals with gardient DESCENT
136            }
137    
138            private double getHeuristic(Problem p, Node aNode) {
139                    return p.getHeuristicFunction().getHeuristicValue(aNode.getState());
140            }
141    }