Theory Observability
section ‹Observability›
text ‹This theory presents the classical algorithm for transforming FSMs into 
      language-equivalent observable FSMs in analogy to the determinisation of nondeterministic
      finite automata.›
theory Observability
imports FSM 
begin
lemma fPow_Pow : "Pow (fset A) = fset (fset |`| fPow A)" 
proof (induction A)
  case empty
  then show ?case by auto
next
  case (insert x A)
  have "Pow (fset (finsert x A)) = Pow (fset A) ∪ (insert x) ` Pow (fset A)"
    by (simp add: Pow_insert) 
  moreover have "fset (fset |`| fPow (finsert x A)) = fset (fset |`| fPow A) ∪ (insert x) ` fset (fset |`| fPow A)"
  proof -
    have "fset |`| ((fPow A) |∪| (finsert x) |`| (fPow A)) = (fset |`| fPow A) |∪| (insert x) |`| (fset |`| fPow A)"
      unfolding fimage_funion
      by fastforce
    moreover have "(fPow (finsert x A)) = (fPow A) |∪| (finsert x) |`| (fPow A)"
      by (simp add: fPow_finsert)
    ultimately show ?thesis
      by auto 
  qed
  ultimately show ?case 
    using insert.IH by simp
qed
lemma fcard_fsubset: "¬ fcard (A |-| (B |∪| C)) < fcard (A |-| B) ⟹ C |⊆| A ⟹ C |⊆| B"
proof (induction C)
  case empty
  then show ?case by auto
next
  case (insert x C)
  then show ?case
    unfolding  finsert_fsubset funion_finsert_right not_less 
  proof -
    assume a1: "fcard (A |-| B) ≤ fcard (A |-| finsert x (B |∪| C))"
    assume "⟦fcard (A |-| B) ≤ fcard (A |-| (B |∪| C)); C |⊆| A⟧ ⟹ C |⊆| B"
    assume a2: "x |∈| A ∧ C |⊆| A"
    have "A |-| (C |∪| finsert x B) = A |-| B ∨ ¬ A |-| (C |∪| finsert x B) |⊆| A |-| B"
      using a1 by (metis (no_types) fcard_seteq funion_commute funion_finsert_right)
    then show "x |∈| B ∧ C |⊆| B"
      using a2 by blast
  qed 
qed
lemma make_observable_transitions_qtrans_helper:
  assumes  "qtrans = ffUnion (fimage (λ q . (let qts = ffilter (λt . t_source t |∈| q) A;
                                           ios = fimage (λ t . (t_input t, t_output t)) qts
                                       in fimage (λ(x,y) . (q,x,y, t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) qts)))) ios)) nexts)"
shows "⋀ t . t |∈| qtrans ⟷ t_source t |∈| nexts ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' . t' |∈| A ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
proof -
  have "fset qtrans = { (q,x,y,q') | q x y q' . q |∈| nexts ∧ q' ≠ {||} ∧ fset q' = t_target ` {t' . t' |∈| A ∧ t_source t' |∈| q ∧ t_input t' = x ∧ t_output t' = y}}"
  proof -
    have "⋀ q . fset (ffilter (λt . t_source t |∈| q) A) = Set.filter (λt . t_source t |∈| q) (fset A)"
      using ffilter.rep_eq assms(1) by auto
    then have "⋀ q . fset (fimage (λ t . (t_input t, t_output t)) (ffilter (λt . t_source t |∈| q) A)) = image (λ t . (t_input t, t_output t)) (Set.filter (λt . t_source t |∈| q) (fset A))"
      by simp
    then have *:"⋀ q . fset (fimage (λ(x,y) . (q,x,y, (t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) (ffilter (λt . t_source t |∈| q) (A))))))) (fimage (λ t . (t_input t, t_output t)) (ffilter (λt . t_source t |∈| q) (A)))) 
                  = image (λ(x,y) . (q,x,y, (t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) (ffilter (λt . t_source t |∈| q) (A))))))) (image (λ t . (t_input t, t_output t)) (Set.filter (λt . t_source t |∈| q) (fset A)))"
      by (metis (no_types, lifting) ffilter.rep_eq fset.set_map)
    
    have **: "⋀ f1 f2 xs ys ys' . (⋀ x . fset (f1 x ys) = (f2 x ys')) ⟹ 
          fset (ffUnion (fimage (λ x . (f1 x ys)) xs)) = (⋃ x ∈ fset xs . (f2 x ys'))"
      unfolding ffUnion.rep_eq fimage.rep_eq by force
    have "fset (ffUnion (fimage (λ q . (fimage (λ(x,y) . (q,x,y, (t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) (ffilter (λt . t_source t |∈| q) (A))))))) (fimage (λ t . (t_input t, t_output t)) (ffilter (λt . t_source t |∈| q) (A))))) nexts)) 
              = (⋃ q ∈ fset nexts . image (λ(x,y) . (q,x,y, (t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) (ffilter (λt . t_source t |∈| q) (A))))))) (image (λ t . (t_input t, t_output t)) (Set.filter (λt . t_source t |∈| q) (fset A))))"
      unfolding ffUnion.rep_eq fimage.rep_eq
      using "*" by force
    also have "… = { (q,x,y,q') | q x y q' . q |∈| nexts ∧ q' ≠ {||} ∧ fset q' = t_target ` {t' . t' |∈| A ∧ t_source t' |∈| q ∧ t_input t' = x ∧ t_output t' = y}}"
    (is "?A = ?B") proof -
      have "⋀ t . t ∈ ?A ⟹ t ∈ ?B"
      proof -
        fix t assume "t ∈ ?A"
        then obtain q where "q ∈ fset nexts"
                      and   "t ∈ image (λ(x,y) . (q,x,y, (t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) (ffilter (λt . t_source t |∈| q) (A))))))) (image (λ t . (t_input t, t_output t)) (Set.filter (λt . t_source t |∈| q) (fset A)))"
          by blast
        then obtain x y q' where *: "(x,y) ∈ (image (λ t . (t_input t, t_output t)) (Set.filter (λt . t_source t |∈| q) (fset A)))"
                           and      "t = (q,x,y,q')"
                           and   **:"q' = (t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) (ffilter (λt . t_source t |∈| q) (A)))))"
          by force
        
        have "q |∈| nexts" 
          using ‹q ∈ fset nexts›
          by simp 
        moreover have "q' ≠ {||}"
        proof -
          have ***:"(Set.filter (λt . t_source t |∈| q) (fset A)) = fset (ffilter (λt . t_source t |∈| q) (A))"
            by auto
          have "∃ t . t |∈| (ffilter (λt. t_source t |∈| q) A) ∧ (t_input t, t_output t) = (x,y)"
            using *
            by (metis (no_types, lifting) "***" image_iff) 
          then show ?thesis unfolding **
            by force 
        qed
        moreover have "fset q' = t_target ` {t' . t' |∈| A ∧ t_source t' |∈| q ∧ t_input t' = x ∧ t_output t' = y}"
        proof -
          have "{t' . t' |∈| A ∧ t_source t' |∈| q ∧ t_input t' = x ∧ t_output t' = y} = ((Set.filter (λt . (t_input t, t_output t) = (x,y)) (fset (ffilter (λt . t_source t |∈| q) (A)))))"
             by fastforce
          also have "… = fset ((ffilter (λt . (t_input t, t_output t) = (x,y)) (ffilter (λt . t_source t |∈| q) (A))))"
            by fastforce
          finally have "{t' . t' |∈| A ∧ t_source t' |∈| q ∧ t_input t' = x ∧ t_output t' = y} = fset ((ffilter (λt . (t_input t, t_output t) = (x,y)) (ffilter (λt . t_source t |∈| q) (A))))" .
          then show ?thesis
            unfolding **
            by simp  
        qed
        ultimately show "t ∈ ?B"
          unfolding ‹t = (q,x,y,q')› 
          by blast
      qed
      moreover have "⋀ t . t ∈ ?B ⟹ t ∈ ?A" 
      proof -
        fix t assume "t ∈ ?B"
        then obtain q x y q' where "t = (q,x,y,q')" and "(q,x,y,q') ∈ ?B" by force
        then have "q |∈| nexts" 
             and  "q' ≠ {||}" 
             and  *: "fset q' = t_target ` {t' . t' |∈| A ∧ t_source t' |∈| q ∧ t_input t' = x ∧ t_output t' = y}"
          by force+
        then have "fset q' ≠ {}"
          by (metis bot_fset.rep_eq fset_inject)
        have "(x,y) ∈ (image (λ t . (t_input t, t_output t)) (Set.filter (λt . t_source t |∈| q) (fset A)))"
          using ‹fset q' ≠ {}› unfolding * Set.filter_def by blast
        moreover have "q' = t_target |`| ffilter (λt. (t_input t, t_output t) = (x, y)) (ffilter (λt. t_source t |∈| q) A)"
        proof -
          have "{t' . t' |∈| A ∧ t_source t' |∈| q ∧ t_input t' = x ∧ t_output t' = y} = ((Set.filter (λt . (t_input t, t_output t) = (x,y)) (fset (ffilter (λt . t_source t |∈| q) (A)))))"
            by fastforce
          also have "… = fset ((ffilter (λt . (t_input t, t_output t) = (x,y)) (ffilter (λt . t_source t |∈| q) (A))))"
            by fastforce
          finally have ***:"{t' . t' |∈| A ∧ t_source t' |∈| q ∧ t_input t' = x ∧ t_output t' = y} = fset ((ffilter (λt . (t_input t, t_output t) = (x,y)) (ffilter (λt . t_source t |∈| q) (A))))" .
          
          show ?thesis
            using * 
            unfolding ***
            by (metis (no_types, lifting) fimage.rep_eq fset_inject)
        qed 
        ultimately show "t ∈ ?A"
          using ‹q |∈| nexts›
          unfolding ‹t = (q,x,y,q')› 
          by force
      qed
      ultimately show ?thesis
        by (metis (no_types, lifting) Collect_cong Sup_set_def mem_Collect_eq) 
    qed
    finally show ?thesis 
      unfolding assms Let_def by blast
  qed
  then show "⋀ t . t |∈| qtrans ⟷ t_source t |∈| nexts ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' . t' |∈| A ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
    by force
qed
function make_observable_transitions :: "('a,'b,'c) transition fset ⇒ 'a fset fset ⇒ 'a fset fset ⇒ ('a fset × 'b × 'c × 'a fset) fset ⇒ ('a fset × 'b × 'c × 'a fset) fset" where
  "make_observable_transitions base_trans nexts dones ts = (let
            qtrans = ffUnion (fimage (λ q . (let qts = ffilter (λt . t_source t |∈| q) base_trans;
                                         ios = fimage (λ t . (t_input t, t_output t)) qts
                                     in fimage (λ(x,y) . (q,x,y, t_target |`| (ffilter (λt . (t_input t, t_output t) = (x,y)) qts))) ios)) nexts);
            dones' = dones |∪| nexts;
            ts' = ts |∪| qtrans;
            nexts' = (fimage t_target qtrans) |-| dones' 
          in if nexts' = {||}
            then ts'
            else make_observable_transitions base_trans nexts' dones' ts')"
  by auto
termination 
proof -
  {
    fix base_trans :: "('a,'b,'c) transition fset"
    fix nexts :: "'a fset fset" 
    fix dones :: "'a fset fset" 
    fix ts    :: "('a fset × 'b × 'c × 'a fset) fset"
    fix q x y q'
  
    assume assm1: "¬ fcard
             (fPow (t_source |`| base_trans |∪| t_target |`| base_trans) |-|
              (dones |∪| nexts |∪|
               t_target |`|
               ffUnion
                ((λq. let qts = ffilter (λt. t_source t |∈| q) base_trans
                       in ((λ(x, y). (q, x, y, t_target |`| ffilter (λt. t_input t = x ∧ t_output t = y) qts)) ∘ (λt. (t_input t, t_output t))) |`|
                          qts) |`|
                 nexts)))
            < fcard (fPow (t_source |`| base_trans |∪| t_target |`| base_trans) |-| (dones |∪| nexts))"
  
    and assm2: "(q, x, y, q') |∈|
         ffUnion
          ((λq. let qts = ffilter (λt. t_source t |∈| q) base_trans
                 in ((λ(x, y). (q, x, y, t_target |`| ffilter (λt. t_input t = x ∧ t_output t = y) qts)) ∘ (λt. (t_input t, t_output t))) |`| qts) |`|
           nexts)"
  
    and assm3: "q' |∉| nexts"
  
  
  
    define qtrans where qtrans_def: "qtrans = ffUnion (fimage (λ q . (let qts = ffilter (λt . t_source t |∈| q) base_trans;
                                             ios = fimage (λ t . (t_input t, t_output t)) qts
                                         in fimage (λ(x,y) . (q,x,y, t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) qts)))) ios)) nexts)"
    
    have qtrans_prop: "⋀ t . t |∈| qtrans ⟷ t_source t |∈| nexts ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' | t' . t' |∈| base_trans ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
      using make_observable_transitions_qtrans_helper[OF qtrans_def]
      by presburger
  
    have "⋀ t . t |∈| qtrans ⟹ t_target t |∈| fPow (t_target |`| base_trans)"
    proof -
      fix t assume "t |∈| qtrans"
      then have *: "fset (t_target t) = t_target ` {t' . t' |∈| base_trans ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
        using qtrans_prop by blast
      then have "fset (t_target t) ⊆ t_target ` (fset base_trans)"
        by (metis (mono_tags, lifting) imageI image_Collect_subsetI)
      then show "t_target t |∈| fPow (t_target |`| base_trans)"
        by (simp add: less_eq_fset.rep_eq) 
    qed
    then have "t_target |`| qtrans |⊆| (fPow (t_source |`| base_trans |∪| t_target |`| base_trans))"
      by fastforce
    moreover have " ¬ fcard (fPow (t_source |`| base_trans |∪| t_target |`| base_trans) |-| (dones |∪| nexts |∪| t_target |`| qtrans))
                  < fcard (fPow (t_source |`| base_trans |∪| t_target |`| base_trans) |-| (dones |∪| nexts))"
      using assm1 unfolding qtrans_def by force
    ultimately have "t_target |`| qtrans |⊆| dones |∪| nexts" 
      using fcard_fsubset by fastforce
    moreover have "q' |∈| t_target |`| qtrans"
      using assm2 unfolding qtrans_def by force
    ultimately have "q' |∈| dones"
      using ‹q' |∉| nexts› by blast
  } note t = this
  show ?thesis
    apply (relation "measure (λ (base_trans, nexts, dones, ts) . fcard ((fPow (t_source |`| base_trans |∪| t_target |`| base_trans)) |-| (dones |∪| nexts)))")
    apply auto
    by (erule t)
