Zulip Chat Archive

Stream: Is there code for X?

Topic: nat.factorial lemmas


Yaël Dillies (May 28 2021 at 11:37):

I have a goal of the form

import data.real.basic
import nat.choose.basic

example (n : ) : (n.choose 2 : )  n ^ 2 / 2 :=
begin
  sorry
end

Is there anything close to what I want, and if not what's the biggest generality I can prove stuff in? I can probably prove

lemma nat.choose_le_pow(n k : ) : (n.choose k : )  n^k / k.factorial :=
begin
  sorry
end

but I assume is not general enough?

Yaël Dillies (May 28 2021 at 11:43):

I already know this by the way: https://leanprover-community.github.io/mathlib_docs/data/nat/choose/dvd.html#nat.choose_eq_factorial_div_factorial

Eric Rodriguez (May 28 2021 at 11:45):

I'd probably prove it for docs#nat.desc_fac? With something like docs#nat.desc_fac_eq_factorial_mul_choose, I think that's essentially what you're doing, and then you can use a couple of the lemmas below there to transfer it to nCk. Also, desc_fac is well-linked to docs#pochhammer, which is a sweeping generalization, and all results transfer almost automatically

Kevin Buzzard (May 28 2021 at 12:57):

You asked a slightly different question on the Discord and here's my answer to that one:

import data.real.basic
import tactic

-- should be in mathlib?
theorem nat.cast_div {D : Type*} [division_ring D] (a b : ) (hba : b  a) (hb : (b : D)  0) :
  ((a / b : ) : D) = a / b :=
