Theory RRn_Automata

theory RRn_Automata
imports Tree_Automata_Utils Ground_Terms
theory RRn_Automata
  imports Tree_Automata_Utils Ground_Terms
begin

subsection ‹Misc›

lemma gterm_of_term_inv':
  "ground t ⟹ term_of_gterm (gterm_of_term t) = adapt_vars t"
  by (induct t) (auto 0 0 intro!: nth_equalityI)


subsection ‹Encoding pairs of terms›

text ‹The encoding of two terms $s$ and $t$ is given by its tree domain, which is the union of the
domains of $s$ and $t$, and the labels, which arise from looking up each position in $s$ and $t$,
respectively.›

definition gpair :: "'f gterm ⇒ 'g gterm ⇒ ('f option × 'g option) gterm" where
  "gpair s t = glabel (λp. (gfun_at s p, gfun_at t p)) (gunion (gdomain s) (gdomain t))"

text ‹We provide an efficient implementation of gpair.›

definition zip_fill :: "'a list ⇒ 'b list ⇒ ('a option × 'b option) list" where
  "zip_fill xs ys = zip (map Some xs @ replicate (length ys - length xs) None)
    (map Some ys @ replicate (length xs - length ys) None)"

lemma zip_fill_code [code]:
  "zip_fill xs [] = map (λx. (Some x, None)) xs"
  "zip_fill [] ys = map (λy. (None, Some y)) ys"
  "zip_fill (x # xs) (y # ys) = (Some x, Some y) # zip_fill xs ys"
  subgoal by (induct xs) (auto simp: zip_fill_def)
  subgoal by (induct ys) (auto simp: zip_fill_def)
  subgoal by (auto simp: zip_fill_def)
  done

lemma length_zip_fill [simp]:
  "length (zip_fill xs ys) = max (length xs) (length ys)"
  by (auto simp: zip_fill_def)

lemma nth_zip_fill:
  assumes "i < max (length xs) (length ys)"
  shows "zip_fill xs ys ! i = (if i < length xs then Some (xs ! i) else None, if i < length ys then Some (ys ! i) else None)"
  using assms by (auto simp: zip_fill_def nth_append)

fun gpair_impl :: "'f gterm option ⇒ 'g gterm option ⇒ ('f option × 'g option) gterm" where
  "gpair_impl (Some s) (Some t) = gpair s t"
| "gpair_impl (Some s) None     = map_gterm (λf. (Some f, None)) s"
| "gpair_impl None     (Some t) = map_gterm (λf. (None, Some f)) t"
| "gpair_impl None     None     = GFun (None, None) []"

declare gpair_impl.simps(2-4)[code]

lemma gpair_impl_code [simp, code]:
  "gpair_impl (Some s) (Some t) =
    (case s of GFun f ss ⇒ case t of GFun g ts ⇒
    GFun (Some f, Some g) (map (λ(s, t). gpair_impl s t) (zip_fill ss ts)))"
proof (induct "gdomain s" "gdomain t" arbitrary: s t rule: gunion.induct)
  case (1 f ss g ts)
  obtain f' ss' where [simp]: "s = GFun f' ss'" by (cases s)
  obtain g' ts' where [simp]: "t = GFun g' ts'" by (cases t)
  show ?case using 1(2,3) 1(1)[of i "ss' ! i" "ts' ! i" for i]
    by (auto simp: gpair_def comp_def nth_zip_fill intro: glabel_map_gterm_conv[unfolded comp_def]
      intro!: nth_equalityI)
qed

lemma gpair_code [code]:
  "gpair s t = gpair_impl (Some s) (Some t)"
  by simp

(* export_code gpair in Haskell *)

declare gpair_impl.simps(1)[simp del]

text ‹We can easily prove some basic properties. I believe that proving them by induction with a
definition along the lines of @{const gpair_impl} would be very cumbersome.›

lemma gpair_swap:
  "map_gterm prod.swap (gpair s t) = gpair t s"
  by (intro eq_gterm_by_gposs_gfun_at) (auto simp: gpair_def)

lemma gpair_assoc:
  defines "f ≡ λ(f, gh). (f, gh ⤜ fst, gh ⤜ snd)"
  defines "g ≡ λ(fg, h). (fg ⤜ fst, fg ⤜ snd, h)"
  shows "map_gterm f (gpair s (gpair t u)) = map_gterm g (gpair (gpair s t) u)"
  by (intro eq_gterm_by_gposs_gfun_at) (auto simp: gpair_def f_def g_def)


subsection ‹Decoding of pairs›

fun gcollapse :: "'f option gterm ⇒ 'f gterm option" where
  "gcollapse (GFun None _) = None"
| "gcollapse (GFun (Some f) ts) = Some (GFun f (map the (filter (λt. ¬ Option.is_none t) (map gcollapse ts))))"

definition gfst :: "('f option × 'g option) gterm ⇒ 'f gterm" where
  "gfst = the ∘ gcollapse ∘ map_gterm fst"

definition gsnd :: "('f option × 'g option) gterm ⇒ 'g gterm" where
  "gsnd = the ∘ gcollapse ∘ map_gterm snd"

lemma filter_less_upt:
  "[i←[i..<m] . i < n] = [i..<min n m]"
proof (cases "i ≤ m")
  case True then show ?thesis
  proof (induct rule: inc_induct)
    case (step n) then show ?case by (auto simp: upt_rec[of n])
  qed simp
qed simp

lemma gcollapse_aux:
  assumes "gposs s = {p. p ∈ gposs t ∧ gfun_at t p ≠ Some None}"
  shows "gposs (the (gcollapse t)) = gposs s"
    "⋀p. p ∈ gposs s ⟹ gfun_at (the (gcollapse t)) p = (gfun_at t p ⤜ id)"
proof (goal_cases)
  define s' t' where "s' ≡ gdomain s" and "t' ≡ gdomain t"
  have *: "gposs (the (gcollapse t)) = gposs s ∧
    (∀p. p ∈ gposs s ⟶ gfun_at (the (gcollapse t)) p = (gfun_at t p ⤜ id))"
  using assms s'_def t'_def
  proof (induct s' t' arbitrary: s t rule: gunion.induct)
    case (1 f' ss' g' ts')
    obtain f ss where s [simp]: "s = GFun f ss" by (cases s)
    obtain g ts where t [simp]: "t = GFun (Some g) ts"
      using arg_cong[OF 1(2), of "λP. [] ∈ P"] by (cases t) auto
    have *: "i < length ts ⟹ ¬ Option.is_none (gcollapse (ts ! i)) ⟷ i < length ss" for i
      using arg_cong[OF 1(2), of "λP. [i] ∈ P"] by (cases "ts ! i") auto
    have l: "length ss ≤ length ts"
      using arg_cong[OF 1(2), of "λP. [length ss-1] ∈ P"] by auto
    have [simp]: "[t←map gcollapse ts . ¬ Option.is_none t] = take (length ss) (map gcollapse ts)"
      by (subst (2) map_nth[symmetric]) (auto simp add: * filter_map comp_def filter_less_upt
        cong: filter_cong[OF refl, of "[0..<length ts]", unfolded set_upt atLeastLessThan_iff]
        intro!: nth_equalityI)
    have [simp]: "i < length ss ⟹ gposs (ss ! i) = gposs (the (gcollapse (ts ! i)))" for i
      using conjunct1[OF 1(1), of i "ss ! i" "ts ! i"] l arg_cong[OF 1(2), of "λP. {p. i # p ∈ P}"]
        1(3,4) by simp
    show ?case
    proof (intro conjI allI, goal_cases A B)
      case A show ?case using l by (auto simp: comp_def split: if_splits)
    next
      case (B p) show ?case
      proof (cases p)
        case (Cons i q) then show ?thesis using arg_cong[OF 1(2), of "λP. {p. i # p ∈ P}"]
          spec[OF conjunct2[OF 1(1)], of i "ss ! i" "ts ! i" q] 1(3,4) by auto
      qed auto
    qed
  qed
  { case 1 then show ?case using * by blast
  next
    case 2 then show ?case using * by blast }
qed

lemma gfst_gpair:
  "gfst (gpair s t) = s"
