"When you step through the corresponding Prolog program using the Prolog debugger you will get an output like this one (after declaring t/0 as dynamic to avoid error messages):\n",
"```\n",
"| ?- p.\n",
" 1 1 Call: p ? \n",
" 2 2 Call: q ? \n",
" 3 3 Call: t ? \n",
" 3 3 Fail: t ? \n",
" 4 3 Call: s ? \n",
" 4 3 Exit: s ? \n",
" 2 2 Exit: q ? \n",
" 5 2 Call: r ? \n",
" 5 2 Exit: r ? \n",
" 1 1 Exit: p ? \n",
"yes\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "b4b28da4",
...
...
%% Cell type:markdown id:9eb5530a tags:
# Linear Resolution and SLD Resolution
```Horn clauses``` are clauses (i.e., disjunctions of literals) with at most one positive literal.
A ```query``` or denial is a Horn clause without positive literal.
A ```fact``` is a Horn clause consisting of one positive literal and no negative literals.
Note that the CNF of our knights and knaves puzzle, contains one query, no facts and
two non-Horn clauses ```B ∨ A``` and ```C ∨ A```:
```
{¬A ∨ ¬B ∨ ¬C, B ∨ A, C ∨ A, ¬B ∨ A, ¬A ∨ B}
```
Note that Prolog programs are automatically Horn clauses in CNF.
For example, the Prolog clause
```
wet :- rains, outside.
```
corresponds to the Horn clause:
```
wet ∨ ¬rains ∨ ¬outside
```
The head of a Prolog clause is a positive literal. The calls in the body correspond to negative literals.
Querys are not Prolog program clauses but stem from the Prolog console.
%% Cell type:markdown id:1eb3920b tags:
Let us now encode a simple Prolog program using the representation from the previous notebook:
%% Cell type:code id:964ebd52 tags:
``` prolog
:- discontiguous program/3.
:- dynamic program/3.
program(1,'weather',
[ [pos(wet),neg(rains),neg(outside)],
[pos(rains)],
[pos(outside)] ]).
negate(pos(A),neg(A)).
negate(neg(A),pos(A)).
```
%% Cell type:markdown id:a4dc903e tags:
For simplicity we will assume that the positive literal is always the first literal in a clause.
Let us now perform resolution with a negative literal:
%% Cell type:code id:26e0221b tags:
``` prolog
resolve_literal(neg(Prop),Clauses,Resolvent) :-
member([pos(Prop)|Body],Clauses), % find a clause with a corresponding positive literal
The above predicate uses the predicate ```select/3``` from ```library(lists)```, which can be used to select an element from a list and returns a modified list with the element removed:
We have now reached the empty clause, i.e., a contradiction.
This means that the original proposition wet is a logical consequence of our logic program.
%% Cell type:markdown id:e67981b4 tags:
We can now try to encode the search for a contradiction systematically as follows:
%% Cell type:code id:d7a0cc62 tags:
``` prolog
prove(Proposition,Clauses) :-
find_contradiction([neg(Proposition)],Clauses).
find_contradiction([],_). % empty clause found
find_contradiction(Query,Clauses) :-
resolve_query(Query,Clauses,NewQuery),
find_contradiction(NewQuery,Clauses).
```
%% Cell type:markdown id:2b2ea192 tags:
Let us now check whether wet is a logical consequence of program 1:
%% Cell type:code id:78163b60 tags:
``` prolog
?- program(1,_,Clauses), prove(wet,Clauses).
```
%% Output
%% Cell type:markdown id:a70dbafb tags:
There are a few important observations:
- negative literals always come from the query
- positive literals always come from the program clauses
- the resolve_literal predicate can have multiple solutions
- the clauses in the logic program (```Clauses``` above) are never changed.
The last point means that there is no exponential growth of the number of clauses during execution and it means we can precompile the logic program to find clauses for the given positive literals.
This procedure is called linear resolution, as the pool of clauses remains of constant size
and we "forget" earlier denials, except for backtracking when no contradiction is found.
The above code also contains a so-called selection rule/function ```selection_rule```.
This one selects the next literal chosen for resolution in the query.
- it is a function: no backtracking is needed
- there is an important property, called "independence of the selection function" which stipulates that it is correct to not backtrack
- Prolog's execution engine uses the leftmost literal by default; we have done the same above
The procedure we have implemente is called SLD-resolution:
- Selection rule driven
- Linear resolution for
- Definite clauses
(Definite clauses are clauses without using Prolog's negation operator. We will return to this later.)
%% Cell type:markdown id:4759110b tags:
Let us augment the above procedure with a few print statements:
The above sequence of denials is also called an ```SLD-derivation```.
An SLD-derivation which ends in a contradiction is called an ```SLD-refutation```.
%% Cell type:markdown id:d32921c9 tags:
Let us examine a more complicated example corresponding to the Prolog program
```
p :- q,r.
q :- t.
q :- s.
r.
s.
```
%% Cell type:code id:26924712 tags:
``` prolog
program(2,'Backtracking',[
[pos(p),neg(q),neg(r)],
[pos(q),neg(t)],
[pos(q),neg(s)],
[pos(r)],
[pos(s)]
]) :- true.
```
%% Cell type:markdown id:17dd1525 tags:
If we now try to prove that ```p``` is a logical consequence of program 2 we will see that backtracking is required:
%% Cell type:code id:7d90c4ad tags:
``` prolog
?- program(2,_,Clauses), trace_prove(p,Clauses).
```
%% Output
%% Cell type:markdown id:b59dd776 tags:
When you step through the corresponding Prolog program using the Prolog debugger you will get an output like this one (after declaring t/0 as dynamic to avoid error messages):
```
| ?- p.
1 1 Call: p ?
2 2 Call: q ?
3 3 Call: t ?
3 3 Fail: t ?
4 3 Call: s ?
4 3 Exit: s ?
2 2 Exit: q ?
5 2 Call: r ?
5 2 Exit: r ?
1 1 Exit: p ?
yes
```
%% Cell type:markdown id:b4b28da4 tags:
By putting the above queries into a tree we obtain a so-called ```SLD-tree``` for the program and top-level query under consideration.
%% Cell type:markdown id:29e3d513 tags:
Below is some code to display this SLD-tree graphically in Jupyter: