Skip to content

feat(NumberTheory): Chebyshev's lower bound on primorial#37299

Open
XC0R wants to merge 5 commits intoleanprover-community:masterfrom
XC0R:chebyshev-lower-bound
Open

feat(NumberTheory): Chebyshev's lower bound on primorial#37299
XC0R wants to merge 5 commits intoleanprover-community:masterfrom
XC0R:chebyshev-lower-bound

Conversation

@XC0R
Copy link
Copy Markdown

@XC0R XC0R commented Mar 28, 2026

Summary

Prove primorial(n) ≥ 2^(n/2) for all n ≥ 2 (Chebyshev's 1852 lower bound). This is the lower bound complement to primorial_le_four_pow. Addresses the TODO at Chebyshev.lean line 50: "Prove Chebyshev's lower bound."

New file: Mathlib/NumberTheory/PrimorialLowerBound.lean

Main theorems:

  • two_pow_le_primorial: 2 ^ n ≤ primorial (2 * n) for n ≥ 29
  • two_pow_div_two_le_primorial: 2 ^ (n / 2) ≤ primorial n for n ≥ 2

Key intermediates:

  • centralBinom_le_pow_mul_primorial: C(2n,n) ≤ (2n)^{π(√(2n)+1)} * primorial(2n)
  • eight_mul_sq_add_le_two_pow: 8u² + 16u + 8 ≤ 2^u for u ≥ 10

Proof technique

Central binomial decomposition: from four_pow_lt_mul_centralBinom and factorization_choose_le_log, bound C(2n,n) above by (2n)^{π(√(2n)+1)} * primorial(2n). Rearranging gives primorial(2n) ≥ 2^n for n ≥ 29. Base cases by norm_num + decide, large n analytically via √n factoring.

AI disclosure

Claude (Anthropic) was used as a coding assistant for Lean tactic exploration, file structuring, and CI debugging. All proof strategy, mathematical content, and final code have been reviewed and are understood by the author.

Prove primorial(n) ≥ 2^(n/2) for n ≥ 2, the lower bound complement
to primorial_le_four_pow. First formalization of this result.

Main theorems:
- two_pow_le_primorial: 2^n ≤ primorial(2n) for n ≥ 29
- two_pow_div_two_le_primorial: 2^(n/2) ≤ primorial(n) for n ≥ 2
@github-actions github-actions bot added the new-contributor This PR was made by a contributor with at most 5 merged PRs. Welcome to the community! label Mar 28, 2026
@github-actions
Copy link
Copy Markdown

Welcome new contributor!

Thank you for contributing to Mathlib! If you haven't done so already, please review our contribution guidelines, as well as the style guide and naming conventions. In particular, we kindly remind contributors that we have guidelines regarding the use of AI when making pull requests.

We use a review queue to manage reviews. If your PR does not appear there, it is probably because it is not successfully building (i.e., it doesn't have a green checkmark), has the awaiting-author tag, or another reason described in the Lifecycle of a PR. The review dashboard has a dedicated webpage which shows whether your PR is on the review queue, and (if not), why.

If you haven't already done so, please come to https://leanprover.zulipchat.com/, introduce yourself, and mention your new PR.

Thank you again for joining our community.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 28, 2026

PR summary e960b84129

Import changes for modified files

No significant changes to the import graph

Import changes for all files
Files Import difference
Mathlib.NumberTheory.PrimorialLowerBound (new file) 966

Declarations diff

+ centralBinom_le_pow_mul_primorial
+ eight_mul_sq_add_le_two_pow
+ numerical_bound
+ numerical_bound_large
+ numerical_bound_small
+ primesBelow_filter_le
+ primesBelow_filter_lt
+ two_pow_div_two_le_primorial
+ two_pow_div_two_le_primorial_base
+ two_pow_le_primorial
+ two_pow_le_primorial_base
+ two_pow_le_primorial_of_le

You can run this locally as follows
## summary with just the declaration names:
./scripts/pr_summary/declarations_diff.sh <optional_commit>

## more verbose report:
./scripts/pr_summary/declarations_diff.sh long <optional_commit>

The doc-module for scripts/pr_summary/declarations_diff.sh contains some details about this script.


No changes to technical debt.

You can run this locally as

./scripts/reporting/technical-debt-metrics.sh pr_summary
  • The relative value is the weighted sum of the differences with weight given by the inverse of the current value of the statistic.
  • The absolute value is the relative value divided by the total sum of the inverses of the current values (i.e. the weighted average of the differences).

@github-actions github-actions bot added the t-number-theory Number theory (also use t-algebra or t-analysis to specialize) label Mar 28, 2026
@MichaelStollBayreuth
Copy link
Copy Markdown
Contributor

I think this should eventually go into NumberTheory.Primorial. But moving the material can wait until code review is done.

@XC0R
Copy link
Copy Markdown
Author

XC0R commented Mar 29, 2026

Noted, happy to move it into NumberTheory.Primorial when ready.

@wwylele
Copy link
Copy Markdown
Collaborator

wwylele commented Mar 29, 2026

The character-per-line limit looks smaller than Mathlib's default. Could you format the code to make it look about the same width as the rest of the codebase?

Copy link
Copy Markdown
Contributor

@MichaelStollBayreuth MichaelStollBayreuth left a comment

Choose a reason for hiding this comment

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

Here are some suggestions (unfortunately the layout has changed whie I was working on the review). I'll continue at a later time.


public import Mathlib.Data.Nat.Choose.Factorization
public import Mathlib.NumberTheory.Primorial

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change

No empty lines between public import statements (but an empty line separating them from plain imports, which is done here).

We prove `2 ^ (n / 2) ≤ n#` for all `n ≥ 2`, where `n#` denotes the
primorial (product of all primes ≤ n). This is the lower bound complement
to `primorial_le_four_pow` (which gives the upper bound `n# ≤ 4^n`).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It would be good to mention that this is weaker than (√2)^n ≤ n# when n is odd.

Comment on lines +25 to +27
Central binomial decomposition: from `4^n < n * C(2n,n)` (Mathlib's
`Nat.four_pow_lt_mul_centralBinom`) and `v_p(C(2n,n)) ≤ log_p(2n)`
(Mathlib's `Nat.factorization_choose_le_log`), we bound `C(2n,n)` above
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
Central binomial decomposition: from `4^n < n * C(2n,n)` (Mathlib's
`Nat.four_pow_lt_mul_centralBinom`) and `v_p(C(2n,n)) ≤ log_p(2n)`
(Mathlib's `Nat.factorization_choose_le_log`), we bound `C(2n,n)` above
Central binomial decomposition: from `4^n < n * C(2n,n)` (`Nat.four_pow_lt_mul_centralBinom`) and `v_p(C(2n,n)) ≤ log_p(2n)` (`Nat.factorization_choose_le_log`), we bound `C(2n,n)` above

We are within Mathlib here!


The key analytical step shows `(log₂(2n)+1) * (√(2n)+2) ≤ n` for
`n > 400` by factoring through `r = √n`:
- `2*(L+1) ≤ r` via `2n < 2^(r/2)` (exponential beats polynomial)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is L?

The key analytical step shows `(log₂(2n)+1) * (√(2n)+2) ≤ n` for
`n > 400` by factoring through `r = √n`:
- `2*(L+1) ≤ r` via `2n < 2^(r/2)` (exponential beats polynomial)
- `√(2n)+2 ≤ 2*r` via `zify` + `nlinarith`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mentioning tactics here does not really add information (they will be visible in the proof).

(2 * n) ^ (Nat.sqrt (2 * n) + 1).primesBelow.card *
(2 * n)# := by
set s := Nat.sqrt (2 * n)
let S := {p ∈ Finset.range (2 * n + 1) | Nat.Prime p}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
let S := {p ∈ Finset.range (2 * n + 1) | Nat.Prime p}
let S := (2 * n + 1).primesBelow

Since this exists, it makes sense to use it.

(Nat.centralBinom n) h, _root_.pow_zero]
rw [← Nat.prod_pow_factorization_centralBinom n, ← hS,
← Finset.prod_filter_mul_prod_filter_not S (· ≤ s)]
apply Nat.mul_le_mul
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
apply Nat.mul_le_mul
gcongr

is more idiomatic; it avoids mentioning a lemma name and makes clear what is going on.

Comment on lines +83 to +93
· refine (Finset.prod_le_prod' fun p _ =>
(?_ : f p ≤ 2 * n)).trans ?_
· exact Nat.pow_factorization_choose_le
(by omega : 0 < 2 * n)
rw [Finset.prod_const]
apply Nat.pow_le_pow_right (by omega)
apply Finset.card_le_card
intro p hp
obtain ⟨h1, _⟩ := Finset.mem_filter.1 hp
rw [Nat.mem_primesBelow]
exact ⟨by omega, (Finset.mem_filter.mp h1).2⟩
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You can add

lemma Nat.primesBelow_filter_lt {m n : ℕ} (h : m ≤ n) :
    n.primesBelow.filter (· < m) = m.primesBelow := by
  grind [primesBelow]

lemma Nat.primesBelow_filter_le {m n : ℕ} (h : m < n) :
    n.primesBelow.filter (· ≤ m) = (m + 1).primesBelow := by
  convert primesBelow_filter_lt (show m + 1 ≤ n by lia) using 2
  grind

to Mathlib.NumberTheory.SmoothNumbers; then this part of the proof reduces to

Suggested change
· refine (Finset.prod_le_prod' fun p _ =>
(?_ : f p ≤ 2 * n)).trans ?_
· exact Nat.pow_factorization_choose_le
(by omega : 0 < 2 * n)
rw [Finset.prod_const]
apply Nat.pow_le_pow_right (by omega)
apply Finset.card_le_card
intro p hp
obtain ⟨h1, _⟩ := Finset.mem_filter.1 hp
rw [Nat.mem_primesBelow]
exact ⟨by omega, (Finset.mem_filter.mp h1).2
· rw [primesBelow_filter_le (sqrt_le_self (2 * n) |>.trans_lt (lt_add_one _))]
exact Finset.prod_le_pow_card _ _ _ fun _ _ ↦ pow_factorization_choose_le (by lia)

Comment on lines +94 to +113
· calc ∏ p ∈ S.filter (¬ · ≤ s), f p
≤ ∏ p ∈ S.filter (¬ · ≤ s), p := by
apply Finset.prod_le_prod' fun p hp => ?_
obtain ⟨h1, _⟩ := Finset.mem_filter.1 hp
have hp_prime := (Finset.mem_filter.1 h1).2
dsimp only [f]
refine (Nat.pow_le_pow_right hp_prime.pos
?_).trans (pow_one p).le
exact Nat.factorization_choose_le_one
(Nat.sqrt_lt'.mp (by omega))
_ ≤ (2 * n)# := by
unfold primorial
apply Finset.prod_le_prod_of_subset_of_one_le'
· intro p hp
obtain ⟨h1, _⟩ := Finset.mem_filter.1 hp
rw [Finset.mem_filter, Finset.mem_range]
at h1 ⊢
exact ⟨h1.1, h1.2⟩
· intro p hp _
exact (Finset.mem_filter.1 hp).2.one_le
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This part of the argument can be simplified a bit (it is still basically the same proof, though), for example as follows.

Suggested change
· calc ∏ p ∈ S.filter (¬ · ≤ s), f p
≤ ∏ p ∈ S.filter (¬ · ≤ s), p := by
apply Finset.prod_le_prod' fun p hp => ?_
obtain ⟨h1, _⟩ := Finset.mem_filter.1 hp
have hp_prime := (Finset.mem_filter.1 h1).2
dsimp only [f]
refine (Nat.pow_le_pow_right hp_prime.pos
?_).trans (pow_one p).le
exact Nat.factorization_choose_le_one
(Nat.sqrt_lt'.mp (by omega))
_ ≤ (2 * n)# := by
unfold primorial
apply Finset.prod_le_prod_of_subset_of_one_le'
· intro p hp
obtain ⟨h1, _⟩ := Finset.mem_filter.1 hp
rw [Finset.mem_filter, Finset.mem_range]
at h1 ⊢
exact ⟨h1.1, h1.2
· intro p hp _
exact (Finset.mem_filter.1 hp).2.one_le
· rw [primorial, ← primesBelow]
have H : ∀ p ∈ {x ∈ (2 * n + 1).primesBelow | ¬x ≤ s}, f p ≤ p := by
intro p hp
nth_rewrite 2 [← pow_one p]
exact pow_le_pow_right₀ (by grind) <| factorization_choose_le_one <| sqrt_lt'.mp (by grind)
refine Finset.prod_le_prod' H |>.trans ?_
exact Finset.prod_le_prod_of_subset_of_one_le (filter_subset ..) (by lia)
fun p hp _ ↦ (prime_of_mem_primesBelow hp).one_le

Most of the simplification comes from using grind to do the fairly obvious (but slightly tedious) parts.

In the end, one can also eliminate the definition of S; then the first part of the proof becomes

  set s := sqrt (2 * n)
  let f x := x ^ (centralBinom n).factorization x
  have hS : ∏ x ∈ (2 * n + 1).primesBelow, f x = ∏ x ∈ Finset.range (2 * n + 1), f x := by
    refine Finset.prod_filter_of_ne fun p _ h ↦ ?_
    contrapose! h; dsimp only [f]
    rw [factorization_eq_zero_of_not_prime _ h, pow_zero]
  rw [← prod_pow_factorization_centralBinom, ← hS,
    ← Finset.prod_filter_mul_prod_filter_not _ (· ≤ s)]

Comment on lines +119 to +122
private theorem numerical_bound_small (n : ℕ)
(hn : 29 ≤ n) (hn2 : n ≤ 400) :
n * (2 * n) ^
(Nat.sqrt (2 * n) + 1).primesBelow.card ≤ 2 ^ n := by
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
private theorem numerical_bound_small (n : ℕ)
(hn : 29 ≤ n) (hn2 : n ≤ 400) :
n * (2 * n) ^
(Nat.sqrt (2 * n) + 1).primesBelow.card ≤ 2 ^ n := by
private theorem numerical_bound_small {n : ℕ} (hn : 29 ≤ n) (hn2 : n ≤ 400) :
n * (2 * n) ^ (sqrt (2 * n) + 1).primesBelow.card ≤ 2 ^ n := by

See above.

This proof is very slow. There should be a faster argument.

Here is one possibility (trade-off between code length and code speed).

private theorem numerical_bound_small {n : ℕ} (hn : 29 ≤ n) (hn2 : n ≤ 400) :
    n * (2 * n) ^ (sqrt (2 * n) + 1).primesBelow.card ≤ 2 ^ n := by
  rcases le_or_gt n 84 with h₁ | h₁
  · interval_cases n <;> (norm_num [primesBelow]; decide)
  rcases le_or_gt n 264 with h₂ | h₂
  · have : #((2 * n).sqrt + 1).primesBelow ≤ 8 := by
      rw [show 8 = #((2 * 264).sqrt + 1).primesBelow by norm_num [primesBelow]; decide]
      refine card_le_card <| Finset.filter_subset_filter Prime <| Finset.range_subset_range.mpr ?_
      gcongr
    grw [this]
    · calc
        _ ≤ 264 * (2 * 264) ^ 8 := by gcongr
        _ ≤ 2 ^ 85 := by norm_num
        _ ≤ 2 ^ n := pow_le_pow_right₀ (by lia) (by lia)
    · lia
  have : #((2 * n).sqrt + 1).primesBelow ≤ 9 := by
    rw [show 9 = #((2 * 400).sqrt + 1).primesBelow by norm_num [primesBelow]; decide]
    refine card_le_card <| Finset.filter_subset_filter Prime <| Finset.range_subset_range.mpr ?_
    gcongr
  grw [this]
  · calc
      _ ≤ 400 * (2 * 400) ^ 9 := by gcongr
      _ ≤ 2 ^ 100 := by norm_num
      _ ≤ 2 ^ n := pow_le_pow_right₀ (by lia) (by lia)
  · lia

…cal_bound

- Remove redundant prime_pow_factorization_centralBinom_le (already in Mathlib)
- Make theorem parameters implicit where deducible
- Replace omega with lia throughout
- Drop Nat. prefix where namespace is open
- Add primesBelow_filter_lt and primesBelow_filter_le to SmoothNumbers
- Simplify centralBinom proof using new helpers and grind
- Replace slow interval_cases numerical_bound_small with 3-interval approach
- Move proof remarks from docstrings to comments
- Generalize two_pow_div_two_le_primorial to all n (no hypothesis needed)
- Note odd-n weakness in module docstring
exact numerical_bound_large (by lia)

/-- **Chebyshev's lower bound (even form)**: `2 ^ n ≤ (2n)#` for `n ≥ 29`. -/
theorem two_pow_le_primorial {n : ℕ} (hn : 29 ≤ n) : 2 ^ n ≤ (2 * n)# := by
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The 29 ≤ n assumption can be removed. This should be done in a separate lemma two_pow_le_primorial while renaming this lemma to two_pow_le_primorial_of_le.

Remove the `29 ≤ n` hypothesis from `two_pow_le_primorial` by
adding a computational base case for `n ≤ 28`. The previous version
is retained as `two_pow_le_primorial_of_le`.

Addresses review suggestion by Parcly-Taxel.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-contributor This PR was made by a contributor with at most 5 merged PRs. Welcome to the community! t-number-theory Number theory (also use t-algebra or t-analysis to specialize)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants