Zulip Chat Archive
Stream: metaprogramming / tactics
Topic: Positivity extension for Finset.card
Yaël Dillies (Dec 14 2023 at 04:06):
Would someone kindly explain me how to use Qq in positivity extensions? Every time I try to write an extension, I'm fairly confident I can adapt an existing extension, and every time I hit a different stumbling block.
Yaël Dillies (Dec 14 2023 at 04:07):
Here's my attempt at an extension for docs#Finset.card, which fails because I don't know how to tell Qq that e is Finset.card s, which I can't do because e is supposed to have type β2 : Type u while Finset.card s is supposed to have type ℕ : Type:
import Mathlib.Data.Fintype.Card
import Mathlib.Tactic.Positivity.Core
open Finset
open Qq Lean Meta
#check @Finset.card
@[positivity Finset.card _]
def Mathlib.Meta.Positivity.evalFinsetCard : PositivityExt where eval {u β2} zβ pβ e := do
  let ⟨_v, _l, _r⟩ ← inferTypeQ' <| (Expr.getAppArgs (← withReducible (whnf e))).get! 1
  let .app (.app _f (α : Q(Type u))) (s : Q(Finset $α)) ← withReducible (whnf e) | throwError "not `Finset.card`"
  haveI' : $e =Q Finset.card $s := ⟨⟩
  /-
  type mismatch
    card «$s»
  has type
    ℕ : Type
  but is expected to have type
    «$β2» : Type u
  -/
  let so : Option Q(Finset.Nonempty $s) ← do -- TODO: if I make a typo it doesn't complain?
    try {
      let _fi ← synthInstanceQ (q(Fintype $α) : Q(Type u))
      let _no ← synthInstanceQ (q(Nonempty $α) : Q(Prop))
      match s with
      | ~q(@univ _ $fi) => pure (some q(Finset.univ_nonempty (α := $α)))
      | _ => pure none }
    catch _e => do
      let .some fv ← findLocalDeclWithType? q(Finset.Nonempty $s) | pure none
      pure (some (.fvar fv))
  match so with
  | .some (fi : Q(Finset.Nonempty $s)) => do
    pure (.positive q(@Finset.Nonempty.card_pos.{u} $α $s $fi))
    /-
    type mismatch
      Nonempty.card_pos «$fi»
    has type
      0 < card «$s» : Prop
    but is expected to have type
      0 < «$e» : Prop
    -/
  | _ => pure .none
Yaël Dillies (Dec 14 2023 at 04:08):
I must say I am confounded by the fiercely displayed "intuitive" in the description of https://github.com/leanprover-community/quote4 :sad:
Patrick Massot (Dec 14 2023 at 04:13):
It says "the intuitiveness of modal sequent calculus". I've always interpreted that as a very fine example of Gabriel's sense of humor.
Patrick Massot (Dec 14 2023 at 04:14):
As far as I'm concerned, it could just as well say "the intuitiveness of -categories".
Yaël Dillies (Dec 14 2023 at 04:47):
:bulb: It pays to lie
Yaël Dillies (Dec 14 2023 at 04:47):
import Mathlib.Data.Fintype.Card
import Mathlib.Tactic.Positivity.Core
open Finset
open Qq Lean Meta
@[positivity Finset.card _]
def Mathlib.Meta.Positivity.evalFinsetCard : PositivityExt where eval _ _ e := do
  let ⟨v, _l, _r⟩ ← inferTypeQ' <| (Expr.getAppArgs (← withReducible (whnf e))).get! 1
  let .app (.app _f (α : Q(Type v))) (s : Q(Finset $α)) ← withReducible (whnf e) | throwError "not `Finset.card`"
  let so : Option Q(Finset.Nonempty $s) ← do -- TODO: if I make a typo it doesn't complain?
    try {
      let _fi ← synthInstanceQ (q(Fintype $α) : Q(Type v))
      let _no ← synthInstanceQ (q(Nonempty $α) : Q(Prop))
      match s with
      | ~q(@univ _ $fi) => pure (some q(Finset.univ_nonempty (α := $α)))
      | _ => pure none }
    catch _e => do
      let .some fv ← findLocalDeclWithType? q(Finset.Nonempty $s) | pure none
      pure (some (.fvar fv))
  match so with
  | .some (fi : Q(Finset.Nonempty $s)) => do
    return .positive (q(@Finset.Nonempty.card_pos.{v} $α $s $fi) : Expr)
  | _ => pure .none
