Skip to content

Commit b66c970

Browse files
committed
feat: gcongr works with eta and proofs (#15364)
* `gcongr` now recognizes eta-expanded variables as variables * `gcongr` now recognizes all proofs as "defeq" even if the proofs have different types (in e.g. `Finset.sup'_mono` * Give a bit more information in the error message when not accepting a `gcongr`-lemma.
1 parent cdcd883 commit b66c970

File tree

8 files changed

+61
-82
lines changed

8 files changed

+61
-82
lines changed

Mathlib/Algebra/Order/BigOperators/Group/Finset.lean

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ variable {f g : ι → N} {s t : Finset ι}
9696
/-- In an ordered commutative monoid, if each factor `f i` of one finite product is less than or
9797
equal to the corresponding factor `g i` of another finite product, then
9898
`∏ i ∈ s, f i ≤ ∏ i ∈ s, g i`. -/
99-
@[to_additive sum_le_sum]
99+
@[to_additive (attr := gcongr) sum_le_sum]
100100
theorem prod_le_prod' (h : ∀ i ∈ s, f i ≤ g i) : ∏ i ∈ s, f i ≤ ∏ i ∈ s, g i :=
101101
Multiset.prod_map_le_prod_map f g h
102102

@@ -105,22 +105,6 @@ or equal to the corresponding summand `g i` of another finite sum, then
105105
`∑ i ∈ s, f i ≤ ∑ i ∈ s, g i`. -/
106106
add_decl_doc sum_le_sum
107107

108-
/-- In an ordered commutative monoid, if each factor `f i` of one finite product is less than or
109-
equal to the corresponding factor `g i` of another finite product, then `s.prod f ≤ s.prod g`.
110-
111-
This is a variant (beta-reduced) version of the standard lemma `Finset.prod_le_prod'`, convenient
112-
for the `gcongr` tactic. -/
113-
@[to_additive (attr := gcongr) GCongr.sum_le_sum]
114-
theorem _root_.GCongr.prod_le_prod' (h : ∀ i ∈ s, f i ≤ g i) : s.prod f ≤ s.prod g :=
115-
s.prod_le_prod' h
116-
117-
/-- In an ordered additive commutative monoid, if each summand `f i` of one finite sum is less than
118-
or equal to the corresponding summand `g i` of another finite sum, then `s.sum f ≤ s.sum g`.
119-
120-
This is a variant (beta-reduced) version of the standard lemma `Finset.sum_le_sum`, convenient
121-
for the `gcongr` tactic. -/
122-
add_decl_doc GCongr.sum_le_sum
123-
124108
@[to_additive sum_nonneg]
125109
theorem one_le_prod' (h : ∀ i ∈ s, 1 ≤ f i) : 1 ≤ ∏ i ∈ s, f i :=
126110
le_trans (by rw [prod_const_one]) (prod_le_prod' h)
@@ -385,29 +369,18 @@ theorem prod_lt_prod' (hle : ∀ i ∈ s, f i ≤ g i) (hlt : ∃ i ∈ s, f i <
385369
∏ i ∈ s, f i < ∏ i ∈ s, g i :=
386370
Multiset.prod_lt_prod' hle hlt
387371

388-
@[to_additive sum_lt_sum_of_nonempty]
372+
/-- In an ordered commutative monoid, if each factor `f i` of one nontrivial finite product is
373+
strictly less than the corresponding factor `g i` of another nontrivial finite product, then
374+
`s.prod f < s.prod g`. -/
375+
@[to_additive (attr := gcongr) sum_lt_sum_of_nonempty]
389376
theorem prod_lt_prod_of_nonempty' (hs : s.Nonempty) (hlt : ∀ i ∈ s, f i < g i) :
390377
∏ i ∈ s, f i < ∏ i ∈ s, g i :=
391378
Multiset.prod_lt_prod_of_nonempty' (by aesop) hlt
392379

393-
/-- In an ordered commutative monoid, if each factor `f i` of one nontrivial finite product is
394-
strictly less than the corresponding factor `g i` of another nontrivial finite product, then
395-
`s.prod f < s.prod g`.
396-
397-
This is a variant (beta-reduced) version of the standard lemma `Finset.prod_lt_prod_of_nonempty'`,
398-
convenient for the `gcongr` tactic. -/
399-
@[to_additive (attr := gcongr) GCongr.sum_lt_sum_of_nonempty]
400-
theorem _root_.GCongr.prod_lt_prod_of_nonempty' (hs : s.Nonempty) (Hlt : ∀ i ∈ s, f i < g i) :
401-
s.prod f < s.prod g :=
402-
s.prod_lt_prod_of_nonempty' hs Hlt
403-
404380
/-- In an ordered additive commutative monoid, if each summand `f i` of one nontrivial finite sum is
405381
strictly less than the corresponding summand `g i` of another nontrivial finite sum, then
406-
`s.sum f < s.sum g`.
407-
408-
This is a variant (beta-reduced) version of the standard lemma `Finset.sum_lt_sum_of_nonempty`,
409-
convenient for the `gcongr` tactic. -/
410-
add_decl_doc GCongr.sum_lt_sum_of_nonempty
382+
`s.sum f < s.sum g`. -/
383+
add_decl_doc sum_lt_sum_of_nonempty
411384

412385
-- Porting note (#11215): TODO -- calc indentation
413386
@[to_additive sum_lt_sum_of_subset]

Mathlib/Algebra/Order/BigOperators/Ring/Finset.lean

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ lemma prod_nonneg (h0 : ∀ i ∈ s, 0 ≤ f i) : 0 ≤ ∏ i ∈ s, f i :=
3232
/-- If all `f i`, `i ∈ s`, are nonnegative and each `f i` is less than or equal to `g i`, then the
3333
product of `f i` is less than or equal to the product of `g i`. See also `Finset.prod_le_prod'` for
3434
the case of an ordered commutative multiplicative monoid. -/
35+
@[gcongr]
3536
lemma prod_le_prod (h0 : ∀ i ∈ s, 0 ≤ f i) (h1 : ∀ i ∈ s, f i ≤ g i) :
3637
∏ i ∈ s, f i ≤ ∏ i ∈ s, g i := by
3738
induction' s using Finset.cons_induction with a s has ih h
@@ -44,16 +45,6 @@ lemma prod_le_prod (h0 : ∀ i ∈ s, 0 ≤ f i) (h1 : ∀ i ∈ s, f i ≤ g i)
4445
· apply prod_nonneg fun x H ↦ h0 x (subset_cons _ H)
4546
· apply le_trans (h0 a (mem_cons_self a s)) (h1 a (mem_cons_self a s))
4647

47-
/-- If all `f i`, `i ∈ s`, are nonnegative and each `f i` is less than or equal to `g i`, then the
48-
product of `f i` is less than or equal to the product of `g i`.
49-
50-
This is a variant (beta-reduced) version of the standard lemma `Finset.prod_le_prod`, convenient
51-
for the `gcongr` tactic. -/
52-
@[gcongr]
53-
lemma _root_.GCongr.prod_le_prod (h0 : ∀ i ∈ s, 0 ≤ f i) (h1 : ∀ i ∈ s, f i ≤ g i) :
54-
s.prod f ≤ s.prod g :=
55-
s.prod_le_prod h0 h1
56-
5748
/-- If each `f i`, `i ∈ s` belongs to `[0, 1]`, then their product is less than or equal to one.
5849
See also `Finset.prod_le_one'` for the case of an ordered commutative multiplicative monoid. -/
5950
lemma prod_le_one (h0 : ∀ i ∈ s, 0 ≤ f i) (h1 : ∀ i ∈ s, f i ≤ 1) : ∏ i ∈ s, f i ≤ 1 := by

Mathlib/Combinatorics/Schnirelmann.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ lemma schnirelmannDensity_eq_zero_of_one_not_mem (h : 1 ∉ A) : schnirelmannDen
108108
lemma schnirelmannDensity_le_of_subset {B : Set ℕ} [DecidablePred (· ∈ B)] (h : A ⊆ B) :
109109
schnirelmannDensity A ≤ schnirelmannDensity B :=
110110
ciInf_mono ⟨0, fun _ ⟨_, hx⟩ ↦ hx ▸ by positivity⟩ fun _ ↦ by
111-
gcongr; exact monotone_filter_right _ h
111+
gcongr; exact h
112112

113113
/-- The Schnirelmann density of `A` is `1` if and only if `A` contains all the positive naturals. -/
114114
lemma schnirelmannDensity_eq_one_iff : schnirelmannDensity A = 1 ↔ {0}ᶜ ⊆ A := by

Mathlib/Data/Finset/Basic.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2221,7 +2221,7 @@ theorem filter_subset_filter {s t : Finset α} (h : s ⊆ t) : s.filter p ⊆ t.
22212221

22222222
theorem monotone_filter_left : Monotone (filter p) := fun _ _ => filter_subset_filter p
22232223

2224-
-- TODO: `@[gcongr]` doesn't accept this lemma because of the `DecidablePred` arguments
2224+
@[gcongr]
22252225
theorem monotone_filter_right (s : Finset α) ⦃p q : α → Prop⦄ [DecidablePred p] [DecidablePred q]
22262226
(h : p ≤ q) : s.filter p ⊆ s.filter q :=
22272227
Multiset.subset_of_le (Multiset.monotone_filter_right s.val h)

Mathlib/Data/Finset/Lattice.lean

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -844,18 +844,11 @@ lemma sup'_comp_eq_map {s : Finset γ} {f : γ ↪ β} (g : β → α) (hs : s.N
844844
s.sup' hs (g ∘ f) = (s.map f).sup' (map_nonempty.2 hs) g :=
845845
.symm <| sup'_map _ _
846846

847+
848+
@[gcongr]
847849
theorem sup'_mono {s₁ s₂ : Finset β} (h : s₁ ⊆ s₂) (h₁ : s₁.Nonempty) :
848850
s₁.sup' h₁ f ≤ s₂.sup' (h₁.mono h) f :=
849851
Finset.sup'_le h₁ _ (fun _ hb => le_sup' _ (h hb))
850-
851-
/-- A version of `Finset.sup'_mono` acceptable for `@[gcongr]`.
852-
Instead of deducing `s₂.Nonempty` from `s₁.Nonempty` and `s₁ ⊆ s₂`,
853-
this version takes it as an argument. -/
854-
@[gcongr]
855-
lemma _root_.GCongr.finset_sup'_le {s₁ s₂ : Finset β} (h : s₁ ⊆ s₂)
856-
{h₁ : s₁.Nonempty} {h₂ : s₂.Nonempty} : s₁.sup' h₁ f ≤ s₂.sup' h₂ f :=
857-
sup'_mono f h h₁
858-
859852
end Sup'
860853

861854
section Inf'
@@ -1001,18 +994,11 @@ lemma inf'_comp_eq_map {s : Finset γ} {f : γ ↪ β} (g : β → α) (hs : s.N
1001994
s.inf' hs (g ∘ f) = (s.map f).inf' (map_nonempty.2 hs) g :=
1002995
sup'_comp_eq_map (α := αᵒᵈ) g hs
1003996

997+
@[gcongr]
1004998
theorem inf'_mono {s₁ s₂ : Finset β} (h : s₁ ⊆ s₂) (h₁ : s₁.Nonempty) :
1005999
s₂.inf' (h₁.mono h) f ≤ s₁.inf' h₁ f :=
10061000
Finset.le_inf' h₁ _ (fun _ hb => inf'_le _ (h hb))
10071001

1008-
/-- A version of `Finset.inf'_mono` acceptable for `@[gcongr]`.
1009-
Instead of deducing `s₂.Nonempty` from `s₁.Nonempty` and `s₁ ⊆ s₂`,
1010-
this version takes it as an argument. -/
1011-
@[gcongr]
1012-
lemma _root_.GCongr.finset_inf'_mono {s₁ s₂ : Finset β} (h : s₁ ⊆ s₂)
1013-
{h₁ : s₁.Nonempty} {h₂ : s₂.Nonempty} : s₂.inf' h₂ f ≤ s₁.inf' h₁ f :=
1014-
inf'_mono f h h₁
1015-
10161002
end Inf'
10171003

10181004
section Sup

Mathlib/MeasureTheory/Integral/Lebesgue.lean

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,17 @@ theorem lintegral_mono' {m : MeasurableSpace α} ⦃μ ν : Measure α⦄ (hμν
8787
rw [lintegral, lintegral]
8888
exact iSup_mono fun φ => iSup_mono' fun hφ => ⟨le_trans hφ hfg, lintegral_mono (le_refl φ) hμν⟩
8989

90-
-- workaround for the known eta-reduction issue with `@[gcongr]`
90+
-- version where `hfg` is an explicit forall, so that `@[gcongr]` can recognize it
9191
@[gcongr] theorem lintegral_mono_fn' ⦃f g : α → ℝ≥0∞⦄ (hfg : ∀ x, f x ≤ g x) (h2 : μ ≤ ν) :
92-
lintegral μ f ≤ lintegral ν g :=
92+
∫⁻ a, f a ∂μ ≤ ∫⁻ a, g a ∂ν :=
9393
lintegral_mono' h2 hfg
9494

9595
theorem lintegral_mono ⦃f g : α → ℝ≥0∞⦄ (hfg : f ≤ g) : ∫⁻ a, f a ∂μ ≤ ∫⁻ a, g a ∂μ :=
9696
lintegral_mono' (le_refl μ) hfg
9797

98-
-- workaround for the known eta-reduction issue with `@[gcongr]`
98+
-- version where `hfg` is an explicit forall, so that `@[gcongr]` can recognize it
9999
@[gcongr] theorem lintegral_mono_fn ⦃f g : α → ℝ≥0∞⦄ (hfg : ∀ x, f x ≤ g x) :
100-
lintegral μ f ≤ lintegral μ g :=
100+
∫⁻ a, f a ∂μ ≤ ∫⁻ a, g a ∂μ :=
101101
lintegral_mono hfg
102102

103103
theorem lintegral_mono_nnreal {f g : α → ℝ≥0} (h : f ≤ g) : ∫⁻ a, f a ∂μ ≤ ∫⁻ a, g a ∂μ :=

Mathlib/Tactic/GCongr/Core.lean

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -156,25 +156,32 @@ initialize registerBuiltinAttribute {
156156
add := fun decl _ kind ↦ MetaM.run' do
157157
let declTy := (← getConstInfo decl).type
158158
withReducible <| forallTelescopeReducing declTy fun xs targetTy => do
159-
let fail := throwError "\
160-
@[gcongr] attribute only applies to lemmas proving \
161-
x₁ ~₁ x₁' → ... xₙ ~ₙ xₙ' → f x₁ ... xₙ ∼ f x₁' ... xₙ', got {declTy}"
162-
-- verify that conclusion of the lemma is of the form `rel (head x₁ ... xₙ) (head y₁ ... yₙ)`
163-
let .app (.app rel lhs) rhs ← whnf targetTy | fail
164-
let some relName := rel.getAppFn.constName? | fail
165-
let (some head, lhsArgs) := lhs.withApp fun e a => (e.constName?, a) | fail
166-
let (some head', rhsArgs) := rhs.withApp fun e a => (e.constName?, a) | fail
167-
unless head == head' && lhsArgs.size == rhsArgs.size do fail
159+
let fail (m : MessageData) := throwError "\
160+
@[gcongr] attribute only applies to lemmas proving f x₁ ... xₙ ∼ f x₁' ... xₙ'.\n \
161+
{m} in the conclusion of {declTy}"
162+
-- verify that conclusion of the lemma is of the form `f x₁ ... xₙ ∼ f x₁' ... xₙ'`
163+
let .app (.app rel lhs) rhs ← whnf targetTy |
164+
fail "No relation with at least two arguments found"
165+
let some relName := rel.getAppFn.constName? | fail "No relation found"
166+
let (some head, lhsArgs) := lhs.withApp fun e a => (e.constName?, a) |
167+
fail "LHS is not a function"
168+
let (some head', rhsArgs) := rhs.withApp fun e a => (e.constName?, a) |
169+
fail "RHS is not a function"
170+
unless head == head' && lhsArgs.size == rhsArgs.size do
171+
fail "LHS and RHS do not have the same head function and arity"
168172
let mut varyingArgs := #[]
169173
let mut pairs := #[]
170174
-- iterate through each pair of corresponding (LHS/RHS) inputs to the head function `head` in
171175
-- the conclusion of the lemma
172176
for e1 in lhsArgs, e2 in rhsArgs do
173177
-- we call such a pair a "varying argument" pair if the LHS/RHS inputs are not defeq
174-
let isEq ← isDefEq e1 e2
178+
-- (and not proofs)
179+
let isEq := (← isDefEq e1 e2) || ((← isProof e1) && (← isProof e2))
175180
if !isEq then
176-
-- verify that the "varying argument" pairs are free variables
177-
unless e1.isFVar && e2.isFVar do fail
181+
let e1 := e1.eta
182+
let e2 := e2.eta
183+
-- verify that the "varying argument" pairs are free variables (after eta-reduction)
184+
unless e1.isFVar && e2.isFVar do fail "Not all arguments are free variables"
178185
-- add such a pair to the `pairs` array
179186
pairs := pairs.push (varyingArgs.size, e1, e2)
180187
-- record in the `varyingArgs` array a boolean (true for varying, false if LHS/RHS are defeq)
@@ -337,9 +344,12 @@ partial def _root_.Lean.MVarId.gcongr
337344
-- (if not, stop and report the existing goal)
338345
return (false, names, #[g])
339346
-- and also build an array of booleans according to which arguments `_ ... _` to the head
340-
-- function differ between the LHS and RHS
341-
(lhsArgs.zip rhsArgs).mapM fun (lhsArg, rhsArg) =>
342-
return (none, !(← withReducibleAndInstances <| isDefEq lhsArg rhsArg))
347+
-- function differ between the LHS and RHS. We treat always treat proofs as being the same
348+
-- (even if they have differing types).
349+
(lhsArgs.zip rhsArgs).mapM fun (lhsArg, rhsArg) => do
350+
let isSame ← withReducibleAndInstances <|
351+
return (← isDefEq lhsArg rhsArg) || ((← isProof lhsArg) && (← isProof rhsArg))
352+
return (none, !isSame)
343353
-- Name the array of booleans `varyingArgs`: this records which arguments to the head function are
344354
-- supposed to vary, according to the template (if there is one), and in the absence of a template
345355
-- to record which arguments to the head function differ between the two sides of the goal.

test/GCongr/inequalities.lean

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Authors: Heather Macbeth
55
-/
66
import Mathlib.Algebra.Order.BigOperators.Ring.Finset
77
import Mathlib.Algebra.Order.Field.Basic
8+
import Mathlib.Data.Finset.Lattice
89
import Mathlib.Tactic.Linarith
910
import Mathlib.Tactic.GCongr
1011
import Mathlib.Tactic.SuccessIfFailWithMsg
@@ -169,6 +170,12 @@ example {a b c d e : ℝ} (_h1 : 0 ≤ b) (_h2 : 0 ≤ c) (hac : a * b + 1 ≤ c
169170
guard_target =ₛ a * b ≤ c * d
170171
linarith
171172

173+
-- test big operators
174+
example (f g : ℕ → ℕ) (s : Finset ℕ) (h : ∀ i ∈ s, f i ≤ g i) :
175+
∑ i ∈ s, (3 + f i ^ 2) ≤ ∑ i ∈ s, (3 + g i ^ 2) := by
176+
gcongr with i hi
177+
exact h i hi
178+
172179
-- this tests templates with binders
173180
example (f g : ℕ → ℕ) (s : Finset ℕ) (h : ∀ i ∈ s, f i ^ 2 + 1 ≤ g i ^ 2 + 1) :
174181
∑ i ∈ s, f i ^ 2 ≤ ∑ i ∈ s, g i ^ 2 := by
@@ -209,3 +216,15 @@ example {x y : ℕ} (h : x ≤ y) (l) : dontUnfoldMe 14 l + x ≤ 0 + y := by
209216
gcongr
210217
guard_target = dontUnfoldMe 14 l ≤ 0
211218
apply test_sorry
219+
220+
/-! Test that `gcongr` works well with proof arguments -/
221+
222+
example {α β : Type*} [SemilatticeSup α] (f : β → α)
223+
{s₁ s₂ : Finset β} (h : s₁ ⊆ s₂) (h₁ : s₁.Nonempty) :
224+
s₁.sup' h₁ f ≤ s₂.sup' (h₁.mono h) f := by
225+
gcongr
226+
227+
example {α β : Type*} [SemilatticeSup α] (f : β → α)
228+
{s₁ s₂ : Finset β} (h : s₁ ⊆ s₂) (h₁ : s₁.Nonempty) (h₂ : s₂.Nonempty) :
229+
s₁.sup' h₁ f ≤ s₂.sup' h₂ f := by
230+
gcongr

0 commit comments

Comments
 (0)