The tableau procedure is a refutation technique which processes a formula as a tree. Each node in the tree is a set of formulas and each branch is a conjunction of its nodes. The subtrees of a node form a disjunction. A refutation is a tree where all branches contain a contradiction.
In the following we describe the tableau procedure for propositional logic.
The procedure involves systematically growing branches on the tree then checking for contradictions. Starting with a single node containing the formula to be refuted, a tree is grown in accordance with the tableau rules.
F1 ∧ F2
is grown by extending it with a subbranch containing two nodes, F1
and F2
.
F1 ∨ F2
is grown by extending it with two subtrees, F1
and F2
.
¬f
is grown by extending it with a subbranch of one node, t
.
¬t
is grown by extending it with a subbranch of one node, f
.
¬(¬F)
is grown by extending it with a subbranch of one node, F
.
Other connectives, and the negation of compound formulas, can be first rewritten to an equivalent in terms of ¬
, ∧
,
and ∨
, then made subject to the rules just given.
Essentially, we are rewriting to Negation Normal Form (NNF).
The table below shows the rewrite rules used.
Note that ⇔
is rewritten in terms of ⇒
. It is intended that this is in turn rewritten to remove ⇒
.
Formula | Equivalent |
---|---|
F1 ⇒ F2 |
¬F1 ∨ F2 |
F1 ⇔ F2 |
(F1 ⇒ F2) ∧ (F2 ⇒ F1) |
¬(F1 ∧ F2) |
¬F1 ∨ ¬F2 |
¬(F1 ∨ F2) |
¬F1 ∧ ¬F2 |
¬(F1 ⇒ F2) |
F1 ∧ ¬F2 |
¬(F1 ⇔ F2) |
F1 ⇔ ¬F2 |
After expanding the tree we can close branches which give a contradiction.
If the only connectives used are ¬
, ∧
, and ∨
,
then the only possible contradictions are branches with a node
containing false
, and branches with a node containing some formula F
and a node containing ¬F
, since
F ∧ ¬F
is a contradiction.
If we can close all the branches in the tree, then we have a refutation.
The following code can be found in the examples directory of the Barry's Prolog distribution.
We'll represent the tree as a list of branches, each branch being a list of the formulas at all of the nodes on the branch. We don't need to store all of the nodes separately, and the order of the formulas in the list is irrelevant as these formulas are conjuncts and conjunction is an associative and commutative operation. The procedure description given above talks of rewriting nodes, but due to the tree representation just described, we'll actually rewrite branches.
The initial tree for input formula F
will be [[F]]
, i.e. a disjunction with a single disjunct which is the conjunction
of the single conjunct F
.
When we create the two new subtrees with the rewrite rule for ∨
, we'll replace the branch being rewritten
with two branches and any formula common to both branches occurs in each branch — there is no sharing.
When we expand a branch by rewriting one of its formulas, we will remove the formula from that branch.
We are allowed to do this as we don't need to use that formula in a given branch more than once.
Examples:
[[a ∧ b, c]]
.
This tree contains one branch with two formulas: a ∧ b
and c
.
We can rewrite this tree using the rewrite rule for ∧
into [[a, b, c]]
.
[[a ∨ b, c]]
.
We can rewrite this tree using the rewrite rule for ∨
into [[a, c], [b, c]]
.
This new tree has two branches.
The following Prolog code in this section implements the procedure given earlier using the tree representation described above.
First we fix a set of operators which we will need to represent the formulas of propositional logic.
:-op(200, fy, ~). % Negation
:-op(0, yfx, (/\)).
:-op(400, xfy, (/\)). % Conjunction (not ISO associativity which is yfx)
:-op(0, yfx, (\/)).
:-op(500, xfy, (\/)). % Disjunction (not ISO associativity which is yfx)
:-op(600, xfy, =>). % Implication
:-op(700, xfy, <=>). % Equivalence
The procedure pl_negate(A, B)
succeeds if A
is the negation of B
.
pl_negate(true, false) :- !.
pl_negate(false, true) :- !.
pl_negate(~A, A) :- !.
pl_negate(A, ~A).
The procedure pl_tableau_transform(TreeIn, TreeOut)
transforms TreeIn
into TreeOut
.
The representation of a tree is a list of complete branches.
Each branch is a conjunction of its node's formulas represented as a list of these formulas.
It is here that we rewrite formulas to NNF.
pl_tableau_transform([Branch|Branches], NewBranches) :-
del(~ true, Branch, Rest),
NewBranches = [[false|Rest]|Branches].
pl_tableau_transform([Branch|Branches], NewBranches) :-
del(~ false, Branch, Rest),
NewBranches = [[true|Rest]|Branches].
pl_tableau_transform([Branch|Branches], NewBranches) :-
del(~(~A), Branch, Rest),
NewBranches = [[A|Rest]|Branches].
pl_tableau_transform([Branch|Branches], [NewBranch|Branches]) :-
del(A /\ B, Branch, Rest),
NewBranch = [A, B|Rest].
pl_tableau_transform([Branch|Branches], NewBranches) :-
del(A \/ B, Branch, Rest),
NewBranches = [[A|Rest], [B|Rest]|Branches].
pl_tableau_transform([Branch|Branches], NewBranches) :-
del(A => B, Branch, Rest),
pl_tableau_transform([[~A \/ B|Rest]|Branches], NewBranches).
pl_tableau_transform([Branch|Branches], NewBranches) :-
del(A <=> B, Branch, Rest),
pl_tableau_transform([[(A => B) /\ (B => A)|Rest]|Branches], NewBranches).
pl_tableau_transform([Branch|Branches], NewBranches) :-
del(~(A /\ B), Branch, Rest),
pl_tableau_transform([[~A \/ ~B|Rest]|Branches], NewBranches).
pl_tableau_transform([Branch|Branches], NewBranches) :-
del(~(A \/ B), Branch, Rest),
pl_tableau_transform([[~A /\ ~B|Rest]|Branches], NewBranches).
pl_tableau_transform([Branch|Branches], NewBranches) :-
del(~(A => B), Branch, Rest),
pl_tableau_transform([[A /\ ~B|Rest]|Branches], NewBranches).
pl_tableau_transform([Branch|Branches], NewBranches) :-
del(~(A <=> B), Branch, Rest),
pl_tableau_transform([[A <=> ~B|Rest]|Branches], NewBranches).
The procedure pl_close_branches(TreeIn, TreeOut)
creates TreeOut
which is TreeIn
with all closed branches removed.
A closed branch is a contradiction which in our case means either false
, or,
F /\ ~F
for any formula F
.
A tree where all branches have been closed will be represented by the empty list []
.
pl_close_branches([], []).
pl_close_branches([H|T], Result) :-
member(false, H),
!,
pl_close_branches(T, Result).
pl_close_branches([H|T], Result) :-
member(A, H),
pl_negate(A, NegA),
member(NegA, H),
!,
pl_close_branches(T, Result).
pl_close_branches([H|T], [H|Result]) :-
pl_close_branches(T, Result).
The procedure pl_tableau_loop(Tree)
succeeds if Tree
can have all of its branches closed.
pl_tableau_loop([]) :- !.
pl_tableau_loop(Tree0) :-
pl_tableau_transform(Tree0, Tree1),
!,
pl_close_branches(Tree1, Tree2),
pl_tableau_loop(Tree2).
pl_tableau_loop(Tree1) :-
pl_close_branches(Tree1, Tree2),
pl_tableau_loop(Tree2).
The procedure pl_tableau(Formula)
succeeds if Formula
can be refuted.
pl_tableau(Formula) :-
pl_tableau_loop([[Formula]]).
The procedure pl_tableau_tautology(Formula)
succeeds if Formula
is a tautology.
pl_tableau_tautology(Formula) :-
pl_tableau(~Formula).
Fitting, Melvin. First-Order Logic and Automated Theorem Proving. Springer, 1990.
Copyright © 2014 Barry Watson. All rights reserved.