import Mathlib import Langlib.Utilities.Homomorphism import Langlib.Classes.Regular.Closure.Concatenation import Langlib.Classes.Regular.Closure.Star import Langlib.Classes.Regular.Closure.Substitution /-! # Regular Closure Under (String) Homomorphism This file proves that regular languages are closed under symbol maps (letter-to-letter homomorphisms) and, more generally, under string homomorphisms. ## Main results - `Language.IsRegular.map` — if `L` is regular then `Language.map f L` is regular. - `Language.IsRegular.homomorphicImage` — if `L` is regular (over a finite alphabet) then its image under a string homomorphism is regular. -/ namespace Language open Classical variable {α β : Type*} section SymbolMap variable {σ : Type*} [Fintype σ] /-- NFA recognising the image of a DFA language under a symbol map. For a DFA `M` over `α` and a function `f : α → β`, the NFA over `β` has the same states. On symbol `b`, from state `q`, the NFA non-deterministically guesses a preimage `a` with `f a = b` and transitions to `M.step q a`. -/ noncomputable def mapNFA (M : DFA α σ) (f : α → β) : NFA β σ where step := fun q b => { q' | ∃ a, f a = b ∧ M.step q a = q' } start := {M.start} accept := M.accept variable (M : DFA α σ) (f : α → β) /- The map-NFA evaluates singleton sets correctly: from `{q}` on word `List.map f v`, the result contains `M.evalFrom q v`. -/ private lemma mapNFA_evalFrom_map (q : σ) (v : List α) : M.evalFrom q v ∈ (mapNFA M f).evalFrom {q} (List.map f v) := by induction' v using List.reverseRecOn with v a ih generalizing q <;> simp_all +decide [ NFA.evalFrom_append ]; exact Set.mem_biUnion ( ih q ) ⟨ a, rfl, rfl ⟩ /- If `q'` is in the NFA evaluation from `{q}` on `w`, then there exists a word `v` with `List.map f v = w` and `M.evalFrom q v = q'`. -/ private lemma mapNFA_evalFrom_inv (q q' : σ) (w : List β) (h : q' ∈ (mapNFA M f).evalFrom {q} w) : ∃ v, List.map f v = w ∧ M.evalFrom q v = q' := by revert h; induction' w with b w ih generalizing q q' <;> simp +decide [ *, NFA.evalFrom_cons ]; · exact fun h => h.symm; · intro h obtain ⟨q'', hq'', hq'⟩ : ∃ q'' ∈ (mapNFA M f).step q b, q' ∈ (mapNFA M f).evalFrom {q''} w := by contrapose! h; have h_foldl_stepSet : ∀ (S : Set σ) (w : List β), (mapNFA M f).evalFrom S w = ⋃ q'' ∈ S, (mapNFA M f).evalFrom {q''} w := by intro S w; induction' w using List.reverseRecOn with w ih <;> simp +decide [ *, NFA.evalFrom_cons ] ; simp +decide [ Set.ext_iff, NFA.stepSet ]; rw [ h_foldl_stepSet ] ; simp +contextual [ h ]; obtain ⟨ v, hv₁, hv₂ ⟩ := ih q'' q' hq'; obtain ⟨ a, ha₁, ha₂ ⟩ := hq''; use a :: v; aesop; /- The map-NFA accepts exactly the image of the DFA language under `List.map f`. -/ theorem mapNFA_correct : (mapNFA M f).accepts = Language.map f M.accepts := by ext w; constructor; · rintro ⟨ S, hS₁, hS₂ ⟩; obtain ⟨ v, hv₁, hv₂ ⟩ := mapNFA_evalFrom_inv M f ( M.start ) S w hS₂; exact ⟨ v, by aesop ⟩; · rintro ⟨ v, hv, rfl ⟩; exact ⟨ _, hv, mapNFA_evalFrom_map M f _ _ ⟩ end SymbolMap /-- Regular languages are closed under symbol maps (letter-to-letter homomorphisms). -/ theorem IsRegular.map {L : Language α} (hL : L.IsRegular) (f : α → β) : (Language.map f L).IsRegular := by obtain ⟨σ, _, M, rfl⟩ := hL rw [← mapNFA_correct M f] exact ⟨_, inferInstance, (mapNFA M f).toDFA, (mapNFA M f).toDFA_correct⟩ section StringHomomorphism variable {γ : Type} /-! ### Singleton word languages are regular -/ /- The language `{[]}` (containing only the empty word) is regular. -/ theorem isRegular_epsilon : ({[]} : Language γ).IsRegular := by -- The DFA has two states: state 0 (start and accepting) and state 1 (sink). All transitions from state 0 go to state 1, and all transitions from state 1 go to state 1. use Fin 2; use inferInstance; fconstructor; exact ⟨ fun _ _ => 1, 0, { 0 } ⟩; ext ( _ | w ) <;> simp +decide [ DFA.accepts ]; · exact (iff_true_right rfl).mpr rfl; · simp +decide [ DFA.acceptsFrom ]; induction ‹List γ› <;> simp_all +decide [ DFA.evalFrom ]; · grind +revert; · grind /- The language `{[a]}` (containing only the single-letter word) is regular. -/ theorem isRegular_singleton_letter (a : γ) : ({[a]} : Language γ).IsRegular := by constructor; refine' ⟨ _, _, _ ⟩; case w => exact Fin 3; exact inferInstance; refine' { start := 0, step := fun q x => if q = 0 ∧ x = a then 1 else 2, accept := { 1 } }; ext w; simp [DFA.accepts]; rcases w with ( _ | ⟨ x, _ | ⟨ y, w ⟩ ⟩ ) <;> simp_all +decide [ DFA.acceptsFrom ]; · simp +decide [ DFA.evalFrom ]; grind +extAll; · grind +suggestions; · induction w <;> simp_all +decide [ DFA.evalFrom ]; · grind; · grind /- The singleton set `{a :: w}` equals the concatenation `{[a]} * {w}`. -/ private lemma singleton_cons_eq_mul (a : γ) (w : List γ) : ({a :: w} : Language γ) = {[a]} * {w} := by ext x; constructor; · rintro rfl; exact ⟨ [a], by aesop ⟩; · rintro ⟨ u, v, hu, hv, rfl ⟩; cases v ; cases hv ; aesop /-- Any singleton word language is regular. -/ theorem isRegular_singleton_word : ∀ (w : List γ), ({w} : Language γ).IsRegular | [] => isRegular_epsilon | a :: w => by rw [singleton_cons_eq_mul a w] exact (isRegular_singleton_letter a).mul' (isRegular_singleton_word w) /-- Regular languages are closed under string homomorphism. Given a regular language `L` over a finite alphabet and a string homomorphism `h : α' → List γ`, the image `{h(w) | w ∈ L}` is regular. -/ theorem IsRegular.homomorphicImage {α' : Type} [Fintype α'] {L : Language α'} (hL : L.IsRegular) (h : α' → List γ) : (L.homomorphicImage h).IsRegular := by unfold Language.homomorphicImage exact hL.subst' (fun a => isRegular_singleton_word (h a)) /-- The class of regular languages is closed under homomorphism. -/ theorem RG_closedUnderHomomorphism : ClosedUnderHomomorphism is_RG := by intro α γ _ _ L h hL exact is_RG_of_isRegular (IsRegular.homomorphicImage (isRegular_of_is_RG hL) h) end StringHomomorphism end Language