The deductive rules that will be constructed in this section will be very similar to the onces constructed in Chapter 5 for propositional logic. This set of deductive rules will however be more complex since there are more things to consider once we extend the calculus to include first order logic.
The basic idea however will be the same, we look for connections through the matrix, but this time they do not all have to be ground, sometimes the we need to substitute the free variables to get connections. Sometimes there is no substitution that will give grounds for connections between two literals, since the terms inside the two literals do not unify, this is where the unification algorithm comes into play.
We can start off with the simplest rule first which is also is present in the propositional theorem prover.
rl [init]:
< none ; nix ; [CL1, CLSET1] ; TL1 - TL2 >
=>
---< none ; CL1 ; [CLSET1] ; TL1 - TL2 > .
We start off by selecting a clause from our matrix as our active clause, since all paths must be connected for this to be a valid formula we might as well pick a clause, since all paths traverse every clause. There has been added two TrmLists inside our SearchState elements, as you can see. The reason for including these elements will be clear once we start to look for connections throughout the matrix.
We will once again use a meta program to control the deductive rules, this is why the rules have the same names, this makes it easy to recycle the already somewhat optimized algorithm from the propositional theorem prover. To optimize a first order theorem prover there are more things to consider than in the propositional case, where a smart way to prune the search space will get you far. Here we have efficiency of unification algorithms and clause copy management on top of the already existing search space problem.
The way variables are constructed in this specification forces us to manually bind variables to other terms throughout the matrix. In other programming languages we would use some kind of regular variable to represent the vari-ables in our first order terms (naturally) and once the variable is bound to a value, this is transparent throughout the matrix or formula. This becomes
more complex here, and is also why we need to include the two TrmLists inside our SearchState elements.
It might be useful to see the propositional rule once more before we jump to the first order version of the same rule.
rl [negLitInPath]:
< LIT1, LITSET1 ; [- LIT1, LITSET2] ; M >
=>
---< LIT1, LITSET1 ; [LITSET2] ; M > .
This rule cuts off paths already known to be connected, since any path tra-versing LIT1 and - LIT1 inside the active clause will be connected, those are cut off from our search. The first order version of the same rule states ex-actly the same, the only difference is that the connections here can be either ground or contain free variables.
crl [negLitInPath]:
< ((P1(TL)) , LITSET1) ; [(- (P1(TL’))) , LITSET2 ] ; M ; TL1 - TL2 >
=>
---< ((P1(unify(TL, TL’))) ,
(litSetSubst(LITSET1, genSubstList(TL, TL’)))) ; clauseSubst(([ LITSET2 ]), genSubstList(TL, TL’)) ; matrixSubst(M, genSubstList(TL, TL’)) ; TL - TL’ >
if (unify(TL, TL’) =/= fail) .
This rule is conditional, unlike the rewrite rule in the propositional calculus.
The condition that has to be fulfilled for this pair of predicates to be a con-nection through the matrix, is that the term lists inside the two predicates must have a unifying substitution. This means that both TL and TL’ can be ground and contain the same term lists, or if either contains variables there has to be a unifying substitution for this pair of literals to become a connected pair. All the other function calls inside the SearchState element are functions that help us bind the variables that got bound to different terms during unification. This has to be done to avoid binding variables to different terms. Unfortunately this makes the SearchState element a bit tricky to read, but the principle is exactly the same as in the propositional case, we cut off paths already known to be connected. All paths travers-ing the element - P1(TL) inside the active clause will be connected since
the active path contains the element P1(TL’), if TL and TL’ has a unify-ing substitution. Notice that our two last TrmLists inside the SearchState element is the two TrmLists that had a unifying substitution, which gave ground for the connection. The reason for doing this is that our matrix is in some sense scattered out between the different SearchState elements.
Each SearchState element represent one or more paths through the matrix.
When this rewrite rule is applied to a SearchState element and variables are bound, we must be able to tell the rest of the matrix that these variables are in fact now bound to some terms. Since it is the sum of all the SearchState elements that represents our matrix, we must ensure that all these elements get a hold of the latest information related to variable bindings. This will be handled at the meta-level, where this piece of information will be handed to the other SearchState elements, such that their variables are bound to the same terms as they where here. This has to be done with both rewrite rules that actually bind variables to terms, which are the rewrite rules that look for connections. This does make the code look even more cryptic at the meta-level, but hopefully the idea is clear. This is also a “side-effect”
of not having real variables, we have to manually bind variables to terms throughout the matrix, which is represented in this implementation as a list of SearchState elements.
If no connection was found using the previous rewrite rule we must look for connections between elements inside the active clause and the remaining matrix. Here the similarities between the propositional and the first order version is also transparent. It is easier to see what happens in the proposi-tional version of the rewrite rule so it will be presented first once again.
rl [negLitInMatrix]:
< PATH ; [LIT1, LITSET1] ; [[- LIT1, LITSET2], CLSET1] >
=>
---< PATH, LIT1 ; [LITSET2] ; [CLSET1] >
< PATH ; [LITSET1] ; [[- LIT1, LITSET2], CLSET1] > .
Once a connection is established between an element inside the active clause and the remaining matrix, we can cut off all other paths containing this connection.
crl [negLitInMatrix]:
< PATH ; [(P1(TL)) , LITSET1] ; [[(- (P1(TL’))) , LITSET2], CLSET1] ; TL1 - TL2 >
=>
---< (litSetSubst(PATH, genSubstList(TL, TL’))) , (P1(unify(TL, TL’))) ;
clauseSubst([LITSET2], genSubstList(TL, TL’)) ;
matrixSubst([CLSET1], genSubstList(TL, TL’)) ; TL - TL’ >
< litSetSubst(PATH, genSubstList(TL, TL’)) ; clauseSubst([LITSET1], genSubstList(TL, TL’)) ;
matrixSubst([[(- (P1(TL’))) , LITSET2], CLSET1], genSubstList(TL, TL’))
; TL - TL’ >
if (unify(TL, TL’) =/= fail) .
This rule does exactly the same with the additional condition that the term lists inside the two predicates must unify (TL and TL’). All the other functions calls inside the SearchState element substitute variable bindings that occur as a result of the unification process. The clauseSubst function substitute variable bindings in a clause, the matixSubst substitute variable bindings in a matrix and so on. Here the two TrmLists that had a unifying substitution are placed into the last two slots of the SearchState element again, the reason for doing so is the same as in the previous rewrite rule. We need this information to ensure that we do not bind variables to different terms in our different SearchState elements, which combined represent our matrix.
If connections can not be found between elements inside the active path and the active clause, or between the active clause and the remaining matrix, we have no other choice than to extend our path. All attempts to prune the search space has failed, this next rewrite rule specifies this transition.
rl [extendPath]:
< PATH ; [LIT1, LITSET1] ; [CL1, CLSET1] ; TL1 - TL2 >
=>
---< PATH, LIT1 ; CL1 ; [CLSET1] ; TL1 - TL2 >
< PATH ; [LITSET1] ; [CL1, CLSET1] ; TL1 - TL2 > .
This rule is almost identical to the equivalent rule in the propositional the-orem prover, as are the last two rewrite rules.
rl [removeConnectedPaths]:
< PATH ; [none] ; M ; TL1 - TL2 >
=> ---valid .
rl [counterModel]:
< PATH ; [LIT1, LITSET1] ; [none] ; TL1 - TL2 >
=> ---notvalid .
These two rewrite rules state that we have paths without connections or that a set of paths through the matrix are connected, playing the role of an axiom or a countermodel (closed or open leaf node in an LK proof tree). To construct a theorem prover based on these rewrite rules, we need to control the application of the different rules just like we did in the propositional version. The nice part is that this job has to some extent already been done, since the rewrite rules play the same role here as they did in the propositional case. Our meta program and strategies from our propositional theorem prover can be recycled with very little modification. There is one more problem that has to be addressed for this theorem prover to work, we need to be able to copy free variable clauses. This will be the topic for the next section.