Zulip Chat Archive

Stream: new members

Topic: Basic lemma about Schnirelmann density


Stepan Nesterov (Jan 21 2026 at 02:39):

I found an elementary lemma missing from ## Mathlib.Combinatorics.Schnirelmann, and I thought it could be a nice exercise to fill it in:

open Pointwise in
theorem sumset_univ_of_schirelmannDensity_ge_one {A B : Set } [DecidablePred (·  A)]
    [DecidablePred (·  B)] (h : schnirelmannDensity A + schnirelmannDensity B  1) :
    ({0}  A) + ({0}  B) = Set.univ := by
  rw [Set.eq_univ_iff_forall]
  intro n
  cases n with
    | zero => exact 0, Set.mem_union_left A rfl, 0, Set.mem_union_left B rfl, rfl
    | succ m =>
      set n := m + 1
      by_cases hnA : n  A
      · exact n, Set.mem_union_right {0} hnA, 0, Set.mem_union_left B rfl, rfl
      · by_cases hnB : n  B
        · refine 0, Set.mem_union_left A rfl, n, Set.mem_union_right {0} hnB, ?_⟩
          dsimp
          exact zero_add n
        let f :      := fun z => match z with
          | Sum.inl x => x
          | Sum.inr y => n - y
        have hc : (image f (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B}))).card <
          (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B})).card
          := by
          calc
          (image f (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B}))).card 
            (Ioo 0 n).card := by
            apply card_le_card
            intro k hk
            rw [mem_image] at hk
            obtain z, hz, hz' := hk
            simp only [mem_disjSum, mem_filter, mem_Ioc] at hz
            obtain a, ⟨⟨⟨a_pos, a_le_n, a_in_A, hz⟩⟩ | b, ⟨⟨⟨b_pos, b_le_n, b_in_B, hz⟩⟩ := hz
            · rw [ hz] at hz'
              dsimp [f] at hz'
              rw [ hz']
              rw [mem_Ioo]
              refine a_pos, ?_⟩
              rw [lt_iff_le_and_ne]
              refine a_le_n, ?_⟩
              intro a_eq_n
              rw [ a_eq_n] at hnA
              contradiction
            · rw [ hz] at hz'
              dsimp [f] at hz'
              rw [ hz', mem_Ioo]
              refine ⟨?_, Nat.sub_lt (Nat.zero_lt_succ m) b_pos
              apply Nat.zero_lt_sub_of_lt
              rw [lt_iff_le_and_ne]
              refine b_le_n, ?_⟩
              intro b_eq_n
              rw [ b_eq_n] at hnB
              contradiction
          _ = n - 1 := by rw [Nat.card_Ioo, Nat.sub_zero]
          _ < n := lt_add_one (n - 1)
          _  {a  Ioc 0 n | a  A}.card + {b  Ioc 0 n | b  B}.card := by
            have hA : schnirelmannDensity A * n  #{a  Ioc 0 n | a  A} :=
              schnirelmannDensity_mul_le_card_filter
            have hB : schnirelmannDensity B * n  #{b  Ioc 0 n | b  B} :=
              schnirelmannDensity_mul_le_card_filter
            rify
            nlinarith
          _ = (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B})).card :=
            Eq.symm (card_disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B}))
        obtain x, hx, y, hy, x_ne_y, hxy := exists_ne_map_eq_of_card_image_lt hc
        match x, y with
        | Sum.inl a, Sum.inl a' =>
          dsimp [f] at hxy
          rw [hxy] at x_ne_y
          contradiction
        | Sum.inl a, Sum.inr b =>
          dsimp [f] at hxy
          simp only [inl_mem_disjSum, mem_filter, mem_Ioc, inr_mem_disjSum] at hx hy
          rw [ Nat.Simproc.eq_add_le a hy.1.2] at hxy
          exact a, Set.mem_union_right {0} hx.2, b, Set.mem_union_right {0} hy.2, Eq.symm hxy
        | Sum.inr b, Sum.inl a =>
          dsimp [f] at hxy
          simp only [inl_mem_disjSum, mem_filter, mem_Ioc, inr_mem_disjSum] at hx hy
          rw [Nat.sub_eq_iff_eq_add hx.1.2] at hxy
          exact a, Set.mem_union_right {0} hy.2, b, Set.mem_union_right {0} hx.2, Eq.symm hxy
        | Sum.inr b, Sum.inr b' =>
          dsimp [f] at hxy
          simp only [inr_mem_disjSum, mem_filter, mem_Ioc] at hx hy
          rw [tsub_right_inj hx.1.2 hy.1.2] at hxy
          rw [hxy] at x_ne_y
          contradiction

