First, sorry for the long response time. I think the ACL2 community often responds more quickly (i.e. within a day or two) to queries on the ACL2 Help mailing list than on places like Stack Overflow, though some of us do attempt to check the ACL2 tag here from time to time.
ACL2 defines evenness and oddness by saying that x
is even if (integerp (/ x 2))
and x
is odd if x
is not even. Unfortunately these definitions are a little unwieldy and not very symmetric to each other, so it's very possible for some theorems about evenp
or oddp
to require a lot of coaxing before ACL2 can prove them, even if it's able to prove other theorems about evenp
or oddp
very easily.
For example, ACL2 is able to prove your thm-1
by the following reasoning:
e
is even, meaning that (* e 1/2)
is an integer. We want to prove that (* o e)
is even, i.e. that (* (* o e) 1/2)
is an integer. We expand out this latter expression to (* o e 1/2)
using associativity of *
. But since o
is an integer and (* e 1/2)
is known to be an integer, then (* o e 1/2)
(which is really (* o (* e 1/2))
) must also be an integer.
Here's ACL2's failed attempt at trying to prove thm-0
:
n
is odd, meaning that (* n 1/2)
is not an integer. We want to prove that (* (* n n) 1/2)
is not an integer. Hmm. We expand out this latter expression to (* n n 1/2)
. Hmm. We normalize the (* n 1/2)
inside this latter expression to (* 1/2 n)
because we know *
is commutative and our heuristics say that the constant 1/2
is "lighter" than the variable n
. Hmm. Can't think of anything else to simplify, so let's move on to the next step in the waterfall, which is induction. But we can't figure out what to induct on because no recursive functions are involved.
This results in the goal of (not (integerp (* n 1/2 n)))
and the message "no induction schemes are suggested".
What made the first proof easier than the second? The first proof relied on the property that the set of integers is closed under multiplication, i.e. an integer times an integer must be an integer. In this proof, the analogous step would be to prove that (* n n 1/2)
is a non-integer by using the fact that n
is an integer and (* n 1/2)
is a non-integer.
Unfortunately, it is not true that an integer times a non-integer must be an integer -- for example, (* 2 1/2)
is an integer. So why can't (* n (* n 1/2))
be an integer? To explain why, we need to be able to express the fact (* n 1/2)
is not just any non-integer -- in particular, it's a rational number whose denominator is even, and because n
is odd, multiplying by n
cannot cancel the 2
from the denominator of (* n 1/2)
. From a human reasoning perspective,that's the most direct way I can think of to fix ACL2's unsuccessful attempt at a proof above, but formulating that argument in a way ACL2 can understand might be somewhat difficult -- I'd take a different approach altogether and use induction, as you have attempted to do.
So unfortunately while your thm-0
and thm-1
seem similar to a human, they look pretty different to ACL2.
Now, how can we prove the theorem you wanted? Here's my solution. (Keep in mind that there are often many different ways to solve a certain problem in ACL2, and other more experienced users might come up with a totally different solution from mine.)
First of all, your odd-induction function is unfortunately not doing what you intended. The function recurses on itself with an argument that's decremented by 1, adds 2 to the result, and returns. But when you're creating a function just so that you can use its induction scheme, it doesn't matter what the function returns, just the particular way in which it recurses -- i.e., what all the recursive calls are, what their arguments are, and what conditions must hold for each recursive call to occur.
In this case, there's one recursive call, with argument (1- x)
, which occurs when neither (zp x)
nor (equal x 1)
is true. Everything else about the function is irrelevant. Thus the induction scheme has a base case of (or (zp x) (equal x 1))
and an inductive step (P (1- x))
=> (P x)
. But what you wanted was for the inductive step to be (P (- x 2))
=> (P x)
. So here is a better induction scheme function:
(defun odd-induction (x)
"Induct by going two steps at a time"
(or (zp x)
(equal x 1)
(odd-induction (- x 2))))
Let's try the final theorem, just to see what the induction looks like. Note that your induction scheme actually only works for natural numbers, not all integers, so I'm going to leave in the restriction that we're only proving this property about positive odd numbers, not negative ones.
(defthm thm-0
(implies (and (natp n)
(oddp n))
(oddp (* n n)))
:hints (("Goal" :induct (odd-induction n))))
This fails, but let's ignore the checkpoints at the end and look at the top of the output, where the induction scheme was chosen:
We have been told to use induction. One induction scheme is suggested
by the induction hint.
We will induct according to a scheme suggested by (ODD-INDUCTION N).
This suggestion was produced using the :induction rule ODD-INDUCTION.
If we let (:P N) denote *1 above then the induction scheme we'll use
is
(AND (IMPLIES (AND (NOT (ZP N))
(NOT (EQUAL N 1))
(:P (+ -2 N)))
(:P N))
(IMPLIES (AND (NOT (ZP N)) (EQUAL N 1))
(:P N))
(IMPLIES (ZP N) (:P N))).
So you can see that in the inductive step it's going to try to prove (:P N)
using (:P (+ -2 N))
as a hypothesis. (Here, :P
refers to the goal which you're trying to prove by induction.)
To avoid the mess of directly reasoning about dividing by two and whether or not things are integers, let's disable both evenp
and oddp
. It'd also be simpler to just reason about evenp
and its negation, and forget about oddp
, so we'll prove a theorem that facilitates this before we disable evenp
and oddp
.
(defthm oddp-is-not-evenp
(equal (oddp x)
(not (evenp x))))
(in-theory (disable evenp oddp))
Let's try submitting thm-0
again and pay attention to the checkpoints this time.
Subgoal *1/3.2
(IMPLIES (AND (NOT (ZP N))
(NOT (EQUAL N 1))
(NOT (EVENP (+ 4 (* -2 N) (* -2 N) (* N N))))
(<= 0 N)
(NOT (EVENP N)))
(NOT (EVENP (* N N))))
Subgoal *1/3.1
(IMPLIES (AND (NOT (ZP N))
(NOT (EQUAL N 1))
(EVENP (+ -2 N))
(<= 0 N)
(NOT (EVENP N)))
(NOT (EVENP (* N N))))
From this we can see that ACL2 has begun induction to prove *1
, and has gotten stuck the third case (*1/3
), which it has split into subgoals, two of which (*1/3.1
and *1/3.2
) it was unable to prove.
Glancing at the hypotheses of *1/3.1
, we see that they claim that n
is not even, but (+ -2 n)
is even. We can also see that n
is an integer because (not (zp n))
holds. This is impossible since -2
is even and n
is even, so their sum should be even. If we prove that fact, Subgoal *1/3.1
should go away.
(defthm even-plus-odd-is-odd
(implies (and (evenp x)
(not (evenp y)))
(not (evenp (+ x y))))
:hints (("Goal" :in-theory (enable evenp))))
Note that ACL2 would already "know" this fact if we hadn't disabled evenp
earlier, making the definition of evenp
unavailable. In fact, that's how we were able to prove this theorem, by temporarily enabling evenp
again during the proof. Often it is worthwhile to make ACL2 "forget" a bunch of stuff and then selectively re-prove only particular facts you actually need.
Now let's try submitting thm-0
once again. This time we only get one subgoal:
Subgoal *1/3.2
(IMPLIES (AND (NOT (ZP N))
(NOT (EQUAL N 1))
(NOT (EVENP (+ 4 (* -2 N) (* -2 N) (* N N))))
(<= 0 N)
(NOT (EVENP N)))
(NOT (EVENP (* N N))))
OK, how can we make this one go away? IMO, the best way to look at this subgoal is to flip one of the hypotheses into the conclusion. The ACL2 rewriter functions on "clauses", which are a disjunction of "literals", each literal being an arbitrary term. When a goal looks like
(implies (and [A]
[B]
[C])
[D])
for some terms A, B, C, and D, what ACL2 is actually doing internally is trying to prove
(or (not [A])
(not [B])
(not [C])
D)
ACL2 will try to simplify each of the four disjuncts in the above expression in turn, and when it is simplifying one, the other three are being viewed as hypotheses. So going back to our subgoal, it may be easier to see how to solve the problem if we rearrange the clause to imagine what ACL2 will see when it is simplifying the literal containing the algebraic expression (+ 4 (* -2 n) (* -2 n) (* n n))
:
(IMPLIES (AND (NOT (ZP N))
(NOT (EQUAL N 1))
(EVENP (* N N))
(<= 0 N)
(NOT (EVENP N)))
(EVENP (+ 4 (* -2 N) (* -2 N) (* N N))))
Aside: You might notice a contradiction in the hypotheses -- we have one hypothesis saying that n
is not even, and another saying that (* n n)
is even! -- but the fact that that's a contradiction is what we're trying to prove in the first place, so we can't resolve this subgoal by noticing that contradiction.
Now, how can we prove that (+ 4 (* -2 n) (* -2 n) (* n n))
is even? Well, it's the sum of four even numbers. 4
is obviously an even number -- ACL2 could just directly evaluate the expression (evenp 4)
to satisfy itself of this fact. (* -2 n)
is even because n
is even and an even number (-2
) times any integer is an even number. And (* n n)
is even because one of the hypotheses says so.
So, if we can teach ACL2 the facts about evenness used in the above paragraph, we should be good to go. First we prove that the sum of even numbers is even.
(defthm even-plus-even-is-even
(implies (and (evenp x)
(evenp y))
(evenp (+ x y)))
:hints (("Goal" :in-theory (enable evenp))))
That one worked just fine. Note that proving it for the two-summand case is sufficient, because the rewrite rule produced will telescope out to larger numbers of summands. That is, to prove that (+ a b c d)
is even when a
, b
, c
, and d
are even, ACL2 will in turn prove that (+ c d)
, (+ b c d)
, and finally (+ a b c d)
are even, using the even-plus-even-is-even
rewrite rule three times.
Next we try to prove that an even number times any integer is even.
(defthm even-times-integer-is-even
(implies (and (evenp x)
(integerp y))
(evenp (* x y)))
:hints (("Goal" :in-theory (enable evenp))))
This one didn't work. The checkpoint ACL2 prints says it got stuck trying to prove that (* x 1/2 y)
is an integer given that (* 1/2 x)
is an integer. It would be nice if we could rearrange (* x 1/2 y)
to (* y 1/2 x)
(which is the same as (* y (* 1/2 x))
) so that we could use the closure of the integers under multiplication.
One way to do it is to just prove the theorem with y
and x
swapped in the conclusion, then produce the rewrite we actually wanted by specifying a :corollary
:
(defthm even-times-integer-is-even
(implies (and (evenp x)
(integerp y))
(evenp (* y x))) ; note that x and y are swapped
:hints (("Goal" :in-theory (enable evenp)))
:rule-classes
((:rewrite :corollary
(implies (and (evenp x)
(integerp y))
(evenp (* x y)))))) ; x and y in the correct order
The tweak of changing (* y x)
to (* x y)
between the main theorem statement and the corollary is simple enough for ACL2 to be satisfied with storing the rewrite rule in the (* x y)
form even though the theorem was proved using the (* y x)
form.
I can think of some other ways to prove the theorem even-times-integer-is-even
which are perhaps a little less pat than the one above, but I won't go into them as they'd require yet more lemmas.
Now that even-plus-even-is-even
and even-times-integer-is-even
are successfully proved, Subgoal *1/3.2
should go away -- and indeed it does.
For reference, here's the whole body of code that was used to prove thm-0
:
(defun odd-induction (x)
"Induct by going two steps at a time"
(or (zp x)
(equal x 1)
(odd-induction (- x 2))))
(defthm oddp-is-not-evenp
(equal (oddp x)
(not (evenp x))))
(in-theory (disable evenp oddp))
(defthm even-plus-odd-is-odd
(implies (and (evenp x)
(not (evenp y)))
(not (evenp (+ x y))))
:hints (("Goal" :in-theory (enable evenp))))
(defthm even-plus-even-is-even
(implies (and (evenp x)
(evenp y))
(evenp (+ x y)))
:hints (("Goal" :in-theory (enable evenp))))
(defthm even-times-integer-is-even
(implies (and (evenp x)
(integerp y))
(evenp (* y x))) ; note that x and y are swapped
:hints (("Goal" :in-theory (enable evenp)))
:rule-classes
((:rewrite :corollary
(implies (and (evenp x)
(integerp y))
(evenp (* x y)))))) ; x and y in the correct order
(defthm thm-0
(implies (and (natp n)
(oddp n))
(oddp (* n n)))
:hints (("Goal" :induct (odd-induction n))))
This was my sort of maximally lazy way of trying to prove the specific theorem you wanted, but of course there are more robust ways to approach it, e.g. to build a whole collection of useful theorems about evenp and oddp, from which specific cases like thm-0
would follow. You can find one such example in the community book coi/super-ihs/evenp
.