Schubert's Steamroller in Prolog

“Schubert’s Steamroller” was an unsurmountable problem for the 1978 Markgraf Karl Resolution Theorem Prover (The Markgraf Karl Refutation Procedure, 1981), but today is only of interest as an exercise in modeling a problem expressed in human language in some formalism.

So I have tried to do that. Paper from 1985:

The original problem modeled as an AND-OR set of literals has some interesting aspects:

  • Integrity constraints (“conjunction => false”). It took me some time to figure out how to get them into a Prolog program. In the end I added \+ integrity_constraint_fails(...) at the end of the clauses.
  • Non-Horn clauses (head with disjunction).
  • An apparent XOR in the description which is modelled by an OR.
  • Misses the transitive relationship of “much_smaller_than/2” … it really isn’t in there. What happened?
  • Does some peculiar modeling with function symbols concerning grain-eating animals which I don’t get and which is pointless anyway because the whole universe only has one grain: g.
  • Has a wrongly transcribed clause in the typeset version.

Here we go:

:- discontiguous likes_to_eat/2.
:- discontiguous must_not_occur/0.
:- discontiguous dislikes_to_eat/2.

% ----------------------------------------------------------------------
% From: AAAI-84 Proceedings. Copyright 1984 AAAI. p 330 ff.
% "A MECHANICAL SOLUTION OF SCHUBERT'S STEAMROLLER BY MANY-SORTED RESOLUTION"
% Christoph Walther, Institut für Informatik I, Universität Karlsruhe
%
% "In the fall of 1978 L. Schubert spend his sabbatical at the University of Karlsruhe
%  and a first-order axiomatization of his Problem was given to the Markgraf Karl
%  Refutation Procedure (MKRP), a resolution-based automated theorem prover
%  under development at the University of Karlsruhe. The system generated
%  the clause set of figure 1.1, but failed to find a refutation."
%
% "Wolves, foxes, birds, caterpillars and snails are animals, and there are
%  some of each of them. Also, there are some grains and grains are plants.
%  Every animal either likes to eat all plants or (likes to eat) all animals
%  much smaller than itself that like to eat some plants. Caterpillars and 
%  snails are much smaller than foxes which in turn are much smaller than wolves.
%  Wolves do not like to eat foxes or grains while birds like to eat 
%  caterpillars but not snails. Caterpillar and snails like to eat some plants.
%  Therefore there is an animal that likes to eat a grain-eating animal."
%
% Figure 1.1
%
% Using the following predicates as an abbreviation:
%
% A(x) - x is an animal       W(x) - x is a wolf
% F(x) - x is a fox           B(x) - x is a bird
% C(x) - x is a caterpillar   S(x) - x is a snail
% G(x) - x is a grain         P(x) - x is a plant
% M(xy) - x is much smaller than y
% E(xy) - x likes to eat y
%
% we obtain the following set of clauses as a predicate logic formulation
% of the problem [the commas are ORs, ed.]
%
% (1)  {W(w)}                        (2)  {F(f)}
% (3)  {B(b)}                        (4)  {C(c)}
% (5)  {S(s)}                        (6)  {G(g)}
% (7)  {~W(x1),A(x1)}                (8)  {~F(x1),A(x1)}
% (9)  {~B(x1),A(x1)}                (10) {~C(x1),A(x1)}
% (11) {~S(x1),A(x1)}                (12) {~G(x1),P(x1)}
% (13) {~A(x1),~P(x2),~A(x3),~P(x4),E(x1 x2),~M(x3 x1),~E(x3 x4),E(x1 x3)}
% (14) {~C(x1),~B(x2),M(x1 x2)}      (15) {~S(X1),~B(x2),M(x1 x2}
% (16) {~B(x1),~F(x2),M(x1 x2)}      (17) {~F(x1),~W(x2),M(x1 x2)}
% (18) {~F(x1),~W(x2),~E(x2 x1)}     (19) {~G(x1),~W(x2),~E(x2 x1)}
% (20) {~B(x1),~C(x2),E(x1 x2)}      (21) {~B(x1),~S(x2),~E(x1 x2)}
% (22) {~C(x1),P(h(x1))}             (23) {~C(x1),Ex1 h(x1))}  (22 is wrong in reprint)
% (24) {~S(x1),P(i(x1))}             (25) {~S(x1),E(x1 i(x1))}
% (26) {~A(x1),~A(x2),G(j(x1 x2))}
% (27) {~A(x1),~A(x2),~E(x1 x2),~E(x2 j(x1 x2))}
% ----------------------------------------------------------------------