Would this code be suitable for a mathlib PR, and if not, how do I rewrite it in a more mathlib way?

Yaël Dillies (Jan 21 2026 at 07:49):

There are a lot intro foo; cases foo and fun foo => match foo that you can cancel with each other

Michael Rothgang (Jan 21 2026 at 08:49):

Please make a #mwe (that's a link you can click on): right now, your code doesn't compile on the playground, making it harder to help you.

Michael Rothgang (Jan 21 2026 at 08:51):

Three more style comments:

  • you can remove the dsimp on line 19 (and then combine the exact with the preceding refine)
  • you can combine two subsequent rewrites into one line
  • in general, using tactics can be more readable than constructing proof terms by hand: line 12 can use by simp twice, for example

Michael Rothgang (Jan 21 2026 at 08:52):

This list is not exhaustive. Let me link to this file with some advice on how to golf code: this will be shorter than me repeating everything in there, and I'm sure you'll find another trick or two in there!

Damiano Testa (Jan 21 2026 at 09:38):

Here is a very simple-minded shortening, where I simply tried to use grind more.

import Mathlib

open Finset Pointwise in
theorem sumset_univ_of_schirelmannDensity_ge_one {A B : Set } [DecidablePred (·  A)]
    [DecidablePred (·  B)] (h : schnirelmannDensity A + schnirelmannDensity B  1) :
    ({0}  A) + ({0}  B) = Set.univ := by
  rw [Set.eq_univ_iff_forall]
  rintro (_ | m)
  · exact 0, Set.mem_union_left A rfl, 0, Set.mem_union_left B rfl, rfl
  ·   set n := m + 1
      by_cases hnA : n  A
      · exact n, Set.mem_union_right {0} hnA, 0, Set.mem_union_left B rfl, rfl
      · by_cases hnB : n  B
        · refine 0, Set.mem_union_left A rfl, n, Set.mem_union_right {0} hnB, ?_⟩
          simp
        let f :      := fun z => match z with
          | Sum.inl x => x
          | Sum.inr y => n - y
        have hc : (image f (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B}))).card <
          (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B})).card
          := by
          calc
          (image f (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B}))).card 
            (Ioo 0 n).card := card_le_card (by grind [mem_disjSum])
          _ < n := by grind [Nat.card_Ioo]
          _  {a  Ioc 0 n | a  A}.card + {b  Ioc 0 n | b  B}.card := by
            have hA : schnirelmannDensity A * n  #{a  Ioc 0 n | a  A} :=
              schnirelmannDensity_mul_le_card_filter
            have hB : schnirelmannDensity B * n  #{b  Ioc 0 n | b  B} :=
              schnirelmannDensity_mul_le_card_filter
            rify
            nlinarith
          _ = (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B})).card :=
            Eq.symm (card_disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B}))
        obtain x, hx, y, hy, x_ne_y, hxy := exists_ne_map_eq_of_card_image_lt hc
        match x, y with
        | Sum.inl a, Sum.inl a' => solve_by_elim
        | Sum.inl a, Sum.inr b =>
          dsimp [f] at hxy
          simp only [inl_mem_disjSum, mem_filter, mem_Ioc, inr_mem_disjSum] at hx hy
          rw [ Nat.Simproc.eq_add_le a hy.1.2] at hxy
          exact a, Set.mem_union_right {0} hx.2, b, Set.mem_union_right {0} hy.2, Eq.symm hxy
        | Sum.inr b, Sum.inl a =>
          dsimp [f] at hxy
          simp only [inl_mem_disjSum, mem_filter, mem_Ioc, inr_mem_disjSum] at hx hy
          rw [Nat.sub_eq_iff_eq_add hx.1.2] at hxy
          exact a, Set.mem_union_right {0} hy.2, b, Set.mem_union_right {0} hx.2, Eq.symm hxy
        | Sum.inr b, Sum.inr b' =>
          grind [inr_mem_disjSum]

