Zulip Chat Archive
Stream: new members
Topic: Termination issue with List function
Bashar Hamade (Jun 23 2024 at 12:16):
I starting a project to formalise CCS and i'm struggling to provide a working definition for the reachability of a list of states in LTS because Lean can't show termination on the arguments :
import Batteries.Data.List.Basic
structure State where
val : String
deriving BEq
structure Action where
val : String
structure LTS where
states : List State
actions : List Action
transition : State → Action → List State
s0 : State
def post_A (lts : LTS) (s : State) (actions : List Action) : List State :=
actions.foldl (fun acc act => acc ++ post lts s act) []
def reach_aux (lts : LTS) (visited : List State) (to_visit : List State) (acc : List State): List State :=
match to_visit with
| [] => acc
| s::ss =>
if visited.indexOf? s == none then
reach_aux lts visited ss acc
else
reach_aux lts (s::visited) ( (post_A lts s lts.actions) ++ ss) ( (post_A lts s lts.actions) ++ acc )
Is there a way to bypass this termination failure issue?
Kim Morrison (Jun 23 2024 at 12:18):
You shouldn't think about "bypassing" termination, but instead explain (informally) why you think it does terminate, and then using termination_by
to tell Lean the decreasing quantity, and then possibly decreasing_by
to proof the proof that that quantity decreases, in the event that automation can't do it already.
Bashar Hamade (Jun 23 2024 at 12:24):
unfortunately I haven't dealt with termination at all in proof assistants.How would I clarify that finding all the reachable states in a LTS (or an automaton,the concept is basically the same) does indeed terminate ?
I know it terminates because i'm just iterating over a finite automaton like I would for a finite graph
Markus Himmel (Jun 23 2024 at 12:53):
I assume that you wanted to write if visited.indexOf? s != none then
(with a !=
instead of a ==
)?
Markus Himmel (Jun 23 2024 at 12:54):
If so, I think that your function doesn't actually terminate, because you don't constrain the transition
function to only return states the are in the states
list, so something like
def badLts : LTS where
states := []
actions := [⟨"a"⟩]
transition s a := [⟨s.1 ++ a.1⟩]
s0 := ⟨""⟩
#eval reach_aux badLts [] [⟨"s"⟩] []
should lead to infinite recursion.
Chris Bailey (Jun 23 2024 at 12:56):
Just as a side note, it is permissible to write and execute recursive functions without showing they terminate using partial def
.
Markus Himmel (Jun 23 2024 at 13:12):
Here is one way to show termination using a fixed version of the LTS
structure:
import Batteries.Data.List.Perm
open List
set_option autoImplicit true
@[ext]
structure State where
val : String
deriving BEq
theorem State.beq_iff {s t : State} : (s == t) ↔ s.val = t.val := by
rw [show s == t ↔ s.val == t.val from Iff.rfl, beq_iff_eq]
instance : LawfulBEq State where
rfl := by simp [State.beq_iff]
eq_of_beq h := State.ext _ _ (State.beq_iff.1 h)
structure Action where
val : String
structure LTS where
states : List State
actions : List Action
transition : State → Action → List { s : State // s ∈ states }
s0 : State
def post_A (lts : LTS) (s : State) (actions : List Action) : List { s : State // s ∈ lts.states } :=
actions.foldl (fun acc act => acc ++ lts.transition s act) []
theorem List.not_mem_of_indexOf?_eq_none [BEq α] [LawfulBEq α] (l : List α) (a : α) (ha : l.indexOf? a = none) : ¬a ∈ l := by
induction l with
| nil => simp [indexOf?]
| cons b t ih =>
simp [indexOf?] at *
split at ha
· simp at ha
· rw [Option.map_eq_none'] at ha
refine ⟨Ne.symm ?_, ih ha⟩
next h =>
rintro rfl
exact h LawfulBEq.rfl
def reach_aux (lts : LTS) (visited : { l : List State // l <+~ lts.states } ) (to_visit : List { s : State // s ∈ lts.states }) (acc : List State): List State :=
match to_visit with
| [] => acc
| s::ss =>
if h : visited.1.indexOf? s.1 != none then
reach_aux lts visited ss acc
else
have hx : (s.1 :: visited.1) <+~ lts.states := by
refine List.cons_subperm_of_not_mem_of_mem ?_ s.2 visited.2
rw [Bool.not_eq_true, bne_eq_false_iff_eq] at h
exact List.not_mem_of_indexOf?_eq_none _ _ h
have : lts.states.length - (visited.1.length + 1) < lts.states.length - visited.1.length := by
refine Nat.sub_add_lt_sub ?_ Nat.zero_lt_one
simpa using hx.length_le
reach_aux lts ⟨s::visited, hx⟩ ( (post_A lts s lts.actions) ++ ss) ( (post_A lts s lts.actions).map Subtype.val ++ acc )
termination_by (lts.states.length - visited.1.length, to_visit.length)
Notification Bot (Jun 23 2024 at 13:45):
7 messages were moved here from #general > Beginner Questions by Patrick Massot.
Patrick Massot (Jun 23 2024 at 13:49):
@Bashar Hamade I moved this discussion that used to be in a six years old thread with a very generic title in the general channel. The new members channel is specifically followed by people who are actively willing to help beginners, so you have a higher probability to get an answer there. Then the more specific thread title helps future people who will have a similar question.
Bashar Hamade (Jun 23 2024 at 16:19):
Markus Himmel said:
Here is one way to show termination using a fixed version of the
LTS
structure:import Batteries.Data.List.Perm open List set_option autoImplicit true @[ext] structure State where val : String deriving BEq theorem State.beq_iff {s t : State} : (s == t) ↔ s.val = t.val := by rw [show s == t ↔ s.val == t.val from Iff.rfl, beq_iff_eq] instance : LawfulBEq State where rfl := by simp [State.beq_iff] eq_of_beq h := State.ext _ _ (State.beq_iff.1 h) structure Action where val : String structure LTS where states : List State actions : List Action transition : State → Action → List { s : State // s ∈ states } s0 : State def post_A (lts : LTS) (s : State) (actions : List Action) : List { s : State // s ∈ lts.states } := actions.foldl (fun acc act => acc ++ lts.transition s act) [] theorem List.not_mem_of_indexOf?_eq_none [BEq α] [LawfulBEq α] (l : List α) (a : α) (ha : l.indexOf? a = none) : ¬a ∈ l := by induction l with | nil => simp [indexOf?] | cons b t ih => simp [indexOf?] at * split at ha · simp at ha · rw [Option.map_eq_none'] at ha refine ⟨Ne.symm ?_, ih ha⟩ next h => rintro rfl exact h LawfulBEq.rfl def reach_aux (lts : LTS) (visited : { l : List State // l <+~ lts.states } ) (to_visit : List { s : State // s ∈ lts.states }) (acc : List State): List State := match to_visit with | [] => acc | s::ss => if h : visited.1.indexOf? s.1 != none then reach_aux lts visited ss acc else have hx : (s.1 :: visited.1) <+~ lts.states := by refine List.cons_subperm_of_not_mem_of_mem ?_ s.2 visited.2 rw [Bool.not_eq_true, bne_eq_false_iff_eq] at h exact List.not_mem_of_indexOf?_eq_none _ _ h have : lts.states.length - (visited.1.length + 1) < lts.states.length - visited.1.length := by refine Nat.sub_add_lt_sub ?_ Nat.zero_lt_one simpa using hx.length_le reach_aux lts ⟨s::visited, hx⟩ ( (post_A lts s lts.actions) ++ ss) ( (post_A lts s lts.actions).map Subtype.val ++ acc ) termination_by (lts.states.length - visited.1.length, to_visit.length)
Thanks a lot for the provided code :) . this auxiliary function is to be used in another main reach function that starts with an empty list for 'visited' ,however that would lead to a type mismatch error as the case in this function:
def reach {lts : LTS} (initial_states : List { s : State // s ∈ lts.states }) : List { s : State // s ∈ lts.states } :=
reach_aux lts [] initial_states []
Bashar Hamade (Jun 23 2024 at 16:20):
can I specify an empty list while specifying the exact type it should return to fix the problem?
Bashar Hamade (Jun 23 2024 at 16:21):
also how can I abbreviate { s : State // s ∈ lts.states },I've been repeating it in all my definitions and it gets too tedious quickly
Markus Himmel (Jun 23 2024 at 17:14):
Bashar Hamade said:
can I specify an empty list while specifying the exact type it should return to fix the problem?
The parameter (visited : { l : List State // l <+~ lts.states } )
is a pair of a list and a proof that the list is a subpermutation (docs#List.Subperm) of the states, so the correct invocation would be
def reach {lts : LTS} (initial_states : List { s : State // s ∈ lts.states }) : List { s : State // s ∈ lts.states } :=
reach_aux lts ⟨[], nil_subperm⟩ initial_states []
Markus Himmel (Jun 23 2024 at 17:14):
Bashar Hamade said:
also how can I abbreviate { s : State // s ∈ lts.states },I've been repeating it in all my definitions and it gets too tedious quickly
You could define something like
def ValidState (lts : LTS) :=
{ s : State // s ∈ lts.states }
Bashar Hamade (Jun 23 2024 at 17:29):
perfect.Thanks a lot for your help :)
Last updated: May 02 2025 at 03:31 UTC