Zulip Chat Archive
Stream: lean4
Topic: proof termination
Notification Bot (May 13 2025 at 11:01):
Alexandre Rademaker has marked this topic as unresolved.
Alexandre Rademaker (May 13 2025 at 11:02):
One more proof of termination that I can't finish
def search₂ (f : Nat → Nat) (t : Nat) : List Nat :=
let rec seek (a b : Nat) : List Nat :=
dbg_trace "seek {a} {b}"
let m := (a + b) / 2
if h₁ : a > b then []
else if h₂ : t = f m then [m]
else if h₃ : t < f m then
seek a (m - 1)
else
seek (m + 1) b
termination_by (b - a)
seek 0 t
I have tried many possible variations in the termination_by clause.
EDITED: I was able to obtain a termination with the following code. But I wonder if I can avoid the duplicated test of t = f m
def search₂ (f : Nat → Nat) (t : Nat) : List Nat :=
let rec seek (a b : Nat) : List Nat :=
let m := (a + b) / 2
if h₁ : a ≥ b then
if t = f m then [m] else []
else if h₂ : t = f m then [m]
else if h₃ : t < f m then
seek a (m - 1)
else
seek (m + 1) b
termination_by (b - a)
seek 0 t
Joachim Breitner (May 13 2025 at 12:50):
The first code works with a better termination tactic:
def search₂ (f : Nat → Nat) (t : Nat) : List Nat :=
let rec seek (a b : Nat) : List Nat :=
dbg_trace "seek {a} {b}"
let m := (a + b) / 2
if h₁ : a > b then []
else if h₂ : t = f m then [m]
else if h₃ : t < f m then
seek a (m - 1)
else
seek (m + 1) b
termination_by (b - a)
decreasing_by grind
seek 0 t
So once grind is done and ready for production, it’ll just work.
Johannes Tantow (May 13 2025 at 13:52):
Joachim Breitner schrieb:
The first code works with a better termination tactic:
def search₂ (f : Nat → Nat) (t : Nat) : List Nat := let rec seek (a b : Nat) : List Nat := dbg_trace "seek {a} {b}" let m := (a + b) / 2 if h₁ : a > b then [] else if h₂ : t = f m then [m] else if h₃ : t < f m then seek a (m - 1) else seek (m + 1) b termination_by (b - a) decreasing_by grind seek 0 tSo once
grindis done and ready for production, it’ll just work.
Really? What if f is a constant function larger than t. Than we will always decrease b while a stays zero.
native_decide will overflow in the first case as well, but not in the second case.
Joachim Breitner (May 13 2025 at 13:55):
Sorry, my bad. The “The grind tactic is experimental and still under development. Avoid using it in production projects.” message obscured the fact that the tactic failed.
So whatever Johannes says :-)
Alexandre Rademaker (May 13 2025 at 15:23):
Thank you both for the feedback. The final code I presented above works with some redundant tests. I can finish with an empty list in two cases and return with a singleton list in two cases. Anyway, if I have the proof that h₁: ¬a = b so termination is proved for all recursive calls.
But what is unexpected is that grind didn't work in this code!
def search₂ (f : Nat → Nat) (t : Nat) : List Nat :=
let rec seek (a b : Nat) : List Nat :=
let m := (a + b) / 2
if h₁ : a = b then
if t = f m then [m] else []
else if h₉ : a > b then []
else if h₂ : t = f m then [m]
else if h₃ : t < f m then
seek a (m - 1)
else
seek (m + 1) b
termination_by (b - a)
seek 0 t
I could also have kept the previous version with h₁ : a ≥ b, a single test for the two cases above, but I am not sure if I want to eventually return a value outside of the initial range (a,b) when a > b.
Last updated: Dec 20 2025 at 21:32 UTC