% Part 1
% ------
% "Wolves, foxes, birds, caterpillars and snails are animals, and there are some of 
% each of them."
% "Also, there are some grains and grains are plants."

% Skolemize the existence facts: 
% ∃X:wolf(X) ~~~> wolf(w), where "w" is an atom standing for some wolf.

wolf(wolf).
fox(fox).
bird(bird).
caterpillar(caterpillar).
snail(snail).
grain(grain).

% Define the hierarchy of sorts

animal(X) :- wolf(X).
animal(X) :- fox(X).
animal(X) :- bird(X).
animal(X) :- caterpillar(X).
animal(X) :- snail(X).
plant(X)  :- grain(X).

% Part 2
% ------
% "Every animal either likes to eat all plants ...."
%
%   animal(A) -> (plant(Plant) -> likes_to_eat(A,Plant))
%
% transformed into
%
%   animal(A) & plant(A) -> likes_to_eat(A,Plant)
%
% We add "unless there are exceptions" using NAF because that 
% seems to be a good way to cover the integrity constraints further below.

likes_to_eat(A,Plant) :- 
   animal(A),plant(Plant),
   \+dislikes_to_eat(A,Plant).

% "...or (likes to eat) all animals much smaller than itself that like to eat some plants"
% We add "unless there are exceptions" using NAF.

likes_to_eat(A,Prey) :- 
   animal(A),animal(Prey),plant(Plant),
   much_smaller(Prey,A),likes_to_eat(Prey,Plant),
   \+dislikes_to_eat(A,Prey).

% In the paper, everything is in a single rule, with a disjunction in the consequent.
%
% A(x1) & P(x2) & A(x3) & P(x4) & M(x3,x1) & E(x3,x4) => E(x1,x2) | E(x1,X3).
%
% That is not a Horn clause! Also, the "either ... or" indicates an exclusive OR,
% but in the paper (and here) we have an inclusive OR. Is this really right?

% Part 3
% ------
% "Caterpillars and snails are much smaller than foxes which in turn are much
% smaller than wolves"

much_smaller_direct(X,Y) :- caterpillar(X),bird(Y),!.
much_smaller_direct(X,Y) :- snail(X),bird(Y),!.
much_smaller_direct(X,Y) :- bird(X),fox(Y),!.
much_smaller_direct(X,Y) :- fox(X),wolf(Y),!.

% The paper's formalization seems to miss the transitive relationship completely.
% What happened there? Without that, wolves eat nothing. We add it:

much_smaller(X,Y) :- much_smaller_direct(X,Y).
much_smaller(X,Y) :- much_smaller_direct(X,Z),much_smaller(Z,Y).

% Part 4
% ------
% "Wolves do not like to eat foxes or grains"
%
% These are not clauses, but integrity constraints. In the paper, they are actually 
% given as:
%
% F(x1),W(x2),E(x2x1) => false.
% G(x1),W(x2),E(x2x1) => false.
%
% They actually say something about the exceptions to "likes_to_eat" but 
% one has to think a bit out of the box for that:

dislikes_to_eat(X,Y) :- wolf(X),fox(Y).
dislikes_to_eat(X,Y) :- wolf(X),grain(Y).

% Part 5
% ------
% "...while birds like to eat caterpillars but not snails". Similar to the above.

likes_to_eat(X,Y) :- 
   bird(X),caterpillar(Y),
   \+dislikes_to_eat(X,Y).

dislikes_to_eat(X,Y) :- bird(X),snail(Y).

