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

feat(Data/Nat/Choose): Lucas' theorem #8612

Open
wants to merge 40 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
104a9a9
feat: add lemma `sum_ite_iff_eq` for `Finset.sum`
grhkm21 Nov 24, 2023
3d824f7
feat: add lemma `sum_ite_iff_eq` for `Finsupp.sum`
grhkm21 Nov 24, 2023
8040787
feat: prove Lucas's theorem for `Nat.choose`
grhkm21 Nov 24, 2023
a9838e2
chore: lint
grhkm21 Nov 24, 2023
a5c023b
chore: fix import
grhkm21 Nov 24, 2023
f715fbe
feat: remove duplicate lemma
grhkm21 Nov 27, 2023
2aa16f3
Merge remote-tracking branch 'origin/master' into grhkm/lucas-theorem
grhkm21 Apr 30, 2024
edc6706
chore: remove attr according to reviews
grhkm21 Apr 30, 2024
923f398
feat: fix `lucas` to work in latest Mathlib
grhkm21 May 1, 2024
6c1c5a0
feat: finish `lucas'` and `lucas''`
grhkm21 May 1, 2024
33aa024
Merge remote-tracking branch 'origin/master' into grhkm/lucas-theorem
grhkm21 May 1, 2024
165eeb6
chore: write docs
grhkm21 May 1, 2024
ce6cae3
chore: fix indentation
grhkm21 May 1, 2024
189904b
chore: move `prod_ite_eq'` to reduce delta
grhkm21 May 1, 2024
ec6f322
lint: remove unused import
grhkm21 May 1, 2024
834f203
lint: ite -> if-then-else
grhkm21 May 3, 2024
7b07f3e
chore: ite -> if-then-else
grhkm21 May 3, 2024
9cd40dd
Isolate intermediate lemma into theorem
grhkm21 May 3, 2024
b4e1665
merge to master
grhkm21 May 22, 2024
597d75b
lint: long lines
grhkm21 May 24, 2024
cc7451d
Merge remote-tracking branch 'origin/master' into grhkm/lucas-theorem
grhkm21 May 27, 2024
784377b
chore: ite -> if-then-else
grhkm21 May 27, 2024
b48564b
resolve merge conflict
grhkm21 May 30, 2024
d77a2ed
Merge remote-tracking branch 'origin/master' into grhkm/lucas-theorem
grhkm21 Jun 1, 2024
c208c61
Merge remote-tracking branch 'origin/master' into grhkm/lucas-theorem
grhkm21 Jun 7, 2024
fc2553c
Merge remote-tracking branch 'origin/master' into grhkm/lucas-theorem
grhkm21 Jun 15, 2024
2647738
chore: fix docstrings and other review changes
grhkm21 Jun 18, 2024
d0c1632
chore: revert ite -> if-then-else changes
grhkm21 Jun 18, 2024
af85b80
chore: trailing whitespaces
grhkm21 Jun 18, 2024
5e9fc30
chore: remove lemma (review)
grhkm21 Jun 18, 2024
41ec0b3
lint: attempt to make statement more readable
grhkm21 Jun 19, 2024
9a33f0a
chore: fix stuff, remove stuff, clean stuff
grhkm21 Jun 20, 2024
72d5597
chore: rename theorems to symbol-reading names
grhkm21 Jun 20, 2024
bbe5853
Update Mathlib/Data/Nat/Choose/Lucas.lean
Parcly-Taxel Jun 21, 2024
56cf870
lint: review
grhkm21 Jun 21, 2024
a76ae48
Apply suggestions from code review
grhkm21 Jun 26, 2024
d710bc0
Merge remote-tracking branch 'origin/master' into grhkm/lucas-theorem
grhkm21 Jul 19, 2024
1f6e187
Merge branch 'grhkm/lucas-theorem' of github.com:leanprover-community…
grhkm21 Jul 19, 2024
1d7ae5b
feat: nat MOD version of lucas_theorem
grhkm21 Jul 19, 2024
e82d217
lint: close section
grhkm21 Jul 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Mathlib.lean
Original file line number Diff line number Diff line change
Expand Up @@ -2264,6 +2264,7 @@ import Mathlib.Data.Nat.Choose.Cast
import Mathlib.Data.Nat.Choose.Central
import Mathlib.Data.Nat.Choose.Dvd
import Mathlib.Data.Nat.Choose.Factorization
import Mathlib.Data.Nat.Choose.Lucas
import Mathlib.Data.Nat.Choose.Multinomial
import Mathlib.Data.Nat.Choose.Sum
import Mathlib.Data.Nat.Choose.Vandermonde
Expand Down
10 changes: 10 additions & 0 deletions Mathlib/Algebra/CharP/Basic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ theorem add_pow_char_pow [CommSemiring R] {p : ℕ} [Fact p.Prime] [CharP R p] {
(x + y) ^ p ^ n = x ^ p ^ n + y ^ p ^ n :=
add_pow_char_pow_of_commute _ _ _ (Commute.all _ _)

theorem add_pow_eq_add_pow_mod_mul_pow_add_pow_div
[CommSemiring R] {p : ℕ} [Fact p.Prime] [CharP R p] {n : ℕ} (x y : R) :
(x + y) ^ n = (x + y) ^ (n % p) * (x ^ p + y ^ p) ^ (n / p) := by
rw [← add_pow_char, ← pow_mul, ← pow_add, Nat.mod_add_div]
Comment on lines +113 to +116
Copy link
Collaborator

Choose a reason for hiding this comment

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

This looks like n and p should be explicit. If you're willing to fix it, can you open a separate PR to fix it in the entire file (to avoid scope creep here)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oops I missed this, I will do it yes

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@YaelDillies I think p doesn't have to be explicit since x and y would infer R which should uniquely infer p - at least mathematically the second part is true?

Copy link
Collaborator

@YaelDillies YaelDillies Jul 4, 2024

Choose a reason for hiding this comment

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

Mathematically, yes. In practice, not really 😢

Try it for yourself by rewriting using your lemmas with R instantiated to specific char p rings, like ZMod p or Fin p.


theorem sub_pow_char [CommRing R] {p : ℕ} [Fact p.Prime] [CharP R p] (x y : R) :
(x - y) ^ p = x ^ p - y ^ p :=
sub_pow_char_of_commute _ _ _ (Commute.all _ _)
Expand All @@ -118,6 +123,11 @@ theorem sub_pow_char_pow [CommRing R] {p : ℕ} [Fact p.Prime] [CharP R p] {n :
(x - y) ^ p ^ n = x ^ p ^ n - y ^ p ^ n :=
sub_pow_char_pow_of_commute _ _ _ (Commute.all _ _)

theorem sub_pow_eq_sub_pow_mod_mul_pow_sub_pow_div
[CommRing R] {p : ℕ} [Fact p.Prime] [CharP R p] {n : ℕ} (x y : R) :
(x - y) ^ n = (x - y) ^ (n % p) * (x ^ p - y ^ p) ^ (n / p) := by
rw [← sub_pow_char, ← pow_mul, ← pow_add, Nat.mod_add_div]

theorem CharP.neg_one_pow_char [Ring R] (p : ℕ) [CharP R p] [Fact p.Prime] :
(-1 : R) ^ p = -1 := by
rw [eq_neg_iff_add_eq_zero]
Expand Down
100 changes: 100 additions & 0 deletions Mathlib/Data/Nat/Choose/Lucas.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/-
Copyright (c) 2023 Gareth Ma. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Gareth Ma
-/
import Mathlib.Data.ZMod.Basic
import Mathlib.RingTheory.Polynomial.Basic

/-!
# Lucas's theorem

This file contains a proof of [Lucas's theorem](https://en.wikipedia.org/wiki/Lucas's_theorem) about
binomial coefficients, which says that for primes `p`, `n` choose `k` is congruent to product of
`n_i` choose `k_i` modulo `p`, where `n_i` and `k_i` are the base-`p` digits of `n` and `k`,
respectively.

## Main statements

* `lucas_theorem`: the binomial coefficient `n choose k` is congruent to the product of `n_i choose
k_i` modulo `p`, where `n_i` and `k_i` are the base-`p` digits of `n` and `k`, respectively.
-/

open Finset hiding choose

open Nat BigOperators Polynomial

namespace Choose

variable {n k p : ℕ} [Fact p.Prime]

/-- For primes `p`, `choose n k` is congruent to `choose (n % p) (k % p) * choose (n / p) (k / p)`
modulo `p`. Also see `choose_modEq_choose_mod_mul_choose_div_nat` for the version with `MOD`. -/
theorem choose_modEq_choose_mod_mul_choose_div :
grhkm21 marked this conversation as resolved.
Show resolved Hide resolved
choose n k ≡ choose (n % p) (k % p) * choose (n / p) (k / p) [ZMOD p] := by
Copy link
Contributor

Choose a reason for hiding this comment

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

Both sides of this are nats, right? So maybe it'd be nice to have versions using MOD p as well?

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Bhavik here we should have the Nat version.

@grhkm21, would you mind either adding this, or simply saying "sorry, can someone do it in a subsequent PR", and then you can merge?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It should be a norm_cast away, I think

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

How should I name the theorems? I will add some _nat and _int, but alternatively I can call it zmod vs mod, but that seems unpopular in Mathlib.

Copy link
Collaborator

Choose a reason for hiding this comment

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

modEq and intModEq, maybe?

have decompose : ((X : (ZMod p)[X]) + 1) ^ n = (X + 1) ^ (n % p) * (X ^ p + 1) ^ (n / p) := by
simpa using add_pow_eq_add_pow_mod_mul_pow_add_pow_div _ (X : (ZMod p)[X]) 1
simp only [← ZMod.intCast_eq_intCast_iff, Int.cast_mul, Int.cast_ofNat,
← coeff_X_add_one_pow _ n k, ← eq_intCast (Int.castRingHom (ZMod p)), ← coeff_map,
Polynomial.map_pow, Polynomial.map_add, Polynomial.map_one, map_X, decompose]
simp only [add_pow, one_pow, mul_one, ← pow_mul, sum_mul_sum]
conv_lhs =>
enter [1, 2, k, 2, k']
rw [← mul_assoc, mul_right_comm _ _ (X ^ (p * k')), ← pow_add, mul_assoc, ← cast_mul]
have h_iff : ∀ x ∈ range (n % p + 1) ×ˢ range (n / p + 1),
k = x.1 + p * x.2 ↔ (k % p, k / p) = x := by
intro ⟨x₁, x₂⟩ hx
rw [Prod.mk.injEq]
constructor <;> intro h
· simp only [mem_product, mem_range] at hx
have h' : x₁ < p := lt_of_lt_of_le hx.left $ mod_lt _ Fin.size_pos'
rw [h, add_mul_mod_self_left, add_mul_div_left _ _ Fin.size_pos', eq_comm (b := x₂)]
exact ⟨mod_eq_of_lt h', self_eq_add_left.mpr (div_eq_of_lt h')⟩
· rw [← h.left, ← h.right, mod_add_div]
simp only [finset_sum_coeff, coeff_mul_natCast, coeff_X_pow, ite_mul, zero_mul, ← cast_mul]
rw [← sum_product', sum_congr rfl (fun a ha ↦ if_congr (h_iff a ha) rfl rfl), sum_ite_eq]
split_ifs with h
· simp
· rw [mem_product, mem_range, mem_range, not_and_or, lt_succ, not_le, not_lt] at h
cases h <;> simp [choose_eq_zero_of_lt (by tauto)]

/-- For primes `p`, `choose n k` is congruent to `choose (n % p) (k % p) * choose (n / p) (k / p)`
modulo `p`. Also see `choose_modEq_choose_mod_mul_choose_div` for the version with `ZMOD`. -/
theorem choose_modEq_choose_mod_mul_choose_div_nat :
choose n k ≡ choose (n % p) (k % p) * choose (n / p) (k / p) [MOD p] := by
rw [← Int.natCast_modEq_iff]
exact_mod_cast choose_modEq_choose_mod_mul_choose_div

/-- For primes `p`, `choose n k` is congruent to the product of `choose (⌊n / p ^ i⌋ % p)
(⌊k / p ^ i⌋ % p)` over i < a, multiplied by `choose (⌊n / p ^ a⌋) (⌊k / p ^ a⌋)`, modulo `p`. -/
theorem choose_modEq_choose_mul_prod_range_choose (a : ℕ) :
choose n k ≡ choose (n / p ^ a) (k / p ^ a) *
∏ i in range a, choose (n / p ^ i % p) (k / p ^ i % p) [ZMOD p] :=
match a with
| Nat.zero => by simp
| Nat.succ a => (choose_modEq_choose_mul_prod_range_choose a).trans <| by
rw [prod_range_succ, cast_mul, ← mul_assoc, mul_right_comm]
gcongr
apply choose_modEq_choose_mod_mul_choose_div.trans
simp_rw [pow_succ, Nat.div_div_eq_div_mul, mul_comm]
rfl

/-- **Lucas's Theorem**: For primes `p`, `choose n k` is congruent to the product of
`choose (⌊n / p ^ i⌋ % p) (⌊k / p ^ i⌋ % p)` over `i` modulo `p`. -/
theorem choose_modEq_prod_range_choose {a : ℕ} (ha₁ : n < p ^ a) (ha₂ : k < p ^ a) :
choose n k ≡ ∏ i in range a, choose (n / p ^ i % p) (k / p ^ i % p) [ZMOD p] := by
apply (choose_modEq_choose_mul_prod_range_choose a).trans
simp_rw [Nat.div_eq_of_lt ha₁, Nat.div_eq_of_lt ha₂, choose, cast_one, one_mul, cast_prod,
Int.ModEq.refl]

/-- **Lucas's Theorem**: For primes `p`, `choose n k` is congruent to the product of
`choose (⌊n / p ^ i⌋ % p) (⌊k / p ^ i⌋ % p)` over `i` modulo `p`. -/
theorem choose_modEq_prod_range_choose_nat {a : ℕ} (ha₁ : n < p ^ a) (ha₂ : k < p ^ a) :
choose n k ≡ ∏ i in range a, choose (n / p ^ i % p) (k / p ^ i % p) [MOD p] := by
rw [← Int.natCast_modEq_iff]
exact_mod_cast choose_modEq_prod_range_choose ha₁ ha₂

alias lucas_theorem := choose_modEq_prod_range_choose
alias lucas_theorem_nat := choose_modEq_prod_range_choose_nat

end Choose
Loading