Stepan Nesterov (Jan 21 2026 at 17:39):

Ok I was able to shave a few more lines off @Damiano Testa suggestion, and I believe this should now be an mwe:

import Mathlib

open Finset Pointwise

theorem sumset_univ_of_schirelmannDensity_ge_one {A B : Set } [DecidablePred (·  A)]
    [DecidablePred (·  B)] (h : schnirelmannDensity A + schnirelmannDensity B  1) :
    ({0}  A) + ({0}  B) = Set.univ := by
  rw [Set.eq_univ_iff_forall]
  rintro (_ | m)
  · exact 0, Set.mem_union_left A rfl, 0, Set.mem_union_left B rfl, rfl
  · set n := m + 1
    by_cases hnA : n  A
    · exact n, Set.mem_union_right {0} hnA, 0, Set.mem_union_left B rfl, rfl
    · by_cases hnB : n  B
      · exact 0, Set.mem_union_left A rfl, n, Set.mem_union_right {0} hnB, zero_add n
      let f :     
        | Sum.inl x => x
        | Sum.inr y => n - y
      have hc : (image f (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B}))).card <
        (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B})).card
        := calc
        (image f (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B}))).card 
          (Ioo 0 n).card := card_le_card (by grind [mem_disjSum])
        _ < n := by rw [Nat.card_Ioo, Nat.sub_zero]; exact lt_add_one (n - 1)
        _  {a  Ioc 0 n | a  A}.card + {b  Ioc 0 n | b  B}.card := by
          rify
          nlinarith [@schnirelmannDensity_mul_le_card_filter A _ n,
            @schnirelmannDensity_mul_le_card_filter B _ n]
        _ = (disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B})).card :=
          Eq.symm (card_disjSum ({a  Ioc 0 n | a  A}) ({b  Ioc 0 n | b  B}))
      obtain x, hx, y, hy, x_ne_y, hxy := exists_ne_map_eq_of_card_image_lt hc
      match x, y with
      | Sum.inl a, Sum.inl a' => grind
      | Sum.inl a, Sum.inr b =>
        dsimp [f] at hxy
        simp only [inl_mem_disjSum, mem_filter, mem_Ioc, inr_mem_disjSum] at hx hy
        rw [ Nat.Simproc.eq_add_le a hy.1.2] at hxy
        exact a, Set.mem_union_right {0} hx.2, b, Set.mem_union_right {0} hy.2, Eq.symm hxy
      | Sum.inr b, Sum.inl a =>
        dsimp [f] at hxy
        simp only [inl_mem_disjSum, mem_filter, mem_Ioc, inr_mem_disjSum] at hx hy
        rw [Nat.sub_eq_iff_eq_add hx.1.2] at hxy
        exact a, Set.mem_union_right {0} hy.2, b, Set.mem_union_right {0} hx.2, Eq.symm hxy
      | Sum.inr b, Sum.inr b' => grind [inr_mem_disjSum]

Stepan Nesterov (Jan 21 2026 at 17:40):

In general, is there a rule of thumb for when the proof is too long for Mathlib?

Ruben Van de Velde (Jan 21 2026 at 18:41):

Not as such, no

Vlad Tsyrklevich (Jan 21 2026 at 18:46):