qed
lemma make_observable_transitions_mono: "ts |⊆| (make_observable_transitions base_trans nexts dones ts)"
proof (induction rule: make_observable_transitions.induct[of "λ base_trans nexts dones ts . ts |⊆| (make_observable_transitions base_trans nexts dones ts)"])
  case (1 base_trans nexts dones ts)
  define qtrans where qtrans_def: "qtrans = ffUnion (fimage (λ q . (let qts = ffilter (λt . t_source t |∈| q) base_trans;
                                           ios = fimage (λ t . (t_input t, t_output t)) qts
                                       in fimage (λ(x,y) . (q,x,y, t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) qts)))) ios)) nexts)"
  
  have qtrans_prop: "⋀ t . t |∈| qtrans ⟷ t_source t |∈| nexts ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' | t' . t' |∈| base_trans ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
    using make_observable_transitions_qtrans_helper[OF qtrans_def]
    by presburger
  let ?dones' = "dones |∪| nexts"
  let ?ts'    = "ts |∪| qtrans"
  let ?nexts' = "(fimage t_target qtrans) |-| ?dones'"
  have res_cases: "make_observable_transitions base_trans nexts dones ts = (if ?nexts' = {||} 
            then ?ts'
            else make_observable_transitions base_trans ?nexts' ?dones' ?ts')"
    unfolding make_observable_transitions.simps[of base_trans nexts dones ts] qtrans_def Let_def by simp
  show ?case proof (cases "?nexts' = {||}")
    case True
    then show ?thesis using res_cases by simp
  next
    case False
    then have "make_observable_transitions base_trans nexts dones ts = make_observable_transitions base_trans ?nexts' ?dones' ?ts'"
      using res_cases by simp
    moreover have "ts |∪| qtrans |⊆| make_observable_transitions base_trans ?nexts' ?dones' ?ts'"
      using "1"[OF qtrans_def _ _ _ False, of ?dones' ?ts'] by blast
    ultimately show ?thesis 
      by blast
  qed
qed
inductive pathlike :: "('state, 'input, 'output) transition fset ⇒ 'state ⇒ ('state, 'input, 'output) path ⇒ bool" 
  where
  nil[intro!] : "pathlike ts q []" |
  cons[intro!] : "t |∈| ts ⟹ pathlike ts (t_target t) p ⟹ pathlike ts (t_source t) (t#p)"
inductive_cases pathlike_nil_elim[elim!]: "pathlike ts q []"
inductive_cases pathlike_cons_elim[elim!]: "pathlike ts q (t#p)"
lemma make_observable_transitions_t_source :
  assumes "⋀ t . t |∈| ts ⟹ t_source t |∈| dones ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' . t' |∈| base_trans ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
  and     "⋀ q t' . q |∈| dones ⟹ t' |∈| base_trans ⟹ t_source t' |∈| q ⟹ ∃ t . t |∈| ts ∧ t_source t = q ∧ t_input t = t_input t' ∧ t_output t = t_output t'"
  and     "t |∈| make_observable_transitions base_trans ((fimage t_target ts) |-| dones) dones ts"
  and     "t_source t |∈| dones"
