Download Lab 3 Graph search

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Gene expression programming wikipedia , lookup

Catastrophic interference wikipedia , lookup

Minimax wikipedia , lookup

Rete algorithm wikipedia , lookup

Hierarchical temporal memory wikipedia , lookup

Transcript
AI-Lisp / Lab 3 - Graph search
11
Lab 3
Graph search
3.0 INTRODUCTION
This lab continues to train recursion by searching graphs. You will continue with search in the AI-labs during the second part of this course.
We will start with a simple depth-first search in a graph, and then go over to a breath-first search and in
successive steps make the search smarter and reach to the A*-algorithm. You can read more in Russel-Norvig’s Artificial Intelligence book chapter 4 and section 4.1 about A*, which is a heuristic search algorithm
using extra knowledge from the problem domain which will speed up the search.
We will also use higher-order functions in Common Lisp to be able to reuse the code that we have developed.
3.1 DEPTH-FIRST SEARCH
Exercise 3.1.a. We start with some training to find a path in a maze or graph. We follow the description
and the code from Haraldsson’s Lisp book section 8.3. The book is not necessary to read for this lab.
We assume a maze with an entrance and an exit. We represent the maze as a graph with nodes and arcs. We
want to find a path through the maze. There is no demand to find the best path, only one path but the path
must be without cycles and not include blind alleys.
We give here a maze and its representation as a graph.
IN
IN
D
A
B
K
OUT
C
D
A
C
B
G
E
F
I
H
J
K
G
E
F
I
OUT
H
J
We represent the graph as an association list, where every entry in the list is a pair with the name (as a symbol) of the node and a list of its neighbors. We call the above maze a-maze. We also introduce a primitive
to find the neighbors from a node.
We let path-maze-depth-first to be the main function, which takes a maze, an entry and an exit node.
12
AI- Lisp / Lab 3 - Seaarch in graphs
The code1 from Haraldsson’s book. You will also find all code in a Common Lisp file on the course webpages.
(defun path-maze-depth-first (maze start end)
(path maze start end ’()))
(defun path (maze from to way)
; Find a way from the node from to the node to in the graph maze
; The parameter way holds the way from the initial start node to the node
; from
(if (eq from to)
; Come to the final node
(add-to-the-end to way)
(path-iter maze
(nodes-to from maze)
to
(add-to-the-end from way))))
(defun path-iter (maze alternatives to way)
(cond
((endp alternatives) ’nil)
; no possible way
((member (first alternatives) way)
; already been here
(path-iter maze (rest alternatives) to way))
(t (let ((try-path (path maze (first alternatives) to way)))
; check for a way to the end
(if (eq try-path ’nil)
(path-iter maze (rest alternatives) to way)
; no way, next alerantive
try-path)))))
; a way found to the end
(defun nodes-to (node maze)
(first (cdr (assoc node maze))))
; gives the neighbors
(defun add-to-the-end (node way)
(append way (list node)))
(setq a-maze ’((in (a)) (a (in b c)) (b (a)) (c (a d e))
(d (c)) (e (c f k j)) (f (g e h)) (g (f))
(h (f i j)) (i (h)) (j (e h k out)) (k (e j))
(out (j))))
Load the code from the www-page. Load it into Lisp, trace the functions path and path-iter and follow
the execution so you really understand how the depth-first search works.
Observations:
Notice the way we do back-tracking in the last case in the cond-expression in the function path-iter,
where we have a parameter alternatives of possible nodes to continue with. We take the first alternative
from the list and call the function path, to try to find a path to the goal. We bind up the resulting value and
check to see if we found a path or not (when the value is nil). If we received a path (a non-nil list) we
return that value. If there was no path we continue with the next alternative.
We can also see that it is no guidance to find a good path. The path depends on in what order the neighbors
are placed in the list holding the maze. Another result is giving by
(setq another-maze ’((in (a)) (a (in c b)) (b (a)) (c (e d a))
(d (c)) (e (c k f j)) (f (e g h)) (g (f))
(h (f i j)) (i (h)) (j (h e k out)) (k (e j))
(out (j))))
Compare the results from the two different representations of the maze.
1. There
are small changes from the book. The termination condition is moved to the function path. There
was no arc from node K to node J. Here the representation. consists of the node and a list of the list of neighbors.
AI-Lisp / Lab 3 - Graph search
13
ASSIGNMENT 3A - BREADTH-FIRST SEARCH IN THE MAZE
In breath-first search we expand the nodes in order from the first node to all its neighbors before we expand
a neighbor node. For a breadth-first search algorithm we need to maintain two lists. We need one list with
all nodes which are candidates to expand and to continue the search from. These nodes are neighbors to an
expanded node (not already expanded or stored in the queue), which later, in its turn, may be expanded. We
call that parameter que-of-not-visited-nodes. A "queue element " may contain information about
the node. This queue could be sorted to make a more efficient search. See assignments 3B and 3C. On the
other list visited-node we store all nodes we have visited and expanded.
We also introduce a "queue update function" (bound to the parameter que-update-fn), which updates
the queue que-of-not-visited-nodes every time we have expanded a node with information about
the neighbors we reached from that node. This function will differ depending on type of search as in assignments 3B and 3C.
The structure of the code is such that it can be reused in assignments 3B and 3C. The structure is still such
that the main search has been divided into two functions, where the function path-b searches from a given
node from-node and finds all of its successors and updates the queue with respect to the "queue update
function".
(defun path-maze-breadth-first (maze start end)
(path-b maze (build-que-element start ’()) end ’()
’() #’update-que-breadth-first))
(defun path-b (maze from-node to que-of-not-visited-nodes
visited-nodes que-update-fn)
(print-info from-node que-of-not-visited-nodes visited-nodes)
(if (eq (node-name from-node) to)
(add-to-the-end to (way from-node))
(path-iter-b maze
to
(funcall que-update-fn
from-node
to
(nodes-to (node-name from-node) maze)
que-of-not-visited-nodes
visited-nodes
maze)
(cons (node-name from-node) visited-nodes)
que-update-fn)))
(defun path-iter-b (maze to que-of-not-visited-nodes
visited-nodes que-update-fn)
(if (endp que-of-not-visited-nodes)
’nil
; no way is found
(path-b maze
(first que-of-not-visited-nodes)
to
(rest que-of-not-visited-nodes)
visited-nodes
que-update-fn)))
For this first exercise we need an update function of the queue which takes the old queue and at the end
appends the new nodes which have not yet been visited or which already have been stored into the queue.
We introduce a higher order function filter-and-map which for a list applies a filter-function to decide
if the element should be stored in the queue of not yet visited nodes and a function which updates a node to
be a queue element. Here a queue element is a list with two elements, a node name and the way from the
start node to this actual node.
14
AI- Lisp / Lab 3 - Seaarch in graphs
(defun update-que-breadth-first (from-node to new-nodes-to-visit
que-of-not-visited-nodes
visited-nodes maze)
; Parameters to and maze are not needed here
(append
que-of-not-visited-nodes
(filter-and-map
#’(lambda (new-node) ; filter out new nodes to put on the queue
(not (or
; the node has already been visited
(member new-node visited-nodes)
; the node is already on the queue
(member-if2
#’(lambda (que-element)
(eq new-node (node-name que-element)))
que-of-not-visited-nodes))))
#’(lambda (node)
; creates a queue-object to remember the way up to the node
(build-que-element
node
(add-to-the-end (node-name from-node)
(way from-node))))
new-nodes-to-visit)))
We introduce functions for creating a queue element and to retrieve its parts. We do this function more general and have two optional parameters which we do not need to use here3. They will be used in exercises
3B and 3C.
(defun build-que-element (node-name way
&optional dist-from-start
estimated-total-dist)
(cond ((eq dist-from-start nil) (list node-name way)) ; For 3A
((eq estimated-total-dist nil)
(list node-name way dist-from-start)) ; For 3B
(t (list node-name way dist-from-start estimated-total-dist))))
; For 3C
(defun node-name (que-element)
(first que-element))
(defun way (que-element)
(second que-element))
(defun distance-from-start (que-element)
(third que-element))
(defun estimated-total-dist (que-element)
(fourth que-element))
We also introduce a test print function print-info, which prints the nodes we visit and the queue and the
visited nodes. The printing can be on or off (is on from the beginning) and can be changed by giving the
global variable *print-info* the value t respectively nil.
(defun print-info (from-node que-of-not-visited-nodes visited-nodes)
(when *print-info*
(format t "~&node= ~s~%queue= ~s~%visited nodes= ~s~%~%"
from-node que-of-not-visited-nodes visited-nodes)))
(setf *print-info* t) ; initially print information
2. We
use here a variant of member, where the first argument is a function describing what kindf of membership for an element should be used in the list.
3.
If an optional parameter is not given it is initialized to nil.
AI-Lisp / Lab 3 - Graph search
15
Assignment 3A. Define the higher order function filter-and-map. It takes three arguments. First argument is a predicate function which will be applied on every element in the list and tells if the element should
remain in the result or be filtered away. The second arguments is a function which is applied on every elements which should remain and may update that element. The third argument is the list.
(defun filter-and-map (filter-fn? map-fn l)
.... your solution ...)
(filter-and-map #’numberp #’(lambda (n) (* n n)) ’(1 a 2 b 3 4 c))
=> (1 4 9 16)
; take every numebr and square it
(filter-and-map #’(lambda (n) (> n 0)) #’list ’(1 -2 3 -4 -5 6))
=> ((1) (3) (6))
; take every positive number and put it into a list
With this function you should test the breath-first search and follow its behavior by making i.e. trace with
the print-info function, so you understand how breath-first search works. It is important for the remaining assignments 3B and 3C. Compare also its behavior with depth-first search.
ASSIGNMENT 3B - FIND THE SHORTEST PATH WITH DIJKSTRAS ALGORITHM
In this assignment you will start with the code from 3A and implement an algorithm to find the shortest
path. Search is described in the Russel-Norvig Artificial Intelligence book.
We introduce now a distance between nodes. We change the representation to let every node to be a list
(node-name ((node-name distance) (node-name distance) ...))
(setq a-maze-D ’((in ((a 5)))
(a ((in 5) (b 2) (c 4)))
(b ((a 2)))
(c ((a 4) (d 10) (e 20)))
(d ((c 10)))
(e ((c 20) (f 4) (k 8) (j 15)))
(f ((g 6) (e 4) (h 1)))
(g ((f 6)))
(h ((f 1) (i 3) (j 4)))
(i ((h 3)))
(j ((e 15) (h 4) (k 18) (out 14)))
(k ((e 8) (j 18)))
(out ((j 14)))))
IN
5
D
A
4
10
2
K
18
C
20
B
G
8
E
15
6
4
F
I
3
1
H
4
OUT
14
J
16
AI- Lisp / Lab 3 - Seaarch in graphs
We introduce now a new main function path-maze-Dijkstra:
(defun path-maze-Dijkstra (maze start end)
(path-b maze
(build-que-element start ’() 0)
end
’()
’()
#’que-update-D))
The functions path-b and path-iter-b should not be changed.
Assignment 3B. We need a new update function which we here call que-update-D. The elements in the
queue may look as (nodname path distance-from-start).We store in every queue node element
also the distance from the start node, by adding the distances between nodes on the way up to the actual
node. We also store the elements in sorted order depending of the distance from the start node. This guaranties that we always take the node to expand, which is closest to the start node. We also guarantee that we
never process nodes on paths which are longer than the final path.
Observe the following properties of the algorithm. When you expand a node you have then found the shortest path up to that node. If you come to that node later it should not be placed on the queue again. You may
get to a node again (not yet expanded) which is already stored on the queue. If the way is shorter to that
node it should replace the one already stored on the queue.
Test the function and show by examples that you have understood the algorithm and that you will find the
shortest path.
ASSIGNMENT 3C - HEURISTIC SEARCH WITH THE A*-ALGORITHM
Now we will implement a much better algorithm using knowledge from the problem domain. Read carefully the section in Norvig-Russels Artificial Intelligence book in section 4.1. The information from the
problem domain is that we know in a coordinate system where the nodes are. This will be used to make
much better estimates how close a node is to the goal node.
Now we will store the maze in a coordinate system, so that every node has a coordinate. We expand the
representation so that every node is represented with
(node-name list-with-neighbor-nodes (x-coordinate y-coordinate))
(setq a-maze-A* ’((in ((a 5)) (2 19))
(a ((in 5) (b 2) (c 4)) (2 15))
(b ((a 2)) (1 14))
(c ((a 4) (d 10) (e 20)) (5 16))
(d ((c 10)) (6 19))
(e ((c 20) (f 4) (k 8) (j 15)) (7 12))
(f ((g 6) (e 4) (h 1)) (10 14))
(g ((f 6)) (11 17))
(h ((f 1) (i 3) (j 4)) (10 13))
(i ((h 3)) (8 11))
(j ((e 15) (h 4) (k 18) (out 14)) (11 10))
(k ((e 8) (j 18)) (3 9))
(out ((j 14)) (8 1))
))
We will now use the coordinates to speed up the search. We give a merit to each node we have put on the
queue. The merit is the sum of the actual distance from the start node to the actual node and an estimate of
the remaining distance from the actual node to the goal node. The estimated distance can be calculated as
the “straight distance” between the node and the goal node. In the real world the real distance is equal or
longer than this value, but it is a good estimate how good the node is compared with the other nodes. We
give this merit to each node which we store onto the queue. The A* algorithm works optimal if the estimated merit is equal or shorter than the real distance and the algorithm will find the shortest path. There are
proofs in the AI book, but is not needed for this assignment.
AI-Lisp / Lab 3 - Graph search
17
We introduce a new main function path-maze-A*:
(defun path-maze-A* (maze start end)
(path-b maze
(build-que-element start ’() 0
(estimated-distance start end maze))
end
’()
’()
#’que-update-A*))
It is appropriate to introduce some primitives:
(defun x-koord (node maze)
(first (koordinates node maze)))
(defun y-koord (node maze)
(second (koordinates node maze)))
(defun koordinates (node maze)
(second (cdr (assoc node maze))))
Assignment 3C. Define the function que-update-A*. The structure of the function should follow the
get-update-D function from assignment 3B, but the calculations of the distances differ,
18
AI- Lisp / Lab 3 - Seaarch in graphs