• Aucun résultat trouvé

A Naive Prolog Solution to the 8-Puzzle

Concurrent Search

6.1 A Naive Prolog Solution to the 8-Puzzle

The 8-puzzle is a familiar example used to illustrate state space search problems [Nilsson, 1971]. The problem consists of a 3

×

3 matrix of eight cells marked with the numbers “1” to “8” and one space. A state of the puzzle is represented by a particular matrix of the cells. The successors of a state are generated by up to four different ways in which a marked cell may be moved into the space. Only if the space is in the center will there be four successors. The actions of marked cells turn out to be more conveniently thought of as actions of the space: left, right, up and down. A solution to

the puzzle is a sequence of actions that will transform some initial state to some desired goal state. The goal state conventionally used is shown if Figure 6.1.1.

An arbitrary initial state is given in Figure 6.1.2. There are three possible actions from this state with the three successor states given in Figure 6.1.3. A complete solution to the 8-puzzle with the given initial state is the sequence of actions

[left, down, right, down, right, up, left].

In abstract, this problem is similar to the onTree problem of Chapter 3, where the nodes in the tree are the states and their descendants are the states obtainable from them by legal actions. In the present case, however, the tree is constructed dynamically while it is searched. Part of the search tree for the problem above, including the sequence of actions taken to reach the leaves is given below in Figure 6.1.4.

1 2 3 4 5 6 7 8

Fig. 6.1.1. The goal state of 8-puzzle

2 8 3

1 5

7 4

6

Fig. 6.1.2 An arbitrary initial state of the 8-puzzle

2 8 3

1 5

7 4 6

Left

2 8 3

1 5

7 4

6

Right

2 8 3

1 5

7 4 6

Down Fig. 6.1.3 Successor states

2 8 3

Fig. 6.1.4 Search tree for the 8-puzzle to depth 3

Consider a naive Prolog program to solve this puzzle:

search(State,State) :– goal(State).

search(State, Soln) :– left(State,Left), search(Left,Soln).

search(State, Soln) :– right(State,Right), search(Right,Soln).

search(State, Soln) :– up(State,Up), search(Up,Soln).

search(State, Soln) :– down(State,Down), search(Down,Soln).

The predicates left, right, up and down give the successor states of the state given as their first argument, or fail if there is no such successor state.

This program, though declaratively correct, will most likely fail to terminate since it will perform a depth-first search of a tree with infinite branches. To avoid this, the information stored by each state can be extended to include the sequence of actions used to reach the state from the initial state and the predicates refined to fail if the successor is a previously encountered state. This would limit search to a finite tree, but would still be grossly inefficient. There are well known heuristics to better direct the search for this problem [Nilsson, 1971]. The naive program works poorly because it transfers the built-in left-to-right depth-first scheduling of Prolog directly to the search of the problem that really requires a more sophisticated scheduling.

Notwithstanding the naive nature of the program above, consider a similar solution in GDC:

search(nostate,Soln) :- Soln=none.

search(State,Soln) :- State=/=none

| isgoal(State,Flag), expand(Flag,State,Soln).

expand(true,State,Soln) :- Soln=State.

expand(false,State,Soln)

:- left(State,Left), search(Left,SolnL), right(State,Right), search(Right,SolnR), up(State,Up), search(Up,SolnU),

down(State,Down), search(Down,SolnD), choose(SolnL,SolnR,SolnU,SolnD,Soln).

choose(SolnL,_,_,_,Soln) :- SolnL=/=none | Soln=SolnL.

choose(_,SolnR,_,_,Soln) :- SolnR=/=none | Soln=SolnR.

choose(_,_,SolnU,_,Soln) :- SolnU=/=none | Soln=SolnU.

choose(_,_,_,SolnD,Soln) :- SolnD=/=none | Soln=SolnD.

choose(none,none,none,none,Soln) :- Soln=none.

A number of points may be noted. Firstly, the OR-parallelism, which was implicit in the Prolog program, is converted to GDC AND-parallelism with the termination of any of the OR-parallel branches indicated by the return of the dummy solution none.

Secondly, the actor choose is introduced to make an indeterminate choice between solutions in OR-parallel branches, returning none if no sub-branch returns a solution.

This technique of converting conceptually OR-parallelism to AND-parallelism is a cliché in concurrent logic programming, proposed by Codish and Shapiro [1986] and Gregory [1987]. In fact, the same technique is frequently used in Prolog.

Returning all solutions to a problem where a solution is obtainable by method A or method B is equivalent to combining the solutions obtained from method A and method B. The Prolog all-solutions program for this problem is:

search(nostate,[]) :- !.

search(State,[State]) :- goal(State),!.

search(State,Solns)

:-left(State,Left), search(Left,SolnsL), right(State,Right), search(Right,SolnsR), up(State,Up), search(Up,SolnsU),

down(State,Down), search(Down,SolnsD), append4(SolnsL,SolnsR,SolnsU,SolnsD,Solns).

where append4 simply appends its first four arguments to give its fifth. It is assumed that rather than fail, left(State,Left) will bind Left to nostate if there is no left successor state and likewise with the other actions. The same assumption is made in the original GDC program. The closeness of the two programs is apparent. The main difference is the use of choose as an-indeterministic OR operation picking one of its inputs, as compared with append4, an AND operation which combines its inputs. This is due to the flat nature of GDC: backtracking has to be replaced by an explicit choice. The Prolog test predicate goal is converted to the GDC actor isgoal, which returns true or false in a second argument. This is combined with the introduction of a new actor to cover the search beyond the test, which takes the Boolean value from isgoal and reacts appropriately. For simplicity, it is assumed that goal states in the search tree are leaves. Hence the cut in the Prolog program is a

green cut. A more complex Prolog program would be required to deal with cases where goal states in the search tree have descendants that are also goal states.

The choose actor explicitly represents the independent binding frames for each non-determinately choosable behavior which are implicit in the Prolog program. This corresponds to the onEither actor of Chapter 3. The indeterminism of GDC maps directly onto an indeterministic choice of solutions. The nondeterminism of Prolog is managed by cutting back the binding frame stack on backtracking. In an OR-parallel logic language an unbound channel, like the one that returns the solution in our search example, is duplicated for each OR-parallel computation. Explicit distinct channels in the GDC program replace this implicit copying. It will be assumed that:

:- choose(SolnL,SolnR,SolnU,SolnD,Soln)

is reducible as soon as any of its first four arguments are bound to anything but none, or when all its first four arguments are bound to none.

The result of executing the GDC program will be to create a tree of choose actors, replicating the search tree of the problem. The actor search(State,Soln) will first reduce to

:- isgoal(State,Flag), expand(Flag,State,Soln).

If isgoal(State,Flag) causes Flag to be bound to true, following the reduction of expand(Flag,State,Soln) the situation will be as in Figure 6.1.5.

choose

search search search search

Soln

SolnU SolnR

SolnL SolnD

Left Right Up Down

Fig. 6.1.5 Tree of search actors

This is the diagrammatic representation of Chapter 4, where the nodes are actors and the arcs communication channels. Unless either a goal node is found or no successor states are generated, each of the descendants of the root choose actor will become a further choose actors and so on.

The major difficulty with the program given is that the number of actors will grow exponentially, until any practical distributed or parallel architecture would be overwhelmed. In problems like this there are usually heuristics which indicate a goal state is more likely to be found in one subtree than another, but in GDC as described so far there is no way of giving preference between leaves in the search tree to expand.

choose

choose

search search

search search

choose

search search

search search

choose choose

search search search search search search search search

Soln

SolnL

SolnR SolnU SolnD

Fig. 6.1.6 Recursive tree of search actors