Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create choose.lean #48

Merged
merged 8 commits into from Feb 20, 2018
Merged

Create choose.lean #48

merged 8 commits into from Feb 20, 2018

Conversation

ChrisHughes24
Copy link
Member

Definition of choose function for naturals and basic lemmas

Definition of choose function for naturals and basic lemmas

@[simp]
lemma choose_zero_right (n : ℕ) : choose n 0 = 1 := begin
cases n with n,unfold choose,unfold choose,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here and anywhere else: move the begin to the next line.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think the proof would be clearer as by cases n; refl

lemma choose_succ_succ (n k : ℕ) : choose (succ n) (succ k) = choose n k + choose n (succ k) := rfl

lemma choose_eq_zero_of_lt {n k : ℕ} : n < k → choose n k = 0 := begin
revert k,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of reverting at the beginning state your theorem different, i.e. {n : ℕ} : \Pi k, n < k → choose n k = 0

lemma choose_eq_zero_of_lt {n k : ℕ} : n < k → choose n k = 0 := begin
revert k,
induction n with n' hi,
assume k nk,cases k,exact absurd nk dec_trivial,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid writing multiple tactics in one line.
Here you use multiple tactics to proof the first induction step. Please mark it with { ... } and indent it properly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, space after ,

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also goes for further proofs.

@johoelzl
Copy link
Collaborator

Dear Chris,

this is of course a theory we want to see in mathlib. But for maintenance it is important to see where a proof breaks. The more explicit structure you add to tactic scripts the better. So for induction, cases, etc. we want to see explicitly which tactics are responsible for which subgoal: use { ... } to mark them.

@spl
Copy link
Collaborator

spl commented Feb 14, 2018

@johoelzl:

The more explicit structure you add to tactic scripts the better. So for induction, cases, etc. we want to see explicitly which tactics are responsible for which subgoal: use { ... } to mark them.

I'm just curious about your style guidelines. Would case <constructor> { ... } be better than { ... }? The explicit tags help document the tactics.

@johoelzl
Copy link
Collaborator

Hard to say. For longer proofs were it is hard to see what you are refering to case <constructor> { ... } is preferred. For short proofs { ... } is just less cluttered and hence more readable. But then if your { ... } starts with a intro you might as well use case <constructor> : h ... { .. }

@digama0
Copy link
Member

digama0 commented Feb 14, 2018

My view is that case is most useful when you have >2 constructors and the inductive type is "unfamiliar". If you are doing cases on an expression in the C grammar, absolutely. If it's just or.inl and or.inr, I wouldn't bother.

Especially since or.inl doesn't actually tell the reader much about what the goal is, while case expr.lam does.

@spl
Copy link
Collaborator

spl commented Feb 14, 2018

Thanks for the responses! It seems that there is a spectrum of opinions. I must say that I lean strongly toward the “use case <constructor> often” end of the spectrum. It helps me when revisiting proofs after a long time. I do see @digama0's point about or.inl/or.inr, but I've found that even using case list.nil/case list.cons makes it more clear which part is being proved.

