Theory State_Preamble

section ‹State Preambles›

text ‹This theory defines state preambles.
      A state preamble @{text "P"} of some state @{text "q"} of some FSM @{text "M"} is an acyclic single-input 
      submachine of @{text "M"} that contains for each of its states and defined inputs in that state
      all transitions of @{text "M"} and has @{text "q"} as its only deadlock state.
      That is, @{text "P"} represents a strategy of reaching @{text "q"} in every complete submachine
      of @{text "M"}.
      In testing, preambles are used to reach states in the SUT that must conform to a single known
      state in the specification.›

theory State_Preamble
imports "../Product_FSM" Backwards_Reachability_Analysis
begin


definition is_preamble :: "('a,'b,'c) fsm  ('a,'b,'c) fsm  'a  bool" where
  "is_preamble S M q = 
    ( acyclic S 
     single_input S 
     is_submachine S M 
     q  reachable_states S 
     deadlock_state S q 
     ( q'  reachable_states S . 
        (q = q'  ¬ deadlock_state S q')  
        ( x  inputs M . 
          ( t  transitions S . t_source t = q'  t_input t = x) 
             ( t'  transitions M . t_source t' = q'  t_input t' = x  t'  transitions S))))"

fun definitely_reachable :: "('a,'b,'c) fsm  'a  bool" where
  "definitely_reachable M q = ( S . is_preamble S M q)"




subsection ‹Basic Properties›

lift_definition initial_preamble :: "('a,'b,'c) fsm  ('a,'b,'c) fsm" is FSM_Impl.initial_singleton 
  by auto

lemma initial_preamble_simps[simp] :
  "initial (initial_preamble M) = initial M"
  "states (initial_preamble M) = {initial M}"
  "inputs (initial_preamble M) = inputs M"
  "outputs (initial_preamble M) = outputs M"
  "transitions (initial_preamble M) = {}"
  by (transfer; auto)+


lemma is_preamble_initial : 
  "is_preamble (initial_preamble M) M (initial M)"
proof -
  have "acyclic (initial_preamble M)"
    by (metis acyclic_code empty_iff initial_preamble_simps(5))
  moreover have "single_input (initial_preamble M)"
    by auto
  moreover have "is_submachine (initial_preamble M) M"
    by (simp add: fsm_initial)
  moreover have "(initial M)  reachable_states (initial_preamble M)"
    unfolding reachable_states_def using reachable_states_intro by auto 
  moreover have "deadlock_state (initial_preamble M) (initial M)"
    by simp
  ultimately show ?thesis  
    unfolding is_preamble_def
    using reachable_state_is_state by force
qed
  
 
 

lemma is_preamble_next :
  assumes "is_preamble S M q"
  and "q  initial M"
  and "t  transitions S"  
  and "t_source t = initial M"
shows "is_preamble (from_FSM S (t_target t)) (from_FSM M (t_target t)) q"
(is "is_preamble ?S ?M q")
proof -


  have "acyclic S"
  and  "single_input S" 
  and  "is_submachine S M" 
  and  "q  reachable_states S"
  and  "deadlock_state S q" 
  and  *: "( q'  reachable_states S . (q = q'  ¬ deadlock_state S q') 
             ( x  inputs M . ( t  transitions S . t_source t = q'  t_input t = x) 
                                 ( t'  transitions M . t_source t' = q'  t_input t' = x 
                                                             t'  transitions S)))"
    using assms(1) unfolding is_preamble_def by linarith+

  have "t_target t  states S"
    using assms(3) fsm_transition_target by metis
  have "t_target t  states M"
    using is_submachine S M t_target t  FSM.states S by auto 

  have is_acyclic: "acyclic ?S" 
    using from_FSM_path_initial[OF t_target t  states S]
    unfolding acyclic.simps from_FSM_simps[OF t_target t  states S]
    using acyclic_paths_from_reachable_states[OF acyclic S, of "[t]" "t_target t"]
    by (metis is_submachine S M assms(3) assms(4) is_submachine.elims(2) 
          prod.collapse single_transition_path target_single_transition)

  have is_single_input: "single_input ?S"
    using single_input S 
    unfolding single_input.simps from_FSM_simps[OF t_target t  states S] by blast

  have "initial ?S = initial ?M"
    by (simp add: t_target t  FSM.states M t_target t  FSM.states S) 
  moreover have "inputs ?S = inputs ?M"
    using is_submachine S M by (simp add: t_target t  FSM.states M t_target t  FSM.states S)
  moreover have "outputs ?S = outputs ?M"
    using is_submachine S M by (simp add: t_target t  FSM.states M t_target t  FSM.states S) 
  moreover have "transitions ?S  transitions ?M"
    using is_submachine S M
    by (simp add: t_target t  FSM.states M t_target t  FSM.states S)
  ultimately have is_sub : "is_submachine ?S ?M"
    using is_submachine S M t_target t  FSM.states M t_target t  FSM.states S by auto


  have contains_q : "q  reachable_states ?S"
  proof -
    obtain qd where "qd  reachable_states ?S" and "deadlock_state ?S qd"
      using is_acyclic
      using acyclic_deadlock_reachable by blast 
    
    have "qd  reachable_states S"
      by (metis (no_types, lifting) is_submachine S M qd  reachable_states (FSM.from_FSM S (t_target t)) 
            assms(3) assms(4) from_FSM_reachable_states in_mono is_submachine.elims(2) prod.collapse 
            reachable_states_intro single_transition_path target_single_transition)
    then have "deadlock_state S qd"
      using deadlock_state ?S qd unfolding deadlock_state.simps
      by (simp add: t_target t  FSM.states S)
    then have "qd = q"
      using * qd  reachable_states S
      by fastforce
    then show ?thesis 
      using qd  reachable_states ?S by auto
  qed
  
  have has_deadlock_q : "deadlock_state ?S q"
    using *
    by (metis deadlock_state S q t_target t  FSM.states S deadlock_state.simps from_FSM_simps(4))


  have has_states_prop_1: " q' . q'  reachable_states ?S  deadlock_state ?S q'  q = q'"
  proof -
    fix q' assume "q'  reachable_states ?S" and "deadlock_state ?S q'"
    have "q'  reachable_states S"
      by (metis (no_types, lifting) is_submachine S M q'  reachable_states (FSM.from_FSM S (t_target t)) 
          assms(3) assms(4) from_FSM_reachable_states in_mono is_submachine.elims(2) prod.collapse 
          reachable_states_intro single_transition_path target_single_transition)      
    then have "deadlock_state S q'"
      using deadlock_state ?S q' unfolding deadlock_state.simps
      using q'  reachable_states ?S by (simp add: t_target t  FSM.states S)
    then show "q = q'"
      using * q'  reachable_states S by fastforce 
  qed

  moreover have has_states_prop_2: " x t t' q' .
    q'  reachable_states ?S 
    t  transitions ?S  t_source t = q'  t_input t = x 
    t'  transitions ?M  t_source t' = q'  t_input t' = x  t'  transitions ?S"
  proof -
    fix x tS tM q' assume "q'  reachable_states ?S" and "tS  transitions ?S" and "t_source tS = q'" 
                      and "t_input tS = x" and "tM  transitions ?M" and "t_source tM = q'" 
                      and "t_input tM = x"


    have "q'  reachable_states S"
      by (metis (no_types, lifting) is_submachine S M q'  reachable_states (FSM.from_FSM S (t_target t)) 
            assms(3) assms(4) from_FSM_reachable_states in_mono is_submachine.elims(2) prod.collapse 
            reachable_states_intro single_transition_path target_single_transition)
    

    have "tS  transitions S"
      using tS  transitions ?S by (simp add: t_target t  FSM.states S)
    have "tM  transitions M"
      using tM  transitions ?M
      using t_target t  FSM.states M by (simp add: t_target t  FSM.states S)
    have "t_source tS  states (from_FSM S (t_target t))"
      using tS  transitions ?S by auto
    have "t_source tM  states (from_FSM M (t_target t))"
      using tM  transitions ?M by auto

    have "q'  reachable_states ?M" 
      using q'  reachable_states ?S submachine_path[OF is_submachine ?S ?M]
      unfolding reachable_states_def
    proof -
      assume "q'  {target (FSM.initial (FSM.from_FSM S (t_target t))) p |p. 
                      path (FSM.from_FSM S (t_target t)) (FSM.initial (FSM.from_FSM S (t_target t))) p}"
      then show "q'  {target (FSM.initial (FSM.from_FSM M (t_target t))) ps |ps. 
                        path (FSM.from_FSM M (t_target t)) (FSM.initial (FSM.from_FSM M (t_target t))) ps}"
        using FSM.initial (FSM.from_FSM S (t_target t)) = FSM.initial (FSM.from_FSM M (t_target t)) 
              q p. path (FSM.from_FSM S (t_target t)) q p  path (FSM.from_FSM M (t_target t)) q p 
        by fastforce
    qed
       

    show "tM  transitions ?S" 
      using * q'  reachable_states S
            tM  FSM.transitions M tS  FSM.transitions S t_input tM = x t_input tS = x 
            t_source tM = q' t_source tS = q' t_target t  FSM.states S 
      by fastforce       
  qed 
     

  show ?thesis
    unfolding is_preamble_def
    using is_acyclic 
          is_single_input 
          is_sub
          contains_q
          has_deadlock_q
          has_states_prop_1
    using has_states_prop_2 by blast    
qed


lemma observable_preamble_paths :
  assumes "is_preamble P M q'"
  and     "observable M"
  and     "path M q p"  
  and     "p_io p  LS P q"
  and     "q  reachable_states P"
shows "path P q p"
using assms(3,4,5) proof (induction p arbitrary: q rule: list.induct)
  case Nil
  then show ?case by auto
next
  case (Cons t p)

  have   "is_submachine P M"
  and *: " q' x t t' . q'reachable_states P  xFSM.inputs M 
            tFSM.transitions P  t_source t = q'  t_input t = x 
            t'FSM.transitions M  t_source t' = q'  t_input t' = x  t'  FSM.transitions P"
    using assms(1) unfolding is_preamble_def by blast+

  have "observable P"
    using submachine_observable[OF is_submachine P M observable M] by blast

  obtain t' where "t'FSM.transitions P" and "t_source t' = q" and "t_input t' = t_input t"
    using p_io (t # p)  LS P q by auto

  have "t_source t = q" and "t  transitions M" and "t_input t  inputs M"
    using path M q (t # p) by auto

  have "t  transitions P"
    using *[OF q  reachable_states P t_input t  inputs M t'FSM.transitions P 
               t_source t' = q t_input t' = t_input t t  transitions M t_source t = q]
    by auto

  have "path M (t_target t) p"
    using path M q (t # p) by auto
  moreover have "p_io p  LS P (t_target t)"
  proof -
    have f1: "t_input t = fst (t_input t, t_output t)"
      by (metis fst_conv)
    have f2: "t_output t = snd (t_input t, t_output t)"
      by auto
    have f3: "(t_input t, t_output t) # p_io p  LS P (t_source t)"
      using Cons.prems(2) t_source t = q by fastforce
    have "L (FSM.from_FSM P (t_target t)) = LS P (t_target t)"
      by (meson t  FSM.transitions P from_FSM_language fsm_transition_target)
    then show ?thesis
      using f3 f2 f1 observable P t  FSM.transitions P observable_language_next by blast
  qed   
  moreover have "t_target t  reachable_states P"
    using t  transitions P t_source t = q q  reachable_states P
    by (meson reachable_states_next) 
  ultimately have "path P (t_target t) p"
    using Cons.IH by blast
  then show ?case
    using t  transitions P t_source t = q by auto
qed


lemma preamble_pass_path :
  assumes "is_preamble P M q"
  and     " io x y y' . io@[(x,y)]  L P  io@[(x,y')]  L M'  io@[(x,y')]  L P"
  and     "completely_specified M'"
  and     "inputs M' = inputs M"
obtains p where "path P (initial P) p" and "target (initial P) p = q" and "p_io p  L M'"
proof -
  (* get the longest paths p such that p_io p is still in L M' *)

  let ?ps = "{p . path P (initial P) p  p_io p  L M'}"
  have "?ps  {}"
  proof -
    have "[]  ?ps" by auto
    then show ?thesis by blast
  qed
  moreover have "finite ?ps"
  proof -
    have "acyclic P"
      using assms(1) unfolding is_preamble_def by blast
    have "finite {p. path P (FSM.initial P) p}"
      using acyclic_finite_paths_from_reachable_state[OF acyclic P, of "[]" "initial P"] by auto
    then show ?thesis
      by simp 
  qed
  ultimately obtain p where "p  ?ps" and " p' . p'  ?ps  length p'  length p" 
    by (meson leI max_length_elem) 
  then have "path P (initial P) p"
       and  "p_io p  L M'"
    by blast+

  show ?thesis
  proof (cases "target (initial P) p = q")
    case True
    then show ?thesis using that[OF path P (initial P) p _ p_io p  L M'] by blast
  next
    case False

    (* if p does not target the sole deadlock state q, then it can be extended *)

    then have "¬ deadlock_state P (target (initial P) p)"
      using reachable_states_intro[OF path P (initial P) p] assms(1) unfolding is_preamble_def by fastforce
    then obtain t where "t  transitions P" and "t_source t = target (initial P) p"
      by auto
    then have "path P (initial P) (p@[t])" 
      using path P (initial P) p path_append_transition by simp
    have "(p_io p) @ [(t_input t, t_output t)]  L P"
      using language_intro[OF path P (initial P) (p@[t])] by simp

    have "t_input t  inputs M'"
      using assms(1,4) fsm_transition_input[OF t  transitions P] unfolding is_preamble_def is_submachine.simps by blast
    
    obtain p' where "path M' (initial M') p'" and "p_io p' = p_io p"
      using p_io p  L M' by auto
    obtain t' where "t'  transitions M'" and "t_source t' = target (initial M') p'" and "t_input t' = t_input t"
      using completely_specified M' t_input t  inputs M' path_target_is_state[OF path M' (initial M') p']
      unfolding completely_specified.simps by blast
    then have "path M' (initial M') (p'@[t'])" 
      using path M' (initial M') p' path_append_transition by simp
    have "(p_io p) @ [(t_input t, t_output t')]  L M'"
      using language_intro[OF path M' (initial M') (p'@[t'])] 
      unfolding p_io p' = p_io p[symmetric] t_input t' = t_input t[symmetric] by simp

    have "(p_io p) @ [(t_input t, t_output t')]  L P"
      using assms(2)[OF (p_io p) @ [(t_input t, t_output t)]  L P (p_io p) @ [(t_input t, t_output t')]  L M']
      by assumption
    then obtain pt' where "path P (initial P) pt'" and "p_io pt' = (p_io p) @ [(t_input t, t_output t')]"
      by auto
    then have "pt'  ?ps"
      using (p_io p) @ [(t_input t, t_output t')]  L M' by auto
    then have "length pt'  length p"
      using  p' . p'  ?ps  length p'  length p by blast
    moreover have "length pt' > length p"
      using p_io pt' = (p_io p) @ [(t_input t, t_output t')] 
      unfolding length_map[of "(λ t . (t_input t, t_output t))", symmetric] by simp
    ultimately have "False"
      by simp
    then show ?thesis 
      by simp
  qed
qed


lemma preamble_maximal_io_paths :
  assumes "is_preamble P M q"
  and     "observable M"
  and     "path P (initial P) p"
  and     "target (initial P) p = q"
shows "io' . io'  []  p_io p @ io'  L P" 
proof -
  have "deadlock_state P q"  
  and  "is_submachine P M"
    using assms(1) unfolding is_preamble_def by blast+

  have "observable P"
    using observable M is_submachine P M
    using submachine_observable by blast 

  show "io' . io'  []  p_io p @ io'  L P"
  proof
    assume "io'. io'  []  p_io p @ io'  L P"
    then obtain io' where "io'  []" and "p_io p @ io'  L P"
      by blast

    obtain p1 p2 where "path P (FSM.initial P) p1" 
                   and "path P (target (FSM.initial P) p1) p2" 
                   and "p_io p1 = p_io p" 
                   and "p_io p2 = io'"
      using language_state_split[OF p_io p @ io'  L P] by blast

    have "p1 = p"
      using observable_path_unique[OF observable P path P (FSM.initial P) p1 path P (FSM.initial P) p p_io p1 = p_io p] 
      by assumption

    have "io'  LS P q"
      using path P (target (FSM.initial P) p1) p2 p_io p2 = io'
      unfolding p1 = p assms(4) by auto
    then show "False"
      using io'  [] deadlock_state P q 
      unfolding deadlock_state_alt_def 
      by blast
  qed
qed


lemma preamble_maximal_io_paths_rev :
  assumes "is_preamble P M q"
  and     "observable M"
  and     "io  L P"
  and     "io' . io'  []  io @ io'  L P"
obtains p where "path P (initial P) p"
          and   "p_io p = io"
          and   "target (initial P) p = q"
proof -  
  have "acyclic P"
  and  "deadlock_state P q"  
  and  "is_submachine P M"
  and  " q' . q'reachable_states P  (q = q'  ¬ deadlock_state P q')"
    using assms(1) unfolding is_preamble_def by blast+

  have "observable P"
    using observable M is_submachine P M
    using submachine_observable by blast 

  obtain p where "path P (initial P) p" and "p_io p = io"
    using io  L P by auto

  moreover have "target (initial P) p = q"
  proof (rule ccontr)
    assume "target (FSM.initial P) p  q"
    then have "¬ deadlock_state P (target (FSM.initial P) p)"
      using  q' . q'reachable_states P  (q = q'  ¬ deadlock_state P q')[OF reachable_states_intro[OF path P (initial P) p]] by simp
    then obtain t where "t  transitions P" and "t_source t = target (initial P) p"
      by auto
    then have "path P (initial P) (p @ [t])"
      using path_append_transition[OF path P (initial P) p] by auto
    then have "p_io (p@[t])  L P"
      unfolding LS.simps by (metis (mono_tags, lifting) mem_Collect_eq)
    then have "io @ [(t_input t, t_output t)]  L P"
      using p_io p = io by auto
    then show "False"
      using assms(4) by auto
  qed

  ultimately show ?thesis using that by blast
qed


lemma is_preamble_is_state : 
  assumes "is_preamble P M q"
  shows "q  states M"
  using assms unfolding is_preamble_def
  by (meson nil path_nil_elim reachable_state_is_state submachine_path) 


subsection ‹Calculating State Preambles via Backwards Reachability Analysis›


fun d_states :: "('a::linorder,'b::linorder,'c) fsm  'a  ('a × 'b) list" where
  "d_states M q = (if q = initial M 
                      then [] 
                      else select_inputs (h M) (initial M) (inputs_as_list M) (removeAll q (removeAll (initial M) (states_as_list M))) {q} [])"



lemma d_states_index_properties : 
  assumes "i < length (d_states M q)"
shows "fst (d_states M q ! i)  (states M - {q})" 
      "fst (d_states M q ! i)  q"
      "snd (d_states M q ! i)  inputs M"
      "( qx'  set (take i (d_states M q)) . fst (d_states M q ! i)  fst qx')"
      "( t  transitions M . t_source t = fst (d_states M q ! i)  t_input t = snd (d_states M q ! i))"
      "( t  transitions M . (t_source t = fst (d_states M q ! i)  t_input t = snd (d_states M q ! i))  (t_target t = q  ( qx'  set (take i (d_states M q)) . fst qx' = (t_target t))))"
proof -
  have combined_goals : "fst (d_states M q ! i)  (states M - {q})
                           fst (d_states M q ! i)  q
                           snd (d_states M q ! i)  inputs M
                           ( qx'  set (take i (d_states M q)) . fst (d_states M q ! i)  fst qx')
                           ( t  transitions M . t_source t = fst (d_states M q ! i)  t_input t = snd (d_states M q ! i))
                           ( t  transitions M . (t_source t = fst (d_states M q ! i)  t_input t = snd (d_states M q ! i))  (t_target t = q  ( qx'  set (take i (d_states M q)) . fst qx' = (t_target t))))"
  proof (cases "q = initial M")
    case True
    then have "d_states M q = []" by auto
    then have "False" using assms by auto
    then show ?thesis by simp
  next
    case False
    then have *: "d_states M q = select_inputs (h M) (initial M) (inputs_as_list M) (removeAll q (removeAll (initial M) (states_as_list M))) {q} []" by auto

    have "initial M  states M" by auto
    then have "insert (FSM.initial M) (set (removeAll q (removeAll (FSM.initial M) (states_as_list M)))) = states M - {q}"
      using states_as_list_set False by auto 



    have "i < length (select_inputs (h M) (FSM.initial M) (inputs_as_list M) (removeAll q (removeAll (FSM.initial M) (states_as_list M))) {q} [])"
      using assms * by simp
    moreover have "length []  i" by auto
    moreover have "distinct (map fst [])" by auto
    moreover have "{q} = {q}  set (map fst [])" by auto
    moreover have "initial M  {q}" using False by auto
    moreover have "distinct (removeAll q (removeAll (FSM.initial M) (states_as_list M)))" using states_as_list_distinct
      by (simp add: distinct_removeAll) 
    moreover have "FSM.initial M  set (removeAll q (removeAll (FSM.initial M) (states_as_list M)))" by auto
    moreover have "set (removeAll q (removeAll (FSM.initial M) (states_as_list M)))  {q} = {}" by auto

    moreover show ?thesis 
      using select_inputs_index_properties[OF calculation] 
      unfolding *[symmetric] inputs_as_list_set insert (FSM.initial M) (set (removeAll q (removeAll (FSM.initial M) (states_as_list M)))) = states M - {q} by blast
  qed

  then show "fst (d_states M q ! i)  (states M - {q})" 
      "fst (d_states M q ! i)  q"
      "snd (d_states M q ! i)  inputs M"
      "( qx'  set (take i (d_states M q)) . fst (d_states M q ! i)  fst qx')"
      "( t  transitions M . t_source t = fst (d_states M q ! i)  t_input t = snd (d_states M q ! i))"
      "( t  transitions M . (t_source t = fst (d_states M q ! i)  t_input t = snd (d_states M q ! i))  (t_target t = q  ( qx'  set (take i (d_states M q)) . fst qx' = (t_target t))))"
    by blast+
qed


lemma d_states_distinct :
  "distinct (map fst (d_states M q))"
proof -
  have *: " i q . i < length (map fst (d_states M q))  q  set (take i (map fst (d_states M q)))  ((map fst (d_states M q)) ! i)  q"
    using d_states_index_properties(2,4) by fastforce 
  then have "(i. i < length (map fst (d_states M q)) 
          map fst (d_states M q) ! i  set (take i (map fst (d_states M q))))"
  proof -
    fix i :: nat
    assume a1: "i < length (map fst (d_states M q))"
    then have "p. p  set (take i (d_states M q))  fst (d_states M q ! i)  fst p"
      by (metis (no_types) d_states_index_properties(4) length_map)
    then show "map fst (d_states M q) ! i  set (take i (map fst (d_states M q)))"
      using a1 by (metis (no_types) length_map list_map_source_elem nth_map take_map)
  qed
  then show ?thesis
    using list_distinct_prefix[of "map fst (d_states M q)"] by blast
qed


lemma d_states_states : 
  "set (map fst (d_states M q))  states M - {q}"
  using d_states_index_properties(1)[of _ M q] list_property_from_index_property[of "map fst (d_states M q)" "λq' . q'  states M - {q}"]
  by (simp add: subsetI)


lemma d_states_size :
  assumes "q  states M"
  shows "length (d_states M q)  size M - 1"
proof -
  show ?thesis 
    using d_states_states[of M q]
          d_states_distinct[of M q]
          fsm_states_finite[of M]
          assms
    by (metis card_Diff_singleton_if card_mono distinct_card finite_Diff length_map size_def)    
qed


lemma d_states_initial :
  assumes "qx  set (d_states M q)" 
  and     "fst qx = initial M"
shows "(last (d_states M q)) = qx"
  using assms(1) select_inputs_initial[of qx "h M" "initial M" _ _ _ "[]", OF _ assms(2)]
  by (cases "q = initial M"; auto)


lemma d_states_q_noncontainment :
  shows "¬( qqx  set (d_states M q) . fst qqx = q)" 
  using d_states_index_properties(2)
  by (metis in_set_conv_nth) 


lemma d_states_acyclic_paths' :
  fixes M :: "('a::linorder,'b::linorder,'c) fsm"
  assumes "path (filter_transitions M (λ t . (t_source t, t_input t)  set (d_states M q))) q' p"
  and     "target q' p = q'"
  and     "p  []"
shows "False"
proof -
  from p  [] obtain p' t' where "p = t'#p'"
    using list.exhaust by blast
  then have "path (filter_transitions M (λ t . (t_source t, t_input t)  set (d_states M q))) q' (p@[t'])"
    using assms(1,2) by fastforce

  define f :: "('a × 'b × 'c × 'a)  nat"
    where f_def: "f = (λ t . the (find_index (λ qx . fst qx = t_source t  snd qx = t_input t) (d_states M q)))"
  
  have f_prop: " t . t  set (p@[t'])  (f t < length (d_states M q)) 
                                       ((d_states M q) ! (f t) = (t_source t, t_input t))
                                       ( j < f t . fst (d_states M q ! j)  t_source t)"
  proof -
    fix t assume "t  set (p@[t'])"
    then have "t  set p" using p = t'#p' by auto
    then have "t  transitions M" and "(t_source t, t_input t)  set (d_states M q)"
      using assms(1) path_transitions by fastforce+
    then have " qx  set (d_states M q) . (λ qx . fst qx = t_source t  snd qx = t_input t) qx"
      by (meson fst_conv snd_conv)
    then have "find_index (λ qx . fst qx = t_source t  snd qx = t_input t) (d_states M q)  None"
      by (simp add: find_index_exhaustive) 
    then obtain i where *: "find_index (λ qx . fst qx = t_source t  snd qx = t_input t) (d_states M q) = Some i"
      by auto

    have "f t < length (d_states M q)"
      unfolding f_def using find_index_index(1)[OF *] unfolding * by simp
    moreover have "((d_states M q) ! (f t) = (t_source t, t_input t))"
      unfolding f_def using find_index_index(2)[OF *]
      by (metis "*" option.sel prod.collapse) 
    moreover have " j < f t . fst (d_states M q ! j)  t_source t"
      unfolding f_def using find_index_index(3)[OF *] unfolding * 
      using d_states_distinct[of M q]
      by (metis (mono_tags, lifting) calculation(1) calculation(2) distinct_conv_nth fst_conv length_map less_imp_le less_le_trans not_less nth_map option.sel snd_conv) 
    ultimately show "(f t < length (d_states M q)) 
                                       ((d_states M q) ! (f t) = (t_source t, t_input t))
                                       ( j < f t . fst (d_states M q ! j)  t_source t)" by simp
  qed

  have *: " i . Suc i < length (p@[t'])  f ((p@[t']) ! i) > f ((p@[t']) ! (Suc i))"
  proof -
    fix i assume "Suc i < length (p@[t'])"
    then have "(p@[t']) ! i  set (p@[t'])" and "(p@[t']) ! (Suc i)  set (p@[t'])"
      using Suc_lessD nth_mem by blast+
    then have "(p@[t']) ! i  transitions M" and "(p@[t']) ! Suc i  transitions M" 
      using path_transitions[OF path (filter_transitions M (λ t . (t_source t, t_input t)  set (d_states M q))) q' (p@[t'])]
      using filter_transitions_simps(5) by blast+
            

    have "f ((p@[t']) ! i) < length (d_states M q)"
    and  "(d_states M q) ! (f ((p@[t']) ! i)) = (t_source ((p@[t']) ! i), t_input ((p@[t']) ! i))"
    and  "(j<f ((p@[t']) ! i). fst (d_states M q ! j)  t_source ((p@[t']) ! i))"
      using f_prop[OF (p@[t']) ! i  set (p@[t'])] by auto

    have "f ((p@[t']) ! Suc i) < length (d_states M q)"
    and  "(d_states M q) ! (f ((p@[t']) ! Suc i)) = (t_source ((p@[t']) ! Suc i), t_input ((p@[t']) ! Suc i))"
    and  "(j<f ((p@[t']) ! Suc i). fst (d_states M q ! j)  t_source ((p@[t']) ! Suc i))"
      using f_prop[OF (p@[t']) ! Suc i  set (p@[t'])] by auto

    have "t_target ((p@[t']) ! i) = t_source ((p@[t']) ! Suc i)"
      using Suc i < length (p@[t']) path (filter_transitions M (λ t . (t_source t, t_input t)  set (d_states M q))) q' (p@[t'])
      by (simp add: path_source_target_index) 
    then have "t_target ((p@[t']) ! i)  q"
      using d_states_index_properties(2)[OF f ((p@[t']) ! Suc i) < length (d_states M q)] 
      unfolding (d_states M q) ! (f ((p@[t']) ! Suc i)) = (t_source ((p@[t']) ! Suc i), t_input ((p@[t']) ! Suc i)) by auto
    then have "(qx'set (take (f ((p@[t']) ! i)) (d_states M q)). fst qx' = t_target ((p@[t']) ! i))"
      using d_states_index_properties(6)[OF f ((p@[t']) ! i) < length (d_states M q)] unfolding (d_states M q) ! (f ((p@[t']) ! i)) = (t_source ((p@[t']) ! i), t_input ((p@[t']) ! i)) fst_conv snd_conv
      using (p@[t']) ! i  transitions M
      by blast
    then have "(qx'set (take (f ((p@[t']) ! i)) (d_states M q)). fst qx' = t_source ((p@[t']) ! Suc i))" 
      unfolding t_target ((p@[t']) ! i) = t_source ((p@[t']) ! Suc i) by assumption
    then obtain j where "fst (d_states M q ! j) = t_source ((p@[t']) ! Suc i)" and "j < f ((p@[t']) ! i)"
      by (metis (no_types, lifting) f ((p@[t']) ! i) < length (d_states M q) in_set_conv_nth leD length_take min_def_raw nth_take)
      
    then show "f ((p@[t']) ! i) > f ((p@[t']) ! (Suc i))"
      using (j<f ((p@[t']) ! Suc i). fst (d_states M q ! j)  t_source ((p@[t']) ! Suc i))
      using leI le_less_trans by blast 
  qed   

  have " i j . j < i  i < length (p@[t'])  f ((p@[t']) ! j) > f ((p@[t']) ! i)"
    using list_index_fun_gt[of "p@[t']" f] * by blast
  then have "f t' < f t'"
    unfolding p = t'#p' by fastforce 
  then show "False"
    by auto
qed


lemma d_states_acyclic_paths :
  fixes M :: "('a::linorder,'b::linorder,'c) fsm"
  assumes "path (filter_transitions M (λ t . (t_source t, t_input t)  set (d_states M q))) q' p"
          (is "path ?FM q' p")
shows "distinct (visited_states q' p)"
proof (rule ccontr)
  assume "¬ distinct (visited_states q' p)"
  
  obtain i j where p1:"take j (drop i p)  []"
               and p2:"target (target q' (take i p)) (take j (drop i p)) = (target q' (take i p))"
               and p3:"path ?FM (target q' (take i p)) (take j (drop i p))"
    using cycle_from_cyclic_path[OF assms ¬ distinct (visited_states q' p)] by blast
  
  show "False"
    using d_states_acyclic_paths'[OF p3 p2 p1] by assumption
qed


lemma d_states_induces_state_preamble_helper_acyclic :
  shows "acyclic (filter_transitions M (λ t . (t_source t, t_input t)  set (d_states M q)))"
  unfolding acyclic.simps
  using d_states_acyclic_paths by force

lemma d_states_induces_state_preamble_helper_single_input :
  shows "single_input (filter_transitions M (λ t . (t_source t, t_input t)  set (d_states M q)))"
      (is "single_input ?FM")
  unfolding single_input.simps filter_transitions_simps
  by (metis (no_types, lifting) d_states_distinct eq_key_imp_eq_value mem_Collect_eq)


lemma d_states_induces_state_preamble :
  assumes " qx  set (d_states M q) . fst qx = initial M"
  shows "is_preamble (filter_transitions M (λ t . (t_source t, t_input t)  set (d_states M q))) M q" 
    (is "is_preamble ?S M q")
proof (cases "q = initial M")
  case True
  then have "d_states M q = []" by auto
  then show ?thesis using assms(1) by auto
next
  case False

  have is_acyclic: "acyclic ?S" 
    using d_states_induces_state_preamble_helper_acyclic[of M q] by presburger

  have is_single_input: "single_input ?S" 
    using d_states_induces_state_preamble_helper_single_input[of M q] by presburger

  have is_sub : "is_submachine ?S M"
    unfolding is_submachine.simps filter_transitions_simps by blast

  have has_deadlock_q : "deadlock_state ?S q" 
    using d_states_q_noncontainment[of M q] unfolding deadlock_state.simps
    by fastforce

  have " q' . q'  reachable_states ?S  q'  q  ¬ deadlock_state ?S q'"
  proof -
    fix q' assume "q'  reachable_states ?S" and "q'  q"
    then obtain p where "path ?S (initial ?S) p" and "target (initial ?S) p = q'"
      unfolding reachable_states_def by auto

    have " qx  set (d_states M q) . fst qx = q'"
    proof (cases p rule: rev_cases)
      case Nil
      then show ?thesis 
        using assms(1) target (initial ?S) p = q' unfolding filter_transitions_simps
        by simp
    next
      case (snoc p' t)
      then have "t  transitions ?S" and "t_target t = q'" 
        using path ?S (initial ?S) p target (initial ?S) p = q' by auto
      then have "(t_source t, t_input t)  set (d_states M q)"
        by simp 
      then obtain i where "i < length (d_states M q)" and "d_states M q ! i = (t_source t, t_input t)"
        by (meson in_set_conv_nth)

      have "t  transitions M"
        using t  transitions ?S
        using is_sub by auto
      then show ?thesis
        using t_target t = q' q'  q
        using d_states_index_properties(6)[OF i < length (d_states M q)]
        unfolding d_states M q ! i = (t_source t, t_input t) fst_conv snd_conv
        by (metis in_set_takeD)
    qed

    then obtain qx where "qx  set (d_states M q)" and "fst qx = q'" by blast

    then have "( t  transitions M . t_source t = fst qx  t_input t = snd qx)" 
      using d_states_index_properties(5)[of _ M q]
      by (metis in_set_conv_nth)
    then have "( t  transitions ?S . t_source t = fst qx  t_input t = snd qx)"
      using qx  set (d_states M q) by fastforce      

    then show "¬ deadlock_state ?S q'" 
      unfolding deadlock_state.simps using fst qx = q' by blast
  qed

  then have has_states_prop_1 : " q' . q'  reachable_states ?S  (q = q'  ¬ deadlock_state ?S q')" 
    by blast
  
  have has_states_prop_2 : " q' x t t'. q'  reachable_states ?S   x  inputs M 
            t  transitions ?S  t_source t = q'  t_input t = x 
            t' transitions M  t_source t' = q'  t_input t' = x  t'  transitions ?S"
    by simp

  have contains_q : "q  reachable_states ?S" 
    using q'. q'  reachable_states ?S; q'  q  ¬ deadlock_state ?S q' acyclic_deadlock_reachable is_acyclic 
    by blast

  show ?thesis
    unfolding is_preamble_def
    using is_acyclic 
          is_single_input 
          is_sub
          contains_q
          has_deadlock_q
          has_states_prop_1 has_states_prop_2
    by blast
qed


fun calculate_state_preamble_from_input_choices :: "('a::linorder,'b::linorder,'c) fsm  'a   ('a,'b,'c) fsm option" 
  where
  "calculate_state_preamble_from_input_choices M q = (if q = initial M
    then Some (initial_preamble M)
    else 
      (let DS = (d_states M q);
           DSS = set DS 
        in (case DS of
            []  None |
            _   if fst (last DS) = initial M
                    then Some (filter_transitions M (λ t . (t_source t, t_input t)  DSS))
                    else None)))"


lemma calculate_state_preamble_from_input_choices_soundness :
  assumes "calculate_state_preamble_from_input_choices M q = Some S"
  shows "is_preamble S M q"
proof (cases "q = initial M")
  case True
  then have "S = initial_preamble M" using assms by auto
  then show ?thesis 
    using is_preamble_initial[of M] True by presburger
next
  case False

  then have "S = (filter_transitions M (λ t . (t_source t, t_input t)  set (d_states M q)))"
       and  "length (d_states M q)  0"
       and  "fst (last (d_states M q)) = initial M"
    using assms by (cases "(d_states M q)"; cases "fst (last (d_states M q)) = initial M"; simp)+

  then have " qx  set (d_states M q) . fst qx = initial M"
    by auto

  then show ?thesis 
    using d_states_induces_state_preamble
    unfolding S = (filter_transitions M (λ t . (t_source t, t_input t)  set (d_states M q)))
    by blast 
qed


lemma calculate_state_preamble_from_input_choices_exhaustiveness :
  assumes " S . is_preamble S M q"
  shows "calculate_state_preamble_from_input_choices M q  None"
proof (cases "q = initial M")
  case True
  then show ?thesis by auto
next
  case False
  
  obtain S where "is_preamble S M q"
    using assms by blast

  then have "acyclic S"
        and "single_input S" 
        and "is_submachine S M"
        and "q  reachable_states S"
        and " q' . q'  reachable_states S  (q = q'  ¬ deadlock_state S q')" 
        and *: " q' x . q'  reachable_states S  x  inputs M  ( t  transitions S . t_source t = q'  t_input t = x)  ( t'  transitions M . t_source t' = q'  t_input t' = x  t'  transitions S)"
    unfolding is_preamble_def by blast+

 
  have p1: "(q x. q  reachable_states S  h S (q, x)  {}  h S (q, x) = h M (q, x))"
  proof - 
    fix q x assume "q  reachable_states S" and "h S (q, x)  {}"

    then have "x  inputs M"
      using is_submachine S M fsm_transition_input by force
    have "( t  transitions S . t_source t = q  t_input t = x)"
      using h S (q, x)  {} by fastforce


    have " y q'' . (y,q'')  h S (q,x)  (y,q'')  h M (q,x)" 
      using is_submachine S M by force 
    moreover have " y q'' . (y,q'')  h M (q,x)  (y,q'')  h S (q,x)" 
      using *[OF q  reachable_states S x  inputs M ( t  transitions S . t_source t = q  t_input t = x)]
      unfolding h.simps by force
    ultimately show "h S (q, x) = h M (q, x)" 
      by force
  qed 

  have p2: "q'. q'  reachable_states S  deadlock_state S q'  q'  {q}  set (map fst [])"
    using  q' . q'  reachable_states S  (q = q'  ¬ deadlock_state S q') by fast

  have "q  states M"
    using q  reachable_states S submachine_reachable_subset[OF is_submachine S M]
    by (meson assms is_preamble_is_state) 
  then have p3: "states M = insert (FSM.initial S) (set (removeAll q (removeAll (initial M) (states_as_list M)))  {q}  set (map fst []))"
    using states_as_list_set[of M] fsm_initial[of M]
    unfolding submachine_simps[OF is_submachine S M]
    by auto

  have p4: "initial S  set (removeAll q (removeAll (initial M) (states_as_list M)))  {q}  set (map fst [])"
    using False
    unfolding submachine_simps[OF is_submachine S M] by force

  have "fst (last (d_states M q)) = FSM.initial M" and "length (d_states M q) > 0"
    using False select_inputs_from_submachine[OF single_input S acyclic S is_submachine S M p1 p2 p3 p4]
    unfolding d_states.simps submachine_simps[OF is_submachine S M]
    by auto 


  have "(d_states M q)  []"
    using length (d_states M q) > 0 by auto
  then obtain dl dl' where "(d_states M q) = dl # dl'"
    using list.exhaust by blast
  then have "(fst (last (dl#dl')) = FSM.initial M) = True" using fst (last (d_states M q)) = FSM.initial M by simp
  then show ?thesis
    using False
    unfolding calculate_state_preamble_from_input_choices.simps Let_def (d_states M q) = dl # dl'  
    by auto
qed



subsection ‹Minimal Sequences to Failures extending Preambles›

definition sequence_to_failure_extending_preamble_path :: 
  "('a,'b,'c) fsm  ('d,'b,'c) fsm  ('a × ('a,'b,'c) fsm) set  ('a×'b×'c×'a) list  ('b × 'c) list  bool" 
  where
  "sequence_to_failure_extending_preamble_path M M' PS p io = ( q P . q  states M 
                                                                         (q,P)  PS
                                                                         path P (initial P) p 
                                                                         target (initial P) p = q
                                                                         ((p_io p) @ butlast io)  L M   
                                                                         ((p_io p) @ io)  L M
                                                                         ((p_io p) @ io)  L M')"

lemma sequence_to_failure_extending_preamble_ex :
  assumes "(initial M, (initial_preamble M))  PS" (is "(initial M,?P)  PS")
  and     "¬ L M'  L M"
obtains p io where "sequence_to_failure_extending_preamble_path M M' PS p io"
proof -
  obtain io where "io  L M' - L M"
    using ¬ L M'  L M by auto
  
  obtain j where "take j io  L M" and "take (Suc j) io  L M" 
  proof -
    have " j . take j io  L M  take (Suc j) io  L M"
    proof (rule ccontr)
      assume "j. take j io  LS M (initial M)  take (Suc j) io  LS M (initial M)"
      then have *: " j . take j io  LS M (initial M)  take (Suc j) io  LS M (initial M)" by blast
      
      have " j . take j io  LS M (initial M)"
      proof -
        fix j 
        show "take j io  LS M (initial M)"
          using * by (induction j; auto)
      qed
      then have "take (length io) io  L M" by blast
      then show "False"
        using io  L M' - L M by auto
    qed
    then show ?thesis using that by blast
  qed

  have " i . take i io  L M'" 
  proof -
    fix i show "take i io  L M'" using io  L M' - L M language_prefix[of "take i io" "drop i io" M' "initial M'"] by auto
  qed

  let ?io = "take (Suc j) io"
  

  have "initial M  states M" 
    by auto
  moreover note (initial M, (initial_preamble M))  PS
  moreover have "path ?P (initial ?P) []" by force
  moreover have "((p_io []) @ butlast ?io)  L M" 
    using take j io  L M  
    unfolding List.list.map(1) append_Nil 
    by (metis Diff_iff One_nat_def io  LS M' (initial M') - LS M (initial M) butlast_take 
          diff_Suc_Suc minus_nat.diff_0 not_less_eq_eq take_all)
  moreover have "((p_io []) @ ?io)  L M" 
    using take (Suc j) io  L M by auto
  moreover have "((p_io []) @ ?io)  L M'" 
    using  i . take i io  L M' by auto
  ultimately have "sequence_to_failure_extending_preamble_path M M' PS [] ?io"
    unfolding sequence_to_failure_extending_preamble_path_def by force
  then show ?thesis 
    using that by blast
qed  
     

definition minimal_sequence_to_failure_extending_preamble_path :: 
  "('a,'b,'c) fsm  ('d,'b,'c) fsm  ('a × ('a,'b,'c) fsm) set  ('a×'b×'c×'a) list  ('b × 'c) list  bool" 
  where
  "minimal_sequence_to_failure_extending_preamble_path M M' PS p io 
    = ((sequence_to_failure_extending_preamble_path M M' PS p io)
         ( p' io' . sequence_to_failure_extending_preamble_path M M' PS p' io' 
                         length io  length io'))"


lemma minimal_sequence_to_failure_extending_preamble_ex :
  assumes "(initial M, (initial_preamble M))  PS" (is "(initial M,?P)  PS")
  and     "¬ L M'  L M"
obtains p io where "minimal_sequence_to_failure_extending_preamble_path M M' PS p io"
proof -
  let ?ios = "{io .  p . sequence_to_failure_extending_preamble_path M M' PS p io}"
  let ?io_min = "arg_min length (λio . io  ?ios)"

  have "?ios  {}"
    using sequence_to_failure_extending_preamble_ex[OF assms] by blast
  then have "?io_min  ?ios" and "( io'  ?ios . length ?io_min  length io')"
    by (meson arg_min_nat_lemma some_in_eq)+

  obtain p where "sequence_to_failure_extending_preamble_path M M' PS p ?io_min"
    using ?io_min  ?ios
    by auto
  moreover have "( p' io' . sequence_to_failure_extending_preamble_path M M' PS p' io'  length ?io_min  length io')"
    using ( io'  ?ios . length ?io_min  length io') by blast
  ultimately show ?thesis
    using that[of p ?io_min]
    unfolding minimal_sequence_to_failure_extending_preamble_path_def
    by blast
qed


lemma minimal_sequence_to_failure_extending_preamble_no_repetitions_along_path :
  assumes "minimal_sequence_to_failure_extending_preamble_path M M' PS pP io"
  and     "observable M"
  and     "path M (target (initial M) pP) p"
  and     "p_io p = butlast io"
  and     "q'  io_targets M' (p_io pP) (initial M')"
  and     "path M' q' p'"
  and     "p_io p' = io"
  and     "i < j" 
  and     "j < length (butlast io)"
  and     " q P. (q, P)  PS  is_preamble P M q"
shows "t_target (p ! i)  t_target (p ! j)  t_target (p' ! i)  t_target (p' ! j)"
proof (rule ccontr)
  assume "¬ (t_target (p ! i)  t_target (p ! j)  t_target (p' ! i)  t_target (p' ! j))"

  (* cut the loop and create a shorter path with the same target as p (p') *)

  then have "t_target (p ! i) = t_target (p ! j)"
       and  "t_target (p' ! i) = t_target (p' ! j)"
    by blast+

  have "sequence_to_failure_extending_preamble_path M M' PS pP io"
  and  " p' io' . sequence_to_failure_extending_preamble_path M M' PS p' io' 
                     length io  length io'"
    using minimal_sequence_to_failure_extending_preamble_path M M' PS pP io
    unfolding minimal_sequence_to_failure_extending_preamble_path_def   
    by blast+

  obtain q P where "(q,P)  PS"
              and  "path P (initial P) pP"
              and  "target (initial P) pP = q"
              and  "((p_io pP) @ butlast io)  L M" 
              and  "((p_io pP) @ io)  L M"
              and  "((p_io pP) @ io)  L M'"

    using sequence_to_failure_extending_preamble_path M M' PS pP io
    unfolding sequence_to_failure_extending_preamble_path_def  
    by blast

  have "is_preamble P M q"
    using (q,P)  PS  q P. (q, P)  PS  is_preamble P M q by blast
  then have "q  states M"
    unfolding is_preamble_def
    by (metis path P (FSM.initial P) pP target (FSM.initial P) pP = q 
          path_target_is_state submachine_path) 

  have "initial P = initial M"
    using is_preamble P M q unfolding is_preamble_def by auto
  have "path M (initial M) pP"
    using is_preamble P M q unfolding is_preamble_def using submachine_path_initial
    using path P (FSM.initial P) pP by blast
  have "target (initial M) pP = q"
    using target (initial P) pP = q unfolding initial P = initial M by assumption

  then have "path M q p"
    using path M (target (initial M) pP) p by auto
    
  have "io  []"
    using ((p_io pP) @ butlast io)  L M ((p_io pP) @ io)  L M by auto
  then have "length p' > 0"
    using p_io p' = io by auto
  then have "p' = (butlast p')@[last p']"
    by auto
  then have "path M' q' ((butlast p')@[last p'])"
    using path M' q' p' by simp
  then have "path M' q' (butlast p')" and "(last p')  transitions M'" and "t_source (last p') = target q' (butlast p')"
    by auto
    
  have "p_io (butlast p') = butlast io"
    using p' = (butlast p')@[last p'] p_io p' = io
    using map_butlast by auto 

  let ?p = "((take (Suc i) p) @ (drop (Suc j) p))"
  let ?pCut = "(drop (Suc i) (take (Suc j) p))" (* the loop cut out of p *)
  let ?p' = "((take (Suc i) (butlast p')) @ (drop (Suc j) (butlast p')))"

  have "j < length p"
    using j < length (butlast io) p_io p = butlast io
    by (metis (no_types, lifting) length_map) 
  have "j < length (butlast p')"
    using j < length (butlast io) p_io p' = io p' = (butlast p')@[last p']
    by auto
  then have "t_target ((butlast p') ! i) = t_target ((butlast p') ! j)"
    using t_target (p' ! i) = t_target (p' ! j) 
    by (simp add: i < j dual_order.strict_trans nth_butlast) 

  have "path M q ?p" 
  and  "target q ?p = target q p" 
  and  "length ?p < length p"
  and  "path M (target q (take (Suc i) p)) ?pCut" 
  and  "target (target q (take (Suc i) p)) ?pCut = target q (take (Suc i) p)"
    using path_loop_cut[OF path M q p t_target (p ! i) = t_target (p ! j) i < j j < length p]
    by blast+

  have "path M' q' ?p'" 
  and  "target q' ?p' = target q' (butlast p')" 
  and  "length ?p' < length (butlast p')"
    using path_loop_cut[OF path M' q' (butlast p') t_target ((butlast p') ! i) = t_target ((butlast p') ! j) i < j j < length (butlast p')]
    by blast+
  
  have "path M' q' (?p'@[last p'])"
    using t_source (last p') = target q' (butlast p') 
    using path_append_transition[OF path M' q' ?p' (last p')  transitions M']
    unfolding target q' ?p' = target q' (butlast p') by simp

  have "p_io ?p' = p_io ?p"
    using p_io p = butlast io p_io (butlast p') = butlast io
    by (metis (no_types, lifting) drop_map map_append take_map)

  have min_prop: "length (p_io (?p'@[last p'])) < length io"
    using length ?p' < length (butlast p') p_io p' = io 
    unfolding length_map[of "(λ t . (t_input t, t_output t))"]
    by auto


  (* show that the shorter path would constitute a shorter seq to a failure, contradicting
     the minimality assumption on io *)

  have "q  io_targets M (p_io pP) (initial M)"
    using path M (initial M) pP target (initial M) pP = q unfolding io_targets.simps
    by blast 

  have "((p_io pP) @ (p_io ?p))  L M"
    using language_io_target_append[OF q  io_targets M (p_io pP) (initial M), of "p_io ?p"]
          path M q ?p
    unfolding LS.simps by blast
  then have p1: "((p_io pP) @ butlast (p_io (?p' @ [last p'])))  L M"
    unfolding p_io ?p' = p_io ?p[symmetric]
    by (metis (no_types, lifting) butlast_snoc map_butlast) 

  
  have p2: "((p_io pP) @ (p_io (?p' @ [last p'])))  L M"
  proof 
    assume "((p_io pP) @ (p_io (?p' @ [last p'])))  L M"
    then obtain pCntr where "path M (initial M) pCntr" 
                        and "p_io pCntr = (p_io pP) @ (p_io (?p' @ [last p']))"
      by auto

    let ?pCntr1 = "(take (length (p_io pP)) pCntr)"
    let ?pCntr23 = "(drop (length (p_io pP)) pCntr)"

    have "path M (initial M) ?pCntr1" 
    and  "p_io ?pCntr1 = p_io pP"
    and  "path M (target (initial M) ?pCntr1) ?pCntr23"
    and  "p_io ?pCntr23 = p_io (?p' @ [last p'])"
      using path_io_split[OF path M (initial M) pCntr p_io pCntr = (p_io pP) @ (p_io (?p' @ [last p']))] 
      by blast+

    let ?pCntr2 = "(take (length (p_io (take (Suc i) (butlast p') @ drop (Suc j) (butlast p')))) (drop (length (p_io pP)) pCntr))"
    let ?pCntr3 = "(drop (length (p_io (take (Suc i) (butlast p') @ drop (Suc j) (butlast p')))) (drop (length (p_io pP)) pCntr))"

    have "p_io ?pCntr23 = p_io ?p' @ p_io [last p']"
      using p_io ?pCntr23 = p_io (?p' @ [last p']) by auto
    have "path M (target (initial M) ?pCntr1) ?pCntr2" 
    and  "p_io ?pCntr2 = p_io ?p'"
    and  "path M (target (target (initial M) ?pCntr1) ?pCntr2) ?pCntr3"
    and  "p_io ?pCntr3 = p_io [last p']"
      using path_io_split[OF path M (target (initial M) ?pCntr1) ?pCntr23 p_io ?pCntr23 = p_io ?p' @ p_io [last p']]
      by blast+

    have "?pCntr1 = pP"
      using observable_path_unique[OF observable M path M (initial M) ?pCntr1 path M (initial M) pP p_io ?pCntr1 = p_io pP]
      by assumption
    
    
    then have "(target (initial M) ?pCntr1) = q"
      using target (initial M) pP = q by auto
    then have "path M q ?pCntr2"
         and  "path M (target q ?pCntr2) ?pCntr3"
      using path M (target (initial M) ?pCntr1) ?pCntr2
            path M (target (target (initial M) ?pCntr1) ?pCntr2) ?pCntr3
      by auto

    have "?pCntr2 = ?p"
      using observable_path_unique[OF observable M path M q ?pCntr2 path M q ?p ]
            p_io ?pCntr2 = p_io ?p'
      unfolding p_io ?p' = p_io ?p
      by blast
    then have "(target q ?pCntr2) = (target q ?p)"
      by auto
    then have "(target q ?pCntr2) = (target q p)"
      using target q ?p = target q p by auto    

    have "p_io ?pCntr3 = [last io]"
      using p_io ?pCntr3 = p_io [last p']
      by (metis (mono_tags, lifting) io  [] assms(7) last_map list.simps(8) list.simps(9))

    have "path M (initial M) (pP @ p @ ?pCntr3)"
      using path M (initial M) pP target (initial M) pP = q path M q p path M (target q ?pCntr2) ?pCntr3
      unfolding (target q ?pCntr2) = (target q p) 
      by auto
    moreover have "p_io (pP @ p @ ?pCntr3) = ((p_io pP) @ io)"
      using p_io p = butlast io p_io ?pCntr3 = [last io]
      by (simp add: io  []) 
    ultimately have "((p_io pP) @ io)  L M"
      by (metis (mono_tags, lifting) language_state_containment)
    then show "False"
      using ((p_io pP) @ io)  L M
      by simp
  qed

  have p3: "((p_io pP) @ (p_io (?p' @ [last p'])))  L M'"
    using language_io_target_append[OF q'  io_targets M' (p_io pP) (initial M'), of "(p_io (?p' @ [last p']))"]
    using path M' q' (?p'@[last p']) 
    unfolding LS.simps
    by (metis (mono_tags, lifting) mem_Collect_eq) 

  have "sequence_to_failure_extending_preamble_path M M' PS pP (p_io (?p' @ [last p']))"
    unfolding sequence_to_failure_extending_preamble_path_def
    using q  states M
          (q,P)  PS
          path P (FSM.initial P) pP
          target (FSM.initial P) pP = q
          p1 p2 p3 by blast
  
  show "False"
    using  p' io' . sequence_to_failure_extending_preamble_path M M' PS p' io'  length io  length io'[OF sequence_to_failure_extending_preamble_path M M' PS pP (p_io (?p' @ [last p']))]
          min_prop
    by simp
qed


lemma minimal_sequence_to_failure_extending_preamble_no_repetitions_with_other_preambles :
  assumes "minimal_sequence_to_failure_extending_preamble_path M M' PS pP io"
  and     "observable M"
  and     "path M (target (initial M) pP) p"
  and     "p_io p = butlast io"
  and     "q'  io_targets M' (p_io pP) (initial M')"
  and     "path M' q' p'"
  and     "p_io p' = io"
  and     " q P. (q, P)  PS  is_preamble P M q"
  and     "i < length (butlast io)"
  and     "(t_target (p ! i), P')  PS"
  and     "path P' (initial P') pP'"
  and     "target (initial P') pP' = t_target (p ! i)"
shows "t_target (p' ! i)  io_targets M' (p_io pP') (initial M')"
proof 
  assume "t_target (p' ! i)  io_targets M' (p_io pP') (FSM.initial M')"

  (* Contradiction: (drop (Suc i) io) after (p_io pP') is a shorter sequence to a failure than io *)

  have "sequence_to_failure_extending_preamble_path M M' PS pP io"
  and  " p' io' . sequence_to_failure_extending_preamble_path M M' PS p' io'  length io  length io'"
    using minimal_sequence_to_failure_extending_preamble_path M M' PS pP io
    unfolding minimal_sequence_to_failure_extending_preamble_path_def   
    by blast+

  obtain q P where "(q,P)  PS"
              and  "path P (initial P) pP"
              and  "target (initial P) pP = q"
              and  "((p_io pP) @ butlast io)  L M" 
              and  "((p_io pP) @ io)  L M"
              and  "((p_io pP) @ io)  L M'"

    using sequence_to_failure_extending_preamble_path M M' PS pP io
    unfolding sequence_to_failure_extending_preamble_path_def  
    by blast

  have "is_preamble P M q"
    using (q,P)  PS  q P. (q, P)  PS  is_preamble P M q by blast
  then have "q  states M"
    unfolding is_preamble_def
    by (metis path P (FSM.initial P) pP target (FSM.initial P) pP = q path_target_is_state submachine_path) 

  have "initial P = initial M"
    using is_preamble P M q unfolding is_preamble_def by auto
  have "path M (initial M) pP"
    using is_preamble P M q unfolding is_preamble_def using submachine_path_initial
    using path P (FSM.initial P) pP by blast
  have "target (initial M) pP = q"
    using target (initial P) pP = q unfolding initial P = initial M by assumption

  then have "path M q p"
    using path M (target (initial M) pP) p by auto

  have "is_preamble P' M (t_target (p ! i))"
    using (t_target (p ! i), P')  PS  q P. (q, P)  PS  is_preamble P M q by blast
  then have "(t_target (p ! i))  states M"
    unfolding is_preamble_def
    by (metis path P' (initial P') pP' target (initial P') pP' = t_target (p ! i) path_target_is_state submachine_path) 

  have "initial P' = initial M"
    using is_preamble P' M (t_target (p ! i)) unfolding is_preamble_def by auto
  have "path M (initial M) pP'"
    using is_preamble P' M (t_target (p ! i)) unfolding is_preamble_def using submachine_path_initial
    using path P' (initial P') pP' by blast
  have "target (initial M) pP' = t_target (p ! i)"
    using target (initial P') pP' = t_target (p ! i) unfolding initial P' = initial M by simp

  have "io  []"
    using ((p_io pP) @ butlast io)  L M ((p_io pP) @ io)  L M by auto
  then have "length p' > 0"
    using p_io p' = io by auto
  then have "p' = (butlast p')@[last p']"
    by auto
  then have "path M' q' ((butlast p')@[last p'])"
    using path M' q' p' by simp
  then have "path M' q' (butlast p')" and "(last p')  transitions M'" and "t_source (last p') = target q' (butlast p')"
    by auto    

  have "p_io (butlast p') = butlast io"
    using p' = (butlast p')@[last p'] p_io p' = io
    using map_butlast by auto 

  have "butlast io  []"
    using assms(9) by fastforce    

  let ?p = "(drop (Suc i) p)"
  let ?p' = "(drop (Suc i) (butlast p'))"

  have "i < length p"
    using i < length (butlast io) unfolding p_io p = butlast io[symmetric] length_map[of "(λ t . (t_input t, t_output t))"]
    by assumption
  then have "p ! i = last (take (Suc i) p)"
    by (simp add: take_last_index)
  then have "t_target (p ! i) = target q (take (Suc i) p)"
    unfolding target.simps visited_states.simps
    by (metis (no_types, lifting) i < length p gr_implies_not0 last_ConsR length_0_conv length_map nth_map old.nat.distinct(2) take_eq_Nil take_last_index take_map) 

  have "p = (take (Suc i) p @ ?p)"
    by simp
  then have "p_io p = (p_io (take (Suc i) p)) @ (p_io ?p)"
    by (metis map_append)
  have "(length (p_io (take (Suc i) p))) = Suc i"
    using i < length p
    unfolding length_map[of "(λ t . (t_input t, t_output t))"]
    by auto

  have "path M (t_target (p ! i)) ?p"
    using path_io_split(3)[OF path M q p p_io p = (p_io (take (Suc i) p)) @ (p_io ?p)]
    unfolding (length (p_io (take (Suc i) p))) = Suc i t_target (p ! i) = target q (take (Suc i) p)
    by assumption
  then have "path M (initial M) (pP' @ ?p)"
    using path M (initial M) pP' target (initial M) pP' = t_target (p ! i)
    by (simp add: path_append)
   
  let ?io = "(p_io ?p) @ [last io]"
  have is_shorter: "length ?io < length io"
  proof -
    have "p_io ?p = drop (Suc i) (butlast io)"
      by (metis assms(4) drop_map)
    moreover have "length (drop (Suc i) (butlast io)) < length (butlast io)"
      using assms(9) by auto
    ultimately have "length (p_io ?p) < length (butlast io)" 
      by simp
    then show ?thesis
      by auto
  qed
   
  have p1: "((p_io pP') @ (p_io ?p))  L M" 
    using path M (initial M) (pP' @ ?p)
    by (metis (mono_tags, lifting) language_state_containment map_append) 

  have p2: "((p_io pP') @ ?io)  L M"
  proof 
    assume "((p_io pP') @ ?io)  L M"
    then obtain pCntr where "path M (initial M) pCntr" and "p_io pCntr = (p_io pP') @ (p_io ?p) @ [last io]"
      by auto

    let ?pCntr1 = "(take (length (p_io pP')) pCntr)"
    let ?pCntr23 = "(drop (length (p_io pP')) pCntr)"

    have "path M (initial M) ?pCntr1" 
    and  "p_io ?pCntr1 = p_io pP'"
    and  "path M (target (initial M) ?pCntr1) ?pCntr23"
    and  "p_io ?pCntr23 = (p_io ?p) @ [last io]"
      using path_io_split[OF path M (initial M) pCntr p_io pCntr = (p_io pP') @ (p_io ?p) @ [last io]] 
      by blast+

    have "?pCntr1 = pP'"
      using observable_path_unique[OF observable M path M (initial M) ?pCntr1 path M (initial M) pP' p_io ?pCntr1 = p_io pP']
      by assumption
    then have "(target (initial M) ?pCntr1) = (t_target (p ! i))"
      using target (initial M) pP' = (t_target (p ! i)) by auto
    then have "path M (t_target (p ! i)) ?pCntr23"
      using path M (target (initial M) ?pCntr1) ?pCntr23
      by simp

    have "path M q (take (Suc i) p)"
      using path M q p
      by (metis append_take_drop_id path_prefix) 
    
    then have "path M q ((take (Suc i) p) @ ?pCntr23)"
      using path M (target (initial M) ?pCntr1) ?pCntr23 
      unfolding (target (initial M) ?pCntr1) = (t_target (p ! i))
      unfolding t_target (p ! i) = target q (take (Suc i) p) 
      by auto
    then have "path M (initial M) (pP @ ((take (Suc i) p) @ ?pCntr23))"
      using path M (initial M) pP target (initial M) pP = q
      by auto

    moreover have "p_io (pP @ ((take (Suc i) p) @ ?pCntr23)) = p_io pP @ io"
      using io  [] p_io (drop (length (p_io pP')) pCntr) = p_io (drop (Suc i) p) @ [last io] p_io p = p_io (take (Suc i) p) @ p_io (drop (Suc i) p) append_butlast_last_id assms(4) 
      by fastforce

    ultimately have "(p_io pP @ io)  L M"
      by (metis (mono_tags, lifting) language_state_containment)

    then show "False" 
      using (p_io pP @ io)  L M
      by simp
  qed

  have p3: "((p_io pP') @ ?io)  L M'"
  proof -
    have "i < length (butlast p')"
      using i < length (butlast io) unfolding p_io p' = io[symmetric] 
      using length_map[of "(λ t . (t_input t, t_output t))"]
      by simp
    then have "butlast p' ! i = last (take (Suc i) (butlast p'))"
      by (simp add: nth_butlast take_last_index) 
    moreover have "(take (Suc i) (butlast p'))  []"
      by (metis Zero_not_Suc i < length (butlast p') list.size(3) not_less0 take_eq_Nil) 
    ultimately have "(target q' (take (Suc i) (butlast p'))) = t_target ((butlast p') ! i)"
      unfolding target.simps visited_states.simps
      by (simp add: last_map)
    moreover have "(butlast p') ! i = p' ! i"
      using i < length (butlast p')
      by (simp add: nth_butlast) 
    ultimately have "(target q' (take (Suc i) (butlast p'))) = t_target (p' ! i)"
      by simp

    have "p' = (take (Suc i) (butlast p')) @ ?p' @ [last p']"
      by (metis p' = butlast p' @ [last p'] append.assoc append_take_drop_id) 
    then have "path M' (target q' (take (Suc i) (butlast p'))) (?p' @ [last p'])"
      by (metis assms(6) path_suffix) 
    then have "path M' (t_target (p' ! i)) (?p' @ [last p'])"
      unfolding (target q' (take (Suc i) (butlast p'))) = t_target (p' ! i) by assumption
    moreover have "p_io (?p' @ [last p']) = ?io"
      by (metis (no_types, lifting) io  [] p' = butlast p' @ [last p'] p_io (butlast p') = butlast io append_butlast_last_id assms(4) assms(7) drop_map map_append same_append_eq) 
    ultimately have "?io  LS M' (t_target (p' ! i))"
      by (metis (mono_tags, lifting) language_state_containment)

    show "((p_io pP') @ ?io)  L M'"
      using language_io_target_append[OF t_target (p' ! i)  io_targets M' (p_io pP') (FSM.initial M') ?io  LS M' (t_target (p' ! i))]
      by assumption   
  qed

  have *: " xs x . butlast (xs @ [x]) = xs" by auto

  have "sequence_to_failure_extending_preamble_path M M' PS pP' ?io"
    unfolding sequence_to_failure_extending_preamble_path_def
    using t_target (p ! i)  states M assms(10-12) p1 p2 p3 
    unfolding * by blast

  then have "length io  length ?io"
    using  p' io' . sequence_to_failure_extending_preamble_path M M' PS p' io'  length io  length io'
    by blast

  then show "False"
    using is_shorter
    by simp
qed


end