Eq.symm (card_disjSum ({a ∈ Ioc 0 n | a ∈ A}) ({b ∈ Ioc 0 n | b ∈ B})) could just be (card_disjSum _ _).symm. Eq.symm hxy could be hxy.symm. There's a lot of long types that make this quite dense, here's a golf for the hc hypothesis that is not that much shorter in terms of lines but I think is more readable

  let sA := {a  Ioc 0 n | a  A}
  let sB := {b  Ioc 0 n | b  B}
  have hc : (image f (disjSum sA sB)).card < (disjSum sA sB).card := by
    grw [card_le_card (t := Ioo 0 n) (by grind [mem_disjSum]), Nat.card_Ioo, Nat.sub_zero]
    refine Nat.sub_one_lt_of_le (by lia) ?_
    suffices n  sA.card + sB.card by grw [this,  card_disjSum]
    rify
    nlinarith [@schnirelmannDensity_mul_le_card_filter A _ n,
      @schnirelmannDensity_mul_le_card_filter B _ n]

Vlad Tsyrklevich (Jan 21 2026 at 19:02):

Have not looked at or thought about if it's possible to reduce those case checks early on

import Mathlib

open Finset Pointwise

theorem sumset_univ_of_schirelmannDensity_ge_one {A B : Set } [DecidablePred (·  A)]
    [DecidablePred (·  B)] (h : schnirelmannDensity A + schnirelmannDensity B  1) :
    ({0}  A) + ({0}  B) = Set.univ := by
  refine Set.eq_univ_iff_forall.mpr ?_
  rintro (_ | m)
  · exact 0, by grind
  set n := m + 1
  by_cases hnA : n  A
  · exact n, by grind
  by_cases hnA : n  B
  · exact 0, by grind
  let f :     
    | Sum.inl x => x
    | Sum.inr y => n - y
  let sA := {a  Ioc 0 n | a  A}
  let sB := {b  Ioc 0 n | b  B}
  have hc : (image f (disjSum sA sB)).card < (disjSum sA sB).card := by
    grw [card_le_card (t := Ioo 0 n) (by grind [mem_disjSum]), Nat.card_Ioo, Nat.sub_zero]
    refine Nat.sub_one_lt_of_le (by lia) ?_
    suffices n  sA.card + sB.card by grw [this,  card_disjSum]
    rify
    nlinarith [@schnirelmannDensity_mul_le_card_filter A _ n,
      @schnirelmannDensity_mul_le_card_filter B _ n]
  obtain x, hx, y, hy, _, hxy := exists_ne_map_eq_of_card_image_lt hc
  match x, y with
  | Sum.inl a, Sum.inl a' => grind
  | Sum.inl a, Sum.inr b =>
    simp only [sA, sB, inl_mem_disjSum, mem_filter, mem_Ioc, inr_mem_disjSum] at hx hy
    exact a, by simp [hx], b, by simp [hy], by lia
  | Sum.inr b, Sum.inl a =>
    simp only [sA, sB, inl_mem_disjSum, mem_filter, mem_Ioc, inr_mem_disjSum] at hx hy
    exact a, by simp [hy], b, by simp [hx], by lia
  | Sum.inr b, Sum.inr b' => grind [inr_mem_disjSum]

Vlad Tsyrklevich (Jan 21 2026 at 19:07):

Also mathlib prefers 1 \le x to x \ge 1 so the h hypothesis should be updated.

Stepan Nesterov (Jan 21 2026 at 19:09):

What is the difference between rw [Set.eq_univ_iff_forall] and refine Set.eq_univ_iff_forall.mpr ?_?

Vlad Tsyrklevich (Jan 21 2026 at 19:09):

Ah in this case there is none. I was trying to do something earlier that didn't work out and just forgot to change it back.

Stepan Nesterov (Jan 21 2026 at 19:35):

Ok thank you all for the helpful suggestions, this now feels short enough for Mathlib. I've made a PR of this proof here: https://github.com/leanprover-community/mathlib4/pull/34227

Kevin Buzzard (Jan 22 2026 at 00:09):

Pop a newline after your PR description before the --- to stop yourself from shouting.


Last updated: Feb 28 2026 at 14:05 UTC