A simple planner in Prolog

Below is the source for the "Warplan" basic planner I snaffled from a conference proceedings many years ago.

The original had a few typos that I’ve tried to fix (proceedings sometimes come on toilet paper and the photocopier that produced them has seen better days). Maybe not 100% successfully. 

I normally use an Edinburgh-style interpreter — e.g. SWI Prolog. GNU’s gprolog will definitely give trouble, but you may be able to get something working, if that’s all you have. SWI Prolog (like GNU Prolog) is free s/w and works on a slew of Mac’s, Wintoxboxes, Linux & misc Unixes.

Normally you put all the bits and pieces of your Prolog program in different files and "reconsult" them. E.g. for Warplan you might put the program in "warplan.pl", some particular problem you want to run in another file "problem.pl" and then in SWI Prolog do something like:

:- [warplan,problem].

This ensures Warplan will be loaded first. That’s required because it defines some syntax extensions needed by the various examples.

And talking about examples, here’s another one.

This is an example of a very simple assembler. If a planner can decide what sequence of operations can move blocks around, moving numbers around in a set of registers is about the same kind of thing.

Here’s the unvarnished Prolog code:

———- Example 2: simple compiler using Warplan ———-

%% Machine code generation
:- style_check(-singleton).
 
 :-o p(250,yfx,#).
 :-o p(250,yfx,'IS').
 :-o p(150,xfy,+).
 :-o p(150,xfy,-).
 :-o p(150,fx,load).
 :-o p(150,fx,add).
 :-o p(150,fx,subtract).
 :-o p(150,fx,store).
 :-o p(150,fx,reg).
add(acc 'IS' V1+V2, add R # V1+V2).
add(acc 'IS' V1-V2, subtract R # V1-V2).
add(acc 'IS' V, load R # V).
add(reg R 'IS' V, store R # V).
del(acc 'IS' Z, U) :- add(acc 'IS' V, U).
del(reg R 'IS' Z, U) :- add(reg R 'IS' V, U).
can(load R # V, reg R 'IS' V).
can(store R # V, acc 'IS' V).
can(add R # V1+V2, reg R 'IS' V2 & acc 'IS' V1).
can(subtract R # V1-V2, reg R 'IS' V2 & acc 'IS' V1).
given(start, reg(1) 'IS' c1).
given(start, reg(2) 'IS' c2).
given(start, reg(3) 'IS' c3).
given(start, reg(4) 'IS' c4).

———- end ———-

(Oh damn! The formatter is getting in the way!)

As before, the Warplan rules define what can be done under what conditions, and how different toy assembler instructions change the Warplan database (Prolog has a database, too, but that is a different database).

Note, also, Prolog ":-" instructions up the top have defined some new-to-Prolog operators. These are only introduced to make the reading of the code easier. E.g. the "#" binary operator is introducted so we can add comments after each assembler instruction.

The Warplan code also shows a particular starting state — i.e. with c_i in reg_i, i = 1..4.

Now if we have Warplan and the problem loaded into a Prolog interpreter, we can ask it to plan out some goals.

E.g.

:- plans(acc ‘IS’  (c1-c2)+(c3-c4), start).

We want the accumulator (in old-fashioned machines, different from the "registers") to end up with a particular expression calculated in it.

If all is well, Warplan should spit out:




start;



load 3 # c3;



subtract 4 # c3-c4;



store X1 # c3-c4;



load 1 # c1;



subtract 2 # c1-c2;



add X1 # (c1-c2)+(c3-c4).



 



You can see a mystery "X1" in there. The "compiler" had to generate a



temporary variable to store a partial result.



 



Anyway, enjoy. The Prolog code for a simple Warplan planner is below


%% Warplan: Warren, 1974 %% declarations for SWI-Prolog:- style_check(-singleton).:- unknown(_,fail).%% special Warplan operators:- op(700, xfy, & ).:- op(650, yfx, => ).%% Problem solver entry: Generation and output of a plan plans(C,T) :- \+ consistent(C,true), !, nl, write(impossible), nl, nl.plans(C,T) :- plan(C,true,T,T1), nl, output(T1), nl, nl.output(Xs => X) :- !, output1(Xs), write(X), write(.), nl.output1(Xs => X) :- !, output1(Xs), write(X), write(;), nl.output1(X) :- write(X), write(;), nl.%% Entry point to the main recursive loop plan(X & C,P,T,T2) :- !, solve(X,P,T,P1,T1), plan(C,P1,T1,T2).plan(X,P,T,T1) :- solve(X,P,T,P1,T1).%% Ways of solving a goal solve(X,P,T,P,T) :- always(X) ; call(X).solve(X,P,T,P1,T) :- holds(X,T), and_(X,P,P1).solve(X,P,T,X & P,T1) :- add(X,U), achieve(X,U,P,T,T1).%% Methods of achieving an action %% By extension achieve(X,U,P,T,T1 => U) :- preserves(U,P), can(U,C), consistent(C,P), plan(C,P,T,T1), preserves(U,P).%% By insertion achieve(X,U,P,T => V,T1 => V) :- preserved(X,V), retrace(P,V,P1), achieve(X,U,P1,T,T1), preserved(X,V).%% Check if a fact holds in a certain state holds(X,T => V) :- add(X,V).holds(X,T => V) :- !, preserved(X,V), holds(X,T), preserved(X,V).holds(X,T) :- given(T,X).%% Prove that an action preserves a fact preserves(U,X & C) :- preserved(X,U), preserves(U,C).preserves(_,true).preserved(X,V) :- numbervars(X & V,0,N), del(X,V), !, fail.%% preserved(_,true).preserved(_,_).%% Retracing a goal already achieved retrace(P,V,P2) :- can(V,C), retrace1(P,V,C,P1), append_(C,P1,P2).retrace1(X & P,V,C,P1) :- add(Y,V), equiv(X,Y), !, retrace1(P,V,C,P1).retrace1(X & P,V,C,P1) :- elem(Y,C), equiv(X,Y), !, retrace1(P,V,C,P1).retrace1(true,V,C,true).retrace1(X & P,V,C,X & P1) :- retrace1(P,V,C,P1).%% Consistency with a goal already achieved consistent(C,P) :- numbervars(C & P,0,N), imposs(S), \+ \+ intersect(C,S), implied(S,C & P), !, fail.consistent(_,_).%% Utility routines and_(X,P,P) :- elem(Y,P), equiv(X,Y), !.and_(X,P,X & P).append_(X & C,P,X & P1) :- !, append_(C,P,P1).append_(X,P,X & P).elem(X,Y & C) :- elem(X,Y).elem(X,Y & C) :- !, elem(X,C).elem(X,X).implied(S1 & S2,C) :- !, implied(S1,C), implied(S2,C).implied(X,C) :- elem(X,C).implied(X,C) :- call(X).intersect(S1,S2) :- elem(X,S1), elem(X,S2).not_equal(X,Y) :- X \= Y, X \= '$VAR'(_), Y \= '$VAR'(_).equiv(X,Y) :- \+ nonequiv(X,Y).nonequiv(X,Y) :- numbervars(X & Y,0,N), X = Y, !, fail.nonequiv(_,_).