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}   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 \infty-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   synthInstanceQ (q(Zero ( i, $α i)) : Q(Type (max u v)))
    let   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   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 α}   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 α}   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 π}   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   f
    let rb  core   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: Dec 20 2023 at 11:08 UTC