shows "t |∈| ts"
using assms proof (induction base_trans "(fimage t_target ts) |-| dones" dones ts rule: make_observable_transitions.induct)
  case (1 base_trans dones ts)
  let ?nexts = "(fimage t_target ts) |-| dones"
  define qtrans where qtrans_def: "qtrans = ffUnion (fimage (λ q . (let qts = ffilter (λt . t_source t |∈| q) base_trans;
                                           ios = fimage (λ t . (t_input t, t_output t)) qts
                                       in fimage (λ(x,y) . (q,x,y, t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) qts)))) ios)) ?nexts)"
  
  have qtrans_prop: "⋀ t . t |∈| qtrans ⟷ t_source t |∈| ?nexts ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' . t' |∈| base_trans ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
    using make_observable_transitions_qtrans_helper[OF qtrans_def]
    by presburger
  let ?dones' = "dones |∪| ?nexts"
  let ?ts'    = "ts |∪| qtrans"
  let ?nexts' = "(fimage t_target qtrans) |-| ?dones'"
  have res_cases: "make_observable_transitions base_trans ?nexts dones ts = (if ?nexts' = {||} 
            then ?ts'
            else make_observable_transitions base_trans ?nexts' ?dones' ?ts')"
    unfolding make_observable_transitions.simps[of base_trans ?nexts dones ts] qtrans_def Let_def by simp
  show ?case proof (cases "?nexts' = {||}")
    case True
    then have "make_observable_transitions base_trans ?nexts dones ts = ?ts'"
      using res_cases by auto
    then have "t |∈| ts |∪| qtrans"
      using ‹t |∈| make_observable_transitions base_trans ?nexts dones ts› ‹t_source t |∈| dones› by blast
    then show ?thesis 
      using qtrans_prop "1.prems"(3,4) by blast 
  next
    case False
    then have "make_observable_transitions base_trans ?nexts dones ts = make_observable_transitions base_trans ?nexts' ?dones' ?ts'"
      using res_cases by simp
    have i1: "(⋀t. t |∈| ts |∪| qtrans ⟹
                t_source t |∈| dones |∪| ?nexts ∧
                t_target t ≠ {||} ∧
                fset (t_target t) =
                t_target `
                {t' . t' |∈| base_trans ∧
                 t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t})"
      using "1.prems"(1) qtrans_prop by blast
    have i3: "t_target |`| qtrans |-| (dones |∪| ?nexts) = t_target |`| (ts |∪| qtrans) |-| (dones |∪| ?nexts)"
      unfolding "1.prems"(3) by blast 
  
    have i2: "(⋀q t'.
                  q |∈| dones |∪| ?nexts ⟹
                  t' |∈| base_trans ⟹
                  t_source t' |∈| q ⟹
                  ∃t. t |∈| ts |∪| qtrans ∧ t_source t = q ∧ t_input t = t_input t' ∧ t_output t = t_output t')"
    proof -
      fix q t' assume "q |∈| dones |∪| ?nexts"
               and    *:"t' |∈| base_trans"
               and    **:"t_source t' |∈| q"
  
      then consider (a) "q |∈| dones" | (b) "q |∈| ?nexts" by blast
      then show "∃t. t |∈| ts |∪| qtrans ∧ t_source t = q ∧ t_input t = t_input t' ∧ t_output t = t_output t'" 
      proof cases
        case a
        then show ?thesis using * **
          using "1.prems"(2) by blast
      next
        case b
  
        let ?tgts = "{t'' . t'' |∈| base_trans ∧ t_source t'' |∈| q ∧ t_input t'' = t_input t' ∧ t_output t'' = t_output t'}"
        define tgts where tgts: "tgts = Abs_fset (t_target ` ?tgts)"
      
        have "?tgts ⊆ fset base_trans"
          by fastforce
        then have "finite (t_target ` ?tgts)"
          by (meson finite_fset finite_imageI finite_subset) 
        then have "fset tgts = (t_target ` ?tgts)"
          unfolding tgts 
          using Abs_fset_inverse
          by blast
        have "?tgts ≠ {}"
          using * ** by blast
        then have "t_target ` ?tgts ≠ {}"
          by blast
        then have "tgts ≠ {||}" 
          using ‹fset tgts = (t_target ` ?tgts)›
          by force 
        then have "(q, t_input t', t_output t', tgts) |∈| qtrans"
          using b 
          unfolding qtrans_prop[of "(q,t_input t',t_output t',tgts)"]
          unfolding fst_conv snd_conv 
          unfolding ‹fset tgts = (t_target ` ?tgts)›[symmetric]
          by blast
        then show ?thesis
          by auto
      qed
    qed
    have "t |∈| make_observable_transitions base_trans ?nexts dones ts ⟹ t_source t |∈| dones |∪| ?nexts ⟹ t |∈| ts |∪| qtrans"
      unfolding ‹make_observable_transitions base_trans ?nexts dones ts = make_observable_transitions base_trans ?nexts' ?dones' ?ts'›
      using "1.hyps"[OF qtrans_def _ _ _ _ i1 i2] False i3 by force
    then have "t |∈| ts |∪| qtrans"
      using ‹t |∈| make_observable_transitions base_trans ?nexts dones ts› ‹t_source t |∈| dones› by blast
    then show ?thesis 
      using qtrans_prop "1.prems"(3,4) by blast 
  qed
qed
  
lemma make_observable_transitions_path :
  assumes "⋀ t . t |∈| ts ⟹ t_source t |∈| dones ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' ∈ transitions M . t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
  and     "⋀ q t' . q |∈| dones ⟹ t' ∈ transitions M ⟹ t_source t' |∈| q ⟹ ∃ t . t |∈| ts ∧ t_source t = q ∧ t_input t = t_input t' ∧ t_output t = t_output t'"
  and     "⋀ q . q |∈| (fimage t_target ts) |-| dones ⟹ q |∈| fPow (t_source |`| ftransitions M |∪| t_target |`| ftransitions M)"
  and     "⋀ q . q |∈| dones ⟹ q |∈| fPow (t_source |`| ftransitions M |∪| t_target |`| ftransitions M |∪| {|initial M|})"
  and     "{||} |∉| dones"
  and     "q |∈| dones"