% Part 6
% ------
% "caterpillar and snails like to eat some plants"
%
% Really "there are plants such that caterpillar and snails like to eat those"
% or "Not for all plants, caterpillars or snails do not like them"
%
% In the paper, this employs a function symbol:
%
% C(x1) => P(h(x1))      % "h(x1) is a plant that is a function of caterpillar x1"
% C(x1) => E(x1 h(x1))   % "caterpillar x1 likes to eat h(x1)"
% S(x1) => P(i(x1)       % "i(x1) is a plant that is a function of snail x1"
% S(x1) => E(x1 i(x1))   % "snail x1 likes to eat i(x1)"
%
% There is really not enough info to know what to do here. In fact, our
% whole universe has just one plant: P = grain. So what's the point?

likes_to_eat(X,P) :- caterpillar(X),plant(P),\+dislikes_to_eat(X,P).
likes_to_eat(X,P) :- snail(X),plant(P),\+dislikes_to_eat(X,P).

% Part 7
% ------
% "therefore there is an animal that likes to eat a grain-eating animal" 
% (actually "...that likes to eat an animal that likes to eat grain")
%
% That's the query!
% 
% The paper says:
%
% A(x1),A(x2) => G(j(x1x2))                  % j(x1x2) is grain that depends on the animal pair. WEIRD! WEIRD! WEIRD!
% A(x1),A(x2),E(x1x2),E(x2j(x1x2)) => false  % An integrity constraint that will make the program inconsistent

% Here is ours, we constructively search for the animal...

go(A) :- animal(A),animal(Prey),grain(G),likes_to_eat(A,Prey),likes_to_eat(Prey,G).

/* RUN IT ----------------------------

?- set_prolog_flag(answer_write_options,[max_depth(0)]).
true.

% Ready!


?- time(setof(A,go(A),L)).
% 2,443 inferences, 0.000 CPU in 0.000 seconds (99% CPU, 6955913 Lips)
L = [bird, fox, wolf].


% Let's take a look at these...

?- setof([A,B],likes_to_eat(A,B),C).
C = [[bird,caterpillar],
     [bird,grain],
     [caterpillar,grain],
     [fox,bird],
     [fox,caterpillar],
     [fox,grain],
     [fox,snail],
     [snail,grain],
     [wolf,bird],
     [wolf,caterpillar],
     [wolf,snail]].

?- setof(X,likes_to_eat(wolf,X),L).
L = [bird, caterpillar, snail].
*/
4 Likes

But actually, what I asked myself: What happened to a “Prolog with sorts”? It seems like a straightforward extension and naturally encompasses the default, “unisorted” (anysorted?) Prolog. Extending the unification procedure should not be extreme and the benefits in modeling, search space pruning and debugging feel compelling. Yet … nyet!

I’ve got source code for an order sorted prolog like language with functions. Just seen I’ve got a compiler to prolog as well. What’s the best way to markup code?

Nah, I’m not putting four spaces at the start of every line, too much hassle! Send me a message with your email and I’ll send you a copy.

You can format code in two different ways. The first is to use 4 spaces, like this:

This is some normal text.

    /* this is code */
    foo :- bar.

More text.

The other way is to have three backticks on a line to delimit the code chunk:

This is normal text.

```
/* this is code */
bar :- baz.
```

More text.

Thanks Boris!

Here’s the order sorted logic from the Eco Logic book. The book describes it all in psuedocode which is basically just IF-THEN-ELSE rules - easy tranlsation to Prolog.

%-*-mode:prolog-*-

%%%
%%% Eco-Logic: Logic-Based Approaches to Ecological Modelling.
%%% Robertson et al.
%%%
%%% Chapter 4 An Order Sorted Logic
%%%
%%%
%%% [-'../../misc/Eco_Logic.code/chap4/order_sorted_logic'].
%%%

%%%
%%% If things are not defined then they should fail.
%%% Example : no sort_term predicates defined should fail.
%%%
:-set_prolog_flag(unknown, fail).

:-op(950, xfx, #).
:-op(900, xfx, <-).
:-op(900, xfx, <=).
:-op(700, xfx, in).
:-op(500, yfx, and).
:-op(500, yfx, or).

%%%
%%% Obj in Sort
%%%
%%% Object is a member of Sort.
%%%
Obj in Sort :-
        var(Obj),
        var(Sort),
        throw(error(domain_error(in/2, Obj, Sort))).
Obj in Set :- 
        member(Obj, Set).
Obj in Sort :- 
        subobj(Sort, Obj).
Obj in Sort :- 
        subsort(Sort, SubSort),
        Obj in SubSort.
Obj in Sort :-
        sort_term(Sort, Obj, Consts),
        satisfiable_constraints(Consts).
Obj in srelation(Relation) :-
        sort_relation(Relation, Consts),
        satisfiable_constraints(Consts).
Obj in Sort :-
        sort_test(Sort, Obj, Test),
        call(Test).
Obj in Sort1 and Sort2 :-
        Obj in Sort1,
        Obj in Sort2.
Obj in Sort1 or Sort2 :-
        Obj in Sort1;
        Obj in Sort2.
Obj in inv(Sort) :-
        \+ Obj in Sort.

%%%
%%% satisfiable_constraints(+Consts)
%%%
%%% All of the elements of the list of constraints
%%% Consts are satisfiable.
%%%
satisfiable_constraints([]).
satisfiable_constraints([Obj in Sort|Rest]) :-
        Obj in Sort,
        satisfiable_constraints(Rest).

%%%
%%% solve(+Term, ?Result)
%%%
%%% The interpreter
%%%
solve(Term, Result) :-
        term_class(primitive, Term),
        !,
        Result = Term.
solve((Obj in Sort), Obj) :-
        !,
        Obj in Sort.
solve((A and B), (RA and RB)) :-
        !,
        solve(A, RA),
        solve(B, RB).
solve((A or _), RA) :-
        solve(A, RA).
solve((_ or B), RB) :-
        !,
        solve(B, RB).
solve([H|T], [R|Rest]) :-
        !,
        solve(H, R),
        solve(T, Rest).
solve(((Ins, Outs)#Tm), Result) :-
        !,
        satisfiable_constraints(Ins),
        solve(Tm, Result),
        satisfiable_constraints(Outs).
solve(Term, Result) :-
        meta_fun(Term, Result, Call),
        !,
        call(Call).
solve(Term, Result) :-
        meta_pred(Term, Call),
        !,
        call(Call),
        Term = Result.
solve(Term, Result) :-
%       nonvar(Term),
        compound(Term),
        standardise_term(Term, ETerm),
        (term_class(function, ETerm), !, osl_eval(ETerm, Result)
    ;
        term_class(predicate, ETerm), establish(ETerm, Result)).


%%%
%%% solve_list(+L,-S)
%%%
%%% S is the list L where each element is sorted.
%%%
solve_list([], []).
solve_list([H|T], [SH|ST]) :-
        solve(H,SH),
        solve_list(T,ST).

%%%
%%% standardise_term(+Term, -ETerm)
%%%
%%% The Term in the sorted logic can be standardised to form the term
%%% ETerm with all its arguments fully evaluated.
%%%
standardise_term(Term, ETerm) :-
        Term =..[Functor|Args],
        solve_list(Args, RArgs),
        ETerm =.. [Functor|RArgs].

%%%
%%% osl_eval(+Term, -Result)
%%%
%%%
%%%
osl_eval(Term, Result) :-
        sys_fun(Term, (Ins,Outs), Result in Sort, Call),
        satisfiable_constraints(Ins),
        call(Call),
        satisfiable_constraints(Outs),
        Result in Sort.
osl_eval(Term, Result) :-
        term_class(defined_function, Term),
        (Ins,Outs)#Term1 <= Body,
        standardise_term(Term1, ETerm1),
        Term = ETerm1,
        satisfiable_constraints(Ins),
        solve(Body, Result),
        satisfiable_constraints(Outs).

%%%
%%% establish(+Term, -Result)
%%%
%%% 
%%%
establish(Term, Result) :-
        sys_pred(Term, (Ins, Outs), Call),
        satisfiable_constraints(Ins),
        call(Call),
        satisfiable_constraints(Outs), 
        Result = Term.
establish(Term, Result) :-
        term_class(defined_predicate, Term),
        ((Ins,Outs)#Term1,
        standardise_term(Term1, ETerm1),
        Term=ETerm1,
        satisfiable_constraints(Ins),
        satisfiable_constraints(Outs),
        Result=Term
    ;
        (Ins,Outs)#Term1 <- Body,
        standardise_term(Term1, ETerm1),
        Term=ETerm1,
        satisfiable_constraints(Ins),
        solve(Body,_),
        satisfiable_constraints(Outs),
        Result=Term).

%%%
%%% term_class(C, T)
%%%
%%% Term T is of class C.
%%%
term_class(primitive, Term) :- 
        number(Term), 
        !.
term_class(primitive, Term) :- 
        atom(Term), 
        !. 
term_class(primitive, Term) :-
        var(Term), 
        !.
term_class(defined_predicate, Term) :- 
        def(Term, pred(_,_)),
        !.
term_class(defined_function, Term) :- 
        def(Term, fun(_,_,_)),
        !.
term_class(predicate, Term) :- 
        sys_pred(Term, _, _), 
        !.
term_class(predicate, Term) :- 
        term_class(defined_predicate, Term),
        !.
term_class(function, Term) :- 
        sys_fun(Term, _, _, _), 
        !. 
term_class(function, Term) :- 
        term_class(defined_function, Term).
%%%
%%% Built in sorts
%%%
sort_test(number, N, number(N)).

%%%
%%% Built in system functions.
%%%
sys_fun(A*B, ([A in number, B in number], []), C in number, C is A*B).
sys_fun(A-B, ([A in number, B in number], []), C in number, C is A-B).
sys_fun(A/B, ([A in number, B in number], []), C in number, C is A/B).
sys_fun(A+B, ([A in number, B in number], []), C in number, C is A+B).

%%%
%%% sum_elements(+Lst, +Acc, -Sum)
%%%
%%% The sum of the elements of Lst plus Acc is Sum.
%%%
sum_elements([], Sum, Sum).
sum_elements([H|T], Acc0, Sum) :-
        Acc1 is Acc0 + H,
        sum_elements(T, Acc1, Sum).

%%%
%%% sum_elements(+Lst, -Sum)
%%%
%%% The summation of all of the elements of Lst is Sum.
%%%
sum_elements(Lst, Sum) :-
        sum_elements(Lst, 0, Sum).

sys_fun(sum(Lst), ([], []), Sum in number, sum_elements(Lst, Sum)).

%%%
%%% Built in system predicates.
%%%
sys_pred(A=B, ([], [A in universal, B in universal]), A=B).
sys_pred(A<B, ([], [A in number, B in number]), A<B).
sys_pred(A=<B, ([], [A in number, B in number]), A=<B).
sys_pred(A=:=B, ([], [A in number, B in number]), A=:=B).
sys_pred(A>=B, ([], [A in number, B in number]), A>=B).
sys_pred(A>B, ([], [A in number, B in number]), A>B).

%%%
%%% Built in meta-functions.
%%%
meta_fun(setof(F), Set, (findall(X, solve(F, X), Bag), sort(Bag, Set))).
meta_fun(bagof(F), Bag, findall(X, solve(F, X), Bag)).

My first attempt at the steamroller last night. It got late so I stopped before I got to the interesting clauses and went to sleep. You get an idea of the sort definitions though.

/*
Wolves, fishes, birds, caterpillars, and snails are animals, and there is at least
one of each kind of animal. Grain is a plant, and there exists at least on such
plant. Every animal is an herbivore or else eats eats all herbivores which are
much smaller than itself. Caterpillars and snails are much smaller than birds, which
in turn are much smaller than fish, and fish are much smaller than wolves. 
Caterpillars and snails are herbivores. It is to be shown that there exists an 
animal that eats a grain-eating animal.
*/

subsort(universal, animal).
subsort(universal, plant).

subsort(animal, wolf).
subsort(animal, fish).
subsort(animal, bird).
subsort(animal, caterpillar).
subsort(animal, snail).
subsort(grain, plant).

subobj(wolf, w).
subobj(fish, f).
subobj(bird, b).
subobj(caterpillar, c).
subobj(snail, s).
subobj(grain, g).

def(much_smaller_than(X, Y), pred([X in universal, Y in universal], [])).
def(eats(X, Y), pred([X in universal, Y in universal], [])).

([X in caterpillar, Y in bird], []) # much_smaller_than(X, Y).
([X in snail, Y in bird], []) # much_smaller_than(X, Y). 
([X in bird, Y in fish], []) # much_smaller_than(X, Y). 
([X in fish, Y in wolf], []) # much_smaller_than(X, Y). 

([X in wolf], [ Y in inv(fish or grain)]) # eats(X, Y).
1 Like

I didn’t finish it. I’ve got too much on my plate. I can see some difficulties with Eco Logic and the steamroller but it should be doable. Maybe some time in the future.