proof -
  have *: "gposs s = {p ∈ gposs (map_gterm fst (gpair s t)). gfun_at (map_gterm fst (gpair s t)) p ≠ Some None}"
    by (auto simp: gpair_def gfun_at_poss) (auto simp: fun_at_def' split: if_splits)
  show ?thesis unfolding gfst_def comp_def using gcollapse_aux[OF *]
    by (auto intro!: eq_gterm_by_gposs_gfun_at simp: gpair_def)
qed

lemma gsnd_gpair:
  "gsnd (gpair s t) = t"
  using gfst_gpair[of t s] gpair_swap[of t s, symmetric]
  by (simp add: gfst_def gsnd_def gpair_def gterm.map_comp comp_def)


subsection ‹Encoding of lists of terms›

definition gencode :: "'f gterm list ⇒ 'f option list gterm" where
  "gencode ts = glabel (λp. map (λt. gfun_at t p) ts) (gunions (map gdomain ts))"

definition gdecode_nth :: "'f option list gterm ⇒ nat ⇒ 'f gterm" where
  "gdecode_nth t i = the (gcollapse (map_gterm (λf. f ! i) t))"

lemma gdecode_nth_gencode:
  assumes "i < length ts"
  shows "gdecode_nth (gencode ts) i = ts ! i"
proof -
  have *: "gposs (ts ! i) = {p ∈ gposs (map_gterm (λf. f ! i) (gencode ts)).
           gfun_at (map_gterm (λf. f ! i) (gencode ts)) p ≠ Some None}"
    using assms
    by (auto simp: gencode_def gfun_at_poss) (force simp: fun_at_def' split: if_splits)+
  show ?thesis unfolding gdecode_nth_def comp_def using assms gcollapse_aux[OF *]
    by (auto intro!: eq_gterm_by_gposs_gfun_at simp: gencode_def) force
qed

definition gdecode :: "'f option list gterm ⇒ 'f gterm list" where
  "gdecode t = (case t of GFun f ts ⇒ map (λi. gdecode_nth t i) [0..<length f])"

lemma gdecode_gencode:
  "gdecode (gencode ts) = ts"
proof (cases "gencode ts")
  case (GFun f ts')
  have "length f = length ts" using arg_cong[OF GFun, of "λt. gfun_at t []"]
    by (auto simp: gencode_def)
  then show ?thesis using gdecode_nth_gencode[of _ ts]
    by (auto intro!: nth_equalityI simp: gdecode_def GFun)
qed

definition gencode_impl :: "'f gterm option list ⇒ 'f option list gterm" where
  "gencode_impl ts = glabel (λp. map (λt. t ⤜ (λt. gfun_at t p)) ts) (gunions (map (case_option (GFun () []) gdomain) ts))"

lemma gencode_code [code]:
  "gencode ts = gencode_impl (map Some ts)"
  by (auto simp: gencode_def gencode_impl_def comp_def)

(* TODO: do we even need this? *)
lemma gencode_impl_code [code]:
  "gencode_impl ts = (let tss = map (λt. case t of Some (GFun _ ts) ⇒ ts | _ ⇒ []) ts in
    let len = max_list (map length tss) in
    let tss' = map (λts. map Some ts @ replicate (len - length ts) None) tss in
    GFun (map (λt. case t of Some (GFun f _) ⇒ Some f | _ ⇒ None) ts)
      (map gencode_impl (transpose tss')))"
  (* should we derive and use an induction rule for gunions? *)
  oops
(*
proof -
  define tss where "tss = map (λt. case t of Some (GFun _ ts) ⇒ ts | _ ⇒ []) ts"
  define len where "len = max_list (map length tss)"
  define tss' where "tss' = map (λts. map Some ts @ replicate (len - length ts) None) tss"
  have "map length tss' = replicate (length tss') len" unfolding tss'_def len_def
    using max_list[OF nth_mem, of _ "map length tss"] by (auto simp: comp_def intro!: nth_equalityI)
  then have "sorted (rev (map length tss'))" by (simp add: sorted_replicate)
  note nth_nth_transpose_sorted[OF this]
  show ?thesis
    s*o*r*r*y
qed
*)

lemma gencode_singleton:
  "gencode [t] = map_gterm (λf. [Some f]) t"
  using glabel_map_gterm_conv[unfolded comp_def, of "λt. [t]" t]
  by (simp add: gunions_def gencode_def)

lemma gencode_pair:
  "gencode [t, u] = map_gterm (λ(f, g). [f, g]) (gpair t u)"
  by (simp add: gunions_def gencode_def gpair_def map_gterm_glabel comp_def)


subsection ‹ta_lang for gterms›

definition gta_lang where
  "gta_lang = (λA :: ('q, _) ta. gterm_of_term ` (ta_lang A :: (_, 'q) term set))"

lemma gta_lang_to_ta_lang:
  "term_of_gterm ` gta_lang A = ta_lang A"
  unfolding image_comp gta_lang_def
  apply (auto simp: gterm_of_term_inv' image_def comp_def)
  by (metis adapt_vars_adapt_vars ta_lang_imp_ground adapt_vars_in_ta_lang_conv)

lemma term_of_gterm_in_ta_lang_conv:
  "term_of_gterm t ∈ ta_lang A ⟷ t ∈ gta_lang A"
  by (smt gta_lang_to_ta_lang image_iff term_of_gterm_inv)

lemma gta_lang_def_sym:
  "gterm_of_term ` ta_lang A = gta_lang A"
  (* this is nontrivial because the lhs has a more general type than the rhs of gta_lang_def *)
  unfolding gta_lang_def image_def
  by (intro Collect_cong) (metis gta_lang_def gterm_of_term_inv ta_lang_imp_ground
    term_of_gterm_in_ta_lang_conv imageI term_of_gterm_inv)

lemma fmap_funs_gta_lang:
  shows "gta_lang (fmap_funs_ta f A) = map_gterm f ` gta_lang A"
  unfolding gta_lang_def arg_cong[OF fmap_funs_ta_lang, of "λT. gterm_of_term ` T"]
  by (simp add: image_comp comp_def cong: image_cong)


subsection ‹RRn relations›

definition RR1_spec where
  "RR1_spec A T ⟷ gta_lang A = T"

definition RR2_spec where
  "RR2_spec A T ⟷ gta_lang A = {gpair t u |t u. (t, u) ∈ T}"

definition RRn_spec where
  "RRn_spec n A R ⟷ gta_lang A = gencode ` R ∧ (∀ts ∈ R. length ts = n)"

lemma RR1_to_RRn_spec:
  assumes "RR1_spec A T"
  shows "RRn_spec 1 (fmap_funs_ta (λf. [Some f]) A) ((λt. [t]) ` T)"
proof -
  have [simp]: "inj_on (λf. [Some f]) X" for X by (auto simp: inj_on_def)
  show ?thesis using assms
    by (auto simp: RR1_spec_def RRn_spec_def fmap_funs_gta_lang image_comp comp_def gencode_singleton)
qed

lemma RR2_to_RRn_spec:
  assumes "RR2_spec A T"
  shows "RRn_spec 2 (fmap_funs_ta (λ(f, g). [f, g]) A) ((λ(t, u). [t, u]) ` T)"
proof -
  have [simp]: "inj_on (λ(f, g). [f, g]) X" for X by (auto simp: inj_on_def)
  show ?thesis using assms
    by (auto simp: RR2_spec_def RRn_spec_def fmap_funs_gta_lang image_comp comp_def prod.case_distrib gencode_pair)
qed


subsection ‹Pairing RR1 languages›

text ‹cf. @{const "gpair"}.›

definition pair_automaton :: "('p, 'f) ta ⇒ ('q, 'g) ta ⇒ ('p option × 'q option, 'f option × 'g option) ta" where
  "pair_automaton A B = ta.make (Some ` ta_final A × Some ` ta_final B)
    ({(Some f, None) (map (λp. (Some p, None)) ps) → (Some p, None) |f ps p. f ps → p ∈ ta_rules A} ∪
     {(None, Some g) (map (λq. (None, Some q)) qs) → (None, Some q) |g qs q. g qs → q ∈ ta_rules B} ∪
     {(Some f, Some g) (zip_fill ps qs) → (Some p, Some q) |f ps p g qs q. f ps → p ∈ ta_rules A ∧ g qs → q ∈ ta_rules B})
    ({((Some p, q), (Some p', q)) |p q p'. (p, p') ∈ ta_eps A ∧ q ∈ {None} ∪ Some ` ta_states B} ∪
     {((p, Some q), (p, Some q')) |p q q'. p ∈ {None} ∪ Some ` ta_states A ∧ (q, q') ∈ ta_eps B})"

lemma map_pair_automaton:
  "pair_automaton (fmap_funs_ta f A) (fmap_funs_ta g B) =
   fmap_funs_ta (λ(a, b). (map_option f a, map_option g b)) (pair_automaton A B)"
  by (simp add: pair_automaton_def) (force simp: fmap_funs_ta_def)

lemmas map_pair_automaton_12 =
  map_pair_automaton[of _ _ id, unfolded fmap_funs_ta_id option.map_id]
  map_pair_automaton[of id _ _, unfolded fmap_funs_ta_id option.map_id]

lemma fmap_states_funs_ta_commute:
  "fmap_states_ta f (fmap_funs_ta g A) = fmap_funs_ta g (fmap_states_ta f A)"
  apply (auto simp: ta_rule.case_distrib fmap_states_ta_def fmap_funs_ta_def ta.make_def image_def split: ta_rule.splits)
  apply (metis ta_rule.exhaust_sel ta_rule.simps(1))
  apply (metis ta_rule.exhaust_sel ta_rule.sel(1) ta_rule.sel(2) ta_rule.sel(3))
  done

lemma swap_pair_automaton:
  assumes "(p, q) ∈ ta_res (pair_automaton A B) (term_of_gterm t)"
  shows "(q, p) ∈ ta_res (pair_automaton B A) (term_of_gterm (map_gterm prod.swap t))"
proof -
  have [simp]: "map prod.swap (zip_fill xs ys) = zip_fill ys xs" for xs ys
    by (auto simp: zip_fill_def zip_nth_conv comp_def intro!: nth_equalityI)
  let ?swp = "λX. fmap_states_ta prod.swap (fmap_funs_ta prod.swap X)"
  { fix A B
    let ?AB = "?swp (pair_automaton A B)" and ?BA = "pair_automaton B A"
    have "ta_eps ?AB ⊆ ta_eps ?BA" "ta_final ?AB ⊆ ta_final ?BA" "ta_rules ?AB ⊆ ta_rules ?BA"
      by (auto simp: fmap_states_ta_def fmap_funs_ta_def pair_automaton_def)
  } note * = this
  let ?BA = "?swp (?swp (pair_automaton B A))" and ?AB = "?swp (pair_automaton A B)"
  have "ta_eps ?BA ⊆ ta_eps ?AB" "ta_final ?BA ⊆ ta_final ?AB" "ta_rules ?BA ⊆ ta_rules ?AB"
    using *[of B A]
    unfolding fmap_funs_ta_def fmap_states_ta_def ta.make_def[symmetric] ta_make_simps Let_def
    by (fastforce intro!: image_mono simp: image_def)+
  then have "fmap_states_ta prod.swap (fmap_funs_ta prod.swap (pair_automaton A B)) = pair_automaton B A"
    using *[of A B] by (simp add: fmap_states_funs_ta_commute fmap_funs_ta_comp fmap_states_ta_comp)
  then show ?thesis
    using ta_res_fmap_states_ta[OF ta_res_fmap_funs_ta[OF assms], of prod.swap prod.swap]
    by (simp add: gterm.map_comp comp_def)
qed

lemma to_ta_res_pair_automaton:
  "p ∈ ta_res A (term_of_gterm t) ⟹
    (Some p, None) ∈ ta_res (pair_automaton A B) (term_of_gterm (map_gterm (λf. (Some f, None)) t))"
  "q ∈ ta_res B (term_of_gterm u) ⟹
    (None, Some q) ∈ ta_res (pair_automaton A B) (term_of_gterm (map_gterm (λf. (None, Some f)) u))"
  "p ∈ ta_res A (term_of_gterm t) ⟹ q ∈ ta_res B (term_of_gterm u) ⟹
    (Some p, Some q) ∈ ta_res (pair_automaton A B) (term_of_gterm (gpair t u))"
proof (goal_cases sn ns ss)
  let ?AB = "pair_automaton A B"
  have 1: "(Some p, None) ∈ ta_res (pair_automaton A B) (term_of_gterm (map_gterm (λf. (Some f, None)) s))"
    if "p ∈ ta_res A (term_of_gterm s)" for A B p s
    by (intro subsetD[OF ta_res_mono', OF _ _ ta_res_fmap_states_ta[OF ta_res_fmap_funs_ta[OF that]],
      unfolded map_term_of_gterm gterm.map_ident])
      (auto simp add: pair_automaton_def fmap_states_ta_def fmap_funs_ta_def)
  have 2: "q ∈ ta_res B (term_of_gterm t) ⟹
    (None, Some q) ∈ ta_res ?AB (term_of_gterm (map_gterm (λg. (None, Some g)) t))"
    for q t using swap_pair_automaton[OF 1[of q B t A]] by (simp add: gterm.map_comp comp_def)
  {
    case sn then show ?case by (rule 1)
  next
    case ns then show ?case by (rule 2)
  next
    case ss then show ?case
    proof (induct t arbitrary: p q u)
      case (GFun f ts)
      obtain g us where u [simp]: "u = GFun g us" by (cases u)
      obtain p' ps where p': "f ps → p' ∈ ta_rules A" "(p', p) ∈ (ta_eps A)*" "length ps = length ts"
        "⋀i. i < length ts ⟹ ps ! i ∈ ta_res A (term_of_gterm (ts ! i))" using GFun(2) by auto
      obtain q' qs where q': "g qs → q' ∈ ta_rules B" "(q', q) ∈ (ta_eps B)*" "length qs = length us"
        "⋀i. i < length us ⟹ qs ! i ∈ ta_res B (term_of_gterm (us ! i))" using GFun(3) by auto
      have *: "Some p ∈ Some ` ta_states A" "Some q' ∈ Some ` ta_states B"
        using p'(2,1) q'(1)
        apply (-, cases p' p rule: rtrancl.cases)
        by (auto simp: r_rhs_states) (auto simp: ta_states_def)
      have [simp]: "((Some p', Some q'), (Some p, Some q)) ∈ (ta_eps ?AB)*"
        by (rule rtrancl_trans[OF rtrancl_map[OF _ p'(2), of "λp. (Some p, _)" "ta_eps ?AB"]
          rtrancl_map[OF _ q'(2), of "λq. (_, Some q)" "ta_eps ?AB"]]) (auto simp: pair_automaton_def *)
      have [simp]: "(Some f, Some g) zip_fill ps qs → (Some p', Some q') ∈ ta_rules ?AB"
        using p'(1) q'(1) by (auto simp: pair_automaton_def)
      show ?case using GFun(1)[OF nth_mem p'(4) q'(4)]
        by (auto intro!: exI[of _ "Some p'", OF exI[of _ "Some q'", OF exI[of _ "zip_fill ps qs"]]]
          simp: gpair_code nth_zip_fill less_max_iff_disj p'(3) q'(3) 1[OF p'(4)] 2[OF q'(4)])
    qed
  }
qed

lemma from_ta_res_pair_automaton:
  "(None, None) ∉ ta_res (pair_automaton A B) (term_of_gterm s)"
  "(Some p, None) ∈ ta_res (pair_automaton A B) (term_of_gterm s) ⟹
    ∃t. p ∈ ta_res A (term_of_gterm t) ∧ s = map_gterm (λf. (Some f, None)) t"
  "(None, Some q) ∈ ta_res (pair_automaton A B) (term_of_gterm s) ⟹
    ∃u. q ∈ ta_res B (term_of_gterm u) ∧ s = map_gterm (λf. (None, Some f)) u"
  "(Some p, Some q) ∈ ta_res (pair_automaton A B) (term_of_gterm s) ⟹
   ∃t u. p ∈ ta_res A (term_of_gterm t) ∧ q ∈ ta_res B (term_of_gterm u) ∧ s = gpair t u"
proof (goal_cases nn sn ns ss)
  let ?AB = "pair_automaton A B"
  { fix p s A B assume "(Some p, None) ∈ ta_res (pair_automaton A B) (term_of_gterm s)"
    then have "∃t. p ∈ ta_res A (term_of_gterm t) ∧ s = map_gterm (λf. (Some f, None)) t"
    proof (induct s arbitrary: p)
      case (GFun h ss)
      obtain rs sp nq where r: "h rs → (sp, nq) ∈ ta_rules (pair_automaton A B)"
        "((sp, nq), (Some p, None)) ∈ (ta_eps (pair_automaton A B))*" "length rs = length ss"
        "⋀i. i < length ss ⟹ rs ! i ∈ ta_res (pair_automaton A B) (term_of_gterm (ss ! i))"
        using GFun(2) by auto
      obtain p' where pq: "sp = Some p'" "nq = None" "(p', p) ∈ (ta_eps A)*"
        using r(2)
      proof (induct arbitrary: thesis rule: converse_rtrancl_induct2)
        case (step sp' nq' sp'' nq'')
        guess p'' using step(3) . note [simp] = this(1,2) and pq'' = this(3)
        obtain p' where pq': "sp' = Some p'" "nq' = None"
          "(p', p'') ∈ ta_eps A"
          using step(1) by (auto simp: pair_automaton_def)
        then show ?case using pq'' by (auto intro!: step(4)[of p'])
      qed auto
      obtain f ps where h: "h = (Some f, None)" "rs = map (λx. (Some x, None)) ps"
        "f ps → p' ∈ ta_rules A"
        using r(1) unfolding pq(1,2) by (auto simp: pair_automaton_def)
      obtain ts where "⋀i. i < length ss ⟹
        ps ! i ∈ ta_res A (term_of_gterm (ts i)) ∧ ss ! i = map_gterm (λf. (Some f, None)) (ts i)"
        using GFun(1)[OF nth_mem, of i "ps ! i" for i] r(4)[unfolded h(2)] r(3)[unfolded h(2) length_map]
        by simp metis (* by (metis nth_map) *)
      then show ?case using h(3) pq(3) r(3)[unfolded h(2) length_map]
        by (intro exI[of _ "GFun f (map ts [0..<length ss])"]) (auto simp: h(1) intro!: nth_equalityI)
    qed
  } note 1 = this
  have 2: "∃u. q ∈ ta_res B (term_of_gterm u) ∧ s = map_gterm (λg. (None, Some g)) u"
    if "(None, Some q) ∈ ta_res ?AB (term_of_gterm s)" for q s
    using 1[OF swap_pair_automaton, OF that]
    by (auto simp: gterm.map_comp comp_def gterm.map_ident
      dest!: arg_cong[of "map_gterm prod.swap _" _ "map_gterm prod.swap", unfolded gterm.map_comp])
  {
    case nn
    have *: "((p, q), (None, None)) ∈ (ta_eps ?AB)* ⟹ p = None ∧ q = None" for p q
      by (induct rule: converse_rtrancl_induct2) (auto simp: pair_automaton_def)
    show ?case
      apply (cases s)
      by (auto dest!: *) (auto simp: pair_automaton_def)
  next
    case sn then show ?case by (rule 1)
  next
    case ns then show ?case by (rule 2)
  next
    case ss then show ?case
    proof (induct s arbitrary: p q)
      case (GFun h ss)
      obtain rs sp sq where r: "h rs → (sp, sq) ∈ ta_rules ?AB"
        "((sp, sq), (Some p, Some q)) ∈ (ta_eps ?AB)*" "length rs = length ss"
        "⋀i. i < length ss ⟹ rs ! i ∈ ta_res ?AB (term_of_gterm (ss ! i))"
        using GFun(2) by auto
      obtain p' q' where pq: "sp = Some p'" "sq = Some q'" "(p', p) ∈ (ta_eps A)*" "(q', q) ∈ (ta_eps B)*"
        using r(2)
      proof (induct arbitrary: thesis rule: converse_rtrancl_induct2)
        case (step sp' sq' sp'' sq'')
        guess p'' q'' using step(3) . note [simp] = this(1,2) and pq'' = this(3,4)
        obtain p' q' where pq': "sp' = Some p'" "sq' = Some q'"
          "(p', p'') ∈ ta_eps A ∧ q' = q'' ∨ p' = p'' ∧ (q', q'') ∈ ta_eps B"
          using step(1) by (auto simp: pair_automaton_def)
        then show ?case using pq'' by (auto intro!: step(4)[of p' q'])
      qed auto
      obtain f g ps qs where h: "h = (Some f, Some g)" "rs = zip_fill ps qs"
        "f ps → p' ∈ ta_rules A" "g qs → q' ∈ ta_rules B"
        using r(1) unfolding pq(1,2) by (auto simp: pair_automaton_def)
      have *: "∃t u. ps ! i ∈ ta_res A (term_of_gterm t) ∧ qs ! i ∈ ta_res B (term_of_gterm u) ∧ ss ! i = gpair t u"
        if "i < length ps" "i < length qs" for i
        using GFun(1)[OF nth_mem, of i "ps ! i" "qs ! i"] r(4)[unfolded pq(1,2) h(2), of i] that
          r(3)[symmetric] by (auto simp: nth_zip_fill h(2))
      { fix i assume "i < length ss"
        then have "∃t u. (i < length ps ⟶ ps ! i ∈ ta_res A (term_of_gterm t)) ∧
            (i < length qs ⟶ qs ! i ∈ ta_res B (term_of_gterm u)) ∧
            ss ! i = gpair_impl (if i < length ps then Some t else None) (if i < length qs then Some u else None)"
          using *[of i] 1[of "ps ! i" A B "ss ! i"] 2[of "qs ! i" "ss ! i"] r(4)[of i] r(3)[symmetric]
          by (cases "i < length ps"; cases "i < length qs")
            (auto simp add: h(2) nth_zip_fill less_max_iff_disj gpair_code split: gterm.splits) }
      then obtain ts us where "⋀i. i < length ss ⟹
          (i < length ps ⟶ ps ! i ∈ ta_res A (term_of_gterm (ts i))) ∧
          (i < length qs ⟶ qs ! i ∈ ta_res B (term_of_gterm (us i))) ∧
          ss ! i = gpair_impl (if i < length ps then Some (ts i) else None) (if i < length qs then Some (us i) else None)"
        by metis
      moreover then have "⋀i. i < length ps ⟹ ps ! i ∈ ta_res A (term_of_gterm (ts i))"
         "⋀i. i < length qs ⟹ qs ! i ∈ ta_res B (term_of_gterm (us i))"
        using r(3)[unfolded h(2)] by auto
      ultimately show ?case using h(3,4) pq(3,4) r(3)[symmetric]
        by (intro exI[of _ "GFun f (map ts [0..<length ps])"] exI[of _ "GFun g (map us [0..<length qs])"])
          (auto simp: gpair_code h(1,2) less_max_iff_disj nth_zip_fill intro!: nth_equalityI split: prod.splits gterm.splits)
    qed
  }
qed

lemma pair_automaton:
  assumes "RR1_spec A T" "RR1_spec B U"
  shows "RR2_spec (pair_automaton A B) (T × U)"
proof -
  let ?AB = "pair_automaton A B"
  { fix t u assume t: "t ∈ T" and u: "u ∈ U"
    obtain p where p: "p ∈ ta_final A" "p ∈ ta_res A (term_of_gterm t)" using t assms(1)
      by (auto simp: RR1_spec_def gta_lang_def image_def  gterm_of_term_inv' elim: ta_langE2)
    obtain q where q: "q ∈ ta_final B" "q ∈ ta_res B (term_of_gterm u)" using u assms(2)
      by (auto simp: RR1_spec_def gta_lang_def image_def  gterm_of_term_inv' elim: ta_langE2)
    have "gpair t u ∈ gta_lang (pair_automaton A B)" using p(1) q(1) to_ta_res_pair_automaton(3)[OF p(2) q(2)]
      by (auto simp: gta_lang_def pair_automaton_def
        intro!: image_eqI[where x = "term_of_gterm (gpair t u)"] ta_langI2[of _ "(Some p, Some q)"])
  } moreover
  { fix s assume "s ∈ gta_lang (pair_automaton A B)"
    from this[unfolded gta_lang_def]
    obtain s' r where r: "s = gterm_of_term s'" "ground s'" "r ∈ ta_final ?AB" "r ∈ ta_res ?AB s'"
      by (auto elim!: ta_langE2)
    have s': "s' = term_of_gterm s" using r(1,2) by simp
    obtain p q where pq: "r = (Some p, Some q)" "p ∈ ta_final A" "q ∈ ta_final B"
      using r(3) by (auto simp: pair_automaton_def)
    guess t u using from_ta_res_pair_automaton(4)[OF r(4)[unfolded pq(1) s']]
      by (elim exE conjE) note * = this
    then have "t ∈ gta_lang A" "u ∈ gta_lang B" using pq(2,3)
      by (metis ground_term_of_gterm ta_langI2 ta_res_adapt_vars_ground term_of_gterm_in_ta_lang_conv)+
    then have "∃t u. s = gpair t u ∧ t ∈ T ∧ u ∈ U" using assms
      by (auto simp: RR1_spec_def *(3) intro!: exI[of _ t, OF exI[of _ u]])
  } ultimately show ?thesis by (auto simp: RR2_spec_def)
qed

lemma pair_automaton':
  shows "gta_lang (pair_automaton A B) = case_prod gpair ` (gta_lang A × gta_lang B)"
  using pair_automaton[of A _ B] by (auto simp: RR1_spec_def RR2_spec_def)


subsection ‹Collapsing›

text ‹cf. @{const "gcollapse"}.›

definition collapse_automaton :: "('q, 'f option) ta ⇒ ('q, 'f) ta" where
  "collapse_automaton A = (
    let Qn = {q' |qs q q'. None qs → q ∈ ta_rules A ∧ (q, q') ∈ (ta_eps A)*} in
    let Qs = {q' |f qs q q'. (Some f) qs → q ∈ ta_rules A ∧ (q, q') ∈ (ta_eps A)*} in
    ta.make (ta_final A) {
      f (map the (filter (λq. ¬ Option.is_none q) qs')) → q |f qs qs' q.
      (Some f) qs → q ∈ ta_rules A ∧ length qs = length qs' ∧
      (∀i < length qs. qs ! i ∈ Qn ∧ qs' ! i = None ∨ qs ! i ∈ Qs ∧ qs' ! i = Some (qs ! i))
    } (ta_eps A))"

lemma collapse_automaton':
  assumes "ta_states A ⊆ ta_reachable A" (* cf. ta_trim *)
  shows "gta_lang (collapse_automaton A) = the ` (gcollapse ` gta_lang A - {None})"
proof -
  define Qn where "Qn = {q' |qs q q'. None qs → q ∈ ta_rules A ∧ (q, q') ∈ (ta_eps A)*}"
  define Qs where "Qs = {q' |f qs q q'. (Some f) qs → q ∈ ta_rules A ∧ (q, q') ∈ (ta_eps A)*}"
  { fix t assume t: "t ∈ gta_lang (collapse_automaton A)"
    then obtain q where q: "q ∈ ta_final A" "q ∈ ta_res (collapse_automaton A) (term_of_gterm t)"
      by (auto simp: gta_lang_def image_def gterm_of_term_inv' collapse_automaton_def elim: ta_langE2)
    obtain t' where "q ∈ ta_res A (term_of_gterm t')" "gcollapse t' ≠ None" "t = the (gcollapse t')"
      using q(2)
    proof (induct t arbitrary: q thesis)
      case (GFun f ts)
      obtain qs q' where q': "f qs → q' ∈ ta_rules (collapse_automaton A)"
        "(q', q) ∈ (ta_eps (collapse_automaton A))*" "length qs = length ts"
        "⋀i. i < length ts ⟹ qs ! i ∈ ta_res (collapse_automaton A) (term_of_gterm (ts ! i))"
        using GFun(3) by auto
      have "∃t'. qs ! i ∈ ta_res A (term_of_gterm t') ∧ gcollapse t' ≠ None ∧ ts ! i = the (gcollapse t')"
        if "i < length ts" for i using GFun(1)[OF nth_mem _ q'(4)] that by metis
      then obtain ts' where ts':
        "qs ! i ∈ ta_res A (term_of_gterm (ts' i)) ∧ gcollapse (ts' i) ≠ None ∧ ts ! i = the (gcollapse (ts' i))"
        if "i < length ts" for i by metis
      have "(q', q) ∈ (ta_eps A)*" using q'(2) by (auto simp: collapse_automaton_def)
      moreover obtain ps rs where ps: "(Some f) ps → q' ∈ ta_rules A" "length ps = length rs"
        "⋀i. i < length ps ⟹ ps ! i ∈ Qn ∧ rs ! i = None ∨ ps ! i ∈ Qs ∧ rs ! i = Some (ps ! i)"
        "qs = map the (filter (λq. ¬ Option.is_none q) rs)"
        using q'(1) by (auto simp: collapse_automaton_def Qn_def Qs_def)
      have *: "∃t'. qs ! i ∈ ta_res A (term_of_gterm t') ∧ gcollapse t' ≠ None ∧ ts ! i = the (gcollapse t')"
        if "i < length ts" for i using GFun(1)[OF nth_mem _ q'(4)] that by metis
      obtain ts' where "length ts' = length ps"
        "⋀i. i < length ps ⟹ ps ! i ∈ ta_res A (term_of_gterm (ts' ! i))"
        "ts = map the (filter (λq. ¬ Option.is_none q) (map gcollapse ts'))"
        using ps(2,3,4) q'(3) ts' *
      proof (induct ps arbitrary: rs qs ts ts' thesis)
        case (Cons p ps)
        obtain r rs' where rs [simp]: "rs = r # rs'" using Cons(3) by (cases rs) auto
        show ?case using Cons(4)[of 0, unfolded list.size add_Suc_right, OF zero_less_Suc]
          unfolding nth.simps nat.case mem_Collect_eq rs Qn_def Qs_def
        proof (elim conjE disjE exE, goal_cases None Some)
          case (None qs' q q'')
          { fix i assume i: "i < length qs'"
            then have "qs' ! i ∈ ta_states A" using None(3) by (force simp: ta_states_def r_states_def)
            then have "∃t. qs' ! i ∈ ta_res A (term_of_gterm t)"
              using subsetD[OF assms, of "qs' ! i"] None(3)
              by (auto simp: ta_reachable_def dest: ground_term_to_gtermD)
          }
          then obtain us where "⋀i. i < length qs' ⟹ qs' ! i ∈ ta_res A (term_of_gterm (us i))"
            by metis
          moreover obtain us' where "length us' = length ps"
            "⋀i. i < length ps ⟹ ps ! i ∈ ta_res A (term_of_gterm (us' ! i))"
            "ts = map the (filter (λt. ¬ Option.is_none t) (map gcollapse us'))"
            using Cons(3,5,6,7,8) Cons(4)[of "Suc _"]
            by (subst Cons(1)[of ts _ rs' qs ts']) (auto simp: None(1,2))
          ultimately show ?case using None(3,4)
            by (intro Cons(2)[of "GFun None (map us [0..<length qs']) # us'"])
              (auto 0 4 simp: less_Suc_eq_0_disj None(2))
        next
          case (Some f qs' q p)
          obtain q'' qs'' where [simp]: "qs = q'' # qs''" by (auto simp: Cons(5) Some(1))
          obtain t'' ts'' where [simp]: "ts = t'' # ts''" using Cons(6) by (cases ts) auto
          obtain t' where "qs ! 0 ∈ ta_res A (term_of_gterm t')" "gcollapse t' ≠ None"
            "ts ! 0 = the (gcollapse t')" using Cons(8)[of 0] by auto
          moreover obtain us' where "length us' = length ps"
            "⋀i. i < length ps ⟹ ps ! i ∈ ta_res A (term_of_gterm (us' ! i))"
            "ts'' = map the (filter (λt. ¬ Option.is_none t) (map gcollapse us'))"
            using Cons(3,5,6) Cons(4,7,8)[of "Suc _"]
            by (subst Cons(1)[of ts'' _ rs' qs'' "ts' ∘ Suc"]) (auto simp: Some(1))
          ultimately show ?case using arg_cong[OF Cons(5), of hd]
            by (intro Cons(2)[of "t' # us'"]) (auto simp: less_Suc_eq_0_disj Some(1,2))
        qed
      qed auto
      ultimately show ?case using ps(1,2) by (intro GFun(2)[of "GFun (Some f) ts'"]) auto
    qed
    then have "t ∈ the ` (gcollapse ` gta_lang A - {None})" by (metis q(1) DiffI ground_term_of_gterm
      gterm_of_term_inv gterm_of_term_inv' image_eqI singletonD ta_langI2 term_of_gterm_in_ta_lang_conv)
  } moreover
  { fix t assume t: "t ∈ gta_lang A" "gcollapse t ≠ None"
    obtain q where q: "q ∈ ta_final (collapse_automaton A)" "q ∈ ta_res A (term_of_gterm t)" using t(1)
      by (auto simp: gta_lang_def image_def gterm_of_term_inv' collapse_automaton_def elim: ta_langE2)
    have "q ∈ ta_res (collapse_automaton A) (term_of_gterm (the (gcollapse t)))" using q(2) t(2)
    proof (induct t arbitrary: q)
      case (GFun f ts)
      obtain qs q' where q: "f qs → q' ∈ ta_rules A" "(q', q) ∈ (ta_eps (collapse_automaton A))*"
        "length qs = length ts" "⋀i. i<length ts ⟹ qs ! i ∈ ta_res A (term_of_gterm (ts ! i))"
        using GFun(2) by (auto simp: collapse_automaton_def)
      obtain f' where f [simp]: "f = Some f'" using GFun(3) by (cases f) auto
      define qs' where
        "qs' = map (λi. if Option.is_none (gcollapse (ts ! i)) then None else Some (qs ! i)) [0..<length qs]"
      have "Option.is_none (gcollapse (ts ! i)) ⟹ qs ! i ∈ Qn" if "i < length qs" for i
        using q(4)[of i] that by (cases "ts ! i" rule: gcollapse.cases) (auto simp: q(3) Qn_def)
      moreover have "¬ Option.is_none (gcollapse (ts ! i)) ⟹ qs ! i ∈ Qs" if "i < length qs" for i
        using q(4)[of i] that by (cases "ts ! i" rule: gcollapse.cases) (auto simp: q(3) Qs_def)
      ultimately have "f' (map the (filter (λq. ¬ Option.is_none q) qs')) → q' ∈ ta_rules (collapse_automaton A)"
        using q(1,4) unfolding collapse_automaton_def Qn_def[symmetric] Qs_def[symmetric]
        by (fastforce simp: qs'_def q(3) intro!: exI[of _ qs] exI[of _ qs'] split: if_splits)
      moreover have ***: "length (filter (λi.¬ Option.is_none (gcollapse (ts ! i))) [0..<length ts]) =
        length (filter (λt. ¬ Option.is_none (gcollapse t)) ts)" for ts
        by (subst length_map[of "(!) ts", symmetric] filter_map[unfolded comp_def, symmetric] map_nth)+
          (rule refl)
      note this[of ts]
      moreover have "the (filter (λq. ¬ Option.is_none q) qs' ! i) ∈ ta_res (collapse_automaton A)
        (term_of_gterm (the (filter (λt. ¬ Option.is_none t) (map gcollapse ts) ! i)))"
        if "i < length [x←ts . ¬ Option.is_none (gcollapse x)]" for i
        unfolding qs'_def using that q(3) GFun(1)[OF nth_mem q(4)]
      proof (induct ts arbitrary: qs rule: List.rev_induct)
        case (snoc t ts)
        have x1 [simp]: "i < length xs ⟹ (xs @ ys) ! i = xs ! i" for xs ys i by (auto simp: nth_append)
        have x2: "i = length xs ⟹ (xs @ ys) ! i = ys ! 0" for xs ys i by (auto simp: nth_append)
        obtain q qs' where qs [simp]: "qs = qs' @ [q]" using snoc(3) by (cases "rev qs") auto
        have [simp]:
          "map (λx. if Option.is_none (gcollapse ((ts @ [t]) ! x)) then None else Some ((qs' @ [q]) ! x)) [0..<length ts] =
           map (λx. if Option.is_none (gcollapse (ts ! x)) then None else Some (qs' ! x)) [0..<length ts]"
          using snoc(3) by auto
        show ?case
        proof (cases "Option.is_none (gcollapse t)")
          case True then show ?thesis using snoc(1)[of qs'] snoc(2,3)
            snoc(4)[unfolded length_append list.size add_0 add_0_right add_Suc_right, OF less_SucI]
            by (auto cong: if_cong)
        next
          case False note False' = this
          show ?thesis
          proof (cases "i = length [x←ts . ¬ Option.is_none (gcollapse x)]")
            case True
            then show ?thesis using snoc(3) snoc(4)[of "length ts"]
              unfolding qs length_append list.size add_0 add_0_right add_Suc_right
                upt_Suc_append[OF zero_le] filter_append map_append
              by (subst (5 6) x2) (auto simp: comp_def *** False' Option.is_none_def[symmetric])
          next
            case False
            then show ?thesis using snoc(1)[of qs'] snoc(2,3)
              snoc(4)[unfolded length_append list.size add_0 add_0_right add_Suc_right, OF less_SucI]
              unfolding qs length_append list.size add_0 add_0_right add_Suc_right
                upt_Suc_append[OF zero_le] filter_append map_append
              by (subst (5 6) x1) (auto simp: comp_def *** False')
          qed
        qed
      qed auto
      ultimately show ?case using q(2) by (auto simp: qs'_def comp_def q(3)
        intro!: exI[of _ q'] exI[of _ "map the (filter (λq. ¬ Option.is_none q) qs')"])
    qed
    then have "the (gcollapse t) ∈ gta_lang (collapse_automaton A)" by (metis q(1)
      ground_term_of_gterm gterm_of_term_inv' term_of_gterm_in_ta_lang_conv term_of_gterm_inv ta_langI2)
  } ultimately show ?thesis by blast
qed

lemma collapse_automaton:
  assumes "ta_states A ⊆ ta_reachable A" "RR1_spec A T"
  shows "RR1_spec (collapse_automaton A) (the ` (gcollapse ` gta_lang A - {None}))"
  using collapse_automaton'[OF assms(1)] assms(2) by (simp add: RR1_spec_def)


subsection ‹Cylindrification›

(* cylindrification is a product ("pairing") of a RR1 language accepting all terms, and an RRn language,
modulo some fairly trivial renaming of symbols. *)

definition pad_with_Nones where
  "pad_with_Nones n m = (λ(f, g). case_option (replicate n None) id f @ case_option (replicate m None) id g)"

lemma gencode_append:
  "gencode (ss @ ts) = map_gterm (pad_with_Nones (length ss) (length ts)) (gpair (gencode ss) (gencode ts))"
proof -
  have [simp]: "p ∉ gposs (gunions (map gdomain ts)) ⟹ map (λt. gfun_at t p) ts = replicate (length ts) None"
    for p ts by (intro nth_equalityI) (fastforce simp: fun_at_def' image_def all_set_conv_all_nth)+
  note [simp] = glabel_map_gterm_conv[of "λ(_ :: unit option). ()", unfolded comp_def gdomain_id]
  show ?thesis by (auto intro!: arg_cong[of _ _ "λx. glabel x _"] simp del: gposs_gunions
    simp: pad_with_Nones_def gencode_def gunions_append gpair_def map_gterm_glabel comp_def)
qed

lemma append_automaton:
  assumes "RRn_spec n A T" "RRn_spec m B U"
  shows "RRn_spec (n + m) (fmap_funs_ta (pad_with_Nones n m) (pair_automaton A B)) {ts @ us |ts us. ts ∈ T ∧ us ∈ U}"
  using assms pair_automaton[of A "gencode ` T" B "gencode ` U"]
  unfolding RRn_spec_def
proof (intro conjI set_eqI iffI, goal_cases)
  case (1 s)
  then obtain ts us where "ts ∈ T" "us ∈ U" "s = gencode (ts @ us)"
    by (fastforce simp: RR1_spec_def RR2_spec_def gencode_append fmap_funs_gta_lang)
  then show ?case by blast
qed (fastforce simp: RR1_spec_def RR2_spec_def gencode_append fmap_funs_gta_lang)+

lemma cons_automaton:
  assumes "RR1_spec A T" "RRn_spec m B U"
  shows "RRn_spec (Suc m) (fmap_funs_ta (λ(f, g). pad_with_Nones 1 m (map_option (λf. [Some f]) f, g))
   (pair_automaton A B)) {t # us |t us. t ∈ T ∧ us ∈ U}"
proof -
  have [simp]: "{ts @ us |ts us. ts ∈ (λt. [t]) ` T ∧ us ∈ U} = {t # us |t us. t ∈ T ∧ us ∈ U}"
    by (auto intro: exI[of _ "[_]", OF exI])
  show ?thesis using append_automaton[OF RR1_to_RRn_spec, OF assms]
    by (auto simp: fmap_funs_gta_lang map_pair_automaton_12 fmap_funs_ta_comp prod.case_distrib
      gencode_append[of "[_]", unfolded gencode_singleton List.append.simps])
qed


subsection ‹Projection›

(* projection is composed from fmap_funs_ta and collapse_automaton, corresponding to gsnd *)

lemma drop_automaton:
  assumes "ta_states A ⊆ ta_reachable A" "m < n" "RRn_spec n A T"
  defines "f ≡ λfs. if list_all (Option.is_none) (drop m fs) then None else Some (drop m fs)"
  shows "RRn_spec (n - m) (collapse_automaton (fmap_funs_ta f A)) (drop m ` T)"
proof -
  have [simp]: "length ts = n - m ==> p ∈ gposs (gencode ts) ⟹ ∃f. ∃t∈set ts. Some f = gfun_at t p" for p ts
  proof (cases p, goal_cases Empty PCons)
    case Empty
    obtain t where "t ∈ set ts" using assms(2) Empty(1) by (cases ts) auto
    moreover then obtain f where "Some f = gfun_at t p" using Empty(3) by (cases t rule: gterm.exhaust) auto
    ultimately show ?thesis by auto
  next
    case (PCons i p')
    then have "p ≠ []" by auto
    then show ?thesis using PCons(2)
      by (auto 0 3 simp: gencode_def eq_commute[of "gfun_at _ _" "Some _"] dest!: gfun_at_poss)
  qed
  { fix p ts y assume that: "gfun_at (gencode ts) p = Some y"
    have "p ∈ gposs (gencode ts) ⟹ gfun_at (gencode ts) p = Some (map (λt. gfun_at t p) ts)"
      by (auto simp: gencode_def)
    moreover have "gfun_at (gencode ts) p = Some y ⟹ p ∈ gposs (gencode ts)"
      by (auto simp: fun_at_def' split: if_splits)
    ultimately have "y = map (λt. gfun_at t p) ts ∧ p ∈ gposs (gencode ts)" by (simp add: that)
  } note [dest!] = this
  have [simp]: "list_all f (replicate n x) ⟷ n = 0 ∨ f x" for f n x by (induct n) auto
  have [dest]: "x ∈ set xs ⟹ list_all f xs ⟹ f x" for f x xs by (induct xs) auto
  have *: "f (pad_with_Nones m' n' z) = snd z"
    if  "fst z = None ∨ fst z ≠ None ∧ length (the (fst z)) = m"
      "snd z = None ∨ snd z ≠ None ∧ length (the (snd z)) = n - m ∧ (∃y. Some y ∈ set (the (snd z)))"
      "m' = m" "n' = n - m" for z m' n'
    using that by (auto simp: f_def pad_with_Nones_def split: option.splits prod.splits)
  { fix ts assume ts: "ts ∈ T" "length ts = n"
    have "gencode (drop m ts) = the (gcollapse (map_gterm f (gencode ts)))"
      "gcollapse (map_gterm f (gencode ts)) ≠ None"
    proof (goal_cases)
      case 1 show ?case
        using ts assms(2)
        apply (subst gsnd_gpair[of "gencode (take m ts)", symmetric])
        apply (subst gencode_append[of "take m ts" "drop m ts", unfolded append_take_drop_id])
        unfolding gsnd_def comp_def gterm.map_comp
        apply (intro arg_cong[where f = "λx. the (gcollapse x)"] gterm.map_cong refl)
        by (subst *) (auto simp: gpair_def set_gterm_gposs_conv image_def)
    next
      case 2
      have [simp]: "gcollapse t = None ⟷ gfun_at t [] = Some None" for t
        by (cases t rule: gcollapse.cases) auto
      obtain t where "t ∈ set (drop m ts)" using ts(2) assms(2) by (cases "drop m ts") auto
      moreover then have "¬ Option.is_none (gfun_at t [])" by (cases t rule: gterm.exhaust) auto
      ultimately show ?case
        by (auto simp: f_def gencode_def list_all_def drop_map)
    qed
  }
  then show ?thesis using assms(3)
    by (fastforce simp: RRn_spec_def fmap_funs_gta_lang gsnd_def gpair_def gterm.map_comp comp_def
      glabel_map_gterm_conv[unfolded comp_def] assms(1) collapse_automaton')
qed


subsection ‹Permutation›

(* permutation are a direct application of fmap_funs_ta *)

lemma gencode_permute:
  assumes "set ps = {0..<length ts}"
  shows "gencode (map ((!) ts) ps) = map_gterm (λxs. map ((!) xs) ps) (gencode ts)"
proof -
  have *: "(!) ts ` set ps = set ts" using assms by (auto simp: image_def set_conv_nth)
  show ?thesis using subsetD[OF equalityD1[OF assms]]
    apply (intro eq_gterm_by_gposs_gfun_at)
    unfolding gencode_def gposs_glabel gposs_map_gterm gposs_gunions gfun_at_map_gterm gfun_at_glabel
      set_map * by auto
qed

lemma permute_automaton:
  assumes "RRn_spec n A T" "set ps = {0..<n}"
  shows "RRn_spec (length ps) (fmap_funs_ta (λxs. map ((!) xs) ps) A) ((λxs. map ((!) xs) ps) ` T)"
  using assms by (auto simp: RRn_spec_def gencode_permute fmap_funs_gta_lang image_def)


subsection ‹Intersection›

(* intersection is already defined in IsaFoR *)

lemma intersect_automaton:
  assumes "RRn_spec n A T" "RRn_spec n B U"
  shows "RRn_spec n (intersect_ta A B) (T ∩ U)"
proof -
  have *: "ta_lang A ⊆ (term_of_gterm ` UNIV :: (_, _) term set)" for A
    by (auto dest: ta_lang_imp_ground simp: image_def ground_term_to_gtermD)
  have "inj_on gterm_of_term (term_of_gterm ` UNIV)" by (simp add: inj_on_def)
  note [simp] = inj_on_image_Int[OF this * *, of A B]
  have "inj gencode" unfolding inj_def using gdecode_gencode by metis
  note [simp] = image_Int[OF this, of T U]
  show ?thesis using assms
    by (simp add: gta_lang_def_sym RRn_spec_def intersect_ta[of A B] cong[OF gta_lang_def refl, of "intersect_ta _ _"])
qed


subsection ‹Union›

definition union_automaton :: "('p, 'f) ta ⇒ ('q, 'f) ta ⇒ ('p + 'q, 'f) ta" where
  "union_automaton A B = ta.make (Inl ` ta_final A ∪ Inr ` ta_final B)
    (map_ta_rule Inl id ` ta_rules A ∪ map_ta_rule Inr id ` ta_rules B)
    (map_prod Inl Inl ` ta_eps A ∪ map_prod Inr Inr ` ta_eps B)"

lemma swap_union_automaton:
  "fmap_states_ta (case_sum Inr Inl) (union_automaton B A) = union_automaton A B"
  by (simp add: fmap_states_ta_def' union_automaton_def image_Un image_comp case_sum_o_inj
    ta_rule.map_comp prod.map_comp comp_def id_def ac_simps)

lemma union_automaton':
  "gta_lang (union_automaton A B) = gta_lang A ∪ gta_lang B"
proof (intro set_eqI iffI, goal_cases lr rl)
  case (lr t)
  then obtain t' q where q: "t = gterm_of_term t'" "ground t'" "q ∈ ta_final (union_automaton A B)"
    "q ∈ ta_res (union_automaton A B) t'" by (auto simp: gta_lang_def elim!: ta_langE2)
  from q(1,2) have t': "t' = term_of_gterm t" by (auto dest!: ground_term_to_gtermD)
  { fix q A B assume "Inl q ∈ ta_res (union_automaton A B) (term_of_gterm t)"
    then have "q ∈ ta_res A (adapt_vars (term_of_gterm t))"
    proof (induct t arbitrary: q)
      case (GFun f ts)
      obtain sqs sq' where q: "f sqs → sq' ∈ ta_rules (union_automaton A B)"
        "(sq', Inl q) ∈ (ta_eps (union_automaton A B))*" "length sqs = length ts"
        "⋀i. i < length ts ⟹ sqs ! i ∈ ta_res (union_automaton A B) (term_of_gterm (ts ! i))"
        using GFun(2) by auto
      obtain q' where q': "sq' = Inl q'" "(q', q) ∈ (ta_eps A)*" using q(2)
      proof (induct sq' arbitrary: thesis rule: converse_rtrancl_induct)
        case (step sq sq')
        obtain q' where "sq' = Inl q'" "(q', q) ∈ (ta_eps A)*" using step(3) .
        moreover then obtain q'' where "sq = Inl q''" "(q'', q') ∈ ta_eps A"
          using step(1) by (auto simp: union_automaton_def)
        ultimately show ?case by (auto intro!: step(4)[of q''])
      qed auto
      have [simp]: "map_ta_rule f g r = (case r of h qs → q ⇒ (g h) (map f qs) → f q)" for f g r
        by (cases r) auto
      obtain qs where "sqs = map Inl qs" "f qs → q' ∈ ta_rules A" using q(1) q'(1)
        by (auto simp: union_automaton_def split: ta_rule.splits)
      then show ?case using q(3,4) GFun(1)[OF nth_mem, of i "qs ! i" for i] q'(2)
        by (auto intro!: exI[of _ q'] exI[of _ qs])
    qed
  } note * = this
  note this[of _ A B] this[of _ B A, folded swap_union_automaton[of A B]]
    ta_res_fmap_states_ta[of "Inr _" "union_automaton A B" "term_of_gterm t" "case_sum Inr Inl"]
  then show ?case
  proof (cases q)
    case (Inl q')
    then have "q' ∈ ta_final A" using q(3) by (auto simp: union_automaton_def)
    then show ?thesis using q(2,4) *[of q' A B]
      by (auto intro!: UnI1 image_eqI[of _ _ "adapt_vars t'"] ta_langI2[of _ q']
        simp: gterm_of_term_inv'[symmetric] Inl gta_lang_def t')
  next
    case (Inr q')
    then have "q' ∈ ta_final B" using q(3) by (auto simp: union_automaton_def)
    then show ?thesis using q(2,4) *[of q' B A, folded swap_union_automaton[of A B]]
       ta_res_fmap_states_ta[of "Inr q'" "union_automaton A B" "term_of_gterm t" "case_sum Inr Inl"]
      by (auto intro!: UnI2 image_eqI[of _ _ "adapt_vars t'"] ta_langI2[of _ q']
        simp: gterm_of_term_inv'[symmetric] gterm.map_ident t' Inr gta_lang_def)
  qed
next
  case (rl t)
  have "ta_subset (fmap_states_ta Inl A) (union_automaton A B)"
    "ta_subset (fmap_states_ta Inr B) (union_automaton A B)"
    by (force simp: ta_subset_def union_automaton_def fmap_states_ta_def split: ta_rule.splits)+
  note this[THEN ta_lang_mono, THEN image_mono[of _ _ gterm_of_term]]
  then show ?case using rl
    by (auto simp: fmap_ta(2)[of Inl A] fmap_ta(2)[of Inr B] gta_lang_def_sym)
qed

lemma union_automaton:
  assumes "RRn_spec n A T" "RRn_spec n B U"
  shows "RRn_spec n (union_automaton A B) (T ∪ U)"
  using assms by (auto simp: RRn_spec_def union_automaton')


subsection ‹All terms over a signature›

definition term_automaton :: "('f × nat) set ⇒ (unit, 'f) ta" where
  "term_automaton ℱ = ta.make {()} {f (replicate n ()) → () |f n. (f, n) ∈ ℱ} {}"

lemma term_automaton:
  "RR1_spec (term_automaton ℱ) {t. funas_term (term_of_gterm t :: (_, unit) term) ⊆ ℱ}"
  unfolding RR1_spec_def gta_lang_def
proof (intro set_eqI iffI, goal_cases lr rl)
  case (lr t)
  then have "() ∈ ta_res (term_automaton ℱ) (term_of_gterm t)"
    by (auto elim: ta_langE2)
  then show ?case
    by (induct t) (fastforce simp: term_automaton_def set_conv_nth split: if_splits)
next
  case (rl t)
  then have "() ∈ ta_res (term_automaton ℱ) (term_of_gterm t)"
    by (induct t) (auto simp: term_automaton_def UN_subset_iff intro!: exI[of _ "replicate (length _) ()"])
  then show ?case
    by (auto simp: term_automaton_def intro: ta_langI2 intro!: image_eqI[of _ _ "term_of_gterm t"])
qed

(* TODO: this locale is copied from Lisa.LS_Persistence, but should really be shared ... which
 * means it should be moved to a small theory of its own. Note that Lisa.LS_Persistence_Impl
 * also contains some code for dealing with many-sorted signatures that can be shared. *)

(* begin of copy *)
locale many_sorted_terms =
  fixes sigF :: "('f × nat) ⇒ ('t list × 't) option" and sigV :: "'v ⇒ 't option"
  assumes arity: "sigF (f, n) = Some (tys, tr) ⟹ length tys = n"
begin

definition  where
  "ℱ = { fn. sigF fn ≠ None }"
definition 𝒱 where
  "𝒱 = { v. sigV v ≠ None }"

inductive 𝒯α :: "'t ⇒ ('f, 'v) term ⇒ bool" where
  var  [intro]: "sigV x = Some α ⟹ 𝒯α α (Var x)"
| funs [intro]: "sigF (f, length ts) = Some (tys, α) ⟹
                 (∀ i < length ts. 𝒯α (tys ! i) (ts ! i)) ⟹ 𝒯α α (Fun f ts)"
(* end of copy *)

definition term_automaton :: "('t, 'f) ta" where
  "term_automaton = ta.make {tr |f n tys tr. sigF (f, n) = Some (tys, tr)}
    {f tys → tr |f n tys tr. sigF (f, n) = Some (tys, tr)} {}"

lemma ta_res_term_automaton:
  "α ∈ ta_res term_automaton (term_of_gterm t) ⟷ 𝒯α α (term_of_gterm t)"
proof (intro iffI, goal_cases lr rl)
  case lr then show ?case
  proof (induct t arbitrary: α)
    case (GFun f ts)
    obtain qs where "length qs = length ts" "sigF (f, length ts) = Some (qs, α)"
      "⋀i. i < length ts ⟹ qs ! i ∈ ta_res term_automaton (term_of_gterm (ts ! i))"
      using GFun(2) by (auto simp: term_automaton_def arity)
    then show ?case using GFun(1)[OF nth_mem, of i "_ ! i" for i] by auto
  qed
next
  case rl then show ?case
  proof (induct t arbitrary: α)
    case (GFun f ts)
    obtain qs where "sigF (f, length ts) = Some (qs, α)" "length qs = length ts"
      "⋀i. i < length ts ⟹ 𝒯α (qs ! i) (term_of_gterm (ts ! i))"
      using GFun(2) by (auto elim!: 𝒯α.cases simp: arity)
    then show ?case using GFun(1)[OF nth_mem, of i "qs ! i" for i]
      by (auto simp: term_automaton_def intro!: exI[of _ qs])
  qed
qed

lemma term_automaton:
  "RR1_spec term_automaton {t |α t. 𝒯α α (term_of_gterm t)}"
  unfolding RR1_spec_def gta_lang_def
proof (intro set_eqI iffI, goal_cases lr rl)
  case (lr t)
  then obtain α where "α ∈ ta_res term_automaton (term_of_gterm t)"
    by (auto elim: ta_langE2)
  then show ?case by (auto simp: ta_res_term_automaton)
next
  case (rl t)
  then obtain α where "𝒯α α (term_of_gterm t)" by auto
  moreover then have "α ∈ ta_final term_automaton"
    by (cases t) (auto simp: ta_res_term_automaton[symmetric] term_automaton_def elim!: 𝒯α.cases)
  ultimately show ?case by (auto simp: ta_res_term_automaton[symmetric] intro: ta_langI2
    intro!: image_eqI[of _ _ "term_of_gterm t"])
qed

end

end