Conversation
Added regex_of_dfa' to avoid equiv with Fin n
…into IsRegularIffRegex
I personally prefer to have the list of lemmas given to grind in order of application. I have not seen adding a whole by tactic into grind before. I think it might be too much golfing
RegularExpresions.lean has been saved in SummerResearch2026
ctchou
left a comment
There was a problem hiding this comment.
This is the first batch of my comments. I will have more in the future.
My general point is that by defining FLTS.execution, you can express directly the concepts in the textbook proof, rather than using bespoke recursive definitions. This allows you to leverage the large number of results about lists in mathlib.
ctchou
left a comment
There was a problem hiding this comment.
I think you can benefit from having the following functions about List:
namespace List
variable {α : Type*}
def AllButFirstLast (p : α → Bool) (as : List α) : Prop :=
∀ i, ∀ _ : 0 < i ∧ i + 1 < as.length, p as[i]
def findIdxButFirst (p : α → Bool) (as : List α) : ℕ :=
as.tail.findIdx p + 1
def revFindIdxButLast (p : α → Bool) (as : List α) : ℕ :=
as.length - as.reverse.findIdxButFirst p
end List
Then not only a lot of your definitions can be simplified (see the inline comments), but you can state and prove properties about the above List functions without distractions.
I have not looked into the proofs in detail.
| /-- A Bounded Path (`BddPathLTS`) has states `Fin n` and accepts strings (lists of symbols) | ||
| starting with state `start` and ending with state `last` | ||
| with the interior states less than `bound`. -/ | ||
| structure BddPathLTS (n : ℕ) (Symbol : Type*) extends LTS (Fin n) Symbol where | ||
| /-- The start state of the path. -/ | ||
| start : Fin n | ||
| /-- The last state of the path. -/ | ||
| last : Fin n | ||
| /-- The bound for interior states of the path. -/ | ||
| bound : ℕ | ||
|
|
||
| instance : Acceptor (BddPathLTS n Symbol) Symbol where | ||
| Accepts (p : BddPathLTS n Symbol) (xs : List Symbol) := | ||
| ∃ ss : List (Fin n), | ||
| p.toLTS.Execution p.start xs p.last ss ∧ | ||
| ∀ idx, ∀ _ : 0 < idx ∧ idx + 1 < ss.length, ss[idx] < p.bound | ||
| -- ∀ s ∈ ss.tail.dropLast, s < p.bound | ||
|
|
||
| theorem language_bddpath_eq_nfa (lts : LTS (Fin n) Symbol) (i j : Fin n) {k : ℕ} (hk : n ≤ k) : | ||
| language (BddPathLTS.mk lts i j k) = | ||
| language (NA.FinAcc.mk {Tr := lts.Tr, start := {i}} {j}) := by | ||
| simp [language, Accepts] | ||
| grind |
There was a problem hiding this comment.
I think this whole block of code can be replaced by:
namespace Cslib.LTS
variable {Symbol : Type*} {n : ℕ}
def BddExec (lts : LTS (Fin n) Symbol) (bound : ℕ)
(start : Fin n) (xs : List Symbol) (last : Fin n) (ss : List (Fin n)) : Prop :=
lts.Execution start xs last ss ∧ ss.AllButFirstLast (· < bound)
def bddLang (lts : LTS (Fin n) Symbol) (bound : ℕ) (start last : Fin n) : Language Symbol :=
{ xs | ∃ ss, lts.BddExec bound start xs last ss }
end Cslib.LTS
Note that your main proof will refer to different starts, lasts, and bounds in the same statement, so it is better to have them as explicit parameters rather than as fields of structures which you need to construct and destruct all the time.
| Accepts (p : BddPathLTS n Symbol) (xs : List Symbol) := | ||
| ∃ ss : List (Fin n), | ||
| p.toLTS.Execution p.start xs p.last ss ∧ | ||
| ∀ idx, ∀ _ : 0 < idx ∧ idx + 1 < ss.length, ss[idx] < p.bound |
There was a problem hiding this comment.
Just a nitpick: use i instead of idx. I understand that you want to use i, j, k etc to denote states from Fin n. But I think it is clearer to keep denoting the latter using s, t, r, etc.
| /-- Starting at state `i`, the function `splitFirst` sends a string to its shortest prefix | ||
| ending at state `k`. -/ | ||
| def splitFirst {lts : LTS (Fin n) Symbol} {i j : Fin n} {xs : List Symbol} {ss : List (Fin n)} | ||
| (_ : LTS.Execution lts i xs j ss) (k : Fin n) : List Symbol := | ||
| xs.take (ss.tail.findIdx (· = k) + 1) | ||
|
|
||
| /-- Starting at state `i`, the function `splitFirstCompl` sends a string to its longest suffix | ||
| starting at state `k`. -/ | ||
| def splitFirstCompl {lts : LTS (Fin n) Symbol} {i j : Fin n} {xs : List Symbol} {ss : List (Fin n)} | ||
| (_ : LTS.Execution lts i xs j ss) (k : Fin n) : List Symbol := | ||
| xs.drop (ss.tail.findIdx (· = k) + 1) |
There was a problem hiding this comment.
It is in general a good idea to include only those parameters which are actually needed:
def splitFirst (xs : List Symbol) (ss : List (Fin n)) (k : Fin n) : List Symbol :=
xs.take (ss.findIdxButFirst (· = k))
def splitFirstCompl (xs : List Symbol) (ss : List (Fin n)) (k : Fin n) : List Symbol :=
xs.drop (ss.findIdxButFirst (· = k))
The same comment applies to SplitLast[Compl] above.
|
|
||
| open List | ||
|
|
||
| section splitLast |
There was a problem hiding this comment.
I think it is probably better to put the splitLast section after the splitFirst section, because the latter seems easier to reason about than the former.
We introduce
IsRegular.iff_regex, a language is regular if and only if it matches a regular expression. It is a combination of the existingIsRegular.regex, a language matching a regular expression is regular and our new direction. The new direction is proven through Kleene’s algorithm.It is the result that was anticipated in PR#846 by @ctchou.
The pull request introduces a new file,
KleeneAlgorithm.lean. In it, we proved a language defined by a DFA onFin nwith one accepting state has a matching regular expression. The algorithm applies induction on a bound which restricts which interior states that a run may pass through.Implemented collaboratively by Brooke Gill and Chi-Yun Hsu