do {
(def (get-proof x) (nth 1 @ @ nth 6 @ get-decl x))
(display @ pp @ get-proof 'pm2_48)
};
In the example above, the generated proof of
pm2_48 is:
(:conv
(im (not (or ph ps)) (or ph (not ps)))
(im
(not (:unfold or (ph ps) (im (not ph) ps)))
(:unfold or (ph (not ps)) (im (not ph) (not ps))))
(impneg (~ph) ps (~ph -> ~ps)
(expn (~ph) ps (~ph -> ~ps)
(expn (~(~ph -> ps)) ph (~ps)
(impneg (~(~ph -> ps)) ph (~ps)
(impneg (~ph) ps (~ph -> ~ps)
(com23 (~ph) (~ph) (~ps) (~ps)
(expi (~ph) (~ph) (~ps -> ~ps)
(idd (~(~ph -> ~~ph)) (~ps))))))))))
The tactic is designed for generality rather than for producing short proofs. Most generated proofs will be longer than their
set.mm counterparts.
I tested the tactic against 786 theorems of
set.mm (all theorems in closed form preceding the predicate logic section). The tactic successully found a proof for all of them. To check that the tactic was indeed successful, you can verify the proofs with the following commands (if mm0-c prints nothing then it means the databases have no errors):
./mm0-rs compile -W tauto.mm1 tauto.mmb
./mm0-c tauto.mmb < tauto.mm0
It's interesting to compare the MM1 tauto tactic with the
Rumm tactics I wrote a year ago. The latter ones were tested against 112 theorems and solver1.rmm took about 2 seconds to compute all combined proofs while solver2.rmm took about 4 seconds. Note that the Rumm tactics only support primitive symbols, so every statement has to be written using implication "->" and negation "-." only. The general algorithm that I used in MM1 is roughly the same as the Rumm one. The tactic splits the main goal into statements in implicational normal form and then proves them. One of the main reasons I wanted to try out MM1 is that it supports work variables (see
this issue), but I was also curious to see whether MM1 definitional unfolding makes the proof development easier (the only unfortunate aspect of MM1 is that it does not have a translator to MM, see
this discussion).
I'm not sure how to measure the time MM1 needs to generate proofs. One approach that I found reasonable is to run "mm0-rs compile tauto.mm1 tauto.mmu", which produces an MMU file containing all the generated proof objects from a MM1 file in a readable format.
This command completes in about 2-3 seconds on my machine, so maybe I can claim that my tauto tactic proved all those 786 theorems in that amount of time.
It would also be interesting to compare it to Lean's tauto tactic. I don’t know much about Lean at the moment, so I’m putting this forward as a question to people with more experience: how quickly would Lean’s tauto prove those 786 theorems from
set.mm? What are its limitations?
(I guess the Lean version is likely to be a lot more efficient than mine, given that it has a very active community of hundreds if not thousands of people constantly updating and optimizing the mathlib4 database).
Perhaps, one idea to make the MM1 tauto faster would be to avoid doing definition unfolding explicitely. Normally, the MM1 refine tactic is able to unfold definitions on its own. The issue is that the match tactic does not unfold a goal when matched against a pattern, which is the main reason why I had to write the unfolding myself. Such feature could look like this:
(match $ ,x /\ ,y $
[ $ ~(,x -> ~,y) $ #f]
[ (unfold $ ~(,x -> ~,y) $) #t])
Where the above match returns #t rather than a matching failure.
The MM1 tauto tactic can also be used as a SAT solver. To determine the satisfiability of a statement, it is sufficient to ask tauto to prove the negation of that statement. If it finds a proof, the original statement is unsatisfiable, if it does not find a proof then it is satisfiable.
When tested against an unprovable statement, tauto should return the error "Statement not provable". If it returns an overflow error then it means I missed something (indeed, the "Statement not provable" error was put to avoid overflow).
Due to MM0 approach to definitions,
some theorems beyond propositional logic can be proved with tauto more easily, example:
We first introduce class terms and define class equality (as done in the
set.mm0 file of the MM0 repo):
strict sort class;
term welc (a: set) (A: class): wff; infixl welc: $ec.$ prec 50;
def wceq {.x: set} (A B: class): wff = $ A. x (x ec. A <-> x ec. B) $;
infixl wceq: $=$ prec 50;
And then we can prove some theorems of class equality with the help of tauto:
theorem eqcid (A: class): $ A = A $= '(!! ax_gen x ,tauto);
theorem eqscom (A B: class): $ A = B -> B = A $ = '(!! alimi x ,tauto);
theorem eqscomb (A B: class): $ A = B <-> B = A $ = '(!! albii x ,tauto);
theorem eqstr (A B C: class): $ A = B -> B = C -> A = C $ = '(!! al2imi x ,tauto);
theorem eqstr2 (A B C: class): $ A = B -> B = C -> C = A $ = '(!! al2imi x ,tauto);
theorem eqstr3 (A B C: class): $ B = A -> B = C -> A = C $ = '(!! al2imi x ,tauto);
theorem eqstr4 (A B C: class): $ A = B -> C = B -> A = C $ = '(!! al2imi x ,tauto);