example {α : Type*} {s : Finset α} (hs : s.Nonempty) : 0 < s.card := by positivity
example {α : Type*} [Fintype α] [Nonempty α] : 0 < (univ : Finset α).card := by positivity
Yaël Dillies (Dec 14 2023 at 04:48):
This is bad, but type-ascripting with : Expr unblocks me at least.
Thomas Murrills (Dec 14 2023 at 04:53):
Instead of Expr, you can simply use the correct Q type: Q(0 < card $s)!
Yaël Dillies (Dec 14 2023 at 04:54):
:open_mouth: Where do you learn your tricks?
Thomas Murrills (Dec 14 2023 at 04:54):
norm_num :grinning_face_with_smiling_eyes:
Yaël Dillies (Dec 14 2023 at 04:55):
We, as a community, have a documentation issue :face_palm:
Thomas Murrills (Dec 14 2023 at 04:55):
(Just kidding, we actually lie a lot there to make the refl proofs work!) What tipped me off here was the presence of $e.
Mario Carneiro (Dec 14 2023 at 04:56):
you just noticed? I'm hoping that if I do enough oral teaching eventually someone will write the docs :D
Yaël Dillies (Dec 14 2023 at 04:57):
Oh that's certainly not news to me! I've been a big advocate of docs in the past and I've written a lot myself. But here I had no idea what repo to even look into.
Mario Carneiro (Dec 14 2023 at 04:58):
most examples of use of Qq are in mathlib tactics
Yaël Dillies (Dec 14 2023 at 04:59):
Yeah but I was hoping I could find something in the metaprogramming book, typically.
Mario Carneiro (Dec 14 2023 at 04:59):
I don't think it covers Qq
Yaël Dillies (Dec 14 2023 at 04:59):
... or the Qq readme
Yaël Dillies (Dec 14 2023 at 05:01):
Mario, while you are here, how do I extract the u from an expression e whose type is {ι : Type u} → {α : Type v} → [Semifield α] → Finset ι → (ι → α) → α?
Yaël Dillies (Dec 14 2023 at 05:01):
I tried let ⟨v, _l, _r⟩ ← inferTypeQ' <| (Expr.getAppArgs (← withReducible (whnf e))).get! 1 but it doesn't seem to work
Mario Carneiro (Dec 14 2023 at 05:03):
(<- whnf (<- whnf (<- inferType e)).bindingDomain!).sortLevel! maybe?
Mario Carneiro (Dec 14 2023 at 05:03):
it's a bit of a weird situation to be in
Yaël Dillies (Dec 14 2023 at 05:04):
Mario Carneiro said:
(<- whnf (<- inferType (<- whnf e).bindingDomain!)).sortLevel!maybe?
Wait sorry to be dump but what is the full line supposed to look like?
Mario Carneiro (Dec 14 2023 at 05:04):
let u := (<- whnf (<- whnf (<- inferType e)).bindingDomain!).sortLevel!
Yaël Dillies (Dec 14 2023 at 05:05):
Okay, this does not seem to work because it does not cooperate nicely with Qq. I assume this is what inferTypeQ' is about.
Mario Carneiro (Dec 14 2023 at 05:06):
you can coerce the types once you have all the exprs and levels you need
Yaël Dillies (Dec 14 2023 at 05:06):
Sorry, you will to be more precise. I'm a Lean 4 noob and I learn by seeing code.
Mario Carneiro (Dec 14 2023 at 05:10):
I assume you are talking about the code you posted above... but then what is this function of type {ι : Type u} → {α : Type v} → [Semifield α] → Finset ι → (ι → α) → α you are referring to?
Mario Carneiro (Dec 14 2023 at 05:11):
you can do this:
@[positivity Finset.card _]
def Mathlib.Meta.Positivity.evalFinsetCard : PositivityExt where eval _ _ e := do
  let .app (.app (.const _ [u]) (α : Q(Type u))) (s : Q(Finset $α)) ← withReducible (whnf e)
    | throwError "not `Finset.card`"
  let so ← do
    try
      let _fi ← synthInstanceQ (q(Fintype $α) : Q(Type u))
      let _no ← synthInstanceQ (q(Nonempty $α) : Q(Prop))
      match s with
      | ~q(@univ _ $fi) => pure (some q(Finset.univ_nonempty (α := $α)))
      | _ => pure none
    catch _e => do
      let .some fv ← findLocalDeclWithType? q(Finset.Nonempty $s) | pure none
      pure (some (.fvar fv))
  match so with
  | .some (fi : Q(Finset.Nonempty $s)) => do
    return .positive (q(@Finset.Nonempty.card_pos.{u} $α $s $fi) : Q(0 < card $s))
  | _ => pure .none
