Zulip Chat Archive
Stream: mathlib4
Topic: linarith fails in a simple example
Michael Stoll (Nov 09 2023 at 21:51):
I have a tactic state as follows:
xy: ℂ
hx₀: x ≠ 0
hx₁: -Real.pi / 2 < arg x
hx₂: arg x ≤ Real.pi / 2
hy₀: y ≠ 0
hy₁: -Real.pi / 2 < arg y
hy₂: arg y ≤ Real.pi / 2
k: ℤ
hk: arg (x * y) - (arg x + arg y) = 2 * Real.pi * ↑k
H₁: -2 * Real.pi < arg (x * y) - (arg x + arg y)
H₂: arg (x * y) - (arg x + arg y) < 2 * Real.pi
H₂': k < 1
H₁': -1 < k
⊢ k = 0
Here linarith
"fails to find a contradiction", but have : k = 0 := by linarith; exact this
works.
WE (not minimal; see this thread for context):
import Mathlib.Analysis.SpecialFunctions.Complex.Log
lemma arg_mul {x y : ℂ} (hx₀ : x ≠ 0) (hx₁ : -Real.pi/2 < x.arg) (hx₂ : x.arg ≤ Real.pi/2)
(hy₀ : y ≠ 0) (hy₁ : -Real.pi/2 < y.arg) (hy₂ : y.arg ≤ Real.pi/2) :
(x * y).arg = x.arg + y.arg := by
have := arg_mul_coe_angle hx₀ hy₀
rw [← Real.Angle.coe_add, Real.Angle.angle_eq_iff_two_pi_dvd_sub] at this
obtain ⟨k, hk⟩ := this
rw [← sub_eq_zero, hk]
have H₁ : -2 * Real.pi < arg (x * y) - (arg x + arg y)
· sorry
have H₂ : arg (x * y) - (arg x + arg y) < 2 * Real.pi
· sorry
have H₁' : -2 * Real.pi < 2 * Real.pi * k := H₁.trans_eq hk
have H₂' : 2 * Real.pi * k < 2 * Real.pi := hk.symm.trans_lt H₂
rw [neg_mul, ← mul_one (-(2 * Real.pi)), neg_mul_comm, mul_lt_mul_left (by positivity)] at H₁'
simp [Real.pi_pos, Real.pi_ne_zero, -neg_mul] at H₁' H₂' ⊢
norm_cast at H₁' H₂'
change -1 < _ at H₁' -- why does `norm_cast` introduce `Int.negSucc 0`??
-- `linarith` does not work here
have hk₀ : k = 0 := by linarith
exact hk₀
Alex J. Best (Nov 10 2023 at 01:30):
This is very strange, the minified example is:
import Mathlib.Tactic.Linarith
--set_option pp.raw true
--set_option trace.linarith true
example (k : ℤ) (h : k < 1) (h₁ : -1 < k)
: k = 0 := by
-- linarith -- works
change -1 < _ at h₁
-- linarith -- fails!
have hk : k = 0 := by
linarith
exact hk
But I cannot see why linarith suddely fails, the first stage of the linarith traces I can see that differs is in the strengthen int inequalities step, but I have no idea why
David Renshaw (Nov 10 2023 at 03:09):
sometimes this kind of thing is due to mdata: mathlib4#1930
Kyle Miller (Nov 10 2023 at 03:16):
pp.raw
shows the exact same term -- I'm going to go with a missing withContext
David Renshaw (Nov 10 2023 at 04:18):
This seems to fix it.
diff --git a/Mathlib/Tactic/Linarith/Preprocessing.lean b/Mathlib/Tactic/Linarith/Preprocessing.lean
index 0b0aae659..c666acc9f 100644
--- a/Mathlib/Tactic/Linarith/Preprocessing.lean
+++ b/Mathlib/Tactic/Linarith/Preprocessing.lean
@@ -212,7 +212,7 @@ If `pf` is a proof of a strict inequality `(a : ℤ) < b`,
and similarly if `pf` proves a negated weak inequality.
-/
def mkNonstrictIntProof (pf : Expr) : MetaM Expr := do
- match (← inferType pf).getAppFnArgs with
+ match (← whnfR (← inferType pf)).getAppFnArgs with
| (``LT.lt, #[_, _, a, b]) =>
return mkApp (← mkAppM ``Iff.mpr #[← mkAppOptM ``Int.add_one_le_iff #[a, b]]) pf
| (``GT.gt, #[_, _, a, b]) =>
@@ -233,7 +233,7 @@ into a proof of `t1 ≤ t2 + 1`. -/
def strengthenStrictInt : Preprocessor where
name := "strengthen strict inequalities over int"
transform h := do
- if isStrictIntComparison (← inferType h) then
+ if isStrictIntComparison (← whnfR (← inferType h)) then
return [← mkNonstrictIntProof h]
else
return [h]
David Renshaw (Nov 10 2023 at 04:19):
I'll put up a PR
David Renshaw (Nov 10 2023 at 04:20):
I believe that it's an fvar (not an mdata as I previously guessed) that needs to be weak-head-normalized.
David Renshaw (Nov 10 2023 at 04:25):
Kevin Buzzard (Nov 10 2023 at 07:14):
Nice detective work David!
Alex J. Best (Nov 10 2023 at 10:00):
Any thoughts on whether there is actually an underlying issue with change
producing a sub-optimal term? Or is this just to be expected
David Renshaw (Nov 10 2023 at 14:00):
I looked into this a bit deeper.
In the failure case, when the strengthenStrictInt
preprocessor does inferType
on the h₁ : -1 < k
hypothesis, it sees it as an mvar.
Adding a whnfR
normalizes past that mvar, and makes the expression an app, so that the isStrictIntComparison function acts as expected.
David Renshaw (Nov 10 2023 at 14:02):
This makes me wonder whether we should additionally add in an instantiateMVars
.
David Renshaw (Nov 10 2023 at 14:02):
Or maybe add that instead of whnfR
.
David Renshaw (Nov 10 2023 at 14:06):
Alex J. Best said:
Any thoughts on whether there is actually an underlying issue with
change
producing a sub-optimal term? Or is this just to be expected
I think it's expected that change
will produce a metavariable.
Michael Stoll (Nov 10 2023 at 14:17):
So the problem was caused by a combination of
norm_cast at H₁' H₂'
change -1 < _ at H₁' -- why does `norm_cast` introduce `Int.negSucc 0`??
and the fact that change
produced a sub-optimal term for linarith
to work with.
Is the norm_cast
thing a bug?
import Mathlib.Tactic
example (n : ℤ) : (-37 : ℤ) = n := by
norm_cast -- goal is `Int.negSucc 36 = -n`
sorry
David Renshaw (Nov 10 2023 at 14:23):
Maybe the norm_cast thing is a delaborator bug?
Alex J. Best (Nov 10 2023 at 14:28):
Yeah that certainly looks like a bug too, but not the cause of this later one.
Michael Stoll (Nov 10 2023 at 14:29):
show_term norm_cast
gives
refine Eq.mpr (id (congrFun (congrArg Eq (Mathlib.Data.Int.Cast.Basic._auxLemma.3 36)) n)) ?_
where Mathlib.Data.Int.Cast.Basic._auxLemma.3
is not actually a lemma name ("unknown identifier"). It seems to be an auto-generated lemma from Mathlib.Data.Int.Cast.Basic
.
Michael Stoll (Nov 10 2023 at 14:29):
And docs#Int.cast_negSucc has a norm_cast
attribute.
Michael Stoll (Nov 10 2023 at 14:30):
Trying attribute [-norm_cast] Int.cast_negSucc
, I get the message "attribute cannot be erased".
Michael Stoll (Nov 10 2023 at 14:32):
The docs don't show the norm_cast
attribute, BTW. The source code is
open Nat
namespace Int
variable {R : Type u} [AddGroupWithOne R]
@[simp, norm_cast]
theorem cast_negSucc (n : ℕ) : (-[n+1] : R) = -(n + 1 : ℕ) :=
AddGroupWithOne.intCast_negSucc n
Does norm_cast
apply these lemmas right-to-left?
Michael Stoll (Nov 10 2023 at 14:37):
The strange thing is that rw [← Int.cast_negSucc]
does not work.
Michael Stoll (Nov 10 2023 at 14:42):
But I guess I would have to rewrite with the aux lemma, but I don't find a way to actually see it...
Yaël Dillies (Nov 10 2023 at 14:52):
norm_cast
applies right to left, push_cast
left to right.
Michael Stoll (Nov 10 2023 at 15:01):
The auto-generated lemma seems to look like this (found by copying into a file importing Mathlib and using whatsnew in
):
theorem test : ∀ {R : Type u} [AddGroupWithOne R] (n : ℕ),
-↑(n + 1) = (Int.negSucc n : R) :=
fun {R} [AddGroupWithOne R] n => (cast_negSucc n).symm
Then
example (n : ℤ) : (-37 : ℤ) = n := by
refine (test 36).trans ?_
rw [Int.cast_id]
sorry
has the same effect as norm_cast
.
Michael Stoll (Nov 10 2023 at 15:02):
Should the norm_cast
attribute be removed? Or is it there for a good reason?
Michael Stoll (Nov 12 2023 at 08:42):
@Mario Carneiro Would you agree that it would make sense to remove the norm_cast
attribute from Int.cast_negSucc
? The way it is now, norm_cast
exposes the ugly implementation details of Int
.
Mario Carneiro (Nov 12 2023 at 08:43):
Mario Carneiro (Nov 12 2023 at 08:44):
Is it being used backwards by norm_cast
? It doesn't seem like it would expose any implementation details if used in the forward direction
Mario Carneiro (Nov 12 2023 at 08:45):
norm_cast
is supposed to categorize the lemmas it is given into a few groups depending on the shape, which determines whether it is used in the "push" or "pull" phases
Michael Stoll (Nov 12 2023 at 08:45):
Yes; see the example further up:
import Mathlib.Tactic
example (n : ℤ) : (-37 : ℤ) = n := by
norm_cast -- goal is `Int.negSucc 36 = n`
sorry
Michael Stoll (Nov 12 2023 at 08:46):
(deleted)
Mario Carneiro (Nov 12 2023 at 08:47):
that is, it could be an issue in the categorization, what happens if you use @[norm_cast squash]
or so?
Michael Stoll (Nov 12 2023 at 08:48):
No idea. I don't know about the internal workings of norm_cast
; I just came across a situation like the one in the example and thought that this is likely a bug.
Mario Carneiro (Nov 12 2023 at 08:53):
the question is whether it is something that better tagging can fix
Mario Carneiro (Nov 12 2023 at 08:53):
or if it is a bug in the tactic
Michael Stoll (Nov 12 2023 at 08:59):
I've now tried
@[simp, norm_cast squash]
theorem cast_negSucc (n : ℕ) : (-[n+1] : R) = -(n + 1 : ℕ) :=
AddGroupWithOne.intCast_negSucc n
and this makes the example behave as expected.
But I don't know if this has perhaps bad side effects, but I can try to find out.
Michael Stoll (Nov 12 2023 at 09:04):
Scott Morrison (Nov 12 2023 at 09:10):
Can we please merge https://github.com/leanprover/std4/pull/351 first?
Michael Stoll (Nov 12 2023 at 09:17):
What is that supposed to do?
Scott Morrison (Nov 12 2023 at 09:36):
It moves norm_cast
to Std, and also sets up norm_cast
lemmas in Std.
Scott Morrison (Nov 12 2023 at 09:37):
It's possible we can make the changes independently.
Michael Stoll (Nov 12 2023 at 09:38):
But docs#Int.cast_negSucc is in Mathlib.Data...
Anyway, the concrete use case where this came up was superseded by a better proof, so from my POV it is not urgent.
Mario Carneiro (Nov 12 2023 at 09:53):
yeah, this issue has nothing to do with std
Michael Stoll (Nov 12 2023 at 10:14):
In any case, #8365 is green now, so nothing broke.
Michael Stoll (Nov 12 2023 at 10:15):
What is squash
meant to do, BTW?
Michael Stoll (Nov 16 2023 at 08:42):
Can #8365 be merged now?
Last updated: Dec 20 2023 at 11:08 UTC