Hopefully stylistically better now
lemma succ_mul_choose_eq (n : ℕ) : ∀ k, succ n * choose n k = choose (succ n) (succ k) * succ k :=
begin
induction n with n' hi,
{ assume k, simp,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using simp in the middle should be avoided. When the set of simp-rules changes (and it changes fast) then the goals afterwards change also. This results in unpredictable goals. Try to do a more structured approach:

  • introduce a have with some subgoal?
  • add maybe simp rules to solve the goal in one step
  • move the simp further down, in this case distribute it into both cases

induction n with n' hi,
{ assume k, simp,
cases k with k',
simp,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put simp into { simp }. This is especially important for simp. Otherwise it might happen that the simp set changes and the next tactics still work. Then it is hard to figure out which simp call failed.

begin
induction n with n' hi,
{ assume k hk,
rw [eq_zero_of_le_zero hk,choose_zero_right],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you replace this branch by a simp call a la: simp [nat.le_zero_iff] {contextual := tt} (maybe with dec_trivial afterwards)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually know what {contextual := tt} means, so possibly, but I don't really know how.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Play around! With this the simplifier gets very powerful :-)
What does it do: when the simplifier rewrites a term of the form a -> b, it first rewrites a = a', and then when it rewrites b it adds a' to its new context. So if a' is a rewrite rule it can use it for rewriting, if a' is a proposition it will at least use this proposition to fullfill any conditions on rewrite rules.

Your goal has the form ∀n, 0 ≥ n → choose n k = ..., now with nat.le_zero_iff the simplifier will rewrite (0 ≥ n) = (n = 0). This is again a nice rewrite rule, so choose n k = ... is rewritten to choose 0 k = ... and choose_zero_right matches!

@johoelzl
Copy link
Collaborator

Hey Chris,

some further notes. The changed indentation helped me to see further problems in the tactic scripts.

Also I have more stylisic change requests:

  • If you write a list of rules (for example: rw [a, b, c]) there is always a space after the comma
  • similar with ;: e.g. by cases n; simp
  • no comma before end: begin t1, t2, t3 end
  • put a space before the closing bracket when you have a space after the opening one:
    { a, b, c }
    or
    { t1,
      t2,
      t3 }

I know all these stylistic changes seam to be annoying and bike shedding, but they drastically increase readability.

Copy link
Collaborator

@johoelzl johoelzl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further proof cleanup suggestions: choose is a nice recursively defined function. So lets use the equation compiler to simplify our proofs.

Warning: the induction hypothesis has the name of the lemma you proof. It looks very general, but the equation compiler foes not support more than primitive induction. So avoid giving it directly to the simplifier (no simp [*]), but only instantiated

def choose : ℕ → ℕ → ℕ
| _ 0 := 1
| 0 (succ k) := 0
| (succ n) (succ k) := choose n k + choose n (succ k)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The equation compiler and the elaborator understand that succ n = n + 1, so it's nicer to use n + 1 instead of succ.

| 0 (succ k) := 0
| (succ n) (succ k) := choose n k + choose n (succ k)

@[simp]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@[simp] is on the same line as the lemma. Change this here and in the following lemmas.
Move the proof to the next line, especially when the line gets longer than 100 characters.

lemma choose_succ_succ (n k : ℕ) : choose (succ n) (succ k) = choose n k + choose n (succ k) := rfl

lemma choose_eq_zero_of_lt {n : ℕ} : ∀ {k}, n < k → choose n k = 0 :=
begin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be written very nice using the equation compiler:

lemma choose_eq_zero_of_lt : ∀ {n k}, n < k → choose n k = 0
| 0     (k+1) hk := by simp
| (n+1) (k+1) hk := 
  have hnk : n < k, from lt_of_succ_lt_succ hk,
  have hnk1 : n < k + 1, from lt_of_succ_lt hk,
  by rw [choose_succ_succ, choose_eq_zero_of_lt hnk, choose_eq_zero_of_lt hnk1]

The equation compiler takes care of the absurd cases (n < m is n + 1 ≤ m which is inductively defined). My personal preference is to state proofs explicitly using have .. instead of adding the proof to to the rw list.

by induction n; simp [*, choose]

lemma choose_pos {n : ℕ} : ∀ {k}, k ≤ n → 0 < choose n k :=
begin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may also be a good application for the equation compiler. I guess 3 cases (0 n, (n+1) 0, (n+1) (k+1)) are needed, but they could go through using simp.

end

lemma succ_mul_choose_eq (n : ℕ) : ∀ k, succ n * choose n k = choose (succ n) (succ k) * succ k :=
begin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try again the equation compiler

end

lemma choose_mul_fact_mul_fact {n : ℕ} : ∀ {k}, k ≤ n → choose n k * fact k * fact (n - k) = fact n :=
begin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dito

Used the equation compiler for inductive proofs
{ rw [hk₁, choose_succ_self, zero_mul, zero_mul, add_zero, fact_succ, mul_comm] }
end

theorem choose_def_alt {n k : ℕ} (hk : k ≤ n) : choose n k = fact n / (fact k * fact (n - k)) :=
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about calling it: choose_eq_fact_div_fact

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

State in the have what you get after the rw [..] at this. And then prove it using the rw and the choose_mul_fact_mul. Then you can even replace the begin .. end part by have _, begin ... end, _

rw [choose_succ_succ, succ_sub_succ, add_mul, add_mul],
have : choose n k * fact (succ k) * fact (n - k) = choose n k * fact k * fact (n - k) * succ k :=
by unfold fact; simp[mul_comm, mul_assoc, mul_left_comm],
rw [this, choose_mul_fact_mul_fact (le_of_succ_le_succ hk)],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indentation is wrong after this line; remove two spaces from the following tactics

rw [this, choose_mul_fact_mul_fact (le_of_succ_le_succ hk)],
cases lt_or_eq_of_le hk with hk₁ hk₁,
{ have : n - k = succ (n - succ k) := by rw [←succ_sub (le_of_succ_le_succ hk₁), succ_sub_succ],
rw [this, fact_succ (n - succ k), mul_comm (succ (n - succ k)), ←mul_assoc],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you sqash the follow rw into one?

| (n + 1) 0 hk := by simp
| (n + 1) (succ k) hk :=
begin
rw [choose_succ_succ, succ_sub_succ, add_mul, add_mul],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you first rw, then show something, then continue rw, then a case and then more rw?
Move the have to the front. Then make the case distinction. And then do the rw at once. But take a look at the goal of you rw, maybe you can do it in one simp call?

Slightly neater proof of `choose_mul_fact_mul_fact`. The proof involves subtraction of naturals, so cannot easily be done using simp.
@johoelzl johoelzl merged commit 504a2dc into leanprover-community:master Feb 20, 2018
johoelzl pushed a commit to johoelzl/mathlib that referenced this pull request Mar 5, 2018
deat(data/nat): add choose function to compute the binomial coefficients
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants