Skip to content

Commit 70816ae

Browse files
committed
feat(Computability): regular languages are closed under reversal (#21573)
This PR proves that regular languages are closed under reversal. It works by reversing the transitions in the NFA, and proving that the resulting automaton matches the reversed language. The argument proceeds by induction on the input word, ensuring that, at each character, the state sets for the forward and reverse automaton correspond to each other. I also clean up the existing NFA module, which includes making the `M` argument implicit by default. I can revert that if it's controversial. Thank you @Rob23oba for their help with the induction proof.
1 parent 67198de commit 70816ae

File tree

1 file changed

+78
-7
lines changed

1 file changed

+78
-7
lines changed

Mathlib/Computability/NFA.lean

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ open Computability
2323

2424
universe u v
2525

26-
2726
/-- An NFA is a set of states (`σ`), a transition function from state to state labelled by the
2827
alphabet (`step`), a set of starting states (`start`) and a set of acceptance states (`accept`).
2928
Note the transition function sends a state to a `Set` of states. These are the states that it
@@ -36,64 +35,76 @@ structure NFA (α : Type u) (σ : Type v) where
3635
/-- Set of accepting states -/
3736
accept : Set σ
3837

39-
variable {α : Type u} {σ σ' : Type v} (M : NFA α σ)
38+
variable {α : Type u} {σ : Type v} {M : NFA α σ}
4039

4140
namespace NFA
4241

4342
instance : Inhabited (NFA α σ) :=
4443
⟨NFA.mk (fun _ _ => ∅) ∅ ∅⟩
4544

45+
variable (M) in
4646
/-- `M.stepSet S a` is the union of `M.step s a` for all `s ∈ S`. -/
4747
def stepSet (S : Set σ) (a : α) : Set σ :=
4848
⋃ s ∈ S, M.step s a
4949

50-
theorem mem_stepSet (s : σ) (S : Set σ) (a : α) : s ∈ M.stepSet S a ↔ ∃ t ∈ S, s ∈ M.step t a := by
50+
theorem mem_stepSet {s : σ} {S : Set σ} {a : α} : s ∈ M.stepSet S a ↔ ∃ t ∈ S, s ∈ M.step t a := by
5151
simp [stepSet]
5252

53+
variable (M) in
5354
@[simp]
5455
theorem stepSet_empty (a : α) : M.stepSet ∅ a = ∅ := by simp [stepSet]
5556

57+
variable (M) in
5658
/-- `M.evalFrom S x` computes all possible paths through `M` with input `x` starting at an element
5759
of `S`. -/
58-
def evalFrom (start : Set σ) : List α → Set σ :=
59-
List.foldl M.stepSet start
60+
def evalFrom (S : Set σ) : List α → Set σ :=
61+
List.foldl M.stepSet S
6062

63+
variable (M) in
6164
@[simp]
6265
theorem evalFrom_nil (S : Set σ) : M.evalFrom S [] = S :=
6366
rfl
6467

68+
variable (M) in
6569
@[simp]
6670
theorem evalFrom_singleton (S : Set σ) (a : α) : M.evalFrom S [a] = M.stepSet S a :=
6771
rfl
6872

73+
variable (M) in
6974
@[simp]
7075
theorem evalFrom_append_singleton (S : Set σ) (x : List α) (a : α) :
7176
M.evalFrom S (x ++ [a]) = M.stepSet (M.evalFrom S x) a := by
7277
simp only [evalFrom, List.foldl_append, List.foldl_cons, List.foldl_nil]
7378

79+
variable (M) in
7480
/-- `M.eval x` computes all possible paths though `M` with input `x` starting at an element of
7581
`M.start`. -/
7682
def eval : List α → Set σ :=
7783
M.evalFrom M.start
7884

85+
variable (M) in
7986
@[simp]
8087
theorem eval_nil : M.eval [] = M.start :=
8188
rfl
8289

90+
variable (M) in
8391
@[simp]
8492
theorem eval_singleton (a : α) : M.eval [a] = M.stepSet M.start a :=
8593
rfl
8694

95+
variable (M) in
8796
@[simp]
8897
theorem eval_append_singleton (x : List α) (a : α) : M.eval (x ++ [a]) = M.stepSet (M.eval x) a :=
89-
evalFrom_append_singleton _ _ _ _
98+
evalFrom_append_singleton ..
9099

100+
variable (M) in
91101
/-- `M.accepts` is the language of `x` such that there is an accept state in `M.eval x`. -/
92102
def accepts : Language α := {x | ∃ S ∈ M.accept, S ∈ M.eval x}
93103

94104
theorem mem_accepts {x : List α} : x ∈ M.accepts ↔ ∃ S ∈ M.accept, S ∈ M.evalFrom M.start x := by
95105
rfl
96106

107+
variable (M) in
97108
/-- `M.toDFA` is a `DFA` constructed from an `NFA` `M` using the subset construction. The
98109
states is the type of `Set`s of `M.state` and the step function is `M.stepSet`. -/
99110
def toDFA : DFA α (Set σ) where
@@ -121,7 +132,7 @@ namespace DFA
121132

122133
/-- `M.toNFA` is an `NFA` constructed from a `DFA` `M` by using the same start and accept
123134
states and a transition function which sends `s` with input `a` to the singleton `M.step s a`. -/
124-
@[simps] def toNFA (M : DFA α σ') : NFA α σ' where
135+
@[simps] def toNFA (M : DFA α σ) : NFA α σ where
125136
step s a := {M.step s a}
126137
start := {M.start}
127138
accept := M.accept
@@ -146,3 +157,63 @@ theorem toNFA_correct (M : DFA α σ) : M.toNFA.accepts = M.accepts := by
146157
· exact fun h => ⟨M.eval x, h, rfl⟩
147158

148159
end DFA
160+
161+
namespace NFA
162+
163+
variable (M) in
164+
/-- `M.reverse` constructs an NFA with the same states as `M`, but all the transitions reversed. The
165+
resulting automaton accepts a word `x` if and only if `M` accepts `List.reverse x`. -/
166+
@[simps]
167+
def reverse : NFA α σ where
168+
step s a := { s' | s ∈ M.step s' a }
169+
start := M.accept
170+
accept := M.start
171+
172+
variable (M) in
173+
@[simp]
174+
theorem reverse_reverse : M.reverse.reverse = M := by
175+
simp [reverse]
176+
177+
theorem disjoint_stepSet_reverse {a : α} {S S' : Set σ} :
178+
Disjoint S (M.reverse.stepSet S' a) ↔ Disjoint S' (M.stepSet S a) := by
179+
rw [← not_iff_not]
180+
simp only [Set.not_disjoint_iff, mem_stepSet, reverse_step, Set.mem_setOf_eq]
181+
tauto
182+
183+
theorem disjoint_evalFrom_reverse {x : List α} {S S' : Set σ}
184+
(h : Disjoint S (M.reverse.evalFrom S' x)) : Disjoint S' (M.evalFrom S x.reverse) := by
185+
simp only [evalFrom, List.foldl_reverse] at h ⊢
186+
induction x generalizing S S' with
187+
| nil =>
188+
rw [disjoint_comm]
189+
exact h
190+
| cons x xs ih =>
191+
rw [List.foldl_cons] at h
192+
rw [List.foldr_cons, ← NFA.disjoint_stepSet_reverse, disjoint_comm]
193+
exact ih h
194+
195+
theorem disjoint_evalFrom_reverse_iff {x : List α} {S S' : Set σ} :
196+
Disjoint S (M.reverse.evalFrom S' x) ↔ Disjoint S' (M.evalFrom S x.reverse) :=
197+
⟨disjoint_evalFrom_reverse, fun h ↦ List.reverse_reverse x ▸ disjoint_evalFrom_reverse h⟩
198+
199+
@[simp]
200+
theorem mem_accepts_reverse {x : List α} : x ∈ M.reverse.accepts ↔ x.reverse ∈ M.accepts := by
201+
simp [mem_accepts, ← Set.not_disjoint_iff, not_iff_not, disjoint_evalFrom_reverse_iff]
202+
203+
end NFA
204+
205+
namespace Language
206+
207+
protected theorem IsRegular.reverse {L : Language α} (h : L.IsRegular) : L.reverse.IsRegular :=
208+
have ⟨σ, _, M, hM⟩ := h
209+
⟨_, inferInstance, M.toNFA.reverse.toDFA, by ext; simp [hM]⟩
210+
211+
protected theorem IsRegular.of_reverse {L : Language α} (h : L.reverse.IsRegular) : L.IsRegular :=
212+
L.reverse_reverse ▸ h.reverse
213+
214+
/-- Regular languages are closed under reversal. -/
215+
@[simp]
216+
theorem isRegular_reverse_iff {L : Language α} : L.reverse.IsRegular ↔ L.IsRegular :=
217+
⟨.of_reverse, .reverse⟩
218+
219+
end Language

0 commit comments

Comments
 (0)