Yaël Dillies (Dec 14 2023 at 05:13):
Mario Carneiro said:
I assume you are talking about the code you posted above... but then what is this function of type
{ι : Type u} → {α : Type v} → [Semifield α] → Finset ι → (ι → α) → αyou are referring to?
Sorry, this is referring to this message where the function is basically docs#Finset.sum (except that I divide by the cardinality of the finset to get an average).
Yaël Dillies (Dec 14 2023 at 05:14):
Oh I see! Is this a general solution?
Mario Carneiro (Dec 14 2023 at 05:15):
at the beginning of one of these matches you usually know that the input is a constant applied to some stuff so you can use that to get the main universe levels
Yaël Dillies (Dec 14 2023 at 05:15):
because it seems
let ⟨v, _l, _r⟩ ← inferTypeQ' <| (Expr.getAppArgs (← withReducible (whnf e))).get! some_small_number
does actually work, except that I have no way other than trial and error to figure out some_small_number.
Mario Carneiro (Dec 14 2023 at 05:15):
some_small_number should be the arity of the constant
Yaël Dillies (Dec 14 2023 at 05:15):
What's a constant in that context?
Mario Carneiro (Dec 14 2023 at 05:15):
?
Mario Carneiro (Dec 14 2023 at 05:16):
.const
Mario Carneiro (Dec 14 2023 at 05:16):
in this case, it is Finset.sum.{u}
Mario Carneiro (Dec 14 2023 at 05:16):
and we use that to nab the u
Yaël Dillies (Dec 14 2023 at 05:16):
Yes but does correspond to a declaration in the environment, a variable in the local context... ?
Mario Carneiro (Dec 14 2023 at 05:17):
it's a level expression, whatever level happens to be used on this Finset.sum application found in the goal or whatever
Yaël Dillies (Dec 14 2023 at 05:17):
Okay so what if I have a declaration with two universe variables and I want to nab the second one?
Mario Carneiro (Dec 14 2023 at 05:17):
you would use a list with two elements
Mario Carneiro (Dec 14 2023 at 05:18):
The length of the list should match the constant's number of universes
Mario Carneiro (Dec 14 2023 at 05:18):
if you mouse over Finset.card you can see it has one universe argument
Yaël Dillies (Dec 14 2023 at 05:18):
Okay great! This was very instructive. I shall come back to complain if need arises.
Yaël Dillies (Dec 14 2023 at 10:52):
Here I am again with a MWE (that I've already shown to @Alex J. Best):
import Mathlib.Analysis.Normed.Group.Basic
open scoped ENNReal NNReal
namespace Mathlib.Meta.Positivity
open Lean Meta Qq Function
def lp {ι : Type*} [Fintype ι] {α : ι → Type*} [∀ i, NormedAddCommGroup (α i)] (p : ℝ≥0∞)
    (f : ∀ i, α i) : ℝ := sorry
lemma lp_nonneg {ι : Type*} [Fintype ι] {α : ι → Type*} [∀ i, NormedAddCommGroup (α i)] {p : ℝ≥0∞}
    {f : ∀ i, α i} : 0 ≤ lp p f := sorry
lemma lp_pos {ι : Type*} [Fintype ι] {α : ι → Type*} [∀ i, NormedAddCommGroup (α i)] {p : ℝ≥0∞}
    {f : ∀ i, α i} (hf : f ≠ 0) : 0 < lp p f := sorry
lemma lp_pos_of_pos {ι : Type*} [Fintype ι] {α : ι → Type*} [∀ i, NormedAddCommGroup (α i)]
    [∀ i, Preorder (α i)] {p : ℝ≥0∞} {f : ∀ i, α i} (hf : 0 < f) : 0 < lp p f := lp_pos hf.ne'
/-- The `positivity` extension which identifies expressions of the form `lp p f`. -/
@[positivity lp _ _] def evalLp : PositivityExt where eval z p e := do
  let .app (.app (.app (.app (.app (.app (.const _ [u, v]) (ι : Q(Type v))) (fi : Q(Fintype $ι)))
    (α : Q($ι → Type u))) (no : Q(∀ i, NormedAddCommGroup ($α i)))) (p : Q(ℝ≥0∞)))
      (f : Q(∀ i, $α i)) ← withReducible (whnf e)
    | throwError "not lp"
  try {
    let zα ← synthInstanceQ (q(Zero (∀ i, $α i)) : Q(Type (max u v)))
    let pα ← synthInstanceQ (q(PartialOrder (∀ i, $α i)) : Q(Type (max u v)))
    let pα' ← synthInstanceQ (q(∀ i, Preorder ($α i)) : Q(Type (max u v)))
    assumeInstancesCommute
    match ← core zα pα f with
    | .positive pf => return .positive (q(@lp_pos_of_pos $ι $fi $α $no $pα' $p $f $pf) : Expr)
    | .nonzero pf => return .positive (q(@lp_pos $ι $fi $α $no $p $f $pf) : Expr)
    | _ => return .nonnegative (q(@lp_nonneg $ι $fi $α $no $p $f) : Expr)
  } catch _ => try {
    let pf ← findLocalDeclWithType? q($f ≠ 0)
    let pf ← pf
    return .positive (← mkAppM ``lp_pos #[.fvar pf])
  } catch _ =>
    return .nonnegative (q(@lp_nonneg $ι $fi $α $no $p $f) : Expr)
variable {ι : Type*} [Fintype ι] {α : ι → Type*} [∀ i, NormedAddCommGroup (α i)] {w : ι → ℝ≥0}
  {f : ∀ i, α i}
-- All of these are expected to work
example {p : ℝ≥0∞} : 0 ≤ lp p f := by positivity
example {p : ℝ≥0∞} (hf : f ≠ 0) : 0 < lp p f := by positivity
example {p : ℝ≥0∞} {f : ι → ℝ} (hf : 0 < f) : 0 < lp p f := by positivity
example {p : ℝ≥0} : 0 ≤ ‖f‖_[p, w] := by positivity
Yaël Dillies (Dec 14 2023 at 10:54):
I don't know how to tell Qq that the PartialOrder (∀ i, α i) and ∀ i, Preorder (α i) instances result in the same Preorder (∀ i, α i) instance. I would have hoped assumeInstancesCommute does this, but maybe that only works for instances existing in the local context, not derived ones?
Eric Wieser (Dec 14 2023 at 11:09):
Not answering the question directly, but here's how to avoid all the app stuff:
/-- The `positivity` extension which identifies expressions of the form `lp p f`. -/
@[positivity lp _ _] def evalLp : PositivityExt where eval {u} α z p e := do
  if let 0 := u then do -- lean4#3060 means we can't use `|` here
    let ~q(ℝ) := α | throwError "not lp"
    let ~q(@lp $ι $instι $αi $instnorm $p $f) := e | throwError "not lp"
    sorry
  else
    throwError "not lp"
Eric Wieser (Dec 14 2023 at 11:16):
Here's an error-free version:
/-- The `positivity` extension which identifies expressions of the form `lp p f`. -/
@[positivity lp _ _] def evalLp : PositivityExt where eval {u} α _z _p e := do
  if let 0 := u then do -- lean4#3060 means we can't combine this with the match below
    match α, e with
    | ~q(ℝ), ~q(@lp $ι $instι $α $instnorm $p $f) =>
      try {
        let _pα ← synthInstanceQ (q(∀ i, PartialOrder ($α i)) : Q(Type (max u_1 u_2)))
        assumeInstancesCommute
        match ← core q(inferInstance) q(inferInstance) f with
        | .positive pf => return .positive q(lp_pos_of_pos $pf)
        | .nonzero pf => return .positive q(lp_pos $pf)
        | _ => return .nonnegative (q(lp_nonneg))
      } catch _ =>
        assumeInstancesCommute
        if let some pf ← findLocalDeclWithType? q($f ≠ 0) then
          let pf : Q($f ≠ 0) := .fvar pf
          return .positive q(lp_pos $pf)
        else
          return .nonnegative q(lp_nonneg)
    | _ => throwError "not lp"
  else
    throwError "not lp"
Eric Wieser (Dec 14 2023 at 11:20):
Note the trick here: only use synthInstanceQ for when you need new instances that require a global isntance lookup; if the instance you want is a consequence of a local instance, then use q(inferInstance) instead
Yaël Dillies (Dec 14 2023 at 12:57):
And now what if I want to pattern-match on whether my type is a pi-type?
import Mathlib.Algebra.Ring.Defs
import Mathlib.Data.Fintype.Basic
import Mathlib.Tactic.Positivity.Core
def conv {α β : Type*} [Fintype α] [DecidableEq α] [AddCommGroup α] [CommSemiring β]
    (f g : α → β) : α → β := sorry
namespace Mathlib.Meta.Positivity
open Lean Meta Qq
@[positivity conv _ _] def evalConv : PositivityExt where eval {u α} zα pα e := do
  match α, e with
  /-
  stuck at solving universe constraint
    imax ?u.573 ?u.575 =?= u+1
  while trying to unify
    Sort (imax ?u.573 ?u.575) : Type (imax ?u.573 ?u.575)
  with
    Type u : Type (u + 1)
  -/
  | ~q($β → $γ), ~q(@conv _ _ $instβfin $instβdec $instβgroup $instγring) =>
      sorry
  | _ => throwError "not conv"
Eric Wieser (Dec 14 2023 at 13:09):
Does this work?
abbrev TypeFunction (α β : Type*) := α → β
@[positivity conv _ _] def evalConv : PositivityExt where eval {u α} zα pα e := do
  let u1 ← mkFreshLevelMVar
  let u2 ← mkFreshLevelMVar
  let _ : u =QL max u1 u2 := ⟨⟩
  match α, e with
  | ~q(TypeFunction.{u2,u1} $α $β), ~q(@conv _ _ $instβfin $instβdec $instβgroup $instγring $f $g) =>
      sorry
  | _ => throwError "not conv"
Eric Wieser (Dec 14 2023 at 13:25):
I think this raises that matching on universes is a bad idea, because max 1 1 is not equal to 1
Yaël Dillies (Dec 14 2023 at 14:28):
Here's a non-working minimal example based off your idea:
import Mathlib.Algebra.Ring.Defs
import Mathlib.Data.Fintype.Basic
import Mathlib.Tactic.Positivity.Core
section Defs
variable {α β : Type*}
def conv [Fintype α] [DecidableEq α] [AddCommGroup α] [CommSemiring β] (f g : α → β) : α → β :=
  sorry
section OrderedCommSemiring
variable [Fintype α] [DecidableEq α] [AddCommGroup α] [OrderedCommSemiring β] {f g : α → β}
lemma conv_nonneg (hf : 0 ≤ f) (hg : 0 ≤ g) : 0 ≤ conv f g := sorry
lemma conv_nonneg_of_pos_of_nonneg (hf : 0 < f) (hg : 0 ≤ g) : 0 ≤ conv f g := conv_nonneg hf.le hg
lemma conv_nonneg_of_nonneg_of_pos (hf : 0 ≤ f) (hg : 0 < g) : 0 ≤ conv f g := conv_nonneg hf hg.le
end OrderedCommSemiring
section StrictOrderedCommSemiring
variable [Fintype α] [DecidableEq α] [AddCommGroup α] [StrictOrderedCommSemiring β] {f g : α → β}
lemma conv_pos (hf : 0 < f) (hg : 0 < g) : 0 < conv f g := sorry
end StrictOrderedCommSemiring
end Defs
private abbrev TypeFunction (α β : Type*) := α → β
namespace Mathlib.Meta.Positivity
open Lean Meta Qq
@[positivity conv _ _] def evalConv : PositivityExt where eval {u π} zπ pπ e := do
  let u1 ← mkFreshLevelMVar
  let u2 ← mkFreshLevelMVar
  let _ : u =QL max u1 u2 := ⟨⟩
  match π, e with
  | ~q(TypeFunction.{u2, u1} $α $β),
    ~q(@conv _ _ $instαfin $instαdec $instαgrp $instβring $f $g) =>
    let instβstrictring ← synthInstanceQ (q(StrictOrderedCommSemiring $β) : Q(Type u2))
    let instβordring : Q(OrderedCommSemiring $β) := q(inferInstance)
    assumeInstancesCommute
    let ra ← core zπ pπ f
    let rb ← core zπ pπ g
    match ra, rb with -- unknown free variable '_uniq.3469'
    | .positive pa, .positive pb =>
       return .positive q(@conv_pos $α $β $instαfin $instαdec $instαgrp $instβstrictring $f $g $pa $pb)
    -- | .positive pa, .nonnegative pb => return .nonnegative q(conv_nonneg_of_pos_of_nonneg $pa $pb)
    -- | .nonnegative pa, .positive pb => return .nonnegative q(conv_nonneg_of_nonneg_of_pos $pa $pb)
    -- | .nonnegative pa, .nonnegative pb => return .nonnegative q(conv_nonneg $pa $pb)
    | _, _ => pure .none
  | _ => throwError "not conv"
end Mathlib.Meta.Positivity
section Examples
variable {α β : Type*}
section OrderedCommSemiring
variable [Fintype α] [DecidableEq α] [AddCommGroup α] [OrderedCommSemiring β] {f g : α → β}
example (hf : 0 ≤ f) (hg : 0 ≤ g) : 0 ≤ conv f g := by positivity
example (hf : 0 < f) (hg : 0 ≤ g) : 0 ≤ conv f g := by positivity
example (hf : 0 ≤ f) (hg : 0 < g) : 0 ≤ conv f g := by positivity
end OrderedCommSemiring
section StrictOrderedCommSemiring
variable [Fintype α] [DecidableEq α] [AddCommGroup α] [StrictOrderedCommSemiring β] {f g : α → β}
example (hf : 0 < f) (hg : 0 < g) : 0 < conv f g := by positivity
end StrictOrderedCommSemiring
end Examples
Yaël Dillies (Dec 14 2023 at 14:40):
If you uncomment the three branches, you instead get a timeout.
Eric Wieser (Dec 14 2023 at 15:22):
let zπ : Q(Zero ($α → $β)) := zπ  seems to help
Last updated: May 02 2025 at 03:31 UTC