Zulip Chat Archive

Stream: mathlib4

Topic: bug in `positivity`?


Heather Macbeth (Mar 07 2023 at 23:59):

@Gabriel Ebner 's PR !4#2685 rewrites positivity thanks to some very nice new Qq functionality. It seems to have broken certain positivity applications, though. Here is a test case: a positivity call occurring as a discharger inside solve_by_elim is broken.

It's not broken when I try to replicate the behaviour with the positivity call outside the solve_by_elim application, so I'm having trouble tracking it down further.

import Mathlib.Tactic.SolveByElim
import Mathlib.Tactic.Positivity

open Lean Meta Mathlib

attribute [dummy_label_attr] le_refl add_le_add mul_le_mul_of_nonneg_right

-- works
example {x y : } (hx : x  12) : y + x * x  y + 12 * x := by
  apply_rules only [hx] using dummy_label_attr
  positivity

def PositivityDischarge (g : MVarId) : MetaM (Option (List MVarId)) :=
do withTransparency .reducible (Meta.Positivity.positivity g); pure (some []) <|> pure none

-- works before #2685, fails on #2685 with "not a positivity goal"
example {x y : } (hx : x  12) : y + x * x  y + 12 * x := by
  solve_by_elim (config := { discharge := PositivityDischarge }) only [hx] using dummy_label_attr

Gabriel Ebner (Mar 08 2023 at 00:05):

This is the goal that positivity is called on. I'm not sure how this worked before.

example {x y : } (hx : x  12) : False := by positivity

Heather Macbeth (Mar 08 2023 at 00:06):

It used to be called on 0 ≤ x (to make the mul_le_mul_of_nonneg_right application work).

Heather Macbeth (Mar 08 2023 at 00:07):

That is, apply mul_le_mul_of_nonneg_right generates two subgoals and positivity discharged one of them.

Gabriel Ebner (Mar 08 2023 at 00:21):

This doesn't work either:

example {x y : } (hx : x  12) : x  0 := by positivity

Heather Macbeth (Mar 08 2023 at 00:21):

Wait, that's with x negative though!

Gabriel Ebner (Mar 08 2023 at 00:22):

Oh :face_palm:

Gabriel Ebner (Mar 08 2023 at 00:57):

Apparently, it can't prove that two typeclass instances are defeq:

[isDefEq]  CommMonoidWithZero.toZero =?= MonoidWithZero.toZero

Heather Macbeth (Mar 08 2023 at 00:57):

I just reached the same conclusion myself, and had come over to report it :-)

Gabriel Ebner (Mar 08 2023 at 00:57):

Fixed:

import Mathlib.Tactic.Positivity
open Lean Meta Mathlib

attribute [dummy_label_attr] le_refl add_le_add mul_le_mul_of_nonneg_right

def PositivityDischarge (g : MVarId) : MetaM (Option (List MVarId)) := do
  withDefault (Meta.Positivity.positivity g)
  pure (some [])

example {x y : } (hx : x  12) : y + x * x  y + 12 * x := by
  solve_by_elim (config := { discharge := PositivityDischarge }) only [hx] using dummy_label_attr

Heather Macbeth (Mar 08 2023 at 00:58):

Thank you! I don't understand the fix, though -- is it the withDefault? What does that do?

Heather Macbeth (Mar 08 2023 at 00:59):

It replaces the withTransparency .reducible in my version?

Heather Macbeth (Mar 08 2023 at 01:00):

Or do I want them both?

Gabriel Ebner (Mar 08 2023 at 01:00):

Yes, it's defined as withTransparency .default

Gabriel Ebner (Mar 08 2023 at 01:00):

(There's also withReducible)

Gabriel Ebner (Mar 08 2023 at 01:01):

The withReducible is too strong, positivity expect(s/ed) the transparency to be default. It compares a lot of type class instances using isDefEq (thus requiring default transparency), and adds withReducible itself.

Mario Carneiro (Mar 08 2023 at 01:02):

is this what that assertInstancesCommute macro does?

Mario Carneiro (Mar 08 2023 at 01:03):

because if so I would like to remove it from the places it's been added and put assumeInstancesCommute instead

Gabriel Ebner (Mar 08 2023 at 01:04):

Yes, the assertInstancesCommute macro adds isDefEq calls.

Gabriel Ebner (Mar 08 2023 at 01:04):

because if so I would like to remove it from the places it's been added and put assumeInstancesCommute instead

Why?

Mario Carneiro (Mar 08 2023 at 01:04):

because that's the number one performance issue in these tactics

Mario Carneiro (Mar 08 2023 at 01:05):

plus asserting that the instances themselves are equal is more than needed in most cases

Mario Carneiro (Mar 08 2023 at 01:05):

we only need to know that some projections off the instances are equal

Mario Carneiro (Mar 08 2023 at 01:06):

If this defeq call saved work later that would be a different situation, but here it is pure overhead

Gabriel Ebner (Mar 08 2023 at 01:08):

We don't check that the projections are equal though.

Gabriel Ebner (Mar 08 2023 at 01:08):

The result of isDefEq is also cached.

Mario Carneiro (Mar 08 2023 at 01:08):

no, we just go right ahead and construct the term and it crashes and burns in the kernel if it's not defeq

Mario Carneiro (Mar 08 2023 at 01:10):

I don't think improving the error message when there is an instance diamond is worth the performance degradation in normal uses of the tactic

Mario Carneiro (Mar 08 2023 at 01:10):

because you will be breaking out a debugger when something goes wrong in these tactics in any case

Mario Carneiro (Mar 08 2023 at 01:11):

yes, the result of isDefEq is cached, that's what I tell myself but the profiler doesn't lie, it is very hot code

Gabriel Ebner (Mar 08 2023 at 01:12):

There's plenty of cases with type tags like WithTop etc. where you can run into noncanonical instances.

Mario Carneiro (Mar 08 2023 at 01:12):

if the instances are not defeq then we're going to fail anyway

Gabriel Ebner (Mar 08 2023 at 01:13):

because if so I would like to remove it from the places it's been added and put assumeInstancesCommute instead

isDefEq is obviously hot, because it also does unification with lots of backtracking, has super expensive nested TC synthesis etc. All of this doesn't happen here.

Mario Carneiro (Mar 08 2023 at 01:13):

I mean, I ran a profiler with and without the isDefEq call and it was a significant improvement

Mario Carneiro (Mar 08 2023 at 01:14):

this is how ring was originally written after all

Gabriel Ebner (Mar 08 2023 at 01:31):

In any case, there's an assumeInstancesCommute now.


Last updated: Dec 20 2023 at 11:08 UTC