The following is from Jens Otten's
→ "How to Build an Automated Theorem Prover"Otten describes and implements several provers on
→ Theorem ProversI'm primarily interested in the sequent calculus prover, because I find its method of operation understandable. I've gotten a bit dim in my dotage.
→ sequent calculus(also called "propositional calculus", or sentential calculus), e.g.
p ∧ (p → q) → q
The logical symbols are:
→ Implication ∧ Conjunction ∨ Disjunction ¬ Negation
We can use these (Unicode) symbols directly in Prolog with the following operator definitions:
:- op(1110, xfy, →).
:- op(1000, xfy, ∧).
:- op(1100, xfy, ∨).
:- op( 500, fy, ¬).
See:
→ Logic Notation in PrologAfter that we can do e.g.:
?- write(p ∧ (p → q) → q).
p∧(p→q)→q
true.
We could express this directly in Prolog like so:
p.
q :- p.
And then pose the query:
?- q.
true.
So that's nice.
The prolog code follows the `CSM </source/CSM.html>`_ expressions:
prove(Formula) :-
Formula \= (_>_),
prove([] > [Formula]).
The axiom:
prove(Gamma > Delta) :-
member(A, Gamma),
member(A, Delta), !.
As an aside, the axiom predicate above is quadratic, so that's not ideal, but if we stick to small formulas it won't matter much.
Let's start with the Gamma terms:
% implication
prove(Gamma > Delta) :-
select(A → B, Gamma, Gamma1), !,
prove( Gamma1 > [A|Delta]),
prove([B|Gamma1] > Delta ).
% conjunction
prove(Gamma > Delta) :-
select(A ∧ B, Gamma, Gamma1), !,
prove([A, B|Gamma1] > Delta).
% disjunction
prove(Gamma > Delta) :-
select(A ∨ B, Gamma, Gamma1), !,
prove([A|Gamma1] > Delta),
prove([B|Gamma1] > Delta).
% negation
prove(Gamma > Delta) :-
select(¬A, Gamma, Gamma1), !,
prove(Gamma1 > [A|Delta]).
Then the Delta terms:
% implication
prove(Gamma > Delta) :-
select(A → B, Delta, Delta1), !,
prove([A|Gamma] > [B|Delta1]).
% conjunction
prove(Gamma > Delta) :-
select(A ∧ B, Delta, Delta1), !,
prove(Gamma > [A|Delta1]),
prove(Gamma > [B|Delta1]).
% disjunction
prove(Gamma > Delta) :-
select(A ∨ B, Delta, Delta1), !,
prove(Gamma > [A, B|Delta1]).
% negation
prove(Gamma > Delta) :-
select(¬A, Delta, Delta1), !,
prove([A|Gamma] > Delta1).
That's it. That's all the code to make the sequent prover in Prolog. It's not very dramatic but it works:
?- prove(p ∧ (p → q) → q).
true.
You can trace it to see the proof unfold. Or you can add an extra-logical predicate to print intermediate stages as they unfold::
prove(F) :- writeln(F), fail.
Giving::
?- prove(p ∧ (p → q) → q).
[]>[(p∧(p→q)→q)]
[(p∧(p→q))]>[q]
[p,(p→q)]>[q]
[p]>[p,q]
[q,p]>[q]
true .
Neat, eh?
I find the operation of and proofs generated by the sequent prover to be very understandable. You can add some extra cruft to record the steps taken, and then graph out the proof tree or whatever.
First-order logic— also known as predicate logic, quantificational logic, and first-order predicate calculus —is a collection of formal systems used in mathematics, philosophy, linguistics, and computer science. First-order logic uses quantified variables over non-logical objects, and allows the use of sentences that contain variables, so that rather than propositions such as "Socrates is a man", one can have expressions in the form "there exists x such that x is Socrates and x is a man", where "there exists" is a quantifier, while x is a variable.[1] This distinguishes it from propositional logic, which does not use quantifiers or relations;[2] in this sense, propositional logic is the foundation of first-order logic.→ https://en.wikipedia.org/wiki/First-order_logic
Effectively this means we add::
∀x ∃x
For universal and existential quantifiers:
:- op( 500, fy, ∀). % universal quantifier: ∀[X]:
:- op( 500, fy, ∃). % existential quantifier: ∃[X]:
:- op( 500,xfy, :).
And so::
?- write_term((∃[X]: ∀[Y]: (¬s(Y,Y) → s(X,Y)) → ∃[Z]: s(Z,Z)), [variable_names(['X'=X, 'Y'=Y, 'Z'=Z])]).
∃[X]: ∀[Y]:(¬s(Y,Y)→s(X,Y))→ ∃[Z]:s(Z,Z)
true.
And add rules for them::
TBD