Zulip Chat Archive
Stream: mathlib4
Topic: lots of linarith
Matthew Ballard (Jan 09 2024 at 18:46):
The new leader in instructions is now CategoryTheory.ComposableArrows
and the load seems to be mainly due to the proliferation of by linarith
auto-params.
#9594 uses a macro which tries assumption
before linarith
and decreases the time by about 15%.
Is there more light-weight tactic for Nat
inequalities?
Heather Macbeth (Jan 09 2024 at 18:50):
This might be a great place to compare performance of linarith
and omega
?
Matthew Ballard (Jan 09 2024 at 18:52):
Where does omega
live?
Heather Macbeth (Jan 09 2024 at 18:53):
https://github.com/leanprover/std4/blob/main/Std/Tactic/Omega.lean
Johan Commelin (Jan 09 2024 at 18:54):
Yes, in my work on showing that the nerve is a quasicategory, I used omega
instead. It seems to work quite well.
Johan Commelin (Jan 09 2024 at 18:55):
But I haven't yet tested the change in mathlib. Curious what will happen!
Matthew Ballard (Jan 09 2024 at 18:56):
Lots of errors and warnings. Let me make sure I am not doing something wrong
Matthew Ballard (Jan 09 2024 at 18:58):
`(tactic| first | assumption | omega | linarith)
cuts things by 50-60%
Matthew Ballard (Jan 09 2024 at 18:59):
Oh wait.
Matthew Ballard (Jan 09 2024 at 18:59):
I hit a timeout which might have cut the testing short
Matthew Ballard (Jan 09 2024 at 19:02):
No, cut by 50% after restoring the code generation in the file. Oddly I needed to bump heartbeats on a new declaration
Matthew Ballard (Jan 09 2024 at 19:06):
Both the heartbeat bumps involve aesop
as the auto-param, which then calls this auto-param multiple times
Heather Macbeth (Jan 09 2024 at 19:06):
Is there anything which omega
doesn't solve?
Matthew Ballard (Jan 09 2024 at 19:06):
Strict inequality?
Matthew Ballard (Jan 09 2024 at 19:07):
I got a lot of errors with just omega
as the auto-param. A quick glance made it seem that those were the majority of the problems
Heather Macbeth (Jan 09 2024 at 19:08):
Huh, I thought strict inequalities were in scope.
Matthew Ballard (Jan 09 2024 at 19:08):
I pushed the changes to the PR. Feel free to play with it. There is a docBlame
complaint about the macro that I cannot for the life of me fix. I must be doing something silly
Heather Macbeth (Jan 09 2024 at 19:09):
About to hop on a plane, so only backseat driving from me today :)
Matthew Ballard (Jan 09 2024 at 19:09):
Free wifi on Delta :)
Matthew Ballard (Jan 09 2024 at 19:10):
Odds are nontrivial that I am doing something silly though
Matthew Ballard (Jan 09 2024 at 19:19):
omega
doesn't like auto-params
Matthew Ballard (Jan 09 2024 at 19:27):
import Std.Tactic.Omega
example (i n : Nat) (hi : i ≤ n := by omega) : i < n + 1 := by omega
/-
error:
omega did not find a contradiction:
[1, -1] ∈ [1, ∞)
[0, 1] ∈ [0, ∞)
[1] ∈ [0, ∞)
-/
Heather Macbeth (Jan 09 2024 at 19:37):
Maybe this is std4#510 again
Matthew Ballard (Jan 09 2024 at 19:37):
Do we have to do some special incantation to docs#Lean.Meta.getLocalHyps in the presence of auto-params?
Matthew Ballard (Jan 09 2024 at 19:37):
Yeah that looks correct
Matthew Ballard (Jan 09 2024 at 20:13):
Huh. It seems like std4#510 doesn't fix this.
Eric Rodriguez (Jan 09 2024 at 20:28):
Aren't autoParams a different type as oppposed to just metadata?
Eric Rodriguez (Jan 09 2024 at 20:28):
It may need special support
Matthew Ballard (Jan 09 2024 at 20:29):
Maybe. I was just pretending
Matthew Ballard (Jan 09 2024 at 21:09):
This hacky thing makes the example work
import Std.Tactic.Omega
open Lean Elab Tactic in
elab "clean_up" : tactic =>
withMainContext do
let ctx ← Lean.MonadLCtx.getLCtx
ctx.forM fun decl: Lean.LocalDecl => do
let declExpr := decl.toExpr
let declType := decl.type.cleanupAnnotations
liftMetaTactic fun mvarId => do
let mvarIdNew ← mvarId.assert (←mkFreshId) declType declExpr
let (_, mvarIdNew) ← mvarIdNew.intro1P
return [mvarIdNew]
example (i n : Nat) (hi : i ≤ n := by omega) : i < n + 1 := by
clean_up
omega
Johan Commelin (Jan 09 2024 at 21:11):
cc @Scott Morrison I guess...
Matthew Ballard (Jan 09 2024 at 21:12):
With a little bit of omega
we are speeding up
Benchmark Metric Change
==============================================================================
+ build tactic execution -7.8%
+ ~Mathlib.Algebra.Homology.ExactSequence instructions -10.0%
+ ~Mathlib.CategoryTheory.Abelian.DiagramLemmas.Four instructions -69.8%
+ ~Mathlib.CategoryTheory.ComposableArrows instructions -46.3%
Matthew Ballard (Jan 09 2024 at 21:12):
Using the current fallback to linarith
implementation
Matthew Ballard (Jan 09 2024 at 21:13):
Wall clock is -3% also
Geoffrey Irving (Jan 09 2024 at 21:13):
I'm curious watching this thread: why would omega
be faster than linarith
? Isn't it way more complicated?
Matthew Ballard (Jan 09 2024 at 21:14):
All I know was that I was told to use omega
so I wouldn't mind being enlightened also
Joachim Breitner (Jan 09 2024 at 21:20):
But parts of the complications are put there to make it fast :-)
Josha Dekker (Jan 09 2024 at 21:20):
I don’t want to sidetrack the conversation, but the reason why we don’t do:
- run linarith
- extract the necessary proof steps, use them directly
is that the number of lines required for the proof would be large, right? Would there be some other way around this? E.g. use a companion-file to keep track of long proofs extracted from linarith for a given file? I see the problems with that approach as well, I’m just curious whether there are some thoughts on possible solutions for this?
Geoffrey Irving (Jan 09 2024 at 21:21):
Joachim Breitner said:
But parts of the complications are put there to make it fast :-)
Sure, but doesn't that imply that linarith
could be sped up as well?
Matthew Ballard (Jan 09 2024 at 21:23):
Joachim Breitner said:
But parts of the complications are put there to make it fast :-)
Myles Garrett driven design
Geoffrey Irving (Jan 09 2024 at 21:31):
If we moved linarith
to std
, would it also get way faster? :)
Mario Carneiro (Jan 09 2024 at 22:16):
Geoffrey Irving said:
I'm curious watching this thread: why would
omega
be faster thanlinarith
? Isn't it way more complicated?
omega
is not using norm_num
, which means that all the numeric literals have to be literals. It is written in std
which means that it lacks a lot of the features from mathlib tactics, but that also makes it faster
Mario Carneiro (Jan 09 2024 at 22:18):
It also does not understand anything about the mathlib algebraic hierarchy, which it can get away with because it only works on Nat
and Int
where the operations are known. So that means no typeclass search which is big
Patrick Massot (Jan 09 2024 at 22:56):
Josha Dekker said:
I don’t want to sidetrack the conversation, but the reason why we don’t do:
- run linarith
- extract the necessary proof steps, use them directly
In principle it would output a linear_combination
call, but this require work (and linear_combination
is still only for equalities so far).
Heather Macbeth (Jan 09 2024 at 23:08):
... which someone should fix
Eric Rodriguez (Jan 09 2024 at 23:23):
I was talking with some people about making better use of certificate-based proofs in mathlib, as I think a lot of people already love polyrith
and extending those methods would be incredible :)
Heather Macbeth (Jan 09 2024 at 23:24):
Rob pointed out to me a while ago that linarith
already constructs the thing that could be the certificate, we just don't have the linear-combination-for-inequalities tactic to record it
Rob Lewis (Jan 09 2024 at 23:34):
Extending linear_combination
to handle inequalities is definitely a known tactic improvement that wouldn't be crazy hard. It's waiting for someone with the time to do it. linarith
should then factor through (the backend of) this implementation
Kim Morrison (Jan 10 2024 at 00:07):
omega
is also often faster because it can take shortcuts that linarith
can't. In particular, because it works over the integers whenever it notices a common factor in coefficients, it gets to divide through, rounding constant terms in the "favourable" direction. Often this gives a contradiction when linarith
would still be dealing with rationals.
Kim Morrison (Jan 10 2024 at 00:09):
Separately, the implementation of omega
tries to short-circuit early wherever possible. I suspect that if you add an "easy" contradiction e.g. x + y < 1 && x + y > 1
to a large and difficult problem then omega
will reliably be fast but linarith
will sometimes still "do the work". In practice this makes a big difference, so most problems are actually easy, and the only difficult is avoiding working on the irrelevant facts too early.
Kim Morrison (Jan 10 2024 at 00:10):
@Matthew Ballard, if you are finding first | omega | linarith
is necessary in this file, I would love to have the list of examples where omega
is failing.
Matthew Ballard (Jan 10 2024 at 00:12):
I added one to std4#510 - it is auto/opt params
Matthew Ballard (Jan 10 2024 at 00:13):
I have a fix in #9594 (also above) which cleans those up and adds them to the context before calling omega
Matthew Ballard (Jan 10 2024 at 00:15):
I am sure there is a more idiomatic way to strip those out from the hypotheses in the context :)
Kim Morrison (Jan 10 2024 at 00:18):
Matthew Ballard (Jan 10 2024 at 01:24):
Using std4#518 to fully swap out
Benchmark Metric Change
=============================================================================================
+ build tactic execution -8.8%
+ ~Mathlib.Algebra.Homology.ExactSequence instructions -66.9%
+ ~Mathlib.CategoryTheory.Abelian.DiagramLemmas.Four instructions -75.9%
+ ~Mathlib.CategoryTheory.ComposableArrows instructions -53.4%
- ~Mathlib.CategoryTheory.Limits.FilteredColimitCommutesFiniteLimit instructions 19.5%
with -3% build wall-clock
Kim Morrison (Jan 10 2024 at 01:25):
That's over all of Mathlib?!
Matthew Ballard (Jan 10 2024 at 01:26):
Yes #9594
Kim Morrison (Jan 10 2024 at 01:26):
Nice!
Kim Morrison (Jan 10 2024 at 01:26):
Do you know what the deal is in FilteredColimitCommutesFiniteLimit?
Matthew Ballard (Jan 10 2024 at 01:27):
No idea
Mario Carneiro (Jan 10 2024 at 01:36):
It would be great to have a more targeted head-to-head demonstrating linarith
being slower than omega
, that smells like some missing optimization work
Matthew Ballard (Jan 10 2024 at 01:39):
I imagine you can specialize ComposableArrows
to Type u
Mario Carneiro (Jan 10 2024 at 01:39):
I definitely second Scott's observation that linarith needs to get better at handling easy cases; I see people using linarith
so often to prove really silly things like 2 + 2 = 4
Mario Carneiro (Jan 10 2024 at 01:39):
I mean a microbenchmark, a single problem rather than a whole file
Matthew Ballard (Jan 10 2024 at 01:39):
OK
Rob Lewis (Jan 10 2024 at 14:46):
Improving zify
would be a huge help on its own. This takes .26 sec over Nat
and .15 over Int
. (Compare to .07 for omega
, and negligible time for norm_num
.)
example : (100:ℤ)*100 = 10000 := by
linarith
Eric Rodriguez (Jan 10 2024 at 15:08):
oh, linarith only works on Nat/Int because of zify
?
Rob Lewis (Jan 10 2024 at 15:12):
linarith
needs subtraction, so it only works on Nat
because of zify
. Support for Int
is native but the algorithm isn't complete in this setting.
Matthew Ballard (Jan 10 2024 at 15:14):
import Std.Tactic.Omega
import Mathlib.Tactic.Linarith
set_option profiler.threshold 1 in
set_option profiler true in
example (i j n : Nat) (hij : i ≤ j) (hj : j ≤ n) : i ≤ n := by omega
/-
tactic execution of Std.Tactic.Omega.omegaSyntax took 11.7ms
type checking took 9.1ms
-/
set_option profiler.threshold 1 in
set_option profiler true in
example (i j n : Nat) (hij : i ≤ j) (hj : j ≤ n) : i ≤ n := by linarith
/-
simp took 1.53ms
simp took 1.32ms
simp took 1.52ms
typeclass inference of CovariantClass took 2.16ms
typeclass inference of NeZero took 1.58ms
ring took 4.8ms
typeclass inference of CovariantClass took 2.88ms
tactic execution of linarith took 23.1ms
type checking took 3.82ms
-/
There were lots of things like this in ComposableArrows
Eric Rodriguez (Jan 10 2024 at 15:21):
Rob Lewis said:
linarith
needs subtraction, so it only works onNat
because ofzify
. Support forInt
is native but the algorithm isn't complete in this setting.
could this all be replaced with qify/rify
?
Rob Lewis (Jan 10 2024 at 15:21):
In that example too, linarith
spends half its time in zify
. It's still not as fast as omega
but much closer
Matthew Ballard (Jan 10 2024 at 15:23):
The overall drop in instructions across the file was a little over a half from the swap
Rob Lewis (Jan 10 2024 at 15:24):
Eric Rodriguez said:
Rob Lewis said:
linarith
needs subtraction, so it only works onNat
because ofzify
. Support forInt
is native but the algorithm isn't complete in this setting.could this all be replaced with
qify/rify
?
My impression is that zify
does extra things, in particular to try to make sense of nat subtraction when possible. And you don't want to translate nat problems to rat for linarith, because it has some tricks that only apply on int
Matthew Ballard (Jan 10 2024 at 15:34):
One could probably shrink this file down to nothing with a more simple-minded tactic that just tried using reflexivity and transitivity.
Eric Rodriguez (Jan 10 2024 at 15:40):
what tricks are those? taking advantage of stuff like 1 < x is the same as 2 ≤ x? I should hope rify
and qify
deal with nat-subtraction too.
Rob Lewis (Jan 10 2024 at 19:08):
what tricks are those? taking advantage of stuff like 1 < x is the same as 2 ≤ x?
Yes, exactly.
Rob Lewis (Jan 10 2024 at 19:09):
You're right, rify
does do conditional simplification. I don't know why the implementations of rify
and zify
are different. rify
seems to be much slower, compare:
import Std.Tactic.Omega
import Mathlib.Tactic.Linarith
import Mathlib.Tactic.Rify
set_option profiler.threshold 1 in
set_option profiler true in
example (n : ℕ) (hn : 1 < n) : 0 < n - 1 := by
-- rify [hn]
zify [hn]
Kim Morrison (Jan 10 2024 at 22:49):
Obviously omega
has to do something equivalent to zify
, as well. My initial draft of the frontend for omega
in fact did use zify
, but it was obviously too slow. One obstacle is simply that zify
uses the simplifier, and will traverse deep subexpressions in irrelevant hypotheses looking for things to do. The omega
frontend instead inspects hypotheses structurally from the top down, stopping as soon as it reaches a subexpression which is not "in scope".
Yaël Dillies (Jan 11 2024 at 07:23):
So we could "just" do that in zify
too?
Kim Morrison (Jan 11 2024 at 07:24):
Quite likely!
Last updated: May 02 2025 at 03:31 UTC