New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
typeclass inference can go haywire #2055
Comments
This issue came up while I was porting |
We might be seeing this issue again in the mathlib port: leanprover-community/mathlib4#1979 Minimising would involve building the algebra heirarchy up to modules (which I can do if required); here is a version which depends on mathlib. import Mathlib.Algebra.Module.Submodule.Basic
set_option profiler true
set_option synthInstance.maxHeartbeats 140000 -- default is 20000; five times that not enough here
set_option trace.Meta.synthInstance true
variables {R M : Type _} [Semiring R] [AddCommMonoid M] [Module R M]
{N : Submodule R M} (n : ℕ) in
#synth CoeT ℕ n (Module.End R N) -- takes 7 wall clock seconds
/-
...(chase the large number)
[tryResolve] [7.199349s] ❌ Ring (Module.End R { x // x ∈ N }) ≟ Ring (Module.End ?m.527 ?m.528) ▼
[] [0.115698s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.115001s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.118302s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.119675s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.113969s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.109342s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.110053s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.101198s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.108375s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.109773s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.098758s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.103855s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.106827s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.109454s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.108562s] ❌ AddCommGroup { x // x ∈ N } ▶
[] [0.109977s] ❌ AddCommGroup { x // x ∈ N } ▶
...
-/ There are 70 failures in total. |
The issue also occurs with example : a /ₚ u₁ = b /ₚ u₂ ↔ a * u₂ = b * u₁ := mul_right_eq_self so it doesn't have to do with |
I have minimised the example a lot more. Inspired by Reid's example of changing all the names from NonUnitalSemiring to class A1 (M : Type u) extends Mul M
class A2 (M : Type u) extends A1 M
class A3 (M : Type u) extends A2 M
class A4 (M : Type u) extends A3 M
class B1 (M : Type u) extends Mul M
class CommRing (M : Type u) extends B1 M, A4 M
-- given `CommRing R` there are two distinct routes to `Mul R`:
-- CommRing -> B1 -> Mul
-- CommRing -> A4 -> A3 -> A2 -> A1 -> Mul
-- random extra bad class plus theorem which causes all the trouble
class BadClass (M : Type u) extends A4 M
theorem mul_right_eq_self {M : Type u} [inst : BadClass M] {a b c : M} :
a * b = a ↔ b = c := sorry
set_option trace.Meta.synthInstance true
variable {R : Type _} [i : CommRing R] (a b c : R)
example :
-- use the two (defeq) multiplications to say a * b = c ↔ a * b = c
(@HMul.hMul R R R
(@instHMul R (@A1.toMul R (@A2.toA1 R (@A3.toA2 R (@A4.toA3 R (@CommRing.toA4 R i))))))
a b) = c ↔
(@HMul.hMul R R R
(@instHMul R (@B1.toMul R (@CommRing.toB1 R i)))
a b) = c :=
-- ⊢ a * b = c ↔ a * b = c
mul_right_eq_self
/-
[Meta.synthInstance] ❌ BadClass R ▶
[Meta.synthInstance] ❌ BadClass R ▶
[Meta.synthInstance] ❌ BadClass R ▶
[Meta.synthInstance] ❌ BadClass R ▶
[Meta.synthInstance] ❌ BadClass R ▶
[Meta.synthInstance] ❌ BadClass R ▶
[Meta.synthInstance] ❌ BadClass R ▶
...
490 times
-/ |
A couple of comments about Kevin's test case.
There seem to be (at least) two issues here:
|
Adam Topaz points out that even without the defeq diamond you can get a smaller amount of chaos: class A1 (M : Type u) extends Mul M
class A2 (M : Type u) extends A1 M
class A3 (M : Type u) extends A2 M
class A4 (M : Type u) extends A3 M
class CommRing (M : Type u) extends A4 M
class BadClass (M : Type u) extends A4 M
theorem mul_right_eq_self {M : Type u} [inst : BadClass M] {a b c : M} :
a * b = a ↔ b = c := sorry
set_option trace.Meta.synthInstance true
variable {R : Type _} [CommRing R] (a b c : R)
example : a * b = c ↔ a * b = c :=
mul_right_eq_self
/-
[Meta.synthInstance] ❌ BadClass R ▶
[Meta.synthInstance] ❌ BadClass R ▶
[Meta.synthInstance] ❌ BadClass R ▶
...
54 times
-/ and adding |
And here is a version that makes class A1 (M : Type u) extends Mul M
class A2 (M : Type u) extends A1 M
class A3 (M : Type u) extends A2 M
class A4 (M : Type u) extends A3 M
class CommRing (M : Type u) extends A4 M
class BadClass (M : Type u) extends A4 M
theorem mul_right_eq_self {M : Type u} [inst : BadClass M] {a b c : M} :
a * b = a ↔ b = c := sorry
macro "exponential" "blowup" n:num "from" a:ident "to" c:ident : command => do
let bs ← (List.range n.getNat).mapM fun i =>
Lean.mkIdent <$> Lean.Macro.addMacroScope (.mkSimple s!"B{i}")
let mut cmds : Array Lean.Syntax.Command ←
bs.toArray.mapM (`(class $(·) (M N : Type) : Type))
for b1 in bs, b2 in bs.tail! do
cmds := cmds ++ #[
← `(instance {M N} [$b1 (Option M) N] : $b2 M N where),
← `(instance {M N} [$b1 (Inhabited M) N] : $b2 M N where)]
cmds := cmds ++ #[← `(command| instance {M N} [$a N] : $(bs.head!) M N where)]
cmds := cmds ++ #[← `(command| instance {M} [$(bs.getLast!) M M] : $c M := sorry)]
return ⟨Lean.mkNullNode cmds⟩
class BaseBadClass (N : Type) : Type
instance : BaseBadClass Nat where
exponential blowup 12 from BaseBadClass to BadClass
variable {R : Type} [CommRing R] (a b c : R)
example : a * b = c ↔ a * b = c :=
mul_right_eq_self |
…e_biproducts instance (#18740) This backports a proposed removal of the `abelian.has_finite_biproducts` global instance, instead enabling it locally in the files that need it. The reason for removing it is that it triggers the ~~dreaded~~ leanprover/lean4#2055 during the simpNF linter in leanprover-community/mathlib4#2769, the mathlib4 port of `category_theory.abelian.basic`. This backport verifies that we won't run into further problems downstream if we (hopefully temporarily) remove these instances in mathlib4. Co-authored-by: Scott Morrison <scott.morrison@gmail.com>
From what I can tell, #2152 fixed all of the performance issues here. Unification still does a lot of typeclass queries, but they return instantly due to being cached now. |
Now that leanprover/lean4#2055 is resolved, this test no longer times out.
Prerequisites
Description
Lean's type class inference system sometimes tries and fails to solve the same problem 384 or more times consecutively in the middle of a proof.
Steps to Reproduce
NOTE: Far simpler examples are further down the thread.
The instance trace in this minimal example shows Lean trying and failing to solve
LeftCancelMonoid R
384 times.Expected behavior:
I would hope that Lean does not need to go up this blind alley.
Actual behavior: [What actually happens]
In this minimal example, Lean spends a lot of time trying and failing very quickly to solve
LeftCancelMonoid R
. In the mathlib example which this was minimised from, Lean tries to solveLeftCancelMonoid R
over 500 times and each time it takes a lot longer to fail because in mathlib there are of course ways to prove that something is a left-cancel-monoid. Lean 4 takes about 0.001 seconds to fail each time in the mathlib example, contributing to a 0.5 second delay. Unfortunately it then tries to solveRightCancelMonoid R
hundreds of times, and thenCancelMonoidWithZero R
hundreds of times (I could probably update the MWE to indicate this behaviour if you want to see the wild goose chase happening multiple times), meaning that thesimp
call takes so long to complete that Lean runs out of heartbeats.Reproduces how often: [What percentage of the time does it reproduce?]
100%
Versions
Lean (version 4.0.0-nightly-2023-01-16, commit 5349a08, Release)
Additional Information
Zulip discussion here.
The text was updated successfully, but these errors were encountered: