diff --git a/pascal.v b/pascal.v index 04618d2..1cf0fcb 100644 --- a/pascal.v +++ b/pascal.v @@ -1,3 +1,6 @@ +Require Import Ring. +Require Import ArithRing. + (* Coq is a powerful language, that can search for proofs automatically, and then verify them automatically. This results in proofs like *) @@ -31,97 +34,30 @@ Qed. A number is even if it is equal to some other number, doubled. *) Definition Even (n: nat): Prop := - exists k: nat, 2 * k = n. + exists k: nat, n = 2 * k. (* And a number is odd if it is one more than that. *) Definition Odd (n: nat): Prop := - exists k: nat, 2 * k + 1 = n. + exists k: nat, n = 2 * k + 1. -(* Now let's get into the proof. We are going to try to use automation more - tastefully this time, to only do mundane manipulations. Let's import the - 'Ring' tactics, which is where Coq defines how to automatically simplify - expressions built out of + and * operations. *) +(* Let's try proving a number is even. We'll do zero, since then we have a base + case for induction later. *) -Require Import Ring. -Require Import ArithRing. - -(* Now let's get into the proof. This proof is chosen because it is a good - demonstration of a lot of the features, strengths, and weaknesses of Coq, - all in one place. It will be easier to follow than an 'auto' one-liner, but - it still features a lot of the hidden state that is typical of Coq proofs. - - Feel free to read through by yourself, or to step through with the IDE to - see what that hidden state actually looks like on each line, or once you get - the point, that hidden state is hard to follow, feel free to move on to - where we try and address exactly that, by making the state more explicit. *) - -Theorem even_or_odd: forall n: nat, - Even n \/ Odd n. +Lemma zero_is_even: Even 0. Proof. - intro n. - (* This is an inductive proof. We prove 0 is even or odd, then 1 is even or - odd, then 2 is even or odd, etc. *) - induction n. - (* First prove 0 is even or odd... Let's go with even! *) - *left. - (* We have to choose k so that 2*k = 0. *) - exists 0. - (* Now we have to actually prove that 2*k = 0. In DTT this simplifies to - 0 = 0, so we can use reflexivity on 0. *) - reflexivity. - (* Now the inductive case. If n is even or odd, we want to show that n+1 is - even or odd. Check which case we are in first. *) - *destruct IHn as [IsEven | IsOdd]. - (* The first case is that n is even, in which case n+1 will be odd. *) - +right. - (* So, we know 2*k = n, and we want to show 2*k' + 1 = n + 1... Clearly - k' = k. *) - destruct IsEven as [k Eq]. - exists k. - (* Now Coq gives us the goal 2*k + 1 = n + 1... Let's substitute n in. *) - rewrite <- Eq. - (* Now the goal is 2*k + 1 = 1 + 2 * k. This is clearly true by the - algebraic properties of + and *. We use the 'ring' tactic, even though - natural numbers are only a semiring. The logic still works! *) - ring. - (* That handles the even case. Now, if n is odd, we want to show that n+1 is - even. *) - +left. - (* n is odd, i.e. n = 2*k+1, and we want to show n+1 is even, i.e. - n+1 = 2*k'. Choose k' = k + 1. *) - destruct IsOdd as [k Eq]. - exists (k + 1). - (* The goal is 2*(k+1) = 1+n. Substitute n in. *) - rewrite <- Eq. - (* Now the goal is 2*(k+1) = 1 + (2*k + 1)... Let Coq do the algebra. *) - ring. + (* We need to find a k so that 2 * k = 0. Let's try.... 0 *) + exists 0. + (* Now to show that 2 * 0 = 0, we let Coq beta/eta reduce it to 0 = 0, and + then fall back to 'reflexivity', the defining property of Leibniz equality. *) + reflexivity. Qed. -(* So we see that more explicit control flow sets us up for quite nice proofs, - where we can see that the goal follows by simple algebra, algebra which Coq - is prepared to do for us. - - Agda and Idris programmers will recognize that Even and Odd could have been - jerry-rigged so that the `ring` tactics weren't required at all, and while - learning that sort of trick is immensely productive for understanding DTT, - it is not very useful for understanding number theory. - - The problem remains, however, that we had to write comments to track what - the state of the program was going to be. That is confusing, even with - comments, and who knows if the comments are up to date! They aren't checked, - despite us being in a language specifically designed to check things like - that. Let's make some tactics that let us declare what algebraic expression - we think we are working with, so that our proofs become just a little more - self-documenting. - - A very common pattern when proving relations in real math proofs, is to - start with one side of the relation, and to manipulate it bit by bit, until - it matches the other side that we wanted. As long as each manipulation - proves a transitive relation, all those transitive steps can be composed - together into a bigger proof. Let's make some tactics that let us do that, - when our goal is some relation L = R, or Rel L R more generally. *) - +(* It is annoying that we have to write all of these comments to explain what + the state of the proof assistant is *going* to be. Plus, these comments can + get out of sync with the program, which seems absurd in a language + specifically designed to check such things. Let's make some new tactics to + let us represent this information better. *) (* This first tactic lets us start such a string of manipulations, by declaring what the LHS of our goal is, effectively just a comment, but checked. *) @@ -147,10 +83,8 @@ Ltac want expr := fail "Goal is not a relation. Goal:" Other end. -(* Next, all algebraic manipulations in type theory must be shown to follow - from the lambda calculus rules of beta/eta reduction, or similar, so let's - make a tactic that lets us manipulate our LHS using whatever beta/eta rules - Coq supports. *) +(* Next, all manipulations are ultimately built out of beta/eta equivalence, + so let's make a tactic to check these equivalences for us. *) Ltac thatis expr := lazymatch goal with | |- ?Rel ?L ?R => @@ -160,24 +94,9 @@ Ltac thatis expr := fail "Goal is not a relation. Goal:" Other end. -(* If beta/eta isn't enough, then we can often specify an intermediate - expression, and get Coq to step there using ring algebra. *) -Ltac rearrange expr := - lazymatch goal with - | |- ?Rel ?L ?R => - transitivity expr; [> ring | ] - | |- ?Other => - fail "Goal is not a relation. Goal:" Other - end. - -(* Finally, if we manipulate L using thatis or rewrite tactics, then we will - eventually get to the point where it matches R exactly, at which point one - normally uses the 'reflexivity' tactic, but that is confusing, if we had to - do manipulations precisely because our goal was not reflexive, and further, - reflexivity might sneak more beta/eta manipulations in, making our - annotations irrelevant. This tactic lets us check that L and R match - exactly, no beta/eta required, and only then uses reflexivity to finish the - proof. *) +(* Finally, if we want to be sure that our above annotations are correct, this + tactic will check that the LHS and RHS are not just equivalent, but already + identical. *) Ltac done := lazymatch goal with | |- ?Rel ?L ?L => @@ -188,6 +107,63 @@ Ltac done := fail "Goal is not a relation. Goal:" Other end. +(* Let's try again at that simple zero example, but with annotations. *) + +Lemma zero_is_even_explicit: Even 0. +Proof. + (* We still need to pick a value for k. *) + exists 0. + (* Now let's fill our goal a little more explicitly. *) + want (2 * 0). + have 0. + (* So that is our RHS and LHS... How are we ever going to connect them? + Well let's compute our LHS a little! *) + thatis (2 * 0). + (* And that matches our goal! Look! *) + done. +Qed. + +(* Probably overkill for that case, and frankly unintuitive when all the + computation happens on the RHS. Oh well, let's try something more involved. *) + +Lemma even_implies_odd: forall n: nat, + Even n -> Odd (S n). +Proof. + intros n IsEven. + destruct IsEven as [k Eq]. + (* n is 2 * k, and we need to show that n+1 = 2*k' + 1, I wonder what k' can + be? *) + exists k. + want (2 * k + 1). + have (S n). + (* Now let's do some real tactical proving! The equation that proves n is + even can now be used to manipulate our term. *) + rewrite Eq. + (* Now let's see what that did to our LHS. *) + have (S (2 * k)). + (* This isn't quite our LHS. Let's dig into some natural number identities. *) + rewrite (plus_n_O (2 * k)) at 1. + have (S (2 * k + 0)). + rewrite (plus_n_Sm (2 * k) 0). + have (2 * k + 1). + done. +Qed. + +(* Rewriting n into 2 * k with these tactics was nice, but the identities at + the end sucked. Let's bring back a little more automation; just enough to do + algebra, but without hiding any important control flow. *) + +Ltac rearrange expr := + lazymatch goal with + | |- ?Rel ?L ?R => + transitivity expr; [> ring | ] + | |- ?Other => + fail "Goal is not a relation. Goal:" Other + end. + +(* Before we try using this one, let's also define some things that will help + us annotate these Even/Odd/exists manipulations. *) + (* If our goal isn't a relation yet, we might still want to declare it. *) Ltac havegoal expr := lazymatch goal with @@ -197,8 +173,9 @@ Ltac havegoal expr := fail "Expected goal to be" expr "but it was" Other end. -(* When manipulating dysjunctions, like the Even/Odd case coming up, it can - be useful to restate what our current hypotheses are. *) +(* The place where we learn new equations is often quite far from where we + use them. This tactic lets us state in-context what hypothesis we are + using, and defer choosing a name for it until then as well. *) Ltac weknow newid P := lazymatch goal with | proof: P |- ?Goal => @@ -210,52 +187,64 @@ Ltac weknow newid P := Tactic Notation "weknow" ident(newid) ":" constr(P) := weknow newid P. -(* Now let's try and prove our theorem again, but using these self-documenting - tactics. *) -Theorem even_or_odd_chained: forall n: nat, - Even n \/ Odd n. +(* We're going to need these for odd_implies_even, as that case is harder! *) + +Lemma odd_implies_even: forall n: nat, + Odd n -> Even (S n). Proof. - induction n. - *havegoal (Even 0 \/ Odd 0). - left; havegoal (Even 0). (* Choose (Even 0) as a new goal. *) - exists 0; havegoal (2 * 0 = 0). - have (2 * 0). - thatis 0. - done. - *weknow IndCase: (Even n \/ Odd n). - destruct IndCase. - +havegoal (Even (S n) \/ Odd (S n)). - weknow IsEven: (Even n). - right; havegoal (Odd (S n)). - destruct IsEven as [k Eq]. - weknow Eq: (2 * k = n). - exists k; havegoal (2 * k + 1 = S n). - apply eq_sym. (* Flip the goal, to work right to left. *) - have (S n). - rewrite <- Eq. - have (S (2 * k)). - rearrange (2 * k + 1). - done. - +havegoal (Even (S n) \/ Odd (S n)). - weknow IsOdd: (Odd n). - left; havegoal (Even (S n)). - destruct IsOdd as [k Eq]. - exists (S k). - havegoal (2 * S k = S n). - apply eq_sym. - have (S n). - rewrite <- Eq. - have (S (2 * k + 1)). - rearrange (2 * S k). - done. + intros. + weknow IsOdd: (Odd n). + destruct IsOdd as [k]. + havegoal (Even (S n)). + weknow OddEq: (n = 2 * k + 1). + (* From now on let's do the goal manipulation, and then justify it afterwards + by annotating what our goal is now. *) + exists (S k); havegoal (S n = 2 * S k). + have (S n). + (* Let's also combine rewrites with the new expression, if they'll fit on one line. *) + rewrite OddEq; have (S (2 * k + 1)). + (* Now let's write the algebra we would write on paper, and let Coq justify it. *) + rearrange (2 * S k). + done. Qed. -(* That was much longer, but now the proof guides you through its own state, - and if you edit this file so that any of the annotations are wrong, the - proof no longer completes! In general our proofs are going to be pretty long - from now on, but hopefully they are much more pleasant to read. +(* You might see where this is going now. We have all the pieces to build an + inductive proof! *) - Now, finally, let's get on with some number theory! First let's look at +Theorem even_or_odd: forall n: nat, + Even n \/ Odd n. +Proof. + intro n. + induction n. + *havegoal (Even 0 \/ Odd 0). + left; havegoal (Even 0). + exact zero_is_even. + *havegoal (Even (S n) \/ Odd (S n)). + weknow Ind: (Even n \/ Odd n). + destruct Ind. + +weknow IsEven: (Even n). + right; havegoal (Odd (S n)). + exact (even_implies_odd n IsEven). + +weknow IsOdd: (Odd n). + left; havegoal (Even (S n)). + exact (odd_implies_even n IsOdd). +Qed. + +(* Wasn't that a fun and pleasant adventure? + + So we see that more explicit control flow sets us up for quite nice proofs, + where we can see that the goal follows by simple algebra, algebra which Coq + is prepared to do for us. + + Agda and Idris programmers will recognize that Even and Odd could have been + jerry-rigged so that the `ring` tactics weren't required at all, and while + we will be exploiting that sort of trick to simplify things when they get + hard, but for this simple example, having suboptimal definitions was useful + as it motivated us to learn how to make Coq do the boring parts for us. *) + + + +(* Now, finally, let's get on with some number theory! First let's look at Pascal's triangle, since understanding that turns out to be essential to understanding how to find points on elliptic curves. @@ -281,17 +270,20 @@ Fixpoint partial_sum (seq: nat -> nat) (count: nat): nat := end. (* But Triangular numbers aren't the sum of 0 or more numbers, they are the sum - of one or more numbers! So let's also define a 'sequence' variation. *) + of one or more numbers! So let's also define a 'series' variation. *) -Definition sequence (seq: nat -> nat) (index: nat): nat := +Definition series (seq: nat -> nat) (index: nat): nat := partial_sum seq (1 + index). Fixpoint pascal (r: nat): nat -> nat := match r with | 0 => fun _ => 1 - | S r' => sequence (pascal r') + | S r' => series (pascal r') end. +(* Now the first 'row' of our function is all 1 by definition, but it is useful + to show that the first 'column' is all 1 as well. *) + Lemma pascal_0: forall r: nat, pascal r 0 = 1. Proof. @@ -301,13 +293,16 @@ Proof. thatis 1. done. *have (pascal (S r) 0). - thatis (sequence (pascal r) 0). + thatis (series (pascal r) 0). thatis (pascal r 0). want 1. weknow Ind: (pascal r 0 = 1). exact Ind. Qed. +(* Next let's define some combinatoric functions, and see if we can't prove how + they relate to the recursive formula above. *) + Fixpoint factorial (n : nat): nat := match n with | S n' => n * factorial n' @@ -320,41 +315,9 @@ Fixpoint prod_up (k r: nat): nat := | S k' => prod_up k' (1 + r) * r end. -Lemma prod_up_down: forall k r: nat, - prod_up (1 + k) r = (k + r) * prod_up k r. -Proof. - intro k. - induction k; intro r. - *cbn. - havegoal (r + 0 = r * 1). - ring. - *have (prod_up (1 + S k) r). - thatis (prod_up (1 + k) (1 + r) * r). - weknow Ind: (forall r': nat, prod_up (1 + k) r' = (k + r') * prod_up k r'). - rewrite (Ind (1 + r)). - have ((k + (1 + r)) * prod_up k (1 + r) * r). - rearrange ((S k + r) * (prod_up k (1 + r) * r)). - thatis ((S k + r) * prod_up (S k) r). - done. -Qed. - -Lemma factorial_up: forall n: nat, - factorial n = prod_up n 1. -Proof. - intro n. - induction n. - *reflexivity. - *have (factorial (S n)). - thatis ((S n) * factorial n). - weknow Ind: (factorial n = prod_up n 1). - rewrite Ind. - have ((S n) * prod_up n 1). - rearrange ((n + 1) * prod_up n 1). - rewrite <- (prod_up_down n 1). - have (prod_up (1 + n) 1). - thatis (prod_up (S n) 1). - done. -Qed. +(* Often this permutation prod_up is notated as factorial (k + r) / factorial k, + but we don't have division in peano arithmetic, so let's show this relation + through multiplication instead. *) Lemma prod_up_ratio: forall k r: nat, prod_up k (1 + r) * factorial r = factorial (k + r). @@ -376,6 +339,45 @@ Proof. done. Qed. +(* If we let r = 0 then we even show that the factorial is a special case of + this product function. *) +Lemma factorial_up: forall n: nat, + prod_up n 1 = factorial n. +Proof. + intro n. + have (prod_up n 1). + rearrange (prod_up n 1 * 1). + thatis (prod_up n (1 + 0) * factorial 0). + rewrite (prod_up_ratio n 0); have (factorial (n + 0)). + rewrite <- (plus_n_O); have (factorial n). + done. +Qed. + +(* Another trick that is implicit in pen-and-paper notation, that we have to be + explicit about in code, is the fact that we can pull terms off of either + side of this product, to do algebraic manipulations with. *) + +Lemma prod_up_down: forall k r: nat, + prod_up (1 + k) r = (k + r) * prod_up k r. +Proof. + intro k. + induction k; intro r. + *cbn. + havegoal (r + 0 = r * 1). + ring. + *have (prod_up (1 + S k) r). + thatis (prod_up (1 + k) (1 + r) * r). + weknow Ind: (forall r': nat, prod_up (1 + k) r' = (k + r') * prod_up k r'). + rewrite (Ind (1 + r)). + have ((k + (1 + r)) * prod_up k (1 + r) * r). + rearrange ((S k + r) * (prod_up k (1 + r) * r)). + thatis ((S k + r) * prod_up (S k) r). + done. +Qed. + +(* Now let's get to the theorem! I haven't broken this one up at all. Turn your + font up or something. *) + Theorem pascal_choose: forall r k: nat, pascal r k * factorial k = prod_up k (S r). Proof. @@ -383,7 +385,7 @@ Proof. induction r; intro k. *cbn. ring_simplify. - exact (factorial_up k). + exact (eq_sym (factorial_up k)). *induction k. **cbn. ring_simplify. @@ -416,3 +418,5 @@ Proof. done. Qed. + +