by rw [ (mul_left_inj' hb), div_mul_cancel _ hb,  nat.cast_mul, nat.div_mul_cancel hba]

-- might already be in mathlib?
@[elab_as_eliminator]
def nat.parity.cases_on (n : ) {P :   Prop}
  (h_even :  k, P (2 * k)) (h_odd :  k, P (2 * k + 1)) :
  P n :=
begin
  rw  nat.div_add_mod n 2,
  have hn : n % 2 < 2 := nat.mod_lt _ zero_lt_two,
  interval_cases (n%2); rw h,
  { exact h_even (n/2) },
  { exact h_odd (n/2) }
end

example (n : ) : ((n * (n - 1)/2 : ) : ) = (n * (n - 1) : )/2 :=
begin
  rw nat.cast_div,
  { norm_cast },
  { apply nat.parity.cases_on n,
    { intro k, use k * (2 * k - 1), ring },
    { intro k, use k * (2 * k + 1), rw nat.add_sub_cancel, ring } },
  { norm_num }
end

Probably people can build on this.

Ruben Van de Velde (May 28 2021 at 13:07):

Simpler proofs (though why use nat subtraction?):

@[elab_as_eliminator]
def nat.parity.cases_on (n : ) {P :   Prop}
  (h_even :  k, P (2 * k)) (h_odd :  k, P (2 * k + 1)) :
  P n :=
begin
  obtain k, (rfl|rfl)⟩ := nat.even_or_odd' n,
  exacts [h_even k, h_odd k],
end

example (n : ) : ((n * (n - 1)/2 : ) : ) = (n * (n - 1) : )/2 :=
begin
  rw nat.cast_div,
  { norm_cast },
  { cases n,
    { simp },
    simp only [nat.succ_sub_succ_eq_sub, nat.sub_zero, mul_comm],
    exact nat.even_mul_succ_self n },
  { norm_num }
end

Bhavik Mehta (May 28 2021 at 15:18):

Ultimately the original question doesn't use nat subtraction anyway, so it would be nice to get solutions which don't introduce nat subtraction - Eric's approach seems promising

Eric Rodriguez (May 28 2021 at 15:52):

import data.nat.factorial

open nat

lemma asda {n} :  {k}, desc_fac n k  (n + k)^k
| 0 := by simp
| (k + 1) := by { rw [desc_fac_succ, pow_succ],
  exact nat.mul_le_mul (le_refl _) (by simp [asda.trans, pow_le_pow_of_le_left]) }

this will get you very close to the original lemma, and the desc_fac api will extend that to pochhammer, choose very easily

Yaël Dillies (May 28 2021 at 16:27):

Great! Thanks!

Yaël Dillies (May 28 2021 at 17:06):

Sorry, but why is desc_fac the ascending factorial?

Eric Rodriguez (May 28 2021 at 17:13):

yikes, I just googled and found out there's actually a convention

Eric Rodriguez (May 28 2021 at 17:13):

that's just how I named it at the time, that's pretty bad and probably deserves a rename

Yaël Dillies (May 28 2021 at 17:33):

I'm making a PR now :)

Yaël Dillies (May 28 2021 at 17:34):

I'm also taking the opportunity to define the actual descending factorial, as it seems more relevant in my use case.Real mathmos do not deal with nat substraction!

Eric Rodriguez (May 28 2021 at 17:36):

is this why I'm in Durham instead of Oxford/Cam? ;b

Yaël Dillies (May 28 2021 at 18:04):

If I remember correctly, the point of defining desc_fac (soon-to-be-called asc_fac!) was to allow for quick computation using norm_num for the birthday problem. Did you investigate whether it was preferable to multiply from the left vs from the right time-wise?

Eric Rodriguez (May 28 2021 at 18:14):

I didn't, I'm afraid - sorry! left mul made a lot more sense to me while writing lemmata, though

Mario Carneiro (May 28 2021 at 21:24):

Actually, neither order is optimal. Factorials can be calculated in O(n log n)

Eric Rodriguez (May 28 2021 at 21:41):

What algorithm calculates factorials that fast?

Mario Carneiro (May 28 2021 at 21:54):

Here's a simple recursive algorithm, called "Product Recursive" from http://www.luschny.de/math/factorial/index.html:

import data.nat.factorial

def factorial'_aux :     
| n k := if k  1 then n else let l := k / 2 in
  factorial'_aux n l * factorial'_aux (n + l) (k - l)
using_well_founded {dec_tac := `[sorry]}

def factorial' (n : ) := factorial'_aux 1 n

theorem factorial'_aux_eq (n k : ) : factorial'_aux (n+1) k = nat.desc_fac n k := sorry

theorem factorial'_eq (n k : ) : factorial' n = nat.factorial n := sorry

Mario Carneiro (May 28 2021 at 21:57):

The reason this is faster is that by binary splitting the factors we ensure that all multiplications are well balanced. The cost of a (naive) multiplication is the product of the number of digits of the factors, so it is a good idea to minimize the number of multiplications with a size close to the actual target value

Mario Carneiro (May 28 2021 at 21:59):

This is not the fastest algorithm (that luschny page describes 21(!) algorithms for calculating factorials); the fastest ones do some kind of prime factorization

Yaël Dillies (May 29 2021 at 20:36):

Nice! Will look that up.

Yaël Dillies (May 29 2021 at 20:37):

Where should I put lemmas about inequalities not using natural division? And how should I state them? Should I coerce to ? to ? to something more general?

Kevin Buzzard (May 29 2021 at 21:31):

a division ring if you don't need commutativity of multiplcation, and a field if you do, or maybe a semifield if such a thing exists (maybe the nonnegative reals are one??)

Kevin Buzzard (May 29 2021 at 21:32):

oh you need an order too -- @Damiano Testa what should Yael do here? What axioms do you use in your proofs Yael?

Damiano Testa (May 30 2021 at 04:06):

I would have to take a look, but if the proof mixes add and mul, then the ordered refactor has not reached it yet! I do not have time to look at this now, though. Also, since the lemmas that use the new typeclasses are still in PR, there is not much for you to try either...

Mario Carneiro (May 30 2021 at 04:12):

There are files ordered_group, ordered_ring, ordered_field in algebra roughly corresponding to whether you need addition, multiplication, or division

Mario Carneiro (May 30 2021 at 04:12):

so if you have a general theorem about le and div it would go in ordered_field

Mario Carneiro (May 30 2021 at 04:13):

like docs#le_div_iff

Yaël Dillies (May 30 2021 at 09:32):

My proof uses the def of nat.choose and monotonicity of division by a natural. That's it I think.

lemma choose_le_pow (r n : ) : (n.choose r : )  n^r / r.factorial :=
begin
  rw le_div_iff',
  { norm_cast,
    rw [desc_fact_eq_factorial_mul_choose],
    exact desc_fact_le_pow n r },
  exact_mod_cast r.factorial_pos,
end

So I think ordered_field sounds right.

Yaël Dillies (May 30 2021 at 12:22):

Actually I'm probably missing something. How should I coerce naturals to α where [linear_ordered_field α]? Surely there's a "canonical" way to do that, given that we have a 1 and addition.

Scott Morrison (May 30 2021 at 12:29):

Can't you just write (n : α)? Or just pass the natural number where Lean is expecting an α?

Yaël Dillies (May 30 2021 at 12:32):

Not in the file algebra/ordered_field. Maybe the coercion is set up in a later file?

Johan Commelin (May 30 2021 at 12:34):

In that case the lemma you are proving probably belongs in a later file.

Johan Commelin (May 30 2021 at 12:35):

But I guess the coercion is set up in algebra/group.* which is probably imported by algebra/ordered_field

Yaël Dillies (May 30 2021 at 12:35):

Ahah! I think the cast is in data.nat.cast.

Yaël Dillies (May 30 2021 at 12:37):

But that doesn't sound like a great place to put my inequality. What do you think of me creating a new file for those two lemmas?

Yaël Dillies (May 30 2021 at 12:55):

It looks like there's an instance linear_ordered_field α to ordered_semiring α missing.

import data.nat.choose.basic
import data.nat.cast

variables {α : Type*} [linear_ordered_field α] [nontrivial α]

lemma choose_le_pow (r n : ) : (n.choose r : α)  n^r / r.factorial :=
begin
  rw le_div_iff',
  { norm_cast,
    rw nat.desc_fact_eq_factorial_mul_choose,
    rw nat.cast_le, --fails
    exact n.desc_fact_le_pow r },
  exact_mod_cast r.factorial_pos,
end

Yaël Dillies (May 30 2021 at 13:49):

#7759

Yaël Dillies (Jun 22 2021 at 08:15):

Could anyone review the PR one last time? :grinning_face_with_smiling_eyes:


Last updated: Dec 20 2023 at 11:08 UTC