CPSC 352 -- Artificial Intelligence
Notes: Best First Search in Prolog

Introduction

In this lecture we cover the following Prolog programs from Chapter 15. The programs are listed in the text but you may want to print out these listings and bring them to class:

Search in PROLOG

Three types of search can be implemented in PROLOG using the same basic algorithm. The differences come in the choice of data structure used to implement the open and closed lists:
SearchOpen ListClosed List
Depth FirstStackSet
Breadth FirstQueueSet
Best FirstPriority QueueSet

Setting up the Search

The main predicate for all three types of searches will initialize the data structures and invoke the path/3 predicate, which will recursively search from the start to the goal state.

The main predicate for depth-first search looks as follows:

    go(Start,Goal) :-
        empty_stack(Empty_open),
        stack([Start,nil], Empty_open, Open_list),
        empty_set(Closed_set),
        path(Open_list, Closed_set, Goal).

The main predicate for breadth-first search looks as follows:

    go(Start,Goal) :-
        empty_queue(Empty_open),
        enqueue([Start,nil], Empty_open, Open_list),
        empty_set(Closed_set),
        path(Open_list, Closed_set, Goal).

Note that for depth first and breadth first searches the current state is represented as the list [State,Parent]. For the initial state this gives [Start,nil] because the initial state has no parent. This representation of the current state allows the solution path to be recursively reconstructed from the goal state and the closed list as follows:

    printsolution([State,nil],_) :- write(State),nl.
    printsolution([State,Parent), Closed_set) :-
        member_set([Parent,GrandParent], Closed_set),
        printsolution([Parent,GrandParent], Closed_set),
        write(State), nl. 

The main predicate for best-first search looks as follows. Note the addition of the heuristic/3 predicate:

    go(Start,Goal) :-
        empty_set(Closed_set),
        empty_pq(Empty_open),
        heuristic(Start, Goal, H),
        insert_pq([Start,nil,0,H,H], Empty_open, Open_list),
        path(Open_list, Closed_set, Goal).

Note that for best first search the state is represented as the five-tuple:

     [State,Parent,Depth,Heuristic,Sum]
Recall that the basic form of the evaluation function for heuristic search is :
   f(n) = g(n) + h(n)
In this state representation, the Depth argument corresponds to g(n), the Heuristic argument corresponds to h(n), and the Sum argument corresponds to f(n).

Recursive Search: path/3

With slight variations due to the different data structures used, the path/3 predicate is defined as follows:
   path(Open_list,_,) :-
      empty_stack(Open_list),                        %% Or empty_queue or empty_pq
      write("No solution found with these rules.").

   path(Open_list, Closed_set, Goal) :-
      stack([State,Parent],_,Open_list), State=Goal,        %% Or dequeue or dequeue_pq
      write("The solution path is:"), nl,
      printsolution([State,Parent], Closed_set).      

   path(Open_list, Closed_set, Goal) :-
      stack([State,Parent],Rest_open, Open_list),           %% Or dequeue or dequeue_pq
      get_children(State, Rest_open, Closed_set, Children),
      add_list_to_stack(Children, Rest_open, New_open),       %% Or add_list_to_queue or insert_list_pq
      union([[State,Parent]],Closed_set,New_closed_set),
      path(New_open, New_closed_set, Goal), !.

The comments indicate where the differences occur among these three algorithms. Note how the recursive path/3 predicate manages the updating the open and closed lists for the search. Each level of recursion uses a different open and closed lists. If the open list becomes empty before the goal is reached, that constitutes failure. If the first element on the open list is a Goal state, that constitutes success.

The cut operator is used here to cut off PROLOG's automatic backtracking. This prevents PROLOG from storing the old values of open and closed. When search fails at any given point, the algorithm does not go back to the preceding values of open and closed. Instead, it updates open and closed

In-class Exercise

Hand trace the algorithm, using first stack, then queue, then priority queue, on an instance of a portion of the Knight's Tour search space.