Theory SPY_Framework

section ‹SPY-Framework›

text ‹This theory defines the SPY-Framework and provides completeness properties.›

theory SPY_Framework
imports H_Framework
begin

subsection ‹Definition of the Framework›

definition spy_framework :: "('a::linorder,'b::linorder,'c::linorder) fsm ⇒ 
                              (('a,'b,'c) fsm ⇒ ('a,'b,'c) state_cover_assignment) ⇒                        
                              (('a,'b,'c) fsm ⇒ ('a,'b,'c) state_cover_assignment ⇒ (('a,'b,'c) fsm ⇒ ('b×'c) prefix_tree ⇒ 'd) ⇒ ('d ⇒ ('b×'c) list ⇒ 'd) ⇒ ('d ⇒ ('b×'c) list ⇒ ('b×'c) list list) ⇒ (('b×'c) prefix_tree × 'd)) ⇒
                              (('a,'b,'c) fsm ⇒ ('a,'b,'c) state_cover_assignment ⇒ ('a,'b,'c) transition list ⇒ ('a,'b,'c) transition list) ⇒                              
                              (('a,'b,'c) fsm ⇒ ('a,'b,'c) state_cover_assignment ⇒ ('b×'c) prefix_tree ⇒ 'd ⇒ ('d ⇒ ('b×'c) list ⇒ 'd) ⇒ ('d ⇒ ('b×'c) list ⇒ ('b×'c) list list) ⇒ nat ⇒ ('a,'b,'c) transition ⇒ (('b×'c) prefix_tree × 'd)) ⇒ 
                              (('a,'b,'c) fsm ⇒ ('a,'b,'c) state_cover_assignment ⇒ ('b×'c) prefix_tree ⇒ 'd ⇒ ('d ⇒ ('b×'c) list ⇒ 'd) ⇒ ('d ⇒ ('b×'c) list ⇒ ('b×'c) list list) ⇒ 'a ⇒ 'b ⇒ 'c ⇒ (('b×'c) prefix_tree) × 'd) ⇒ 
                              (('a,'b,'c) fsm ⇒ ('b×'c) prefix_tree ⇒ 'd) ⇒
                              ('d ⇒ ('b×'c) list ⇒ 'd) ⇒
                              ('d ⇒ ('b×'c) list ⇒ ('b×'c) list list) ⇒
                              ('d ⇒ ('b×'c) list ⇒ ('b×'c) list ⇒ 'd) ⇒
                              nat ⇒
                              ('b×'c) prefix_tree" 
  where
  "spy_framework M 
                 get_state_cover 
                 separate_state_cover
                 sort_unverified_transitions
                 establish_convergence
                 append_io_pair
                 cg_initial
                 cg_insert
                 cg_lookup
                 cg_merge
                 m 
  = (let
      rstates_set = reachable_states M;
      rstates     = reachable_states_as_list M;
      rstates_io  = List.product rstates (List.product (inputs_as_list M) (outputs_as_list M));
      undefined_io_pairs = List.filter (λ (q,(x,y)) . h_obs M q x y = None) rstates_io;
      V           = get_state_cover M;
      n           = size_r M;
      TG1         = separate_state_cover M V cg_initial cg_insert cg_lookup;
      sc_covered_transitions = (⋃ q ∈ rstates_set . covered_transitions M V (V q));
      unverified_transitions = sort_unverified_transitions M V (filter (λt . t_source t ∈ rstates_set ∧ t ∉ sc_covered_transitions) (transitions_as_list M));
      verify_transition = (λ (T,G) t . let TGxy = append_io_pair M V T G cg_insert cg_lookup (t_source t) (t_input t) (t_output t);
                                           (T',G') = establish_convergence M V (fst TGxy) (snd TGxy) cg_insert cg_lookup m t;
                                           G'' = cg_merge G' ((V (t_source t)) @ [(t_input t, t_output t)]) (V (t_target t))                                           
                                        in (T',G''));
      TG2         = foldl verify_transition TG1 unverified_transitions;
      verify_undefined_io_pair = (λ T (q,(x,y)) . fst (append_io_pair M V T (snd TG2) cg_insert cg_lookup q x y))
    in
      foldl verify_undefined_io_pair (fst TG2) undefined_io_pairs)"





subsection ‹Required Conditions on Procedural Parameters›

definition verifies_transition :: "(('a::linorder,'b::linorder,'c::linorder) fsm ⇒
                                    ('a,'b,'c) state_cover_assignment ⇒
                                    ('b×'c) prefix_tree ⇒ 
                                    'd ⇒
                                    ('d ⇒ ('b×'c) list ⇒ 'd) ⇒
                                    ('d ⇒ ('b×'c) list ⇒ ('b×'c) list list) ⇒ 
                                    nat ⇒
                                    ('a,'b,'c) transition ⇒  
                                    (('b×'c) prefix_tree × 'd)) ⇒   
                                  ('a::linorder,'b::linorder,'c::linorder) fsm ⇒
                                  ('e,'b,'c) fsm ⇒
                                  ('a,'b,'c) state_cover_assignment ⇒    
                                  ('b×'c) prefix_tree ⇒
                                  ('d ⇒ ('b×'c) list ⇒ 'd) ⇒
                                  ('d ⇒ ('b×'c) list ⇒ ('b×'c) list list) ⇒ 
                                  bool"
  where 
  "verifies_transition f M1 M2 V T0 cg_insert cg_lookup = 
    (∀ T G m t .
        (set T ⊆ set (fst (f M1 V T G cg_insert cg_lookup m t)))
        ∧ (finite_tree T ⟶ finite_tree (fst (f M1 V T G cg_insert cg_lookup m t)))
        ∧ (observable M1 ⟶
            observable M2 ⟶
            minimal M1 ⟶
            minimal M2 ⟶
            size_r M1 ≤ m ⟶
            size M2 ≤ m ⟶
            inputs M2 = inputs M1 ⟶
            outputs M2 = outputs M1 ⟶
            is_state_cover_assignment M1 V ⟶
            preserves_divergence M1 M2 (V ` reachable_states M1) ⟶
            V ` reachable_states M1 ⊆ set T ⟶
            t ∈ transitions M1 ⟶
            t_source t ∈ reachable_states M1 ⟶ 
            ((V (t_source t)) @ [(t_input t,t_output t)]) ≠ (V (t_target t)) ⟶
            ((V (t_source t)) @ [(t_input t,t_output t)]) ∈ L M2 ⟶
            convergence_graph_lookup_invar M1 M2 cg_lookup G ⟶
            convergence_graph_insert_invar M1 M2 cg_lookup cg_insert ⟶
            L M1 ∩ set (fst (f M1 V T G cg_insert cg_lookup m t)) = L M2 ∩ set (fst (f M1 V T G cg_insert cg_lookup m t)) ⟶
            (set T0 ⊆ set T) ⟶
            (converge M2 ((V (t_source t)) @ [(t_input t,t_output t)]) (V (t_target t)))
            ∧ convergence_graph_lookup_invar M1 M2 cg_lookup (snd (f M1 V T G cg_insert cg_lookup m t))))"


definition verifies_io_pair :: "(('a::linorder,'b::linorder,'c::linorder) fsm ⇒
                                            ('a,'b,'c) state_cover_assignment ⇒
                                            ('b×'c) prefix_tree ⇒ 
                                            'd ⇒
                                            ('d ⇒ ('b×'c) list ⇒ 'd) ⇒ 
                                            ('d ⇒ ('b×'c) list ⇒ ('b×'c) list list) ⇒ 
                                            'a ⇒ 'b ⇒ 'c ⇒  
                                            (('b×'c) prefix_tree × 'd)) ⇒
                                          ('a::linorder,'b::linorder,'c::linorder) fsm ⇒
                                          ('e,'b,'c) fsm ⇒
                                          ('d ⇒ ('b×'c) list ⇒ 'd) ⇒
                                          ('d ⇒ ('b×'c) list ⇒ ('b×'c) list list) ⇒                                     
                                          bool"
  where 
  "verifies_io_pair f M1 M2 cg_insert cg_lookup = 
    (∀ V T G q x y .
        (set T ⊆ set (fst (f M1 V T G cg_insert cg_lookup q x y)))
        ∧ (finite_tree T ⟶ finite_tree (fst (f M1 V T G cg_insert cg_lookup q x y)))
        ∧ (observable M1 ⟶
            observable M2 ⟶
            minimal M1 ⟶
            minimal M2 ⟶
            inputs M2 = inputs M1 ⟶
            outputs M2 = outputs M1 ⟶
            is_state_cover_assignment M1 V ⟶
            L M1 ∩ (V ` reachable_states M1) = L M2 ∩ V ` reachable_states M1 ⟶
            q ∈ reachable_states M1 ⟶
            x ∈ inputs M1 ⟶ 
            y ∈ outputs M1 ⟶ 
            convergence_graph_lookup_invar M1 M2 cg_lookup G ⟶   
            convergence_graph_insert_invar M1 M2 cg_lookup cg_insert ⟶
            L M1 ∩ set (fst (f M1 V T G cg_insert cg_lookup q x y)) = L M2 ∩ set (fst (f M1 V T G cg_insert cg_lookup q x y)) ⟶
            (∃ α . 
                 converge M1 α (V q) ∧ 
                 converge M2 α (V q) ∧
                 α ∈ set (fst (f M1 V T G cg_insert cg_lookup q x y)) ∧
                 α@[(x,y)] ∈ set (fst (f M1 V T G cg_insert cg_lookup q x y)))
            ∧ convergence_graph_lookup_invar M1 M2 cg_lookup (snd (f M1 V T G cg_insert cg_lookup q x y))))"

(* functions that verify io pairs w.r.t. the SPY-Framework also handle io pairs w.r.t. the H-Framework *)
lemma verifies_io_pair_handled: 
  assumes "verifies_io_pair f M1 M2 cg_insert cg_lookup" 
shows "handles_io_pair f M1 M2 cg_insert cg_lookup"
proof -
  
  have *:"⋀ V T G q x y . set T ⊆ set (fst (f M1 V T G cg_insert cg_lookup q x y))"
    using assms unfolding verifies_io_pair_def 
    by metis 

  have ***:"⋀ V T G q x y . finite_tree T ⟶ finite_tree (fst (f M1 V T G cg_insert cg_lookup q x y))"
    using assms unfolding verifies_io_pair_def 
    by metis 

  have **:"⋀ V T G q x y.
        observable M1 ⟹
        observable M2 ⟹
        minimal M1 ⟹
        minimal M2 ⟹
        FSM.inputs M2 = FSM.inputs M1 ⟹
        FSM.outputs M2 = FSM.outputs M1 ⟹
        is_state_cover_assignment M1 V ⟹
        L M1 ∩ V ` reachable_states M1 = L M2 ∩ V ` reachable_states M1 ⟹
        q ∈ reachable_states M1 ⟹
        x ∈ inputs M1 ⟹
        y ∈ outputs M1 ⟹ 
        convergence_graph_lookup_invar M1 M2 cg_lookup G ⟹
        convergence_graph_insert_invar M1 M2 cg_lookup cg_insert ⟹
        L M1 ∩ set (fst (f M1 V T G cg_insert cg_lookup q x y)) = L M2 ∩ set (fst (f M1 V T G cg_insert cg_lookup q x y)) ⟹
        ( L M1 ∩ {(V q)@[(x,y)]} = L M2 ∩ {(V q)@[(x,y)]} )
        ∧ convergence_graph_lookup_invar M1 M2 cg_lookup (snd (f M1 V T G cg_insert cg_lookup q x y))"
  proof -
    fix V T G q x y
    assume a01: "observable M1"
    moreover assume a02: "observable M2"
    moreover assume a03: "minimal M1"
    moreover assume a04: "minimal M2"
    moreover assume a05: "FSM.inputs M2 = FSM.inputs M1"
    moreover assume a06: "FSM.outputs M2 = FSM.outputs M1"
    moreover assume a07: "is_state_cover_assignment M1 V"
    moreover assume a09: "L M1 ∩ V ` reachable_states M1 = L M2 ∩ V ` reachable_states M1"
    moreover assume a10: "q ∈ reachable_states M1"
    moreover assume a11: "x ∈ inputs M1"
    moreover assume a12: "y ∈ outputs M1"
    moreover assume a13: "convergence_graph_lookup_invar M1 M2 cg_lookup G"
    moreover assume a14: "convergence_graph_insert_invar M1 M2 cg_lookup cg_insert"
    moreover assume a15: "L M1 ∩ set (fst (f M1 V T G cg_insert cg_lookup q x y)) = L M2 ∩ set (fst (f M1 V T G cg_insert cg_lookup q x y))"


    ultimately have *:"(∃α. converge M1 α (V q) ∧
                          converge M2 α (V q) ∧
                          α ∈ Prefix_Tree.set (fst (f M1 V T G cg_insert cg_lookup q x y)) ∧ α @ [(x, y)] ∈ Prefix_Tree.set (fst (f M1 V T G cg_insert cg_lookup q x y)))"
               and **: "convergence_graph_lookup_invar M1 M2 cg_lookup (snd (f M1 V T G cg_insert cg_lookup q x y))"
      using assms unfolding verifies_io_pair_def  
      by presburger+

    have "( L M1 ∩ {(V q)@[(x,y)]} = L M2 ∩ {(V q)@[(x,y)]} )"
    proof -
      obtain α where "converge M1 α (V q)" and "converge M2 α (V q)" and "α @ [(x, y)] ∈ Prefix_Tree.set (fst (f M1 V T G cg_insert cg_lookup q x y))"
        using * by blast

      have "(V q)@[(x,y)] ∈ L M1 = (α@[(x,y)] ∈ L M1)"
        using ‹converge M1 α (V q)› using a01 a07
        by (meson converge_append_language_iff) 
      moreover have "(V q)@[(x,y)] ∈ L M2 = (α@[(x,y)] ∈ L M2)"
        using ‹converge M2 α (V q)› using a02 a07
        by (meson converge_append_language_iff) 
      moreover have "α @ [(x, y)] ∈ L M1 = (α @ [(x, y)] ∈ L M2)"
        using ‹α @ [(x, y)] ∈ Prefix_Tree.set (fst (f M1 V T G cg_insert cg_lookup q x y))› a15
        by blast
      ultimately show ?thesis
        by blast
    qed
    then show "( L M1 ∩ {(V q)@[(x,y)]} = L M2 ∩ {(V q)@[(x,y)]} ) ∧ convergence_graph_lookup_invar M1 M2 cg_lookup (snd (f M1 V T G cg_insert cg_lookup q x y))"
      using ** by blast
  qed

  show ?thesis
    unfolding handles_io_pair_def
    using * *** ** by presburger
qed



subsection ‹Completeness and Finiteness of the Framework›

lemma spy_framework_completeness_and_finiteness :
  fixes M1 :: "('a::linorder,'b::linorder,'c::linorder) fsm"
  fixes M2 :: "('d,'b,'c) fsm"
  assumes "observable M1"
  and     "observable M2"
  and     "minimal M1"
  and     "minimal M2"
  and     "size_r M1 ≤ m"
  and     "size M2 ≤ m"
  and     "inputs M2 = inputs M1"
  and     "outputs M2 = outputs M1"
  and     "is_state_cover_assignment M1 (get_state_cover M1)"
  and     "⋀ xs . List.set xs = List.set (sort_unverified_transitions M1 (get_state_cover M1) xs)"
  and     "convergence_graph_initial_invar M1 M2 cg_lookup cg_initial"
  and     "convergence_graph_insert_invar M1 M2 cg_lookup cg_insert"
  and     "convergence_graph_merge_invar M1 M2 cg_lookup cg_merge"
  and     "separates_state_cover separate_state_cover M1 M2 cg_initial cg_insert cg_lookup"
  and     "verifies_transition establish_convergence M1 M2 (get_state_cover M1) (fst (separate_state_cover M1 (get_state_cover M1) cg_initial cg_insert cg_lookup)) cg_insert cg_lookup"
  and     "verifies_io_pair append_io_pair M1 M2 cg_insert cg_lookup"
shows "(L M1 = L M2) ⟷ ((L M1 ∩ set (spy_framework M1 get_state_cover separate_state_cover sort_unverified_transitions establish_convergence append_io_pair cg_initial cg_insert cg_lookup cg_merge m))
                          = (L M2 ∩ set (spy_framework M1 get_state_cover separate_state_cover sort_unverified_transitions establish_convergence append_io_pair cg_initial cg_insert cg_lookup cg_merge m)))"
  (is "(L M1 = L M2) ⟷ ((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))")
and "finite_tree (spy_framework M1 get_state_cover separate_state_cover sort_unverified_transitions establish_convergence append_io_pair cg_initial cg_insert cg_lookup cg_merge m)"
proof 
  show "(L M1 = L M2) ⟹ ((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))"
    by blast


  define rstates where rstates: "rstates = reachable_states_as_list M1"
  define rstates_io where rstates_io: "rstates_io = List.product rstates (List.product (inputs_as_list M1) (outputs_as_list M1))"
  define undefined_io_pairs where undefined_io_pairs: "undefined_io_pairs = List.filter (λ (q,(x,y)) . h_obs M1 q x y = None) rstates_io"
  define V where V: "V             = get_state_cover M1"
  define n where n: "n             = size_r M1"
  define TG1 where TG1: "TG1 = separate_state_cover M1 V cg_initial cg_insert cg_lookup"

  define sc_covered_transitions where sc_covered_transitions: "sc_covered_transitions = (⋃ q ∈ reachable_states M1 . covered_transitions M1 V (V q))"
  define unverified_transitions where unverified_transitions: "unverified_transitions = sort_unverified_transitions M1 V (filter (λt . t_source t ∈ reachable_states M1 ∧ t ∉ sc_covered_transitions) (transitions_as_list M1))"
  define verify_transition where verify_transition: "verify_transition = (λ (T,G) t . let TGxy = append_io_pair M1 V T G cg_insert cg_lookup (t_source t) (t_input t) (t_output t);
                                                                                          (T',G') = establish_convergence M1 V (fst TGxy) (snd TGxy) cg_insert cg_lookup m t;
                                       G'' = cg_merge G' ((V (t_source t)) @ [(t_input t, t_output t)]) (V (t_target t))                                           
                                    in (T',G''))"
  define TG2 where TG2: "TG2       = (foldl verify_transition TG1 unverified_transitions)"
  define verify_undefined_io_pair where verify_undefined_io_pair: "verify_undefined_io_pair = (λ T (q,(x,y)) . fst (append_io_pair M1 V T (snd TG2) cg_insert cg_lookup q x y))" 
  define T3 where T3: "T3            = foldl verify_undefined_io_pair (fst TG2) undefined_io_pairs"

  have "?TS = T3"
    unfolding rstates rstates_io undefined_io_pairs V n TG1 sc_covered_transitions unverified_transitions verify_transition TG2 verify_undefined_io_pair T3
    unfolding spy_framework_def Let_def
    by force
  then have "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ L M1 ∩ set T3 = L M2 ∩ set T3"
    by simp

  have "is_state_cover_assignment M1 V"
    unfolding V using assms(9) .

  (* basic properties of T1 and G1 *)

  define T1 where T1: "T1 = fst TG1"
  moreover define G1 where G1: "G1 = snd TG1"
  ultimately have "TG1 = (T1,G1)" 
    by auto 

  have T1_state_cover: "V ` reachable_states M1 ⊆ set T1"
  and  T1_finite: "finite_tree T1"
    using ‹separates_state_cover separate_state_cover M1 M2 cg_initial cg_insert cg_lookup›
    unfolding T1 TG1 separates_state_cover_def
    by blast+
  

  have T1_V_div: "(L M1 ∩ set T1 = (L M2 ∩ set T1)) ⟹ preserves_divergence M1 M2 (V ` reachable_states M1)"
   and G1_invar: "(L M1 ∩ set T1 = (L M2 ∩ set T1)) ⟹ convergence_graph_lookup_invar M1 M2 cg_lookup G1" 
    using ‹separates_state_cover separate_state_cover M1 M2 cg_initial cg_insert cg_lookup›
    unfolding T1 G1 TG1 separates_state_cover_def
    using assms(1-4,7,8) ‹is_state_cover_assignment M1 V› assms(12,11) 
    by blast+

  have "verifies_transition establish_convergence M1 M2 V T1 cg_insert cg_lookup"
    using assms(15)
    unfolding T1 TG1 V .

  (* properties of transition filtering and sorting *)

  have sc_covered_transitions_alt_def: "sc_covered_transitions = {t . t ∈ transitions M1 ∧ t_source t ∈ reachable_states M1 ∧ (V (t_target t) = (V (t_source t))@[(t_input t, t_output t)])}"
    (is "?A = ?B")
  proof 
    show "?A ⊆ ?B"
    proof 
      fix t assume "t ∈ ?A"
      then obtain q where "t ∈ covered_transitions M1 V (V q)" and "q ∈ reachable_states M1"
        unfolding sc_covered_transitions 
        by blast
      then have "V q ∈ L M1" and "after_initial M1 (V q) = q"
        using state_cover_assignment_after[OF assms(1) ‹is_state_cover_assignment M1 V›]
        by blast+

      then obtain p where "path M1 (initial M1) p" and "p_io p = V q"
        by auto
      then have *: "the_elem (paths_for_io M1 (initial M1) (V q)) = p"
        using observable_paths_for_io[OF assms(1) ‹V q ∈ L M1›]
        unfolding paths_for_io_def
        by (metis (mono_tags, lifting) assms(1) mem_Collect_eq observable_path_unique singletonI the_elem_eq)
      
      have "t ∈ list.set p" and "V (t_source t) @ [(t_input t, t_output t)] = V (t_target t)"
        using ‹t ∈ covered_transitions M1 V (V q)› 
        unfolding covered_transitions_def Let_def * 
        by auto

      have "t ∈ transitions M1"
        using ‹t ∈ list.set p› ‹path M1 (initial M1) p›
        by (meson path_transitions subsetD) 
      moreover have "t_source t ∈ reachable_states M1"
        using reachable_states_path[OF reachable_states_initial ‹path M1 (initial M1) p› ‹t ∈ list.set p›] .
      ultimately show "t ∈ ?B"
        using ‹V (t_source t) @ [(t_input t, t_output t)] = V (t_target t)›
        by auto
    qed

    show "?B ⊆ ?A"
    proof 
      fix t assume "t ∈ ?B"
      then have "t ∈ transitions M1" 
                "t_source t ∈ reachable_states M1" 
                "(V (t_source t))@[(t_input t, t_output t)] = V (t_target t)"
        by auto
      then have "t_target t ∈ reachable_states M1"
        using reachable_states_next[of "t_source t" M1 t] 
        by blast
      then have "V (t_target t) ∈ L M1" and "after_initial M1 (V (t_target t)) = (t_target t)"
        using state_cover_assignment_after[OF assms(1) ‹is_state_cover_assignment M1 V›]
        by blast+
      then obtain p where "path M1 (initial M1) p" and "p_io p = V (t_target t)"
        by auto
      then have *: "the_elem (paths_for_io M1 (initial M1) (V (t_target t))) = p"
        using observable_paths_for_io[OF assms(1) ‹V (t_target t) ∈ L M1›]
        unfolding paths_for_io_def
        by (metis (mono_tags, lifting) assms(1) mem_Collect_eq observable_path_unique singletonI the_elem_eq)

      
      have "V (t_source t) ∈ L M1" and "after_initial M1 (V (t_source t)) = (t_source t)"
        using ‹t_source t ∈ reachable_states M1›
        using state_cover_assignment_after[OF assms(1) ‹is_state_cover_assignment M1 V›]
        by blast+
      then obtain p' where "path M1 (initial M1) p'" and "p_io p' = V (t_source t)"
        by auto
      
      have "path M1 (initial M1) (p'@[t])"
        using after_path[OF assms(1) ‹path M1 (initial M1) p'›] ‹path M1 (initial M1) p'› ‹t∈transitions M1›
        unfolding ‹p_io p' = V (t_source t)›
        unfolding ‹after_initial M1 (V (t_source t)) = (t_source t)›
        by (metis path_append single_transition_path)
      moreover have "p_io (p'@[t]) = p_io p"
        using ‹p_io p' = V (t_source t)› 
        unfolding ‹p_io p = V (t_target t)›  ‹(V (t_source t))@[(t_input t, t_output t)] = V (t_target t)›[symmetric]
        by auto
      ultimately have "p'@[t] = p"
        using observable_path_unique[OF assms(1) _ ‹path M1 (initial M1) p›]
        by force
      then have "t ∈ list.set p"
        by auto
      then have "t ∈ covered_transitions M1 V (V (t_target t))"
        using ‹(V (t_source t))@[(t_input t, t_output t)] = V (t_target t)›
        unfolding covered_transitions_def Let_def *
        by auto
      then show "t ∈ ?A"
        using ‹t_target t ∈ reachable_states M1›
        unfolding sc_covered_transitions 
        by blast
    qed
  qed

  have T1_covered_transitions_conv: "⋀ t . (L M1 ∩ set T1 = (L M2 ∩ set T1)) ⟹ t ∈ sc_covered_transitions ⟹ converge M2 (V (t_target t)) ((V (t_source t))@[(t_input t, t_output t)])"
  proof -
    fix t assume "(L M1 ∩ set T1 = (L M2 ∩ set T1))"
                 "t ∈ sc_covered_transitions"

    then have "t ∈ transitions M1" 
              "t_source t ∈ reachable_states M1" 
              "(V (t_source t))@[(t_input t, t_output t)] = V (t_target t)"
      unfolding sc_covered_transitions_alt_def
      by auto
    then have "t_target t ∈ reachable_states M1"
      using reachable_states_next[of "t_source t" M1 t] 
      by blast
    then have "V (t_target t) ∈ L M1"
      using state_cover_assignment_after[OF assms(1) ‹is_state_cover_assignment M1 V›]
      by blast   
    moreover have "V (t_target t) ∈ set T1"
      using T1_state_cover ‹t_target t ∈ reachable_states M1›
      by blast
    ultimately have "V (t_target t) ∈ L M2"
      using ‹(L M1 ∩ set T1 = (L M2 ∩ set T1))›
      by blast
    then show "converge M2 (V (t_target t)) ((V (t_source t))@[(t_input t, t_output t)])"
      unfolding ‹(V (t_source t))@[(t_input t, t_output t)] = V (t_target t)›
      by auto
  qed


  have unverified_transitions_alt_def : "list.set unverified_transitions = {t . t ∈ transitions M1 ∧ t_source t ∈ reachable_states M1 ∧ (V (t_target t) ≠ (V (t_source t))@[(t_input t, t_output t)])}"
    unfolding unverified_transitions sc_covered_transitions_alt_def V
    unfolding assms(10)[symmetric] 
    using transitions_as_list_set[of M1]
    by auto


  (* remaining properties before the computation of TG2 *)

  have cg_insert_invar : "⋀ G γ . γ ∈ L M1 ⟹ γ ∈ L M2 ⟹ convergence_graph_lookup_invar M1 M2 cg_lookup G ⟹ convergence_graph_lookup_invar M1 M2 cg_lookup (cg_insert G γ)"
    using assms(12)
    unfolding convergence_graph_insert_invar_def
    by blast

  have cg_merge_invar : "⋀ G γ γ'. convergence_graph_lookup_invar M1 M2 cg_lookup G ⟹ converge M1 γ γ' ⟹ converge M2 γ γ' ⟹ convergence_graph_lookup_invar M1 M2 cg_lookup (cg_merge G γ γ')"
    using assms(13)
    unfolding convergence_graph_merge_invar_def
    by blast


  (* properties of the computation of TG2 *)

  define T2 where T2: "T2 = fst TG2"
  define G2 where G2: "G2 = snd TG2"

  (* idea: in each application of verify_transition, a further transition of unverified_transitions
           (q,x,y,q') is covered - that is
            - traces α.(x/y), β are added to the test suite such that
              - α converges with V s in both M1 and M2
              - β converges with V s' in both M1 and M2
              - α.(x/y) converges with β in both M1 and M2 
            - the test suite is added as required to establish this convergence
            - the convergence graph is updated, respecting the lookup-invariants due to convergence (in merge)
  *)
                                                                            
  have verify_transition_retains_testsuite: "⋀ t T G . set T ⊆ set (fst (verify_transition (T,G) t))"
  proof -
    fix t T G
    define TGxy where TGxy: "TGxy = append_io_pair M1 V T G cg_insert cg_lookup (t_source t) (t_input t) (t_output t)"
    obtain T' G' where TG': "(T',G') = establish_convergence M1 V (fst TGxy) (snd TGxy) cg_insert cg_lookup m t"
      using prod.exhaust by metis
    define G'' where G'': "G'' = cg_merge G' ((V (t_source t)) @ [(t_input t, t_output t)]) (V (t_target t))"

    have *:"verify_transition (T,G) t = (T',G'')"
      using TG'[symmetric]
      unfolding verify_transition G'' TGxy Let_def case_prod_conv 
      by force

    have "set T ⊆ set (fst TGxy)"
      using ‹verifies_io_pair append_io_pair M1 M2 cg_insert cg_lookup›
      unfolding verifies_io_pair_def TGxy 
      by blast
    also have "set (fst TGxy) ⊆ set (fst (T',G'))"
      using ‹verifies_transition establish_convergence M1 M2 V T1 cg_insert cg_lookup›
      unfolding TG' verifies_transition_def
      by blast
    finally show "set T ⊆ set (fst (verify_transition (T,G) t))"
      unfolding * fst_conv .
  qed

  have verify_transition_retains_finiteness: "⋀ t T G . finite_tree T ⟹ finite_tree (fst (verify_transition (T,G) t))"
  proof -
    fix T :: "('b×'c) prefix_tree"
    fix t G assume "finite_tree T"

    define TGxy where TGxy: "TGxy = append_io_pair M1 V T G cg_insert cg_lookup (t_source t) (t_input t) (t_output t)"
    obtain T' G' where TG': "(T',G') = establish_convergence M1 V (fst TGxy) (snd TGxy) cg_insert cg_lookup m t"
      using prod.exhaust by metis
    define G'' where G'': "G'' = cg_merge G' ((V (t_source t)) @ [(t_input t, t_output t)]) (V (t_target t))"

    have *:"verify_transition (T,G) t = (T',G'')"
      using TG'[symmetric]
      unfolding verify_transition G'' TGxy Let_def case_prod_conv 
      by force

    have "finite_tree (fst TGxy)"
      using ‹verifies_io_pair append_io_pair M1 M2 cg_insert cg_lookup› ‹finite_tree T›
      unfolding verifies_io_pair_def TGxy 
      by blast
    then have "finite_tree (fst (T',G'))"
      using ‹verifies_transition establish_convergence M1 M2 V T1 cg_insert cg_lookup›
      unfolding TG' verifies_transition_def
      by blast
    then show "finite_tree (fst (verify_transition (T,G) t))"
      unfolding * fst_conv .
  qed


  define covers_unverified_transition 
    where covers_unverified_transition: "covers_unverified_transition  = (λt (T',G') .
                                                ((∃ α β . converge M1 α (V (t_source t)) ∧
                                                          converge M2 α (V (t_source t)) ∧
                                                          converge M1 β (V (t_target t)) ∧
                                                          converge M2 β (V (t_target t)) ∧
                                                          α@[(t_input t,t_output t)] ∈ (set T') ∧
                                                          β ∈ (set T'))
                                                  ∧ (converge M2 ((V (t_source t)) @ [(t_input t,t_output t)]) (V (t_target t)))
                                                  ∧ convergence_graph_lookup_invar M1 M2 cg_lookup G'))"

  have verify_transition_cover_prop: "⋀ t T G . (L M1 ∩ (set (fst (verify_transition (T,G) t))) = L M2 ∩ (set (fst (verify_transition (T,G) t))))
                                          ⟹ convergence_graph_lookup_invar M1 M2 cg_lookup G
                                          ⟹ t ∈ transitions M1
                                          ⟹ t_source t ∈ reachable_states M1
                                          ⟹ ((V (t_source t)) @ [(t_input t,t_output t)]) ≠ (V (t_target t))
                                          ⟹ set T1 ⊆ set T
                                          ⟹ covers_unverified_transition t (verify_transition (T,G) t)"
  proof -
    fix t T G
    assume a1: "(L M1 ∩ (set (fst (verify_transition (T,G) t))) = L M2 ∩ (set (fst (verify_transition (T,G) t))))"
    assume a2: "convergence_graph_lookup_invar M1 M2 cg_lookup G"
    assume a3: "t ∈ transitions M1"
    assume a4: "t_source t ∈ reachable_states M1"
    assume a5: "set T1 ⊆ set T"
    assume a6: "((V (t_source t)) @ [(t_input t,t_output t)]) ≠ (V (t_target t))"

    define TGxy where TGxy: "TGxy = append_io_pair M1 V T G cg_insert cg_lookup (t_source t) (t_input t) (t_output t)"
    obtain T' G' where TG': "(T',G') = establish_convergence M1 V (fst TGxy) (snd TGxy) cg_insert cg_lookup m t"
      using prod.exhaust by metis
    have T':  "T'  = fst (establish_convergence M1 V (fst TGxy) (snd TGxy) cg_insert cg_lookup m t)"
     and G':  "G'  = snd (establish_convergence M1 V (fst TGxy) (snd TGxy) cg_insert cg_lookup m t)"
      unfolding TG'[symmetric] by auto
    define G'' where G'': "G'' = cg_merge G' ((V (t_source t)) @ [(t_input t, t_output t)]) (V (t_target t))"

    have "verify_transition (T,G) t = (T',G'')"
      using TG'[symmetric]
      unfolding verify_transition G'' TGxy Let_def case_prod_conv 
      by force
    then have "set T ⊆ set T'"
      using verify_transition_retains_testsuite[of T G t] unfolding T' 
      by auto
    then have "(L M1 ∩ (set T1) = L M2 ∩ (set T1))"
      using a1 a5 unfolding ‹verify_transition (T,G) t = (T',G'')› fst_conv 
      by blast
    then have *: "preserves_divergence M1 M2 (V ` reachable_states M1)"
      using T1_V_div
      by auto

    have "set (fst TGxy) ⊆ set (fst (T',G'))"
      using ‹verifies_transition establish_convergence M1 M2 V T1 cg_insert cg_lookup›
      unfolding TG' verifies_transition_def
      by blast
    then have "set (fst TGxy) ⊆ set (fst (verify_transition (T,G) t))"
      unfolding ‹verify_transition (T,G) t = (T',G'')› fst_conv .
    then have "L M1 ∩ set (fst TGxy) = L M2 ∩ set (fst TGxy)"
      using a1 by blast

    have "L M1 ∩ set T' = L M2 ∩ set T'" and "L M1 ∩ set T = L M2 ∩ set T"
      using a1 ‹set T ⊆ set T'› unfolding T' ‹verify_transition (T,G) t = (T',G'')› fst_conv 
      by blast+

    have **: "V ` reachable_states M1 ⊆ set T"
      using a5 T1_state_cover by blast

    have "L M1 ∩ V ` reachable_states M1 = L M2 ∩ V ` reachable_states M1"
      using T1_state_cover ‹L M1 ∩ Prefix_Tree.set T1 = L M2 ∩ Prefix_Tree.set T1› by blast

    

    have "(∃α. converge M1 α (V (t_source t)) ∧
            converge M2 α (V (t_source t)) ∧
            α ∈ Prefix_Tree.set (fst TGxy) ∧
            α @ [((t_input t), (t_output t))] ∈ Prefix_Tree.set (fst TGxy))"
    and  "convergence_graph_lookup_invar M1 M2 cg_lookup (snd TGxy)"
      using ‹verifies_io_pair append_io_pair M1 M2 cg_insert cg_lookup›
      unfolding verifies_io_pair_def  
      using assms(1-4,7,8) ‹is_state_cover_assignment M1 V› ‹L M1 ∩ V ` reachable_states M1 = L M2 ∩ V ` reachable_states M1› a4 fsm_transition_input[OF a3] fsm_transition_output[OF a3] a2 ‹convergence_graph_insert_invar M1 M2 cg_lookup cg_insert› ‹L M1 ∩ set (fst TGxy) = L M2 ∩ set (fst TGxy)›
      unfolding TGxy
      by blast+ 

    then obtain w where "converge M1 w (V (t_source t))"
                        "converge M2 w (V (t_source t))"
                        "w ∈ Prefix_Tree.set (fst TGxy)"
                        "w @ [((t_input t), (t_output t))] ∈ set (fst TGxy)"
      by blast
    then have "w @ [((t_input t), (t_output t))] ∈ L M1 ⟷ w @ [((t_input t), (t_output t))] ∈ L M2"
      using ‹L M1 ∩ set (fst TGxy) = L M2 ∩ set (fst TGxy)›
      by blast
    moreover have "w @ [((t_input t), (t_output t))] ∈ L M1 ⟷ V (t_source t) @ [(t_input t, t_output t)] ∈ L M1"
      using ‹converge M1 w (V (t_source t))›
      by (meson assms(1) converge_append_language_iff) 
    moreover have "V (t_source t) @ [(t_input t, t_output t)] ∈ L M1"
      using state_cover_transition_converges[OF assms(1) ‹is_state_cover_assignment M1 V› ‹t ∈ transitions M1› ‹t_source t ∈ reachable_states M1›]
      by auto
    ultimately have "w @ [(t_input t, t_output t)] ∈ L M2"
      by blast
    then have "V (t_source t) @ [(t_input t, t_output t)] ∈ L M2"
      using ‹converge M2 w (V (t_source t))›
      by (meson assms(2) converge_append_language_iff) 
    have "V ` reachable_states M1 ⊆ set T"
      using a5 T1_state_cover by blast

    have "set T ⊆ set (fst TGxy)"
      using ‹verifies_io_pair append_io_pair M1 M2 cg_insert cg_lookup› 
      unfolding verifies_io_pair_def TGxy by blast
    then have "set T1 ⊆ set (fst TGxy)"
      using a5 by blast 
    then have "⋀ io . set (after T1 io) ⊆ set (after (fst TGxy) io)"
      unfolding after_set by auto   

    have "V ` reachable_states M1 ⊆ set (fst TGxy)"
      using "**" ‹Prefix_Tree.set T ⊆ Prefix_Tree.set (fst TGxy)› by auto

    have p2: "converge M2 (V (t_source t) @ [(t_input t, t_output t)]) (V (t_target t))" 
     and "convergence_graph_lookup_invar M1 M2 cg_lookup G'"
      using ‹verifies_transition establish_convergence M1 M2 V T1 cg_insert cg_lookup›
      unfolding verifies_transition_def
      using assms(1-8) ‹is_state_cover_assignment M1 V› ‹preserves_divergence M1 M2 (V ` reachable_states M1)› ‹V ` reachable_states M1 ⊆ set (fst TGxy)› a3 a4 a6 ‹V (t_source t) @ [(t_input t, t_output t)] ∈ L M2› ‹convergence_graph_lookup_invar M1 M2 cg_lookup (snd TGxy)› ‹convergence_graph_insert_invar M1 M2 cg_lookup cg_insert› ‹L M1 ∩ set T' = L M2 ∩ set T'› 
      using ‹set T1 ⊆ set (fst TGxy)›
      unfolding T' G'      
      by blast+

    have "w @ [((t_input t), (t_output t))] ∈ set T'"
      using ‹w @ [((t_input t), (t_output t))] ∈ set (fst TGxy)›
      using T' ‹Prefix_Tree.set (fst TGxy) ⊆ Prefix_Tree.set (fst (T', G'))› by auto 

    have p1: "(∃α β.
                converge M1 α (V (t_source t)) ∧
                converge M2 α (V (t_source t)) ∧
                converge M1 β (V (t_target t)) ∧
                converge M2 β (V (t_target t)) ∧
                α @ [(t_input t, t_output t)] ∈ set T' ∧
                β ∈ set T')"
    proof -
      have "V (t_source t) ∈ L M1"
        using state_cover_assignment_after(1)[OF assms(1) ‹is_state_cover_assignment M1 V› ‹t_source t ∈ reachable_states M1›] .
      have "V (t_target t) ∈ L M1"
        using state_cover_assignment_after(1)[OF assms(1) ‹is_state_cover_assignment M1 V› reachable_states_next[OF ‹t_source t ∈ reachable_states M1› ‹t ∈ transitions M1›]] 
        by auto


      note ‹converge M1 w (V (t_source t))› and ‹converge M2 w (V (t_source t))›
      moreover have "converge M1 (V (t_target t)) (V (t_target t))"
        using ‹V (t_target t) ∈ L M1› by auto 
      moreover have "converge M2 (V (t_target t)) (V (t_target t))"
        using reachable_states_next[OF ‹t_source t ∈ reachable_states M1› ‹t ∈ transitions M1›] ‹V (t_target t) ∈ L M1› ‹L M1 ∩ V ` reachable_states M1 = L M2 ∩ V ` reachable_states M1›
        by auto
      moreover note ‹w @ [(t_input t, t_output t)] ∈ set T'› 
      moreover have "V (t_target t) ∈ set T'" 
        using ‹V ` reachable_states M1 ⊆ set T› ‹set T ⊆ set T'› reachable_states_next[OF ‹t_source t ∈ reachable_states M1› ‹t ∈ transitions M1›]
        by auto
      ultimately show ?thesis 
        by blast
    qed

    have p3: "convergence_graph_lookup_invar M1 M2 cg_lookup G''"
      unfolding G''
      using cg_merge_invar[OF ‹convergence_graph_lookup_invar M1 M2 cg_lookup G'›]
            state_cover_transition_converges[OF assms(1) ‹is_state_cover_assignment M1 V› a3 a4]
            ‹converge M2 (V (t_source t) @ [(t_input t, t_output t)]) (V (t_target t))›
      by blast
    show "covers_unverified_transition t (verify_transition (T,G) t)"
      using p1 p2 p3
      unfolding ‹verify_transition (T,G) t = (T',G'')› fst_conv snd_conv covers_unverified_transition
      by blast
  qed



  have verify_transition_foldl_invar_1: "⋀ ts . list.set ts ⊆ list.set unverified_transitions ⟹ 
                set T1 ⊆ set (fst (foldl verify_transition (T1, G1) ts)) ∧ finite_tree (fst (foldl verify_transition (T1, G1) ts))"
  proof -
    fix ts assume "list.set ts ⊆ list.set unverified_transitions" 
    then show "set T1 ⊆ set (fst (foldl verify_transition (T1, G1) ts)) ∧ finite_tree (fst (foldl verify_transition (T1, G1) ts))"
    proof (induction ts rule: rev_induct)
      case Nil
      then show ?case 
        using T1_finite by auto
    next
      case (snoc t ts)
      then have "t ∈ transitions M1" and "t_source t ∈ reachable_states M1"
        unfolding unverified_transitions_alt_def 
        by force+

      have p1: "list.set ts ⊆ list.set unverified_transitions"
        using snoc.prems(1) by auto

      have "set (fst (foldl verify_transition (T1, G1) ts)) ⊆ set (fst (foldl verify_transition (T1, G1) (ts@[t])))"
        using verify_transition_retains_testsuite unfolding foldl_append foldl.simps
        by (metis eq_fst_iff)

      have **: "Prefix_Tree.set T1 ⊆ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) ts))"  
       and ***: "finite_tree (fst (foldl verify_transition (T1, G1) ts))"
        using snoc.IH[OF p1] 
        by auto          
    
      have "Prefix_Tree.set T1 ⊆ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) (ts@[t])))"
        using ** verify_transition_retains_testsuite ‹set (fst (foldl verify_transition (T1, G1) ts)) ⊆ set (fst (foldl verify_transition (T1, G1) (ts@[t])))› 
        by auto 
      moreover have "finite_tree (fst (foldl verify_transition (T1, G1) (ts@[t])))"
        using verify_transition_retains_finiteness[OF ***, of "snd (foldl verify_transition (T1, G1) ts)"] 
        by auto
      ultimately show ?case 
        by simp
    qed
  qed 
  then have T2_invar_1: "set T1 ⊆ set T2"
        and T2_finite : "finite_tree T2"
    unfolding TG2 G2 T2 ‹TG1 = (T1,G1)›
    by auto

  have verify_transition_foldl_invar_2: "⋀ ts . list.set ts ⊆ list.set unverified_transitions ⟹ 
                L M1 ∩ set (fst (foldl verify_transition (T1, G1) ts)) = L M2 ∩ set (fst (foldl verify_transition (T1, G1) ts)) ⟹ 
                convergence_graph_lookup_invar M1 M2 cg_lookup (snd (foldl verify_transition (T1, G1) ts))"
  proof -
    fix ts assume "list.set ts ⊆ list.set unverified_transitions" 
              and "L M1 ∩ set (fst (foldl verify_transition (T1, G1) ts)) = L M2 ∩ set (fst (foldl verify_transition (T1, G1) ts))"
    then show "convergence_graph_lookup_invar M1 M2 cg_lookup (snd (foldl verify_transition (T1, G1) ts))"
    proof (induction ts rule: rev_induct)
      case Nil
      then show ?case 
        using G1_invar by auto
    next
      case (snoc t ts)
      then have "t ∈ transitions M1" and "t_source t ∈ reachable_states M1" and "((V (t_source t)) @ [(t_input t,t_output t)]) ≠ (V (t_target t))"
        unfolding unverified_transitions_alt_def 
        by force+

      have p1: "list.set ts ⊆ list.set unverified_transitions"
        using snoc.prems(1) by auto

      have "set (fst (foldl verify_transition (T1, G1) ts)) ⊆ set (fst (foldl verify_transition (T1, G1) (ts@[t])))"
        using verify_transition_retains_testsuite unfolding foldl_append foldl.simps
        by (metis eq_fst_iff)
      then have p2: "L M1 ∩ set (fst (foldl verify_transition (T1, G1) ts)) = L M2 ∩ set (fst (foldl verify_transition (T1, G1) ts))"
        using snoc.prems(2)
        by blast 

      have *:"convergence_graph_lookup_invar M1 M2 cg_lookup (snd (foldl verify_transition (T1, G1) ts))"                                                           
        using snoc.IH[OF p1 p2] 
        by auto          

      have **: "Prefix_Tree.set T1 ⊆ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) ts))"
        using verify_transition_foldl_invar_1[OF p1] by blast
    
      have "covers_unverified_transition t (verify_transition (fst (foldl verify_transition (T1, G1) ts), snd (foldl verify_transition (T1, G1) ts)) t)"          
        using verify_transition_cover_prop[OF _ * ‹t ∈ transitions M1› ‹t_source t ∈ reachable_states M1› ‹((V (t_source t)) @ [(t_input t,t_output t)]) ≠ (V (t_target t))› **] snoc.prems(2)
        unfolding prod.collapse
        by auto
      then have "convergence_graph_lookup_invar M1 M2 cg_lookup (snd (verify_transition (fst (foldl verify_transition (T1, G1) ts), snd (foldl verify_transition (T1, G1) ts)) t))"
        unfolding covers_unverified_transition
        by auto
      then have "convergence_graph_lookup_invar M1 M2 cg_lookup (snd (foldl verify_transition (T1, G1) (ts@[t])))"
        by auto
      moreover have "Prefix_Tree.set T1 ⊆ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) (ts@[t])))"
        using ** verify_transition_retains_testsuite ‹set (fst (foldl verify_transition (T1, G1) ts)) ⊆ set (fst (foldl verify_transition (T1, G1) (ts@[t])))› 
        by auto 
      ultimately show ?case 
        by simp
    qed
  qed 
  then have T2_invar_2: "L M1 ∩ set T2 = L M2 ∩ set T2 ⟹ convergence_graph_lookup_invar M1 M2 cg_lookup G2"
    unfolding TG2 G2 T2 ‹TG1 = (T1,G1)› by auto

  have T2_cover: "⋀ t . L M1 ∩ set T2 = L M2 ∩ set T2 ⟹ t ∈ list.set unverified_transitions ⟹ covers_unverified_transition t (T2,G2)"
  proof -
    have "⋀ t ts . t ∈ list.set ts ⟹ list.set ts ⊆ list.set unverified_transitions ⟹ L M1 ∩ set (fst (foldl verify_transition (T1, G1) ts)) = L M2 ∩ set (fst (foldl verify_transition (T1, G1) ts)) ⟹ covers_unverified_transition t (foldl verify_transition (T1, G1) ts)"
    proof -
      fix t ts
      assume "t ∈ list.set ts" and "list.set ts ⊆ list.set unverified_transitions" and "L M1 ∩ set (fst (foldl verify_transition (T1, G1) ts)) = L M2 ∩ set (fst (foldl verify_transition (T1, G1) ts))"

      then show "covers_unverified_transition t (foldl verify_transition (T1, G1) ts)"
      proof (induction ts rule: rev_induct)
        case Nil
        then show ?case by auto
      next
        case (snoc t' ts)

        then have "t ∈ transitions M1" and "t_source t ∈ reachable_states M1" and "((V (t_source t)) @ [(t_input t,t_output t)]) ≠ (V (t_target t))"
          unfolding unverified_transitions_alt_def by force+

        have "t' ∈ transitions M1" and "t_source t' ∈ reachable_states M1" and "((V (t_source t')) @ [(t_input t',t_output t')]) ≠ (V (t_target t'))"
          using snoc.prems(2)
          unfolding unverified_transitions_alt_def 
          by auto

        have "set (fst (foldl verify_transition (T1, G1) ts)) ⊆ set (fst (foldl verify_transition (T1, G1) (ts@[t'])))"
          using verify_transition_retains_testsuite unfolding foldl_append foldl.simps
          by (metis eq_fst_iff)
        then have "L M1 ∩ set (fst (foldl verify_transition (T1, G1) ts)) = L M2 ∩ set (fst (foldl verify_transition (T1, G1) ts))"
          using snoc.prems(3) 
          by blast

        have *: "L M1 ∩ Prefix_Tree.set (fst (verify_transition (foldl verify_transition (T1, G1) ts) t')) = L M2 ∩ Prefix_Tree.set (fst (verify_transition (foldl verify_transition (T1, G1) ts) t'))"
          using snoc.prems(3) by auto

        have "L M1 ∩ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) ts)) = L M2 ∩ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) ts))"
          using ‹set (fst (foldl verify_transition (T1, G1) ts)) ⊆ set (fst (foldl verify_transition (T1, G1) (ts@[t'])))› snoc.prems(3)
          by auto
        then have "convergence_graph_lookup_invar M1 M2 cg_lookup (snd (foldl verify_transition (T1, G1) ts)) ∧ Prefix_Tree.set T1 ⊆ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) ts))"
          using snoc.prems(2) verify_transition_foldl_invar_1[of ts] verify_transition_foldl_invar_2[of ts]
          by auto
        then have covers_t': "covers_unverified_transition t' (verify_transition (fst (foldl verify_transition (T1, G1) ts), snd (foldl verify_transition (T1, G1) ts)) t')"          
          using verify_transition_cover_prop[OF _ _ ‹t' ∈ transitions M1› ‹t_source t' ∈ reachable_states M1› ‹((V (t_source t')) @ [(t_input t',t_output t')]) ≠ (V (t_target t'))›, of "(fst (foldl verify_transition (T1, G1) ts))" "(snd (foldl verify_transition (T1, G1) ts))" ]
          unfolding prod.collapse
          using *
          by auto
        then have "convergence_graph_lookup_invar M1 M2 cg_lookup (snd (verify_transition (fst (foldl verify_transition (T1, G1) ts), snd (foldl verify_transition (T1, G1) ts)) t'))"
          unfolding covers_unverified_transition
          by force
        then have "convergence_graph_lookup_invar M1 M2 cg_lookup (snd (foldl verify_transition (T1, G1) (ts@[t'])))"
          by auto            

        show ?case proof (cases "t = t'")
          case True
          then show ?thesis 
            using covers_t' by auto
        next
          case False
          then have "t ∈ list.set ts"
            using snoc.prems(1) by auto

          have "list.set ts ⊆ list.set (unverified_transitions)"
            using snoc.prems(2) by auto

          have "covers_unverified_transition t (foldl verify_transition (T1, G1) ts)"
            using snoc.IH[OF ‹t ∈ list.set ts›] snoc.prems(2) ‹L M1 ∩ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) ts)) = L M2 ∩ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) ts))›
            by auto
          then have "covers_unverified_transition t (fst (foldl verify_transition (T1, G1) ts), snd (foldl verify_transition (T1, G1) ts))"
            by auto
          then have "(∃α β. converge M1 α (V (t_source t)) ∧
                            converge M2 α (V (t_source t)) ∧
                            converge M1 β (V (t_target t)) ∧
                            converge M2 β (V (t_target t)) ∧ α @ [(t_input t, t_output t)] ∈ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) ts)) ∧ β ∈ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) ts))) ∧
                            converge M2 (V (t_source t) @ [(t_input t, t_output t)]) (V (t_target t))"
            unfolding covers_unverified_transition
            by blast 
          moreover have "set (fst (foldl verify_transition (T1, G1) ts)) ⊆ set (fst (foldl verify_transition (T1, G1) (ts@[t'])))"
            using verify_transition_retains_testsuite[of "(fst (foldl verify_transition (T1, G1) ts))" "(snd (foldl verify_transition (T1, G1) ts))"]
            unfolding prod.collapse 
            by auto         
          ultimately have "(∃α β. 
                            converge M1 α (V (t_source t)) ∧
                            converge M2 α (V (t_source t)) ∧
                            converge M1 β (V (t_target t)) ∧
                            converge M2 β (V (t_target t)) ∧ α @ [(t_input t, t_output t)] ∈ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) (ts@[t']))) ∧ β ∈ Prefix_Tree.set (fst (foldl verify_transition (T1, G1) (ts@[t'])))) ∧
                            converge M2 (V (t_source t) @ [(t_input t, t_output t)]) (V (t_target t))"
            by blast
          then have "covers_unverified_transition t (fst (foldl verify_transition (T1, G1) (ts@[t'])), snd (foldl verify_transition (T1, G1) (ts@[t'])))"
            unfolding covers_unverified_transition
            using ‹convergence_graph_lookup_invar M1 M2 cg_lookup (snd (foldl verify_transition (T1, G1) (ts@[t'])))›
            by blast
          then show ?thesis
            by auto
        qed
      qed
    qed

    then show "⋀ t . L M1 ∩ set T2 = L M2 ∩ set T2 ⟹ t ∈ list.set unverified_transitions ⟹ covers_unverified_transition t (T2,G2)"
      unfolding TG2 T2 G2 ‹TG1 = (T1,G1)›
      by simp
  qed


  (* properties of T3 derived from those of T1 and T2 and the assumption that T3 is passed by M2 *)

  have verify_undefined_io_pair_retains_testsuite: "⋀ qxy T . set T ⊆ set (verify_undefined_io_pair T qxy)"
  proof -
    fix qxy :: "('a × 'b × 'c)"
    fix T
    obtain q x y where "qxy = (q,x,y)"
      using prod.exhaust by metis
    show ‹set T ⊆ set (verify_undefined_io_pair T qxy)›
      unfolding ‹qxy = (q,x,y)›
      using ‹verifies_io_pair append_io_pair M1 M2 cg_insert cg_lookup›
      unfolding verifies_io_pair_def verify_undefined_io_pair case_prod_conv
      by blast
  qed
  have verify_undefined_io_pair_folding_retains_testsuite: "⋀ qxys T . set T ⊆ set (foldl verify_undefined_io_pair T qxys)"
  proof -
    fix qxys T
    show "set T ⊆ set (foldl verify_undefined_io_pair T qxys)"
      using verify_undefined_io_pair_retains_testsuite
      by (induction qxys rule: rev_induct; force) 
  qed

  have verify_undefined_io_pair_retains_finiteness: "⋀ qxy T . finite_tree T ⟹ finite_tree (verify_undefined_io_pair T qxy)"
  proof -
    fix qxy :: "('a × 'b × 'c)"
    fix T :: "('b×'c) prefix_tree"
    assume "finite_tree T"
    obtain q x y where "qxy = (q,x,y)"
      using prod.exhaust by metis
    show ‹finite_tree (verify_undefined_io_pair T qxy)›
      unfolding ‹qxy = (q,x,y)›
      using ‹verifies_io_pair append_io_pair M1 M2 cg_insert cg_lookup› ‹finite_tree T›
      unfolding verifies_io_pair_def verify_undefined_io_pair case_prod_conv
      by blast
  qed
  have verify_undefined_io_pair_folding_retains_finiteness: "⋀ qxys T . finite_tree T ⟹ finite_tree (foldl verify_undefined_io_pair T qxys)"
  proof -
    fix qxys 
    fix T :: "('b×'c) prefix_tree"
    assume "finite_tree T"
    then show "finite_tree (foldl verify_undefined_io_pair T qxys)"
      using verify_undefined_io_pair_retains_finiteness
      by (induction qxys rule: rev_induct; force) 
  qed

  have "set T2 ⊆ set T3"
    unfolding T3 T2
  proof (induction undefined_io_pairs rule: rev_induct)
    case Nil
    then show ?case by auto
  next
    case (snoc x xs)
    then show ?case 
      using verify_undefined_io_pair_retains_testsuite[of "(foldl verify_undefined_io_pair (fst TG2) xs)" x]
      by force
  qed
  then have passes_T2: "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ L M1 ∩ set T2 = L M2 ∩ set T2"
    using ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ (L M1 ∩ set T3 = L M2 ∩ set T3)›
    by blast


  have "set T1 ⊆ set T3"
  and  G2_invar: "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ convergence_graph_lookup_invar M1 M2 cg_lookup G2"
    using T2_invar_1 T2_invar_2[OF passes_T2] ‹set T2 ⊆ set T3› 
    by auto
  then have passes_T1: "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ L M1 ∩ set T1 = L M2 ∩ set T1"
    using ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ L M1 ∩ set T3 = L M2 ∩ set T3›
    by blast

  have T3_preserves_divergence : "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ preserves_divergence M1 M2 (V ` reachable_states M1)"
    using T1_V_div[OF passes_T1] .

  have T3_state_cover : "V ` reachable_states M1 ⊆ set T3"
    using T1_state_cover ‹set T1 ⊆ set T3›
    by blast

  have T3_covers_transitions : "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ (⋀ t . t ∈ transitions M1 ⟹ t_source t ∈ reachable_states M1 ⟹ 
          (∃α β.
            converge M1 α (V (t_source t)) ∧
            converge M2 α (V (t_source t)) ∧
            converge M1 β (V (t_target t)) ∧
            converge M2 β (V (t_target t)) ∧ 
            α @ [(t_input t, t_output t)] ∈ set T3 ∧ 
            β ∈ set T3)
          ∧ converge M2 (V (t_source t) @ [(t_input t, t_output t)]) (V (t_target t)))"
    (is "((L M1 ∩ set ?TS') = (L M2 ∩ set ?TS')) ⟹ (⋀ t . t ∈ transitions M1 ⟹ t_source t ∈ reachable_states M1 ⟹ ?P1 t T3 ∧ ?P2 t)")
  proof -
    fix t assume "t ∈ transitions M1" and "t_source t ∈ reachable_states M1" and "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))"
    then consider "t ∈ sc_covered_transitions" | "t ∈ list.set unverified_transitions"
      unfolding sc_covered_transitions_alt_def unverified_transitions_alt_def
      by blast
    then show "?P1 t T3 ∧ ?P2 t"
    proof cases
      case 1         

      have "(V (t_source t)) ∈ L M1"
        using state_cover_assignment_after[OF assms(1) ‹is_state_cover_assignment M1 V› ‹t_source t ∈ reachable_states M1›]
        by auto
      then have p3: "converge M1 (V (t_source t)) (V (t_source t))"
        by auto

      have "(V (t_source t)) ∈ L M2"
        using passes_T1[OF ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))›] T1_state_cover ‹t_source t ∈ reachable_states M1› ‹(V (t_source t)) ∈ L M1›
        by (metis IntI image_subset_iff inf.cobounded1 subsetD)
      then have p4: "converge M2 (V (t_source t)) (V (t_source t))"
        by auto
      
      have "t_target t ∈ reachable_states M1"
        using reachable_states_next[OF ‹t_source t ∈ reachable_states M1› ‹t ∈ transitions M1›] 
        by auto
      then have "(V (t_target t)) ∈ L M1"
        using state_cover_assignment_after[OF assms(1) ‹is_state_cover_assignment M1 V› ]
        by auto 
      then have p5: "converge M1 (V (t_target t)) (V (t_target t))"
        by auto  

      have "(V (t_target t)) ∈ L M2"
        using passes_T1[OF ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))›] T1_state_cover ‹t_target t ∈ reachable_states M1› ‹(V (t_target t)) ∈ L M1› 
        by blast
      then have p6: "converge M2 (V (t_target t)) (V (t_target t))"
        by auto

      have p8: "(V (t_target t)) ∈ set T3"
        using T3_state_cover ‹t_target t ∈ reachable_states M1›
        by auto
      then have p7: "(V (t_source t)) @ [(t_input t, t_output t)] ∈ set T3"
        using 1 
        unfolding sc_covered_transitions_alt_def
        by auto

      have "?P2 t"
        using T1_covered_transitions_conv[OF passes_T1[OF ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))›] 1] 
        by auto
      then show ?thesis
        using p3 p4 p5 p6 p7 p8
        by blast
    next
      case 2

      show ?thesis 
        using T2_cover[OF passes_T2[OF ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))›] 2] ‹set T2 ⊆ set T3›
        unfolding covers_unverified_transition 
        by blast
    qed
  qed

  have T3_covers_defined_io_pairs : "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ (⋀ q x y q' . q ∈ reachable_states M1 ⟹ h_obs M1 q x y = Some q' ⟹
          (∃α β.
            converge M1 α (V q) ∧
            converge M2 α (V q) ∧
            converge M1 β (V q') ∧
            converge M2 β (V q') ∧ 
            α @ [(x,y)] ∈ set T3 ∧ 
            β ∈ set T3)
          ∧ converge M1 (V q @ [(x,y)]) (V q') ∧ converge M2 (V q @ [(x,y)]) (V q'))" 
    (is "((L M1 ∩ set ?TS') = (L M2 ∩ set ?TS')) ⟹ (⋀ q x y q' . q ∈ reachable_states M1 ⟹ h_obs M1 q x y = Some q' ⟹ ?P q x y q')")
  proof -
    fix q x y q' assume "q ∈ reachable_states M1" and "h_obs M1 q x y = Some q'" and "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))"
    then have "(q,x,y,q') ∈ transitions M1" and "t_source (q,x,y,q') ∈ reachable_states M1"
      using h_obs_Some[OF assms(1)] by auto
    moreover have "converge M1 (V q @ [(x,y)]) (V q')" 
      using state_cover_transition_converges[OF assms(1) ‹is_state_cover_assignment M1 V› calculation]
      by auto
    ultimately show "?P q x y q'"
      using T3_covers_transitions[of "(q,x,y,q')", OF ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))›] 
      unfolding fst_conv snd_conv
      by blast
  qed

    


  (* properties of T3 w.r.t. undefined io pairs *)

  have rstates_io_set : "list.set rstates_io = {(q,(x,y)) . q ∈ reachable_states M1 ∧ x ∈ inputs M1 ∧ y ∈ outputs M1}"
    unfolding rstates_io rstates 
    using reachable_states_as_list_set[of M1] inputs_as_list_set[of M1] outputs_as_list_set[of M1] 
    by force
  then have undefined_io_pairs_set : "list.set undefined_io_pairs = {(q,(x,y)) . q ∈ reachable_states M1 ∧ x ∈ inputs M1 ∧ y ∈ outputs M1 ∧ h_obs M1 q x y = None}"
    unfolding undefined_io_pairs 
    by auto

  have verify_undefined_io_pair_prop : "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ (⋀ q x y T . L M1 ∩ set (verify_undefined_io_pair T (q,(x,y))) = L M2 ∩ set (verify_undefined_io_pair T (q,(x,y))) ⟹ 
                                                    q ∈ reachable_states M1 ⟹ x ∈ inputs M1 ⟹ y ∈ outputs M1 ⟹
                                                    V ` reachable_states M1 ⊆ set T ⟹
                                                    ∃ α. converge M1 α (V q) ∧ 
                                                         converge M2 α (V q) ∧ 
                                                         α ∈ set (verify_undefined_io_pair T (q,(x,y))) ∧
                                                         α@[(x,y)] ∈ set (verify_undefined_io_pair T (q,(x,y))))"
  proof -
    fix q x y T
    assume "L M1 ∩ set (verify_undefined_io_pair T (q,(x,y))) = L M2 ∩ set (verify_undefined_io_pair T (q,(x,y)))"
       and "q ∈ reachable_states M1" and "x ∈ inputs M1" and "y ∈ outputs M1"
       and "V ` reachable_states M1 ⊆ set T"
       and "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))"

    have " L M1 ∩ V ` reachable_states M1 = L M2 ∩ V ` reachable_states M1"
      using T3_state_cover ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ L M1 ∩ Prefix_Tree.set T3 = L M2 ∩ Prefix_Tree.set T3› ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))› 
      by blast

    have "L M1 ∩ set (fst (append_io_pair M1 V T G2 cg_insert cg_lookup q x y)) = L M2 ∩ set (fst (append_io_pair M1 V T G2 cg_insert cg_lookup q x y))"
      using ‹L M1 ∩ set (verify_undefined_io_pair T (q,(x,y))) = L M2 ∩ set (verify_undefined_io_pair T (q,(x,y)))›
      unfolding verify_undefined_io_pair case_prod_conv combine_set G2
      by blast


    have "(∃α. converge M1 α (V q) ∧
          converge M2 α (V q) ∧
          α ∈ set (fst (append_io_pair M1 V T G2 cg_insert cg_lookup q x y)) ∧
          α @ [(x, y)] ∈ set (fst (append_io_pair M1 V T G2 cg_insert cg_lookup q x y)))"
      using assms(16)
      unfolding verifies_io_pair_def 
      using assms(1-4,7,8) ‹is_state_cover_assignment M1 V› ‹L M1 ∩ V ` reachable_states M1 = L M2 ∩ V ` reachable_states M1›
            ‹q ∈ reachable_states M1› ‹x ∈ inputs M1› ‹y ∈ outputs M1›
            G2_invar[OF ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))›] ‹convergence_graph_insert_invar M1 M2 cg_lookup cg_insert›
            ‹L M1 ∩ set (fst (append_io_pair M1 V T G2 cg_insert cg_lookup q x y)) = L M2 ∩ set (fst (append_io_pair M1 V T G2 cg_insert cg_lookup q x y))› 
      by blast
    then show "∃ α. converge M1 α (V q) ∧ 
               converge M2 α (V q) ∧ 
               α ∈ set (verify_undefined_io_pair T (q,(x,y))) ∧
               α@[(x,y)] ∈ set (verify_undefined_io_pair T (q,(x,y)))"
      unfolding verify_undefined_io_pair G2 case_prod_conv combine_set
      by blast
  qed

  have T3_covers_undefined_io_pairs : "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ (⋀ q x y . q ∈ reachable_states M1 ⟹ x ∈ inputs M1 ⟹ y ∈ outputs M1 ⟹ h_obs M1 q x y = None ⟹
          (∃ α .
               converge M1 α (V q) ∧ 
               converge M2 α (V q) ∧
               α ∈ set T3∧
               α@[(x,y)] ∈ set T3))" 
  proof -
    fix q x y assume "q ∈ reachable_states M1" and "x ∈ inputs M1" and "y ∈ outputs M1" and "h_obs M1 q x y = None" and "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))"

    have "⋀ q x y qxys T . L M1 ∩ set (foldl verify_undefined_io_pair T qxys) = L M2 ∩ set (foldl verify_undefined_io_pair T qxys) ⟹ (V ` reachable_states M1) ⊆ set T ⟹ (q,(x,y)) ∈ list.set qxys ⟹ list.set qxys ⊆ list.set undefined_io_pairs ⟹ 
              (∃ α .
               converge M1 α (V q) ∧ 
               converge M2 α (V q) ∧
               α ∈ set (foldl verify_undefined_io_pair T qxys)∧
               α@[(x,y)] ∈ set (foldl verify_undefined_io_pair T qxys))"
      (is "⋀ q x y qxys T . ?P1 qxys T ⟹ (V ` reachable_states M1) ⊆ set T ⟹ (q,(x,y)) ∈ list.set qxys ⟹ list.set qxys ⊆ list.set undefined_io_pairs ⟹ ?P2 q x y qxys T")
    proof -
      fix q x y qxys T
      assume "?P1 qxys T" and "(q,(x,y)) ∈ list.set qxys" and "list.set qxys ⊆ list.set undefined_io_pairs" and "(V ` reachable_states M1) ⊆ set T"
      then show "?P2 q x y qxys T"
      proof (induction qxys rule: rev_induct)
        case Nil
        then show ?case by auto
      next
        case (snoc a qxys)

        have "set (foldl verify_undefined_io_pair T qxys) ⊆ set (foldl verify_undefined_io_pair T (qxys@[a]))"
          using verify_undefined_io_pair_retains_testsuite 
          by auto
        then have *:"L M1 ∩ Prefix_Tree.set (foldl verify_undefined_io_pair T qxys) = L M2 ∩ Prefix_Tree.set (foldl verify_undefined_io_pair T qxys)"
          using snoc.prems(1)
          by blast

        have **: "V ` reachable_states M1 ⊆ Prefix_Tree.set (foldl verify_undefined_io_pair T qxys)"
          using snoc.prems(4) verify_undefined_io_pair_folding_retains_testsuite
          by blast

        show ?case proof (cases "a = (q,(x,y))")
          case True
          then have ***: "q ∈ reachable_states M1"
            using snoc.prems(3) 
            unfolding undefined_io_pairs_set
            by auto 

          have "x ∈ inputs M1" and "y ∈ outputs M1"
            using snoc.prems(2,3) unfolding undefined_io_pairs_set by auto

          have ****: "L M1 ∩ set (verify_undefined_io_pair (foldl verify_undefined_io_pair T qxys) (q,(x,y))) = L M2 ∩ set (verify_undefined_io_pair (foldl verify_undefined_io_pair T qxys) (q,(x,y)))"
            using snoc.prems(1) unfolding True by auto
            
          show ?thesis
            using verify_undefined_io_pair_prop[OF ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))› **** *** ‹x ∈ inputs M1› ‹y ∈ outputs M1› **]
            unfolding True 
            by auto
        next
          case False
          then have "(q, x, y) ∈ list.set qxys" and "list.set qxys ⊆ list.set undefined_io_pairs"
            using snoc.prems(2,3) by auto
          then show ?thesis 
            using snoc.IH[OF * _ _  snoc.prems(4)]
            using ‹set (foldl verify_undefined_io_pair T qxys) ⊆ set (foldl verify_undefined_io_pair T (qxys@[a]))›
            by blast
        qed
      qed
    qed
    moreover have "L M1 ∩ set (foldl verify_undefined_io_pair T2 undefined_io_pairs) = L M2 ∩ set (foldl verify_undefined_io_pair T2 undefined_io_pairs)"
      using ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ L M1 ∩ set T3 = L M2 ∩ set T3›  ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))›
      unfolding T3 T2 .
    moreover have "(V ` reachable_states M1) ⊆ set T2"
      using T1_state_cover T2 T2_invar_1 passes_T2 by fastforce
    moreover have "(q,(x,y)) ∈ list.set undefined_io_pairs"
      unfolding undefined_io_pairs_set
      using ‹q ∈ reachable_states M1› ‹x ∈ inputs M1› ‹y ∈ outputs M1› ‹h_obs M1 q x y = None›
      by blast
    ultimately show "(∃ α .
               converge M1 α (V q) ∧ 
               converge M2 α (V q) ∧
               α ∈ set T3∧
               α@[(x,y)] ∈ set T3)"
      unfolding T3 T2 
      by blast
  qed

  (* obtain the transition cover contained in the test suite 
      - this is the set of witnesses for the convergence of all transitions
        and the set of transitions added to cover undefined io 
  *)

  define TCfun where TCfun: "TCfun = (λ (q,(x,y)) . case h_obs M1 q x y of 
                                None ⇒ {{α, α@[(x,y)]} | α . converge M1 α (V q) ∧ converge M2 α (V q) ∧ α ∈ set T3 ∧ α@[(x,y)] ∈ set T3} |
                                Some q' ⇒ {{α,α@[(x,y)], β} | α β . converge M1 α (V q) ∧ converge M2 α (V q) ∧ converge M1 β (V q') ∧ converge M2 β (V q') ∧ α @ [(x,y)] ∈ set T3 ∧ β ∈ set T3 ∧ converge M1 (V q @ [(x,y)]) (V q') ∧ converge M2 (V q @ [(x,y)]) (V q')})"
  define TC where TC: "TC = Set.insert [] (⋃(⋃ (TCfun ` (reachable_states M1 × (inputs M1 × outputs M1)))))"


  (* establish that this transition cover is sufficient to prove equivalence by showing that it is
    - initialised 
    - a proper transition cover
    - convergence preserving
  *)


  have TCfun_nonempty: "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ (⋀ q x y . q ∈ reachable_states M1 ⟹ x ∈ inputs M1 ⟹ y ∈ outputs M1 ⟹ TCfun (q,(x,y)) ≠ {})"
  proof -
    fix q x y assume *:"q ∈ reachable_states M1" and **:"x ∈ inputs M1" and ***:"y ∈ outputs M1" and "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))"

    show "TCfun (q,(x,y)) ≠ {}"
    proof (cases "h_obs M1 q x y")
      case None
      then have "TCfun (q,(x,y)) = {{α, α @ [(x, y)]} |α. converge M1 α (V q) ∧ converge M2 α (V q) ∧ α ∈ set T3 ∧ α @ [(x, y)] ∈ set T3}"
        unfolding TCfun by auto
      moreover have "{{α, α @ [(x, y)]} |α. converge M1 α (V q) ∧ converge M2 α (V q) ∧ α ∈ set T3 ∧ α @ [(x, y)] ∈ set T3} ≠ {}"
        using T3_covers_undefined_io_pairs[OF ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))› * ** *** None]
        by blast
      ultimately show ?thesis 
        by blast 
    next
      case (Some q')
      then have "TCfun (q,(x,y)) = {{α,α@[(x,y)], β} | α β . converge M1 α (V q) ∧ converge M2 α (V q) ∧ converge M1 β (V q') ∧ converge M2 β (V q') ∧ α @ [(x,y)] ∈ set T3 ∧ β ∈ set T3 ∧ converge M1 (V q @ [(x,y)]) (V q') ∧ converge M2 (V q @ [(x,y)]) (V q')}"
        using TCfun by auto
      moreover have "{{α,α@[(x,y)], β} | α β . converge M1 α (V q) ∧ converge M2 α (V q) ∧ converge M1 β (V q') ∧ converge M2 β (V q') ∧ α @ [(x,y)] ∈ set T3 ∧ β ∈ set T3 ∧ converge M1 (V q @ [(x,y)]) (V q') ∧ converge M2 (V q @ [(x,y)]) (V q')} ≠ {}"
        using T3_covers_defined_io_pairs[OF ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))› * Some]
        by blast
      ultimately show ?thesis 
        by blast 
    qed
  qed

  have TC_in_T3: "TC ⊆ set T3"
  proof
    fix α assume "α ∈ TC"
    show "α ∈ set T3"
    proof (cases "α = []")
      case True
      then show ?thesis 
        using T3_state_cover ‹is_state_cover_assignment M1 V› reachable_states_initial[of M1]
        by auto
    next
      case False
      then obtain q x y where "q ∈ reachable_states M1"
                        and "x ∈ inputs M1"
                        and "y ∈ outputs M1"
                        and "α ∈ ⋃ (TCfun (q,(x,y)))"
        using ‹α ∈ TC› unfolding TC 
        by auto

      show "α ∈ set T3"
        using ‹α ∈ ⋃ (TCfun (q,(x,y)))› set_prefix[of "α" "[(x,y)]" T3] unfolding TCfun 
        by (cases "h_obs M1 q x y";auto)
    qed
  qed

    

  have TC_is_transition_cover : "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ transition_cover M1 TC"
  proof -
    assume "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))"

    have "⋀ q x y . q ∈ reachable_states M1 ⟹ x ∈ inputs M1 ⟹ y ∈ outputs M1 ⟹ ∃α. α ∈ TC ∧ α @ [(x, y)] ∈ TC ∧ α ∈ L M1 ∧ after_initial M1 α = q"
    proof -
      fix q x y assume "q ∈ reachable_states M1"
                   and "x ∈ inputs M1"
                   and "y ∈ outputs M1"
      then have "(q,(x,y)) ∈ (reachable_states M1 × FSM.inputs M1 × FSM.outputs M1)"
        by blast

      show "∃α. α ∈ TC ∧ α @ [(x, y)] ∈ TC ∧ α ∈ L M1 ∧ after_initial M1 α = q"
      proof (cases "h_obs M1 q x y")
        case None
        then have "TCfun (q,(x,y)) = {{α, α @ [(x, y)]} |α. converge M1 α (V q) ∧ converge M2 α (V q) ∧ α ∈ set T3 ∧ α @ [(x, y)] ∈ set T3}"
          unfolding TCfun by auto
        then obtain α where "converge M1 α (V q)" and "α ∈ ⋃ (TCfun (q,(x,y))) ∧ α @ [(x, y)] ∈ ⋃ (TCfun (q,(x,y)))"
          using TCfun_nonempty[OF ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))› ‹q ∈ reachable_states M1› ‹x ∈ inputs M1› ‹y ∈ outputs M1›]
          by auto
        then have "after_initial M1 α = q"
          using state_cover_assignment_after[OF assms(1) ‹is_state_cover_assignment M1 V› ‹q ∈ reachable_states M1›]
          using convergence_minimal[OF assms(3,1)]
          by (metis converge.elims(2))
        then have "∃α. α ∈ ⋃ (TCfun (q,(x,y))) ∧ α @ [(x, y)] ∈ ⋃ (TCfun (q,(x,y))) ∧ α ∈ L M1 ∧ after_initial M1 α = q"
          using ‹α ∈ ⋃ (TCfun (q,(x,y))) ∧ α @ [(x, y)] ∈ ⋃ (TCfun (q,(x,y)))›
          using ‹converge M1 α (V q)› converge.elims(2) by blast
        moreover have "⋃ (TCfun (q,(x,y))) ⊆ TC"
          unfolding TC using ‹(q,(x,y)) ∈ (reachable_states M1 × FSM.inputs M1 × FSM.outputs M1)›
          by blast
        ultimately show ?thesis              
          by blast
      next
        case (Some q')
        then have "TCfun (q,(x,y)) = {{α,α@[(x,y)], β} | α β . converge M1 α (V q) ∧ converge M2 α (V q) ∧ converge M1 β (V q') ∧ converge M2 β (V q') ∧ α @ [(x,y)] ∈ set T3 ∧ β ∈ set T3 ∧ converge M1 (V q @ [(x,y)]) (V q') ∧ converge M2 (V q @ [(x,y)]) (V q')}"
          using TCfun 
          by auto
        moreover obtain S where "S ∈ TCfun (q,(x,y))"
          using TCfun_nonempty[OF ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS))› ‹q ∈ reachable_states M1› ‹x ∈ inputs M1› ‹y ∈ outputs M1›]
          by blast
        ultimately obtain α where "converge M1 α (V q)" and "α ∈ S ∧ α @ [(x, y)] ∈ S"
          by auto 
        then have "after_initial M1 α = q"
          using state_cover_assignment_after[OF assms(1) ‹is_state_cover_assignment M1 V› ‹q ∈ reachable_states M1›]
          using convergence_minimal[OF assms(3,1)]
          by (metis converge.elims(2))
        moreover have "α ∈ ⋃ (TCfun (q,(x,y))) ∧ α @ [(x, y)] ∈ ⋃ (TCfun (q,(x,y)))"
          using ‹α ∈ S ∧ α @ [(x, y)] ∈ S› ‹S ∈ TCfun (q,(x,y))›
          by auto
        ultimately have "∃α. α ∈ ⋃ (TCfun (q,(x,y))) ∧ α @ [(x, y)] ∈ ⋃ (TCfun (q,(x,y))) ∧ α ∈ L M1 ∧ after_initial M1 α = q"
          using ‹α ∈ ⋃ (TCfun (q,(x,y))) ∧ α @ [(x, y)] ∈ ⋃ (TCfun (q,(x,y)))›
          using ‹converge M1 α (V q)› converge.elims(2) by blast
        moreover have "⋃ (TCfun (q,(x,y))) ⊆ TC"
          unfolding TC using ‹(q,(x,y)) ∈ (reachable_states M1 × FSM.inputs M1 × FSM.outputs M1)›
          by blast
        ultimately show ?thesis              
          by blast
      qed
    qed
    then show ?thesis
      unfolding transition_cover_def 
      by blast
  qed

  have TC_preserves_convergence: "preserves_convergence M1 M2 TC"
  proof -
    have "⋀ α β . α ∈ L M1 ∩ TC ⟹ β ∈ L M1 ∩ TC ⟹ converge M1 α β ⟹ converge M2 α β"
    proof -
      fix α β assume "α ∈ L M1 ∩ TC" 
                     "β ∈ L M1 ∩ TC" 
                     "converge M1 α β"

      have *: "⋀ α . α ∈ L M1 ⟹ α ∈ TC ⟹ ∃ q . q ∈ reachable_states M1 ∧ converge M1 α (V q) ∧ converge M2 α (V q)"
      proof -
        fix α assume "α ∈ L M1" and "α ∈ TC"

        show "∃ q . q ∈ reachable_states M1 ∧ converge M1 α (V q) ∧ converge M2 α (V q)"
        proof (cases "α = []")
          case True

          then have "V (initial M1) = α"
            using ‹is_state_cover_assignment M1 V› reachable_states_initial[of M1]
            by auto
          then have "converge M1 α (V (initial M1))" and "converge M2 α (V (initial M1))"
            unfolding True by auto
          then show ?thesis
            using reachable_states_initial[of M1]
            by auto
        next
          case False
          then have "α ∈ (⋃(⋃ (TCfun ` (reachable_states M1 × (inputs M1 × outputs M1)))))"
            using ‹α ∈ TC›
            unfolding TC
            by blast
          then obtain q x y where "q ∈ reachable_states M1"
                              and "x ∈ inputs M1"
                              and "y ∈ outputs M1"
                              and "α ∈ ⋃ (TCfun (q,(x,y)))"
            unfolding TC by auto

          show "∃ q . q ∈ reachable_states M1 ∧ converge M1 α (V q) ∧ converge M2 α (V q)"
          proof (cases "h_obs M1 q x y")
            case None
            then have "TCfun (q,(x,y)) = {{α, α @ [(x, y)]} |α. converge M1 α (V q) ∧ converge M2 α (V q) ∧ α ∈ set T3 ∧ α @ [(x, y)] ∈ set T3}"
              unfolding TCfun by auto
            then obtain α' where "α ∈ {α', α' @ [(x, y)]}"
                             and "converge M1 α' (V q)"
                             and "converge M2 α' (V q)"
              using ‹α ∈ ⋃ (TCfun (q,(x,y)))› 
              by auto

            have "[(x,y)] ∉ LS M1 q"
              using None unfolding h_obs_None[OF assms(1)] LS_single_transition 
              by auto
            moreover have "after_initial M1 α' = q"
              using ‹converge M1 α' (V q)› 
              using state_cover_assignment_after[OF assms(1) ‹is_state_cover_assignment M1 V› ‹q ∈ reachable_states M1›]
              using convergence_minimal[OF assms(3,1) _ _]
              by (metis converge.elims(2))
            ultimately have "α' @ [(x, y)] ∉ L M1"
              using after_language_iff[OF assms(1), of α' "initial M1" "[(x,y)]"]  ‹converge M1 α' (V q)›
              by (meson converge.elims(2)) 
            then have "α' = α"
              using ‹α ∈ {α', α' @ [(x, y)]}› ‹α ∈ L M1›
              by blast
            then show ?thesis 
              using ‹q ∈ reachable_states M1› ‹converge M1 α' (V q)› ‹converge M2 α' (V q)›
              by blast
          next
            case (Some q')
            then have "q' ∈ reachable_states M1"
                unfolding h_obs_Some[OF assms(1)]
                using reachable_states_next[OF ‹q ∈ reachable_states M1›, of "(q,x,y,q')"]
                by auto

            have "TCfun (q,(x,y)) = {{α,α@[(x,y)], β} | α β . converge M1 α (V q) ∧ converge M2 α (V q) ∧ converge M1 β (V q') ∧ converge M2 β (V q') ∧ α @ [(x,y)] ∈ set T3 ∧ β ∈ set T3 ∧ converge M1 (V q @ [(x,y)]) (V q') ∧ converge M2 (V q @ [(x,y)]) (V q')}"
              using Some TCfun 
              by auto
            then obtain α' β where "α ∈ {α',α'@[(x,y)], β}"
                               and "converge M1 α' (V q)"
                               and "converge M2 α' (V q)"
                               and "converge M1 β (V q')"
                               and "converge M2 β (V q')"
                               and "converge M1 (V q @ [(x,y)]) (V q')"
                               and "converge M2 (V q @ [(x,y)]) (V q')"
              using ‹α ∈ ⋃ (TCfun (q,(x,y)))› 
              by auto

            then consider "α = α'" | "α = α'@[(x,y)]" | "α = β"
              by blast
            then show ?thesis proof cases
              case 1
              then show ?thesis 
                using ‹q ∈ reachable_states M1› ‹converge M1 α' (V q)› ‹converge M2 α' (V q)›
                by blast
            next
              case 2

              have "converge M1 (α'@[(x,y)]) (V q @ [(x,y)])"
                using ‹converge M1 α' (V q)›  ‹converge M1 (V q @ [(x,y)]) (V q')›
                using converge_append[OF assms(1), of "V q" α' "[(x,y)]"]
                by auto
              then have "converge M1 (α'@[(x,y)]) (V q')"
                using ‹converge M1 β (V q')› ‹converge M1 (V q @ [(x,y)]) (V q')›
                by auto

              have "converge M2 (α'@[(x,y)]) (V q @ [(x,y)])"
                using ‹converge M2 α' (V q)›  ‹converge M2 (V q @ [(x,y)]) (V q')›
                using converge_append[OF assms(2), of "V q" α' "[(x,y)]"]
                by auto
              then have "converge M2 (α'@[(x,y)]) (V q')"
                using ‹converge M2 β (V q')› ‹converge M2 (V q @ [(x,y)]) (V q')›
                by auto
                
              show ?thesis
                using 2 ‹q' ∈ reachable_states M1› ‹converge M1 (α'@[(x,y)]) (V q')› ‹converge M2 (α'@[(x,y)]) (V q')›
                by auto
            next
              case 3
              then show ?thesis 
                using ‹converge M1 β (V q')› ‹converge M2 β (V q')› ‹q' ∈ reachable_states M1›
                by blast
            qed
          qed
        qed
      qed

      obtain q where "q ∈ reachable_states M1" and "converge M1 α (V q)" and "converge M2 α (V q)"
        using * ‹α ∈ L M1 ∩ TC›
        by blast

      obtain q' where "q' ∈ reachable_states M1" and "converge M1 β (V q')" and "converge M2 β (V q')"
        using * ‹β ∈ L M1 ∩ TC›
        by blast

      have "converge M1 (V q) (V q')"
        using ‹converge M1 α (V q)› ‹converge M1 β (V q')› ‹converge M1 α β›
        by auto
      then have "q = q'"
        using convergence_minimal[OF assms(3,1), of "V q" "V q'"]
        unfolding state_cover_assignment_after[OF assms(1) ‹is_state_cover_assignment M1 V› ‹q ∈ reachable_states M1›]
                  state_cover_assignment_after[OF assms(1) ‹is_state_cover_assignment M1 V› ‹q' ∈ reachable_states M1›]
        by auto
      then have "V q = V q'"
        by auto
      then show "converge M2 α β"
        using ‹converge M2 α (V q)› ‹converge M2 β (V q')›
        by auto
    qed

    then show ?thesis
      unfolding preserves_convergence.simps
      by blast
  qed

  have "[] ∈ TC"
    unfolding TC by blast
    
  show "((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ L M1 = L M2"
    using initialised_convergence_preserving_transition_cover_is_complete[OF assms(1-4,7,8) 
                                                                             ‹((L M1 ∩ set ?TS) = (L M2 ∩ set ?TS)) ⟹ L M1 ∩ set T3 = L M2 ∩ set T3› 
                                                                             TC_in_T3 
                                                                             TC_is_transition_cover 
                                                                             ‹[] ∈ TC› 
                                                                             TC_preserves_convergence]
    by assumption

    
  show "finite_tree ?TS"
    using T2 T2_finite T3 verify_undefined_io_pair_folding_retains_finiteness 
    by (simp add: ‹?TS = T3›)
qed

end