shows "(∃ q' p . q' |∈| q ∧ path M q' p ∧ p_io p = io) ⟷ (∃ p'. pathlike (make_observable_transitions (ftransitions M) ((fimage t_target ts) |-| dones) dones ts) q p' ∧ p_io p' = io)"
using assms proof (induction "ftransitions M" "(fimage t_target ts) |-| dones" dones ts  arbitrary: q io rule: make_observable_transitions.induct)
  case (1 dones ts q)
  let ?obs = "(make_observable_transitions (ftransitions M) ((fimage t_target ts) |-| dones) dones ts)"
  let ?nexts = "(fimage t_target ts) |-| dones"
  show ?case proof (cases io)
    case Nil
    have scheme: "⋀ q q' X . q' |∈| q ⟹ q |∈| fPow X ⟹ q' |∈| X"
      by (simp add: fsubsetD)
    obtain q' where "q' |∈| q" 
      using ‹{||} |∉| dones› ‹q |∈| dones›
      by (metis all_not_in_conv bot_fset.rep_eq fset_cong) 
    have "q' |∈| t_source |`| ftransitions M |∪| t_target |`| ftransitions M |∪| {|FSM.initial M|}"
      using scheme[OF ‹q' |∈| q› "1.prems"(4)[OF ‹q |∈| dones›]] .
    then have "q' ∈ states M"
      using ftransitions_source[of q' M]
      using ftransitions_target[of q' M]
      by force
    then have "∃ q' p . q' |∈| q ∧ path M q' p ∧ p_io p = io" 
      using ‹q' |∈| q› Nil by auto
    moreover have "(∃ p'. pathlike ?obs q p' ∧ p_io p' = io)"
      using Nil  by auto
    ultimately show ?thesis 
      by simp
  next
    case (Cons ioT ioP)
    define qtrans where qtrans_def: "qtrans = ffUnion (fimage (λ q . (let qts = ffilter (λt . t_source t |∈| q) (ftransitions M);
                                           ios = fimage (λ t . (t_input t, t_output t)) qts
                                       in fimage (λ(x,y) . (q,x,y, t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) qts)))) ios)) ?nexts)"
  
    have qtrans_prop: "⋀ t . t |∈| qtrans ⟷ t_source t |∈| ?nexts ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' . t' |∈| (ftransitions M) ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
      using make_observable_transitions_qtrans_helper[OF qtrans_def]
      by presburger
    let ?dones' = "dones |∪| ?nexts"
    let ?ts'    = "ts |∪| qtrans"
    let ?nexts' = "(fimage t_target qtrans) |-| ?dones'"
  
    have res_cases: "make_observable_transitions (ftransitions M) ?nexts dones ts = (if ?nexts' = {||} 
              then ?ts'
              else make_observable_transitions (ftransitions M) ?nexts' ?dones' ?ts')"
      unfolding make_observable_transitions.simps[of "ftransitions M" ?nexts dones ts] qtrans_def Let_def by simp
    have i1: "(⋀t. t |∈| ts |∪| qtrans ⟹
                  t_source t |∈| dones |∪| ?nexts ∧
                  t_target t ≠ {||} ∧
                  fset (t_target t) =
                  t_target `
                  {t' ∈ FSM.transitions M .
                   t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t})"
      using "1.prems"(1) qtrans_prop
      using ftransitions_set[of M]
      by (metis (mono_tags, lifting) Collect_cong funion_iff) 
    have i2: "(⋀q t'.
                q |∈| dones |∪| ?nexts ⟹
                t' ∈ FSM.transitions M ⟹
                t_source t' |∈| q ⟹
                ∃t. t |∈| ts |∪| qtrans ∧ t_source t = q ∧ t_input t = t_input t' ∧ t_output t = t_output t')"
    proof -
      fix q t' assume "q |∈| dones |∪| ?nexts"
               and    *:"t' ∈ FSM.transitions M"
               and    **:"t_source t' |∈| q"
  
      then consider (a) "q |∈| dones" | (b) "q |∈| ?nexts" by blast
      then show "∃t. t |∈| ts |∪| qtrans ∧ t_source t = q ∧ t_input t = t_input t' ∧ t_output t = t_output t'" 
      proof cases
        case a
        then show ?thesis using "1.prems"(2) * ** by blast
      next
        case b
  
        let ?tgts = "{t'' ∈ FSM.transitions M. t_source t'' |∈| q ∧ t_input t'' = t_input t' ∧ t_output t'' = t_output t'}"
        have "?tgts ≠ {}"
          using * ** by blast
        let ?tgts = "{t'' . t'' |∈| ftransitions M ∧ t_source t'' |∈| q ∧ t_input t'' = t_input t' ∧ t_output t'' = t_output t'}"
        define tgts where tgts: "tgts = Abs_fset (t_target ` ?tgts)"
        have "?tgts ⊆ transitions M"
          using ftransitions_set[of M]
          by (metis (no_types, lifting) mem_Collect_eq subsetI)   
        then have "finite (t_target ` ?tgts)"
          by (meson finite_imageI finite_subset fsm_transitions_finite)
        then have "fset tgts = (t_target ` ?tgts)"
          unfolding tgts 
          using Abs_fset_inverse
          by blast
        have "?tgts ≠ {}"
          using * **
          by (metis (mono_tags, lifting) empty_iff ftransitions_set mem_Collect_eq)
        then have "t_target ` ?tgts ≠ {}"
          by blast
        then have "tgts ≠ {||}" 
          using ‹fset tgts = (t_target ` ?tgts)›
          by force 
        then have "(q, t_input t', t_output t', tgts) |∈| qtrans"
          using b 
          unfolding qtrans_prop[of "(q,t_input t',t_output t',tgts)"]
          unfolding fst_conv snd_conv 
          unfolding ‹fset tgts = (t_target ` ?tgts)›[symmetric]
          by blast
        then show ?thesis
          by auto
      qed
    qed
    have i3: "t_target |`| (ts |∪| qtrans) |-| (dones |∪| (t_target |`| ts |-| dones)) = t_target |`| qtrans |-| (dones |∪| (t_target |`| ts |-| dones))"
      by blast  
  
    have i4: "(⋀q. q |∈| t_target |`| (ts |∪| qtrans) |-| (dones |∪| (t_target |`| ts |-| dones)) ⟹
                q |∈| fPow (t_source |`| ftransitions M |∪| t_target |`| ftransitions M))"
    proof -
      fix q assume "q |∈| t_target |`| (ts |∪| qtrans) |-| (dones |∪| (t_target |`| ts |-| dones))"
      then have "q |∈| t_target |`| qtrans"
        by auto
      then obtain t where "t |∈| qtrans" and "t_target t = q"
        by auto
      then have "fset q = t_target ` {t'. t' |∈| ftransitions M ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
        unfolding qtrans_prop by auto
      then have "fset q ⊆ t_target ` transitions M"
        by (metis (no_types, lifting) ftransitions_set image_Collect_subsetI image_eqI)
      then show "q |∈| fPow (t_source |`| ftransitions M |∪| t_target |`| ftransitions M)"
        by (metis (no_types, lifting) fPowI fset.set_map fset_inject ftransitions_set le_supI2 sup.orderE sup.orderI sup_fset.rep_eq)
    qed
  
    have i5: "(⋀q. q |∈| dones |∪| ?nexts ⟹ q |∈| fPow (t_source |`| ftransitions M |∪| t_target |`| ftransitions M |∪| {|initial M|}))"
      using "1.prems"(4,3) qtrans_prop
      by auto
  
    have i7: "{||} |∉| dones |∪| ?nexts"
      using "1.prems" by fastforce
    show ?thesis
    proof (cases "?nexts' ≠ {||}")
      case False
      then have "?obs = ?ts'" 
        using res_cases by auto
      have "⋀ q io . q |∈| ?dones' ⟹ q ≠ {||} ⟹ (∃q' p. q' |∈| q ∧ path M q' p ∧ p_io p = io) ⟷ (∃p'. pathlike ?obs q p' ∧ p_io p' = io)"
      proof -
        fix q io assume "q |∈| ?dones'" and "q ≠ {||}"
        then show "(∃q' p. q' |∈| q ∧ path M q' p ∧ p_io p = io) ⟷ (∃p'. pathlike ?obs q p' ∧ p_io p' = io)"
        proof (induction io arbitrary: q)
          case Nil
          have scheme: "⋀ q q' X . q' |∈| q ⟹ q |∈| fPow X ⟹ q' |∈| X"
            by (simp add: fsubsetD)
          obtain q' where "q' |∈| q" 
            using ‹q ≠ {||}› by fastforce 
          have "q' |∈| t_source |`| ftransitions M |∪| t_target |`| ftransitions M |∪| {|FSM.initial M|}"
            using scheme[OF ‹q' |∈| q› i5[OF ‹q |∈| ?dones'›]] .
          then have "q' ∈ states M"
            using ftransitions_source[of q' M]
            using ftransitions_target[of q' M]
            by force
          then have "∃ q' p . q' |∈| q ∧ path M q' p ∧ p_io p = []" 
            using ‹q' |∈| q› by auto
          moreover have "(∃ p'. pathlike ?obs q p' ∧ p_io p' = [])"
            by auto
          ultimately show ?case
            by simp
        next
          case (Cons ioT ioP)
          have "(∃q' p. q' |∈| q ∧ path M q' p ∧ p_io p = ioT # ioP) ⟹ (∃p'. pathlike ?obs q p' ∧ p_io p' = ioT # ioP)"
          proof -
            assume "∃q' p. q' |∈| q ∧ path M q' p ∧ p_io p = ioT # ioP"
            then obtain q' p where "q' |∈| q" and "path M q' p" and "p_io p = ioT # ioP"
              by meson
              
            then obtain tM pM where "p = tM # pM"
              by auto
            then have "tM ∈ transitions M" and "t_source tM |∈| q"
              using ‹path M q' p› ‹q' |∈| q› by blast+
            then obtain tP where "tP |∈| ts |∪| qtrans" 
                       and   "t_source tP = q" 
                       and   "t_input tP = t_input tM" 
                       and   "t_output tP = t_output tM"
              using Cons.prems i2 by blast
            have "path M (t_target tM) pM" and "p_io pM = ioP"
              using ‹path M q' p› ‹p_io p = ioT # ioP› unfolding ‹p = tM # pM› by auto
            moreover have "t_target tM |∈| t_target tP"
              using i1[OF ‹tP |∈| ts |∪| qtrans›]
              using ‹p = tM # pM› ‹path M q' p› ‹q' |∈| q› 
              unfolding ‹t_input tP = t_input tM› ‹t_output tP = t_output tM› ‹t_source tP = q›
              by fastforce 
            ultimately have "∃q' p. q' |∈| t_target tP ∧ path M q' p ∧ p_io p = ioP"
              using ‹p_io pM = ioP› ‹path M (t_target tM) pM› by blast
            have "t_target tP |∈| dones |∪| (t_target |`| ts |-| dones)"
              using False ‹tP |∈| ts |∪| qtrans› by blast
            moreover have "t_target tP ≠ {||}"
              using i1[OF ‹tP |∈| ts |∪| qtrans›] by blast
            ultimately obtain pP where "pathlike ?obs (t_target tP) pP" and "p_io pP = ioP"
              using Cons.IH ‹∃q' p. q' |∈| t_target tP ∧ path M q' p ∧ p_io p = ioP› by blast
            then have "pathlike ?obs q (tP#pP)"
              using ‹t_source tP = q› ‹tP |∈| ts |∪| qtrans› ‹?obs = ?ts'› by auto
            moreover have "p_io (tP#pP) = ioT # ioP"
              using ‹t_input tP = t_input tM› ‹t_output tP = t_output tM› ‹p_io p = ioT # ioP› ‹p = tM # pM› ‹p_io pP = ioP› by simp
            ultimately show ?thesis 
              by auto
          qed
          moreover have "(∃p'. pathlike ?obs q p' ∧ p_io p' = ioT # ioP) ⟹ (∃q' p. q' |∈| q ∧ path M q' p ∧ p_io p = ioT # ioP)"
          proof -
            assume "∃p'. pathlike ?obs q p' ∧ p_io p' = ioT # ioP"
            then obtain p' where "pathlike ?ts' q p'" and "p_io p' = ioT # ioP"
              unfolding ‹?obs = ?ts'› by meson
            then obtain tP pP where "p' = tP#pP"
              by auto
      
            then have "t_source tP = q" and "tP |∈| ?ts'"
              using ‹pathlike ?ts' q p'› by auto
            
      
            have "pathlike ?ts' (t_target tP) pP" and "p_io pP = ioP"
              using ‹pathlike ?ts' q p'› ‹p_io p' = ioT # ioP› ‹p' = tP#pP› by auto
            then have "∃p'. pathlike ?ts' (t_target tP) p' ∧ p_io p' = ioP"
              by auto
            moreover have "t_target tP |∈| dones |∪| (t_target |`| ts |-| dones)"
              using False ‹tP |∈| ts |∪| qtrans› by blast
            moreover have "t_target tP ≠ {||}"
              using i1[OF ‹tP |∈| ts |∪| qtrans›] by blast
            ultimately obtain q'' pM where "q'' |∈| t_target tP" and "path M q'' pM" and "p_io pM = ioP"
              using Cons.IH unfolding ‹?obs = ?ts'› by blast
      
            then obtain tM where "t_source tM |∈| q" and "tM ∈ transitions M" and "t_input tM = t_input tP" and "t_output tM = t_output tP" and "t_target tM = q''"
              using i1[OF ‹tP |∈| ts |∪| qtrans›]
              using ‹q'' |∈| t_target tP›  
              unfolding ‹t_source tP = q› by force
            have "path M (t_source tM) (tM#pM)"
              using ‹tM ∈ transitions M› ‹t_target tM = q''› ‹path M q'' pM› by auto
            moreover have "p_io (tM#pM) = ioT # ioP"
              using ‹p_io pM = ioP› ‹t_input tM = t_input tP› ‹t_output tM = t_output tP› ‹p_io p' = ioT # ioP› ‹p' = tP#pP› by auto
            ultimately show ?thesis 
              using ‹t_source tM |∈| q› by meson 
          qed
          ultimately show ?case
            by meson            
        qed
      qed
      then show ?thesis 
        using ‹q |∈| dones› ‹{||} |∉| dones› by blast
    next
      case True
      have "make_observable_transitions (ftransitions M) ?nexts' ?dones' ?ts' = make_observable_transitions (ftransitions M) ?nexts dones ts"
      proof (cases "?nexts' = {||}")
        case True
        then have "?obs = ?ts'"
          using qtrans_def by auto 
        moreover have "make_observable_transitions (ftransitions M) ?nexts' ?dones' ?ts' = ?ts'"
          unfolding make_observable_transitions.simps[of "ftransitions M" ?nexts' ?dones' ?ts']
          unfolding True Let_def by auto
        ultimately show ?thesis 
          by blast
      next 
        case False
        then show ?thesis 
          unfolding make_observable_transitions.simps[of "ftransitions M" ?nexts dones ts] qtrans_def Let_def by auto
      qed
  
      then have IStep: "⋀ q io . q |∈| ?dones' ⟹ 
                          (∃q' p. q' |∈| q ∧ path M q' p ∧ p_io p = io) =
                            (∃p'. pathlike (make_observable_transitions (ftransitions M) ?nexts dones ts) q p' ∧ p_io p' = io)"
        using "1.hyps"[OF qtrans_def _ _ _ _ i1 i2 i4 i5 i7] True
        unfolding i3 
        by presburger
      show ?thesis 
        unfolding ‹io = ioT # ioP› 
      proof
        show "∃q' p. q' |∈| q ∧ path M q' p ∧ p_io p = ioT # ioP ⟹ ∃p'. pathlike ?obs q p' ∧ p_io p' = ioT # ioP"
        proof -
          assume "∃q' p. q' |∈| q ∧ path M q' p ∧ p_io p = ioT # ioP"
          then obtain q' p where "q' |∈| q" and "path M q' p" and "p_io p = ioT # ioP"
            by meson
            
          then obtain tM pM where "p = tM # pM"
            by auto
          then have "tM ∈ transitions M" and "t_source tM |∈| q"
            using ‹path M q' p› ‹q' |∈| q› by blast+
    
          
          then obtain tP where "tP |∈| ts" 
                     and   "t_source tP = q" 
                     and   "t_input tP = t_input tM" 
                     and   "t_output tP = t_output tM"
            using "1.prems"(2,6) by blast
  
          then have i9: "t_target tP |∈| dones |∪| ?nexts"
            by simp
  
          have "path M (t_target tM) pM" and "p_io pM = ioP"
            using ‹path M q' p› ‹p_io p = ioT # ioP› unfolding ‹p = tM # pM› by auto
          moreover have "t_target tM |∈| t_target tP"
            using "1.prems"(1)[OF ‹tP |∈| ts›] ‹p = tM # pM› ‹path M q' p› ‹q' |∈| q›  
            unfolding ‹t_input tP = t_input tM› ‹t_output tP = t_output tM› ‹t_source tP = q›
            by fastforce 
          ultimately have "∃q' p. q' |∈| t_target tP ∧ path M q' p ∧ p_io p = ioP"
            using ‹p_io pM = ioP› ‹path M (t_target tM) pM› by blast
  
          obtain pP where "pathlike ?obs (t_target tP) pP" and "p_io pP = ioP"
            using ‹∃q' p. q' |∈| t_target tP ∧ path M q' p ∧ p_io p = ioP› unfolding IStep[OF i9] 
            using that by blast
          
          then have "pathlike ?obs q (tP#pP)"
            using ‹t_source tP = q› ‹tP |∈| ts› make_observable_transitions_mono by blast
          moreover have "p_io (tP#pP) = ioT # ioP"
            using ‹t_input tP = t_input tM› ‹t_output tP = t_output tM› ‹p_io p = ioT # ioP› ‹p = tM # pM› ‹p_io pP = ioP› by simp
          ultimately show ?thesis 
            by auto
        qed
        show "∃p'. pathlike ?obs q p' ∧ p_io p' = ioT # ioP ⟹ ∃q' p. q' |∈| q ∧ path M q' p ∧ p_io p = ioT # ioP"
        proof -
          assume "∃p'. pathlike ?obs q p' ∧ p_io p' = ioT # ioP"
          then obtain p' where "pathlike ?obs q p'" and "p_io p' = ioT # ioP"
            by meson
          then obtain tP pP where "p' = tP#pP"
            by auto
          have "⋀ t' . t' |∈| ftransitions M = (t' ∈ transitions M)"
            using ftransitions_set
            by metis
          from ‹p' = tP#pP› have "t_source tP = q" and "tP |∈| ?obs"
            using ‹pathlike ?obs q p'› by auto
          then have "tP |∈| ts"
            using "1.prems"(6) make_observable_transitions_t_source[of ts dones "ftransitions M"] "1.prems"(1,2)
            unfolding ‹⋀ t' . t' |∈| ftransitions M = (t' ∈ transitions M)›
            by blast
          then have i9: "t_target tP |∈| dones |∪| ?nexts"
            by simp           
    
          have "pathlike ?obs (t_target tP) pP" and "p_io pP = ioP"
            using ‹pathlike ?obs q p'› ‹p_io p' = ioT # ioP› ‹p' = tP#pP› by auto
          then have "∃p'. pathlike ?obs (t_target tP) p' ∧ p_io p' = ioP"
            by auto
          then obtain q'' pM where "q'' |∈| t_target tP" and "path M q'' pM" and "p_io pM = ioP"
            using IStep[OF i9] by blast
          obtain tM where "t_source tM |∈| q" and "tM ∈ transitions M" and "t_input tM = t_input tP" and "t_output tM = t_output tP" and "t_target tM = q''"
            using "1.prems"(1)[OF ‹tP |∈| ts›] ‹q'' |∈| t_target tP› 
            unfolding ‹t_source tP = q› 
            by force
          have "path M (t_source tM) (tM#pM)"
            using ‹tM ∈ transitions M› ‹t_target tM = q''› ‹path M q'' pM› by auto
          moreover have "p_io (tM#pM) = ioT # ioP"
            using ‹p_io pM = ioP› ‹t_input tM = t_input tP› ‹t_output tM = t_output tP› ‹p_io p' = ioT # ioP› ‹p' = tP#pP› by auto
          ultimately show ?thesis 
            using ‹t_source tM |∈| q› by meson 
        qed
      qed
    qed
  qed
qed
fun observable_fset :: "('a,'b,'c) transition fset ⇒ bool" where
  "observable_fset ts = (∀ t1 t2 . t1 |∈| ts ⟶ t2 |∈| ts ⟶ 
                          t_source t1 = t_source t2 ⟶ t_input t1 = t_input t2 ⟶ t_output t1 = t_output t2
                            ⟶ t_target t1 = t_target t2)" 
lemma make_observable_transitions_observable :
  assumes "⋀ t . t |∈| ts ⟹ t_source t |∈| dones ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' . t' |∈| base_trans ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
  and     "observable_fset ts"
shows "observable_fset (make_observable_transitions base_trans ((fimage t_target ts) |-| dones) dones ts)"
using assms proof (induction base_trans "(fimage t_target ts) |-| dones" dones ts rule: make_observable_transitions.induct)
  case (1 base_trans dones ts)
  let ?nexts = "(fimage t_target ts) |-| dones"
  
  define qtrans where qtrans_def: "qtrans = ffUnion (fimage (λ q . (let qts = ffilter (λt . t_source t |∈| q) base_trans;
                                           ios = fimage (λ t . (t_input t, t_output t)) qts
                                       in fimage (λ(x,y) . (q,x,y, t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) qts)))) ios)) ?nexts)"
  
  have qtrans_prop: "⋀ t . t |∈| qtrans ⟷ t_source t |∈| ?nexts ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' . t' |∈| base_trans ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
    using make_observable_transitions_qtrans_helper[OF qtrans_def]
    by presburger
   
  let ?dones' = "dones |∪| ?nexts"
  let ?ts'    = "ts |∪| qtrans"
  let ?nexts' = "(fimage t_target qtrans) |-| ?dones'"
  have "observable_fset qtrans" 
    using qtrans_prop
    unfolding observable_fset.simps
    by (metis (mono_tags, lifting) Collect_cong fset_inject)  
  moreover have "t_source |`| qtrans |∩| t_source |`| ts = {||}"
    using "1.prems"(1) qtrans_prop by force
  ultimately have "observable_fset ?ts'"
    using "1.prems"(2) unfolding observable_fset.simps
    by blast
  
  have res_cases: "make_observable_transitions base_trans ?nexts dones ts = (if ?nexts' = {||} 
            then ?ts'
            else make_observable_transitions base_trans ?nexts' ?dones' ?ts')"
    unfolding make_observable_transitions.simps[of base_trans ?nexts dones ts] qtrans_def Let_def by simp
  show ?case proof (cases "?nexts' = {||}")
    case True
    then have "make_observable_transitions base_trans ?nexts dones ts = ?ts'"
      using res_cases by simp
    then show ?thesis 
      using ‹observable_fset ?ts'› by simp
  next
    case False
    then have *: "make_observable_transitions base_trans ?nexts dones ts = make_observable_transitions base_trans ?nexts' ?dones' ?ts'"
      using res_cases by simp
    have i1: "(⋀t. t |∈| ts |∪| qtrans ⟹
                  t_source t |∈| dones |∪| ?nexts ∧
                  t_target t ≠ {||} ∧
                  fset (t_target t) =
                  t_target `
                  {t' . t' |∈| base_trans ∧
                   t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t})"
      using "1.prems"(1) qtrans_prop by blast
    have i3: "t_target |`| (ts |∪| qtrans) |-| (dones |∪| (t_target |`| ts |-| dones)) = t_target |`| qtrans |-| (dones |∪| (t_target |`| ts |-| dones))"
      by auto
    have i4: "t_target |`| (ts |∪| qtrans) |-| (dones |∪| (t_target |`| ts |-| dones)) ≠ {||}"
      using False by auto
    show ?thesis
      using "1.hyps"[OF qtrans_def _ _ i3 i4 i1 ‹observable_fset ?ts'›] unfolding * i3 by metis
  qed
qed
lemma make_observable_transitions_transition_props : 
  assumes "⋀ t . t |∈| ts ⟹ t_source t |∈| dones ∧ t_target t |∈| dones |∪| ((fimage t_target ts) |-| dones) ∧ t_input t |∈| t_input |`| base_trans ∧ t_output t |∈| t_output |`| base_trans"
  assumes "t |∈| make_observable_transitions base_trans ((fimage t_target ts) |-| dones) dones ts"
shows "t_source t |∈| dones |∪| (t_target |`| (make_observable_transitions base_trans ((fimage t_target ts) |-| dones) dones ts))"  
    and "t_target t |∈| dones |∪| (t_target |`| (make_observable_transitions base_trans ((fimage t_target ts) |-| dones) dones ts))" 
    and "t_input t |∈| t_input |`| base_trans"
    and "t_output t |∈| t_output |`| base_trans"  
proof -
  have "t_source t |∈| dones |∪| (t_target |`| (make_observable_transitions base_trans ((fimage t_target ts) |-| dones) dones ts))
          ∧ t_target t |∈| dones |∪| (t_target |`| (make_observable_transitions base_trans ((fimage t_target ts) |-| dones) dones ts)) 
          ∧ t_input t |∈| t_input |`| base_trans 
          ∧ t_output t |∈| t_output |`| base_trans"
    using assms(1,2)
  proof (induction base_trans "((fimage t_target ts) |-| dones)" dones ts rule: make_observable_transitions.induct)
    case (1 base_trans dones ts)
    let ?nexts = "((fimage t_target ts) |-| dones)"
    define qtrans where qtrans_def: "qtrans = ffUnion (fimage (λ q . (let qts = ffilter (λt . t_source t |∈| q) base_trans;
                                           ios = fimage (λ t . (t_input t, t_output t)) qts
                                       in fimage (λ(x,y) . (q,x,y, t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) qts)))) ios)) ?nexts)"
  
    have qtrans_prop: "⋀ t . t |∈| qtrans ⟷ t_source t |∈| ?nexts ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' . t' |∈| base_trans ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
      using make_observable_transitions_qtrans_helper[OF qtrans_def]
      by presburger
    let ?dones' = "dones |∪| ?nexts"
    let ?ts'    = "ts |∪| qtrans"
    let ?nexts' = "(fimage t_target qtrans) |-| ?dones'"
    have res_cases: "make_observable_transitions base_trans ?nexts dones ts = (if ?nexts' = {||} 
              then ?ts'
              else make_observable_transitions base_trans ?nexts' ?dones' ?ts')"
      unfolding make_observable_transitions.simps[of base_trans ?nexts dones ts] qtrans_def Let_def by simp
    have qtrans_trans_prop: "(⋀t. t |∈| qtrans ⟹
                  t_source t |∈| dones |∪| (t_target |`| ts |-| dones) ∧
                  t_target t |∈| dones |∪| (t_target |`| ts |-| dones) |∪| (t_target |`| (ts |∪| qtrans) |-| (dones |∪| (t_target |`| ts |-| dones))) ∧
                  t_input t |∈| t_input |`| base_trans ∧ t_output t |∈| t_output |`| base_trans)" (is "⋀ t . t |∈| qtrans ⟹ ?P t")
    proof -
      fix t assume "t |∈| qtrans"
      then have "t_source t |∈| dones |∪| (t_target |`| ts |-| dones)"
        using ‹t |∈| qtrans› unfolding qtrans_prop[of t] by blast
      moreover have "t_target t |∈| dones |∪| (t_target |`| ts |-| dones) |∪| (t_target |`| (ts |∪| qtrans) |-| (dones |∪| (t_target |`| ts |-| dones)))" 
        using ‹t |∈| qtrans› "1.prems"(1) by blast
      moreover have "t_input t |∈| t_input |`| base_trans ∧ t_output t |∈| t_output |`| base_trans"
      proof -
        obtain t' where "t' ∈ {t'. t' |∈| base_trans ∧ t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
          using ‹t |∈| qtrans› unfolding qtrans_prop[of t]
          by (metis (mono_tags, lifting) Collect_empty_eq bot_fset.rep_eq empty_is_image fset_inject mem_Collect_eq) 
        then show ?thesis
          by force
      qed
      ultimately show "?P t" 
        by blast
    qed    
    
    show ?case proof (cases "?nexts' = {||}")
      case True
      then have "t |∈| ?ts'"
        using "1.prems"(2) res_cases by force
      then show ?thesis
        using "1.prems"(1) qtrans_trans_prop
        by (metis True fimage_funion funion_fminus_cancel funion_iff res_cases) 
    next
      case False
      then have *: "make_observable_transitions base_trans ?nexts dones ts = make_observable_transitions base_trans ?nexts' ?dones' ?ts'"
        using res_cases by simp
      have i1: "t_target |`| (ts |∪| qtrans) |-| (dones |∪| (t_target |`| ts |-| dones)) = t_target |`| qtrans |-| (dones |∪| (t_target |`| ts |-| dones))"
        by blast
      have i2: "t_target |`| (ts |∪| qtrans) |-| (dones |∪| (t_target |`| ts |-| dones)) ≠ {||}"
        using False by blast
      have i3: "(⋀t. t |∈| ts |∪| qtrans ⟹
                  t_source t |∈| dones |∪| (t_target |`| ts |-| dones) ∧
                  t_target t |∈| dones |∪| (t_target |`| ts |-| dones) |∪| (t_target |`| (ts |∪| qtrans) |-| (dones |∪| (t_target |`| ts |-| dones))) ∧
                  t_input t |∈| t_input |`| base_trans ∧ t_output t |∈| t_output |`| base_trans)"
        using "1.prems"(1) qtrans_trans_prop by blast
      have i4: "t |∈| make_observable_transitions base_trans (t_target |`| (ts |∪| qtrans) |-| (dones |∪| (t_target |`| ts |-| dones))) (dones |∪| (t_target |`| ts |-| dones)) (ts |∪| qtrans)"
        using "1.prems"(2) unfolding * i1 by assumption
      show ?thesis
        using "1.hyps"[OF qtrans_def _ _ i1 i2 i3 i4] unfolding  i1 unfolding *[symmetric]
        using make_observable_transitions_mono[of ts base_trans ?nexts dones] by blast
    qed
  qed
  then show "t_source t |∈| dones |∪| (t_target |`| (make_observable_transitions base_trans ((fimage t_target ts) |-| dones) dones ts))"  
        and "t_target t |∈| dones |∪| (t_target |`| (make_observable_transitions base_trans ((fimage t_target ts) |-| dones) dones ts))" 
        and "t_input t |∈| t_input |`| base_trans"
        and "t_output t |∈| t_output |`| base_trans" 
    by blast+
qed
fun make_observable :: "('a :: linorder,'b :: linorder,'c :: linorder) fsm ⇒ ('a fset,'b,'c) fsm" where
  "make_observable M = (let
      initial_trans = (let qts = ffilter (λt . t_source t = initial M) (ftransitions M);
                           ios = fimage (λ t . (t_input t, t_output t)) qts
                       in fimage (λ(x,y) . ({|initial M|},x,y, t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) qts)))) ios);
      nexts = fimage t_target initial_trans |-| {|{|initial M|}|};
      ptransitions = make_observable_transitions (ftransitions M) nexts {|{|initial M|}|} initial_trans;
      pstates = finsert {|initial M|} (t_target |`| ptransitions);
      M' = create_unconnected_fsm_from_fsets {|initial M|} pstates (finputs M) (foutputs M)
  in add_transitions M' (fset ptransitions))"
lemma make_observable_language_observable :
  shows "L (make_observable M) = L M"
    and "observable (make_observable M)"
    and "initial (make_observable M) = {|initial M|}"
    and "inputs (make_observable M) = inputs M"
    and "outputs (make_observable M) = outputs M"
proof -
  define initial_trans where "initial_trans = (let qts = ffilter (λt . t_source t = initial M) (ftransitions M);
                                 ios = fimage (λ t . (t_input t, t_output t)) qts
                             in fimage (λ(x,y) . ({|initial M|},x,y, t_target |`| ((ffilter (λt . (t_input t, t_output t) = (x,y)) qts)))) ios)"
  moreover define ptransitions where "ptransitions = make_observable_transitions (ftransitions M) (fimage t_target initial_trans |-| {|{|initial M|}|}) {|{|initial M|}|} initial_trans"
  moreover define pstates where "pstates = finsert {|initial M|} (t_target |`| ptransitions)"
  moreover define M' where "M' = create_unconnected_fsm_from_fsets {|initial M|} pstates (finputs M) (foutputs M)"
  ultimately have "make_observable M = add_transitions M' (fset ptransitions)"
    unfolding make_observable.simps Let_def by blast
  have "{|initial M|} |∈| pstates"
    unfolding pstates_def by blast
  have "inputs M' = inputs M"
    unfolding M'_def create_unconnected_fsm_from_fsets_simps(3)[OF ‹{|initial M|} |∈| pstates›, of "finputs M" "foutputs M"]
    using fset_of_list.rep_eq inputs_as_list_set by fastforce 
  have "outputs M' = outputs M"
    unfolding M'_def create_unconnected_fsm_from_fsets_simps(4)[OF ‹{|initial M|} |∈| pstates›, of "finputs M" "foutputs M"]
    using fset_of_list.rep_eq outputs_as_list_set by fastforce 
  have "states M' = fset pstates" and "transitions M' = {}" and "initial M' = {|initial M|}"
     unfolding M'_def create_unconnected_fsm_from_fsets_simps(1,2,5)[OF ‹{|initial M|} |∈| pstates›] by simp+
  have initial_trans_prop: "⋀ t . t |∈| initial_trans ⟷ t_source t |∈| {|{|FSM.initial M|}|} ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' ∈ transitions M . t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
  proof -
    have *:"⋀ t' . t' |∈| ftransitions M = (t' ∈ transitions M)"
            using ftransitions_set
            by metis 
    have **: "initial_trans = ffUnion (fimage (λ q . (let qts = ffilter (λt . t_source t |∈| q) (ftransitions M);
                                           ios = fimage (λ t . (t_input t, t_output t)) qts
                                       in fimage (λ(x,y) . (q,x,y, t_target |`| (ffilter (λt . (t_input t, t_output t) = (x,y)) qts))) ios)) {|{|initial M|}|})"
      unfolding initial_trans_def by auto
    show "⋀ t . t |∈| initial_trans ⟷ t_source t |∈| {|{|FSM.initial M|}|} ∧ t_target t ≠ {||} ∧ fset (t_target t) = t_target ` {t' ∈ transitions M . t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
      unfolding make_observable_transitions_qtrans_helper[OF **] *
      by presburger
  qed
  have well_formed_transitions: "⋀ t . t ∈ (fset ptransitions) ⟹ t_source t ∈ states M' ∧ t_input t ∈ inputs M' ∧ t_output t ∈ outputs M' ∧ t_target t ∈ states M'"
    (is "⋀ t .  t ∈ (fset ptransitions) ⟹ ?P1 t ∧ ?P2 t ∧ ?P3 t ∧ ?P4 t")
  proof -
    fix t assume "t ∈ (fset ptransitions)"
    then have i2: "t |∈| make_observable_transitions (ftransitions M) (fimage t_target initial_trans |-| {|{|initial M|}|}) {|{|initial M|}|} initial_trans"
      using ptransitions_def
      by metis 
    have i1: "(⋀t. t |∈| initial_trans ⟹
          t_source t |∈| {|{|FSM.initial M|}|} ∧
          t_target t |∈| {|{|FSM.initial M|}|} |∪| (t_target |`| initial_trans |-| {|{|FSM.initial M|}|}) ∧
          t_input t |∈| t_input |`| ftransitions M ∧ t_output t |∈| t_output |`| ftransitions M)" (is "⋀ t . t |∈| initial_trans ⟹ ?P t")
    proof -
      fix t assume *: "t |∈| initial_trans"
      then have "t_source t |∈| {|{|FSM.initial M|}|}" 
           and  "t_target t ≠ {||}" 
           and  "fset (t_target t) = t_target ` {t' ∈ FSM.transitions M. t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}"
        using initial_trans_prop by blast+
      have "t_target t |∈| {|{|FSM.initial M|}|} |∪| (t_target |`| initial_trans |-| {|{|FSM.initial M|}|})"
        using "*" by blast
        
      moreover have "t_input t |∈| t_input |`| ftransitions M ∧ t_output t |∈| t_output |`| ftransitions M"
      proof -
        obtain t' where "t' ∈ transitions M" and "t_input t = t_input t'" and "t_output t = t_output t'"
          using ‹t_target t ≠ {||}› ‹fset (t_target t) = t_target ` {t' ∈ FSM.transitions M. t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t}›
          by (metis (mono_tags, lifting) bot_fset.rep_eq empty_Collect_eq fset_inject image_empty) 
        have "fset (ftransitions M) = transitions M"
          by (simp add: fset_of_list.rep_eq fsm_transitions_finite) 
        then show ?thesis 
          unfolding ‹t_input t = t_input t'› ‹t_output t = t_output t'›
          using ‹t' ∈ transitions M›
          by auto
      qed
        
      ultimately show "?P t" 
        using ‹t_source t |∈| {|{|FSM.initial M|}|}› by blast
    qed 
    have "?P1 t"
      using make_observable_transitions_transition_props(1)[OF i1 i2] unfolding pstates_def ptransitions_def ‹states M' = fset pstates›
      by (metis finsert_is_funion)
    moreover have "?P2 t" 
    proof-
      have "t_input t |∈| t_input |`| ftransitions M"
        using make_observable_transitions_transition_props(3)[OF i1 i2] by blast
      then have "t_input t ∈ t_input ` transitions M"
        using ftransitions_set by (metis (mono_tags, lifting) fset.set_map)
      then show ?thesis
        using finputs_set fsm_transition_input ‹inputs M' = inputs M› by fastforce 
    qed
    moreover have "?P3 t"
    proof-
      have "t_output t |∈| t_output |`| ftransitions M"
        using make_observable_transitions_transition_props(4)[OF i1 i2] by blast
      then have "t_output t ∈ t_output ` transitions M"
        using ftransitions_set by (metis (mono_tags, lifting) fset.set_map)
      then show ?thesis
        using foutputs_set fsm_transition_output ‹outputs M' = outputs M› by fastforce 
    qed
    moreover have "?P4 t"
      using make_observable_transitions_transition_props(2)[OF i1 i2] unfolding pstates_def ptransitions_def ‹states M' = fset pstates›
      by (metis finsert_is_funion)
      
    ultimately show "?P1 t ∧ ?P2 t ∧ ?P3 t ∧ ?P4 t"
      by blast
  qed
  have "initial (make_observable M) = {|initial M|}"
   and "states (make_observable M) = fset pstates"
   and "inputs (make_observable M) = inputs M"
   and "outputs (make_observable M) = outputs M"
   and "transitions (make_observable M) = fset ptransitions"
    using add_transitions_simps[OF well_formed_transitions, of "fset ptransitions"] 
    unfolding ‹make_observable M = add_transitions M' (fset ptransitions)›[symmetric]
              ‹inputs M' = inputs M› ‹outputs M' = outputs M› ‹initial M' = {|initial M|}› ‹states M' = fset pstates› ‹transitions M' = {}›
    by blast+
  then show "initial (make_observable M) = {|initial M|}" and "inputs (make_observable M) = inputs M" and "outputs (make_observable M) = outputs M"
    by presburger+
  have i1: "(⋀t. t |∈| initial_trans ⟹
                    t_source t |∈| {|{|FSM.initial M|}|} ∧
                    t_target t ≠ {||} ∧
                    fset (t_target t) = t_target ` {t' ∈ FSM.transitions M. t_source t' |∈| t_source t ∧ t_input t' = t_input t ∧ t_output t' = t_output t})"
    using initial_trans_prop by blast
  have i2: "(⋀q t'.
                    q |∈| {|{|FSM.initial M|}|} ⟹
                    t' ∈ FSM.transitions M ⟹ t_source t' |∈| q ⟹ ∃t. t |∈| initial_trans ∧ t_source t = q ∧ t_input t = t_input t' ∧ t_output t = t_output t')"
  proof -
    fix q t' assume "q |∈| {|{|FSM.initial M|}|}" and "t' ∈ FSM.transitions M" and "t_source t' |∈| q"
    then have "q = {|FSM.initial M|}" and "t_source t' = initial M" 
      by auto
    define tgt where "tgt =  t_target ` {t'' ∈ FSM.transitions M. t_source t'' |∈| {|FSM.initial M|} ∧ t_input t'' = t_input t' ∧ t_output t'' = t_output t'}"
    have "t_target t' ∈ tgt"
      unfolding tgt_def using ‹t' ∈ FSM.transitions M› ‹t_source t' = initial M› by auto
    then have "tgt ≠ {}"
      by auto
    have "finite tgt"
      using fsm_transitions_finite[of M] unfolding tgt_def by auto
    then have "fset (Abs_fset tgt) = tgt"
      by (simp add: Abs_fset_inverse)
    then have "Abs_fset tgt ≠ {||}"
      using ‹tgt ≠ {}› by auto
    let ?t = "({|FSM.initial M|}, t_input t', t_output t', Abs_fset tgt)"
    have "?t |∈| initial_trans"
      unfolding initial_trans_prop fst_conv snd_conv ‹fset (Abs_fset tgt) = tgt›
      unfolding ‹tgt =  t_target ` {t'' ∈ FSM.transitions M. t_source t'' |∈| {|FSM.initial M|} ∧ t_input t'' = t_input t' ∧ t_output t'' = t_output t'}›[symmetric]
      using ‹Abs_fset tgt ≠ {||}› 
      by blast
    then show "∃t. t |∈| initial_trans ∧ t_source t = q ∧ t_input t = t_input t' ∧ t_output t = t_output t'"
      using ‹q = {|FSM.initial M|}› by auto
  qed
  have i3: "(⋀q. q |∈| t_target |`| initial_trans |-| {|{|FSM.initial M|}|} ⟹ q |∈| fPow (t_source |`| ftransitions M |∪| t_target |`| ftransitions M))"
  proof -
    fix q assume "q |∈| t_target |`| initial_trans |-| {|{|FSM.initial M|}|}"
    then obtain t where "t |∈| initial_trans" and "t_target t = q"
      by auto
    have "fset q ⊆ t_target ` (transitions M)"
      using ‹t |∈| initial_trans›
      unfolding initial_trans_prop ‹t_target t = q›
      by auto
    then have "q |⊆| (t_target |`| ftransitions M)"
      using ftransitions_set[of M]
      by (simp add: less_eq_fset.rep_eq) 
    then show "q |∈| fPow (t_source |`| ftransitions M |∪| t_target |`| ftransitions M)"
      by auto
  qed
  have i4: "(⋀q. q |∈| {|{|FSM.initial M|}|} ⟹ q |∈| fPow (t_source |`| ftransitions M |∪| t_target |`| ftransitions M |∪| {|FSM.initial M|}))"
   and i5: "{||} |∉| {|{|FSM.initial M|}|}"
   and i6: "{|FSM.initial M|} |∈| {|{|FSM.initial M|}|}"
    by blast+
  show "L (make_observable M) = L M"
  proof -
    have *: "⋀ p . pathlike ptransitions {|initial M|} p = path (make_observable M) {|initial M|} p"
    proof 
      have "⋀ q p . p ≠ [] ⟹ pathlike ptransitions q p ⟹ path (make_observable M) q p"
      proof -
        fix q p assume "p ≠ []" and "pathlike ptransitions q p"
        then show "path (make_observable M) q p"
        proof (induction p arbitrary: q)
          case Nil
          then show ?case by blast
        next
          case (Cons t p)
          then have "t |∈| ptransitions" and "pathlike ptransitions (t_target t) p" and "t_source t = q"
            by blast+
    
          have "t ∈ transitions (make_observable M)"
            using ‹t |∈| ptransitions› ‹transitions (make_observable M) = fset ptransitions›
            by metis
          moreover have "path (make_observable M) (t_target t) p"
            using Cons.IH[OF _ ‹pathlike ptransitions (t_target t) p›] calculation by blast
          ultimately show ?case 
            using ‹t_source t = q› by blast
        qed
      qed
      then show "⋀ p . pathlike ptransitions {|initial M|} p ⟹ path (make_observable M) {|initial M|} p"
        by (metis ‹FSM.initial (make_observable M) = {|FSM.initial M|}› fsm_initial path.nil)
      
      show "⋀ q p . path (make_observable M) q p ⟹ pathlike ptransitions q p"
      proof -
        fix q p assume "path (make_observable M) q p"
        then show "pathlike ptransitions q p"
        proof (induction p arbitrary: q rule: list.induct)
          case Nil
          then show ?case by blast
        next
          case (Cons t p)
          then have "t ∈ transitions (make_observable M)" and "path (make_observable M) (t_target t) p" and "t_source t = q"
            by blast+
  
          have "t |∈| ptransitions"
            using ‹t ∈ transitions (make_observable M)› ‹transitions (make_observable M) = fset ptransitions›
            by metis
          then show ?case 
            using Cons.IH[OF ‹path (make_observable M) (t_target t) p›] ‹t_source t = q› by blast          
        qed
      qed
    qed
    
    have "⋀ io . (∃q' p. q' |∈| {|FSM.initial M|} ∧ path M q' p ∧ p_io p = io) = (∃p'. pathlike ptransitions {|FSM.initial M|} p' ∧ p_io p' = io)"
      using make_observable_transitions_path[OF i1 i2 i3 i4 i5 i6] unfolding ptransitions_def[symmetric] by blast
    then have "⋀ io . (∃p. path M (FSM.initial M) p ∧ p_io p = io) = (∃p' . path (make_observable M) {|initial M|} p' ∧ p_io p' = io)"
      unfolding *
      by (metis (no_types, lifting) fempty_iff finsert_iff) 
    then show ?thesis
      unfolding LS.simps ‹initial (make_observable M) = {|initial M|}› by (metis (no_types, lifting)) 
  qed
  show "observable (make_observable M)"
  proof -
    have i2: "observable_fset initial_trans"
      unfolding observable_fset.simps 
      unfolding initial_trans_prop
      using fset_inject
      by metis
    have "⋀ t' . t' |∈| ftransitions M = (t' ∈ transitions M)"
      using ftransitions_set
      by metis 
    have "observable_fset ptransitions"
      using make_observable_transitions_observable[OF _ i2, of "{| {|initial M|} |}" "ftransitions M"] i1 
      unfolding ptransitions_def ‹⋀ t' . t' |∈| ftransitions M = (t' ∈ transitions M)›
      by blast 
    then show ?thesis
      unfolding observable.simps observable_fset.simps ‹transitions (make_observable M) = fset ptransitions›
      by metis 
  qed
qed
end