;;; ;;; A generic state space search framework ;;; ;;; By Philip W. L. Fong ;;; ;;; ;;; Search Node ;;; ;;; The search routine generates and expands search nodes incrementally. ;;; Search nodes that are generated but not yet expanded are stored ;;; in a container data structure called a node store. ;;; ;;; The designer of a search problem does not need to know the internal ;;; structure of a search node. However, programmers ;;; who want to customize the node expansion strategy of the ;;; search routine will need to understand the structure of a node ;;; object. ;;; (defun make-node (state operator depth path-cost parent) "A search node in the search tree. STATE : A LISP expression representing a state in the search space. OPERATOR : An identifier of the operator that generates this node. DEPTH : Depth of this node. PATH-COST: Cost of the path leading to this node. PARENT : The parent node of this node." (list state operator depth path-cost parent)) (defun node-state (node) "The state represented by NODE." (first node)) (defun node-operator (node) "The identifier of the operator that generates NODE." (second node)) (defun node-depth (node) "The depth of NODE in the search tree." (third node)) (defun node-path-cost (node) "The cost of the path leading to NODE." (fourth node)) (defun node-parent (node) "The parent node of NODE in the search tree." (fifth node)) (defun make-root-node (initial-state) "Create the root node of a search tree." (make-node initial-state ; state 'initial-state ; operator 0 ; depth 0 ; path-cost nil)) ; parent ;;; ;;; A successor function is a function that, when given a state, returns ;;; a (possibly empty) list of effect structures. The designer of ;;; a search problem needs to encode the effect of operator application ;;; in terms of this structure. ;;; (defun make-effect (operator cost state) "The effect of applying an operator. OPERATOR: An identifier expression for the operator. COST : Cost of applying the operator. STATE : The state generated by this operator application." (list operator cost state)) (defun effect-operator (effect) "The identifier of the operator producing EFFECT." (first effect)) (defun effect-cost (effect) "The cost of applying the operator that produces EFFECT." (second effect)) (defun effect-state (effect) "The state represented by EFFECT." (third effect)) (defun expand-node (successor-func node) "A helper function that turns the effects of node expansion to search nodes." (mapcar #'(lambda (effect) (make-node (effect-state effect) ; state (effect-operator effect) ; operator (1+ (node-depth node)) ; depth (+ (effect-cost effect) ; path-cost (node-path-cost node)) node)) ; parent (funcall successor-func (node-state node)))) ;;; ;;; Search problem ;;; ;;; A user may specify a search problem by constructing a problem object. ;;; (defun make-problem (initial-state successor-func goal-test) "A problem structure specifies the initial state, the successor function, and goal test, and the path cost function for a search problem." (list initial-state successor-func goal-test)) (defun problem-initial-state (problem) "The initial state of PROBLEM." (first problem)) (defun problem-successor-func (problem) "The successor function of PROBLEM." (second problem)) (defun problem-goal-test (problem) "The goal test of PROBLEM." (third problem)) ;;; ;;; Search strategy ;;; (defun make-strategy (node-store-insert node-store-remove node-store-empty-p pruning-test) "A strategy object provides access functions for a node store and a predicate for testing if a given node should be pruned. NODE-STORE-INSERT : A single-argument function that produces the side effect of inserting a given search node into the node store. NODE-STORE-REMOVE : A zero-argument function that has the side effect of removing a node from the node store, and a return value of the removed node. NODE-STORE-EMPTY-P : A zero-argument predicate to test if the node store is empty. PRUNING-TEST : A single-argument predicate to test if a given search node is to be pruned." (list node-store-insert node-store-remove node-store-empty-p pruning-test)) (defun strategy-node-store-insert (strategy) "Return a single-argument function, when called with a search node as input, will produce the side effect of inserting the node into the node store." (first strategy)) (defun strategy-node-store-remove (strategy) "Return a zero-argument function that produces the side effect of remove a node from the node store and returns the remove node." (second strategy)) (defun strategy-node-store-empty-p (strategy) "Return a zero-argument predicate that tests if the node store is empty." (third strategy)) (defun strategy-pruning-test (strategy) "Return a single-argument predicate that tests if a given node is to be pruned." (fourth strategy)) ;;; ;;; Generic Search ;;; ;;; Main entry point to the search routine. ;;; (defun generic-search (problem strategy) "Search for a goal node of PROBLEM using STRATEGY. Return four values: goal node (or the atom 'failure if none exists), the number of nodes expanded, the number of nodes generated, and the number of nodes pruned." (let ((initial-state (problem-initial-state problem)) (successor-func (problem-successor-func problem)) (goal-test (problem-goal-test problem)) (node-store-insert (strategy-node-store-insert strategy)) (node-store-remove (strategy-node-store-remove strategy)) (node-store-empty-p (strategy-node-store-empty-p strategy)) (pruning-test (strategy-pruning-test strategy)) (nodes-expanded 0) (nodes-generated 0) (nodes-pruned 0) (node nil)) ;; create root node of the search tree and insert it into node store (funcall node-store-insert (make-root-node initial-state)) (incf nodes-generated) ; collect statistics ;; main search loop (loop ;; search fails if node store becomes empty (if (funcall node-store-empty-p) (return-from generic-search (values 'failure nodes-expanded nodes-generated nodes-pruned))) ;; remove next node from node store (setf node (funcall node-store-remove)) ;; return node if it is a goal (if (funcall goal-test (node-state node)) (return-from generic-search (values node nodes-expanded nodes-generated nodes-pruned))) ;; expand node (incf nodes-expanded) ; collect statistics (dolist (successor (expand-node successor-func node)) (incf nodes-generated) ; collect statistics ;; if successor is not to be pruned then insert into node store (if (and (not (null pruning-test)) (funcall pruning-test successor)) (incf nodes-pruned) ; collect statistics (funcall node-store-insert successor)))))) ;;; ;;; Utility functions for reporting results. ;;; (defun print-node-path (node) "Print out the operator path that generates a node and then return NIL." (if (node-parent node) (print-node-path (node-parent node))) (print (list ':operator (node-depth node) (node-operator node))) (print (list ':state (node-depth node) (node-state node))) nil) (defun solve-problem (problem strategy) "Solve PROBLEM using STRATEGY and then report solution and performance statistics before returning NIL." (multiple-value-bind (node nodes-expanded nodes-generated nodes-pruned) (generic-search problem strategy) (if (eq node 'failure) (print 'failure) (progn (print-node-path node) (print (list ':path-length (node-depth node))) (print (list ':path-cost (node-path-cost node))))) (print (list ':number-of-nodes-expanded nodes-expanded)) (print (list ':number-of-nodes-generated nodes-generated)) (print (list ':number-of-nodes-pruned nodes-pruned)) nil))