Zulip Chat Archive

Stream: lean4

Topic: fastest tactic for proving Nat literal inequalities


Gavin Zhao (Aug 06 2025 at 05:07):

What's the fastest tactic for proving Nat literal inequalities like 3 < 5, 159 < 300, etc? You can assume literals don't exceed three digits. I've been using simp but I'm curious if I can make it even faster.

The context is for bounds check proofs for array indexing. For type-checking performance, I have a custom syntax for array indexing where both the size and the index are literals, e.g. Main : Vector Nat 355 and I access Main[42]$.

syntax:max term noWs "[" withoutPosition(term) "]$" : term
macro_rules | `($x[$i]$) => `(getElem $x $i (by simp))

I currently have simp in place, but for functions with many array index operations (e.g. I have a parameter Main : Vector Nat 251 and there are 752 constant index operations on Main) I still need to adjust maxHeartbeats. I wonder if this can be even faster.

btw I'm not using the default a[i]syntax because the time complexity of get_elem_tactic is quadratic w.r.t. the size of the context, due to the use of assumption.

Gavin Zhao (Aug 06 2025 at 05:10):

Truely a "Lean at scale" moment :sweat_smile:

Kenny Lau (Aug 06 2025 at 08:36):

mathlib's norm_num

Kenny Lau (Aug 06 2025 at 08:36):

by decide might also be fast

Robin Arnez (Aug 06 2025 at 10:15):

decide is probably faster than norm_num

Michael Rothgang (Aug 06 2025 at 10:20):

omega might not be faster, but it's my standard tactic for natural number or integer arithmetic.

Robin Arnez (Aug 06 2025 at 11:56):

When you use a[i], it should already use decide though

Robin Arnez (Aug 06 2025 at 11:56):

and simp +arith when that fails

Robin Arnez (Aug 06 2025 at 11:57):

Oh it's because decide comes too late...

Robin Arnez (Aug 06 2025 at 11:58):

Wait no decide comes before assumption

Robin Arnez (Aug 06 2025 at 11:59):

Oh but get_elem_tactic puts assumption before decide

Gavin Zhao (Aug 06 2025 at 17:46):

Robin Arnez said:

Oh but get_elem_tactic puts assumption before decide

Yeah that was the biggest issue for me. I understand the rationale for putting assumption first but when your context gets large with 600 lets this doesn't scale.

Robin Arnez (Aug 06 2025 at 17:55):

Can't you split up what ever you have into multiple functions..?

Robin Arnez (Aug 06 2025 at 17:56):

You're probably not using all 600 lets at the same time, are you?

Gavin Zhao (Aug 06 2025 at 18:04):

You're probably not using all 600 lets at the same time, are you?

Well actually yes.

Kyle Miller (Aug 06 2025 at 18:06):

Are you able to create some self-contained test case for this @Gavin Zhao? It might be worth creating a Lean issue pointing out these performance issues with index notation.

Gavin Zhao (Aug 06 2025 at 18:09):

@Kyle Miller Not yet but I'll give it a try. This issues comes from some company code that will be open-sourced later but right now I don't think I can share the function that has the performance issue.

Devon Tuma (Aug 06 2025 at 20:49):

Seems like

macro_rules
  | `(tactic| get_elem_tactic) => `(tactic| (decide +kernel); get_elem_tactic; done)

provides similar speedup. Doesn't work with get_elem_tactic_extensible though because of the previously mentioned assumption tactic taking priority

Robin Arnez (Aug 06 2025 at 20:56):

decide is a terminal tactic so get_elem_tactic; done doesn't do anything though

Devon Tuma (Aug 06 2025 at 20:57):

Yeah I've just seen that any other different cases now break

Robin Arnez (Aug 06 2025 at 22:12):

Really? This still seems to work:

macro_rules
  | `(tactic| get_elem_tactic) => `(tactic| decide)

example (h : a  3) : a  4 := by
  get_elem_tactic

Devon Tuma (Aug 06 2025 at 23:53):

I've been looking and I think it seems like it was actually a problem with decide not terminating in time in some edge cases we have, not a global thing. Adding norm_num1 seems to give similar speedups without that being an issue


Last updated: Dec 20 2025 at 21:32 UTC