Zulip Chat Archive
Stream: mathlib4
Topic: Coercion triggers timeout
Michael Stoll (May 09 2025 at 19:39):
I'm opening a new thread since the problem seems to be fairly unrelated to apply?. See for the history.
import Mathlib.Data.Complex.Norm
set_option trace.profiler true
set_option trace.profiler.threshold 10
variable (x : β) (z : β)
-- this works fine:
#check (norm_add_le (x : β) z : βx + zβ β€ βComplex.ofReal xβ + βzβ)
-- this times out:
#check (norm_add_le (βx) z : βx + zβ β€ βComplex.ofReal xβ + βzβ)
In the linked thread, you can see how this comes up when using exact? or apply?, even when avoiding βx in the code.
It appears that lean gets lost in trying to unify @norm β SeminormedAddGroup.toNorm (coming from docs#norm_add_le) and @norm β Complex.instNorm, but for some strange reason only when I use βx and not (x : β) or Complex.ofReal x.
Michael Stoll (May 10 2025 at 18:35):
A possibly more minimal version:
import Mathlib.Data.Complex.Norm
lemma test {Ξ± : Type*} [Norm Ξ±] (a b : Ξ±) : βaβ = βbβ := sorry
variable (x : β) (z : β)
#count_heartbeats in -- Used 34 heartbeats
#check test (βx) z
#count_heartbeats in -- Used 34 heartbeats
#check test (x : β) z
#count_heartbeats in -- Used 41 heartbeats
#check (test (x : β) z : βComplex.ofReal xβ = βzβ)
#count_heartbeats in -- Used 217827 heartbeats
#check (test (βx) z : βComplex.ofReal xβ = βzβ)
seal Real.sqrt in
#count_heartbeats in -- Used 114 heartbeats
#check (test (βx) z : βComplex.ofReal xβ = βzβ)
In the second example, lean seems to introduce a metavariable for (βx) that stays unassigned apparently until the very end, and it keeps unfolding definitions, in particular of the norm on the complex numbers. sealing docs#Real.sqrt stops this (compare docs#Complex.instNorm -- the definition is Real.sqrt (Complex.normSq z)) and makes it almost as fast as with the explicit type ascription.
In #24752, I'm trying to see what the fallout is when I make docs#Real.sqrt irreducible.
Michael Stoll (May 10 2025 at 19:19):
There are no big changes; overall build needs about 50 fewer Giga-instructions.
Michael Stoll (May 10 2025 at 20:25):
But somehow my impression is that this is a work-around and not a fix.
With
set_option trace.profiler true
set_option trace.profiler.threshold 0
set_option pp.all true
building the test file and looking at the output, I see several (13, to be precise) occurrences of
[Meta.isDefEq] [0.00000...] βοΈ Complex.ofReal x =?= ?m.526
and I am wondering why at this point the metavariable ?m.526 does not get assigned to Complex.ofReal x (if it did, the whole going down rabbit holes by unfolding would be avoided).
Michael Stoll (May 10 2025 at 20:34):
Note that what happens is roughly this (many intermediate lines omitted and without pp.all):
[Meta.isDefEq] [0.001783] βοΈ ββxβ = βzβ =?= β?m.526β = βzβ
[Meta.isDefEq] [0.000002] βοΈ βx =?= ?m.526
[Meta.isDefEq] [0.001702] βοΈ Complex.instNorm.1 βx =?= Complex.instNorm.1 ?m.526
[Meta.isDefEq] [0.001668] βοΈ β(Complex.normSq βx) =?= β(Complex.normSq ?m.526)
[Meta.isDefEq] [0.001642] βοΈ Complex.normSq βx =?= Complex.normSq ?m.526
[Meta.isDefEq] [0.000002] βοΈ βx =?= ?m.526
[Meta.isDefEq] [0.001546] βοΈ MonoidWithZeroHom.funLike.1 Complex.normSq
βx =?= MonoidWithZeroHom.funLike.1 Complex.normSq ?m.526
[Meta.isDefEq] [0.001479] βοΈ (βComplex.normSq).toFun βx =?= (βComplex.normSq).toFun ?m.526
[Meta.isDefEq] [0.000002] βοΈ βx =?= ?m.526
[Meta.isDefEq] [0.001420] βοΈ (βComplex.normSq).1 βx =?= (βComplex.normSq).1 ?m.526
[Meta.isDefEq] [0.001352] βοΈ (βx).re * (βx).re +
(βx).im *
(βx).im =?= Complex.re ?m.526 * Complex.re ?m.526 + Complex.im ?m.526 * Complex.im ?m.526
[Meta.isDefEq] [0.000366] βοΈ (βx).re * (βx).re =?= Complex.re ?m.526 * Complex.re ?m.526
[Meta.isDefEq] [0.000072] βοΈ (βx).re =?= Complex.re ?m.526
[Meta.isDefEq] [0.000002] βοΈ βx =?= ?m.526
[Meta.isDefEq] [0.000243] βοΈ instHMul.1 (βx).re
(βx).re =?= instHMul.1 (Complex.re ?m.526) (Complex.re ?m.526)
[Meta.isDefEq] [0.000202] βοΈ Mul.mul (βx).re
(βx).re =?= Mul.mul (Complex.re ?m.526) (Complex.re ?m.526)
[Meta.isDefEq] [0.000001] βοΈ βx =?= ?m.526
[Meta.isDefEq] [0.000121] βοΈ Real.instMul.1 (βx).re
(βx).re =?= Real.instMul.1 (Complex.re ?m.526) (Complex.re ?m.526)
[Meta.isDefEq] [0.000092] βοΈ Real.mulβ (βx).re
(βx).re =?= Real.mulβ (Complex.re ?m.526) (Complex.re ?m.526)
[Meta.isDefEq] [0.000065] βοΈ (βx).re =?= Complex.re ?m.526
[Meta.isDefEq] [0.000001] βοΈ βx =?= ?m.526
[Meta.isDefEq] [0.000813] βοΈ instHAdd.1 ((βx).re * (βx).re)
((βx).im *
(βx).im) =?= instHAdd.1 (Complex.re ?m.526 * Complex.re ?m.526)
(Complex.im ?m.526 * Complex.im ?m.526)
(...)
[Meta.isDefEq.onFailure] [0.000002] βοΈ β(Complex.normSq βx) =?= β(Complex.normSq ?m.526)
[Meta.isDefEq.onFailure] [0.000002] βοΈ ββxβ = βzβ =?= β?m.526β = βzβ
[Meta.isDefEq] [0.000859] βοΈ β?m.526β = βzβ =?= ββxβ = βzβ
[Meta.isDefEq] [0.000835] βοΈ β?m.526β =?= ββxβ
[Meta.isDefEq] [0.000002] βοΈ ?m.526 =?= βx
[Meta.isDefEq] [0.000812] βοΈ Complex.instNorm.1 ?m.526 =?= Complex.instNorm.1 βx
[Meta.isDefEq] [0.000804] βοΈ β(Complex.normSq ?m.526) =?= β(Complex.normSq βx)
[Meta.isDefEq] [0.000788] βοΈ Complex.normSq ?m.526 =?= Complex.normSq βx
[Meta.isDefEq] [0.000001] βοΈ ?m.526 =?= βx
[Meta.isDefEq] [0.000764] βοΈ MonoidWithZeroHom.funLike.1 Complex.normSq
?m.526 =?= MonoidWithZeroHom.funLike.1 Complex.normSq βx
(...)
[Meta.isDefEq.onFailure] [0.000002] βοΈ β(Complex.normSq ?m.526) =?= β(Complex.normSq βx)
[Meta.isDefEq.onFailure] [0.000002] βοΈ β?m.526β = βzβ =?= ββxβ = βzβ
(sealing docs#Real.sqrt prevents it from also unfolding square roots all the way down...)
Michael Stoll (May 10 2025 at 20:41):
Then it goes on a tangent starting with
[Elab.coe] [0.001073] adding coercion for test ?m.526 z : β?m.526β = βzβ =?= ββxβ = βzβ
[Meta.isDefEq] [0.000812] βοΈ Eq β?m.526β =?= Eq ββxβ
[Meta.isDefEq] [0.000788] βοΈ β?m.526β =?= ββxβ
[Meta.isDefEq] [0.000001] βοΈ ?m.526 =?= βx
[Meta.isDefEq] [0.000770] βοΈ Complex.instNorm.1 ?m.526 =?= Complex.instNorm.1 βx
[Meta.isDefEq] [0.000764] βοΈ β(Complex.normSq ?m.526) =?= β(Complex.normSq βx)
[Meta.isDefEq] [0.000747] βοΈ Complex.normSq ?m.526 =?= Complex.normSq βx
[Meta.isDefEq] [0.000001] βοΈ ?m.526 =?= βx
(... repeating its fruitless endeavors)
[Meta.isDefEq.onFailure] [0.000002] βοΈ β(Complex.normSq ?m.526) =?= β(Complex.normSq βx)
[Meta.isDefEq.onFailure] [0.000002] βοΈ Eq β?m.526β =?= Eq ββxβ
[Meta.synthInstance] [0.000207] π₯οΈ CoeT (β?m.526β = βzβ) β― (ββxβ = βzβ)
[Meta.synthInstance] [0.000027] new goal CoeT (β?m.526β = βzβ) β― (ββxβ = βzβ)
[Meta.synthInstance] [0.000145] π₯οΈ apply @instCoeT to CoeT (β?m.526β = βzβ) β― (ββxβ = βzβ)
[Meta.synthInstance.tryResolve] [0.000126] π₯οΈ CoeT (β?m.526β = βzβ) β― (ββxβ = βzβ) β CoeT ?m.601 ?m.602 ?m.601
[Meta.isDefEq] [0.000122] π₯οΈ CoeT (β?m.526β = βzβ) β― (ββxβ = βzβ) =?= CoeT ?m.601 ?m.602 ?m.601
Michael Stoll (May 10 2025 at 20:45):
@Jovan Gerbscheid @Matthew Ballard any ideas as to whether this is expected or possibly a bug? I have no idea how unification works in detail, but my naive expectation would be that when the question is whether I can unify a metavariable with a concrete term, then the answer should certainly be "yes".
Kevin Buzzard (May 10 2025 at 21:39):
Yes I am also surprised by this (in the test file above, with pp.all on):
[Meta.isDefEq] [3.452198] βοΈ @Eq.{1} Real (@Norm.norm.{0} Complex Complex.instNorm ?m.367)
(@Norm.norm.{0} Complex Complex.instNorm
z) =?= @Eq.{1} Real (@Norm.norm.{0} Complex Complex.instNorm (Complex.ofReal x))
(@Norm.norm.{0} Complex Complex.instNorm z) βΆ
You would have thought that there was a pretty obvious answer to this...
Kevin Buzzard (May 10 2025 at 21:40):
It doesn't like the answer for some reason...
[] [3.452163] βοΈ @Norm.norm.{0} Complex Complex.instNorm
?m.367 =?= @Norm.norm.{0} Complex Complex.instNorm (Complex.ofReal x) βΌ
[delta] [0.000008] βοΈ @Norm.norm.{0} Complex Complex.instNorm
?m.367 =?= @Norm.norm.{0} Complex Complex.instNorm (Complex.ofReal x) βΌ
[] [0.000003] βοΈ ?m.367 =?= Complex.ofReal x
Aaron Liu (May 10 2025 at 22:13):
Maybe it's not assignable yet?
Jovan Gerbscheid (May 11 2025 at 01:19):
Yes, the metavariable is not assignable:
[] [5.900020] βοΈ ββxβ =?= β?m.367β βΌ
[] [0.000006] βοΈ βx =?= ?m.367 βΌ
[] βx [nonassignable] =?= ?m.367 [nonassignable]
I'd guess is that this is by design: whenever you write the βx, Lean is only allowed to fill in the arrow by synthesizing the coercion instance.
Jovan Gerbscheid (May 11 2025 at 01:20):
And the slowness in unification is the same as in #mathlib4 > simp timeout at `whnf` @ π¬: exponentially slow unification in the presence of metavariables.
Jovan Gerbscheid (May 11 2025 at 01:42):
Since this unification slowness seems to be quite common, I made a small minimal example. It takes about 6s to fail, and this time is exponential in the number that I set to 15.
class A (n : Nat) where
x : Nat
instance [A n] : A (n+1) where
x := A.x n
theorem test [A 0] : A.x 15 = sorry := sorry
set_option trace.profiler true in
example [A 1] : A.x 15 = sorry := by
rw [@test]
Michael Stoll (May 11 2025 at 08:53):
Jovan Gerbscheid said:
I'd guess is that this is by design: whenever you write the
βx, Lean is only allowed to fill in the arrow by synthesizing the coercion instance.
But at that point, we are already dealing with Complex.ofReal x, so it is clear what the coercion is?
Jovan Gerbscheid (May 11 2025 at 09:15):
The unification algorithm doesn't know anything about coercions. So it has to be filled in by a type class search.
Michael Stoll (May 11 2025 at 09:39):
What I was trying to say is that
[Meta.isDefEq] [0.00000...] βοΈ Complex.ofReal x =?= ?m.526
does not involve a coercion (as far as I understand it): the left hand side is the application of an explicit function to an explicit variable.
Michael Stoll (May 11 2025 at 09:40):
Another observation is that parts of the trace are repeated several times (partly with sides switched), so it appears that very possibly something could be gained by caching the failures.
Jovan Gerbscheid (May 11 2025 at 09:43):
Yes, but the right hand side represents the expression βx. Apparently this how Lean implements the β: replace the entire expression with an unassignable metavariable. And later fill it in.
Michael Stoll (May 11 2025 at 10:00):
Here is a #mwe without using type classes.
def g {Ξ± : Type} (a : Ξ±) : Nat β Nat
| 0 => 0
| n + 1 => g a n
theorem foo {Ξ± : Type} (a b : Ξ±) : g a 1000 = g b 1000 := sorry
variable (x : Nat) (y : Int)
set_option maxRecDepth 2000 -- otherwise error in the second #check below
set_option trace.profiler true
set_option trace.profiler.threshold 0
#check (foo (x : Int) y : g (x : Int) 1000 = g y 1000)
#check (foo (βx) y : g (x : Int) 1000 = g y 1000)
Michael Stoll (May 11 2025 at 10:01):
The first #check gives this trace:
[Elab.command] [0.004925] #check (foo (x : Int) y : g (x : Int) 1000 = g y 1000) βΌ
[step] [0.000174] expected type: Type, term
Nat βΆ
[step] [0.000087] expected type: Type, term
Int βΆ
[step] [0.004360] expected type: <not-available>, term
(foo (x : Int) y : g (x : Int) 1000 = g y 1000) βΌ
[] [0.003584] expected type: Sort ?u.281, term
g (x : Int) 1000 = g y 1000 βΆ
[] [0.000657] expected type: g (βx) 1000 = g y 1000, term
foo (x : Int) y βΆ
[Meta.isDefEq] [0.000056] β
οΈ g (βx) 1000 = g y 1000 =?= g (βx) 1000 = g y 1000 βΌ
[] [0.000002] β
οΈ g (βx) 1000 =?= g (βx) 1000 [] [0.000001] β
οΈ g y 1000 =?= g y 1000 [] [0.000001] β
οΈ Nat =?= Nat [isLevelDefEq] [0.000000] β
οΈ 0 =?= 0
[Meta.check] [0.000095] β
οΈ foo (βx) y βΆ
Michael Stoll (May 11 2025 at 10:04):
The second one gives
[Elab.command] [0.095349] #check (foo (βx) y : g (x : Int) 1000 = g y 1000) βΌ
[step] [0.000106] expected type: Type, term
Nat βΆ
[step] [0.000055] expected type: Type, term
Int βΆ
[step] [0.094532] expected type: <not-available>, term
(foo (βx) y : g (x : Int) 1000 = g y 1000) βΌ
[] [0.003083] expected type: Sort ?u.342, term
g (x : Int) 1000 = g y 1000 βΆ
[] [0.052486] expected type: g (βx) 1000 = g y 1000, term
foo (βx) y βΆ
[Meta.isDefEq] [0.038652] β
οΈ g ?m.395 1000 = g y 1000 =?= g (βx) 1000 = g y 1000 βΌ
[] [0.038588] β
οΈ g ?m.395 1000 =?= g (βx) 1000 βΌ
[delta] [0.000011] βοΈ g ?m.395 1000 =?= g (βx) 1000 βΆ
[whnf] [0.000033] Non-easy whnf: (fun motive x h_1 h_2 β¦ Nat.casesOn x (h_1 ()) fun n β¦ h_2 n) (fun x β¦ Nat) 1000 h_1 h_2 [whnf] [0.000011] Non-easy whnf: (fun motive x h_1 h_2 β¦ Nat.casesOn x (h_1 ()) fun n β¦ h_2 n) (fun x β¦ Nat) 1000 h_1 h_2
[] [0.038467] β
οΈ g ?m.395 999 =?= g (βx) 999 βΌ
[delta] [0.000005] βοΈ g ?m.395 999 =?= g (βx) 999 βΆ
[whnf] [0.000010] Non-easy whnf: (fun motive x h_1 h_2 β¦ Nat.casesOn x (h_1 ()) fun n β¦ h_2 n) (fun x β¦ Nat) 999 h_1 h_2 [whnf] [0.000009] Non-easy whnf: (fun motive x h_1 h_2 β¦ Nat.casesOn x (h_1 ()) fun n β¦ h_2 n) (fun x β¦ Nat) 999 h_1 h_2
[] [0.038418] β
οΈ g ?m.395 998 =?= g (βx) 998 βΆ
[] [0.000004] β
οΈ g y 1000 =?= g y 1000 [] [0.000000] β
οΈ Nat =?= Nat [isLevelDefEq] [0.000000] β
οΈ 0 =?= 0
[step] [0.000173] expected type: Int, term
βx βΆ
[Meta.isDefEq] [0.000000] β
οΈ Int =?= Int
[Meta.check] [0.000031] β
οΈ foo (βx) y βΆ
it unfolds the definition of g until the very end (when it succeeds, but only because g a n does not actually depend on a or n).
Michael Stoll (May 11 2025 at 10:11):
At the point where we try to unify (say) Complex.ofReal x with the metavariable representing βx, we know what type βx is supposed to have. So it should be possible to synthesize the relevant coercion instance at that point and thus fill in the arrow.
Jovan Gerbscheid (May 11 2025 at 10:29):
In your example, the second #check isn't significantly slower than the first, so I think it's not such a big deal. I think the real problem is the exponentially slow unification.
Michael Stoll (May 11 2025 at 10:32):
It's a toy example, and it is still 20 times slower, and increasing 1000 to 10000 or so gives a stack overflow. But you are certainly right that exponentially slow unification is a problem.
Jovan Gerbscheid (May 11 2025 at 10:33):
When I change the 1000 to a 2000, the two #check are similarly slow
Michael Stoll (May 11 2025 at 10:36):
You have to change all 1000 to 2000. On the web server, I then get
[Elab.command] [0.008249] #check (foo (x : Int) y : g (x : Int) 2000 = g y 2000)
[Elab.command] [0.367612] #check (foo (βx) y : g (x : Int) 2000 = g y 2000)
(with set_option maxRecDepth 5000; otherwise the second check gives an error.)
Jovan Gerbscheid (May 11 2025 at 10:37):
Ah sorry, my mistake
Michael Stoll (May 11 2025 at 10:49):
What is the rationale for making the metavariable that stands for the coerced value unassignable?
Jovan Gerbscheid (May 11 2025 at 12:50):
If it were assignable, then it could be assigned anyhting; e.g. something other than a coercion. But I'm just guessing about these implementation details
Michael Stoll (May 11 2025 at 15:50):
In the context of my earlier Mathlib example, consider:
import Mathlib.Data.Complex.Norm
lemma test {Ξ± : Type*} [Norm Ξ±] (a b : Ξ±) : βaβ = βbβ := sorry
variable (x : β) (z : β)
#count_heartbeats in -- Used 217734 heartbeats
#check (test (βx) z : βComplex.ofReal xβ = βzβ)
#count_heartbeats in -- Used 40 heartbeats
#check (test z (βx) : βzβ = βComplex.ofReal xβ)
The difference is that in the first case, the expected type of βx is still a metavariable when the expression left of the colon is elaborated, whereas in the second case, it is already known to be β, which leads to the coercion being looked for and found at this point, and then unification is basically trivial. In the first case, we still have the unassignable metavariable ?m.abcfor βx in the mix when the expression left of the colon is elaborated with the expected type given, which leads to the frantic, but eventually unsuccessful attempt to unify β?m.abcβ with βComplex.ofReal xβ. At this point the expected type of ?m.abc is known to be β, but this does not help.
I'm wondering whether it might by possible to detect this situation (this may involve remembering that the unassignable metavariable came from a coercion) and then re-elaborate the coercion with its then known expected type before proceeding.
I think I will open a thread in the lean4 channel for this (later; I have other obligations now).
Michael Stoll (May 15 2025 at 19:06):
If you have observed apply? or exact? timing out (or taking a very long time) unexpectedly, this may be related to what is described in this thread, and so you may want to up-vote the issue I created for this: lean4#8364
Last updated: Dec 20 2025 at 21:32 UTC