Theory Timed_Automata.Graphs
chapter ‹Graphs›
theory Graphs
  imports
    More_List Stream_More
    "HOL-Library.Rewrite"
begin
section ‹Basic Definitions and Theorems›
locale Graph_Defs =
  fixes E :: "'a ⇒ 'a ⇒ bool"
begin
inductive steps where
  Single: "steps [x]" |
  Cons: "steps (x # y # xs)" if "E x y" "steps (y # xs)"
lemmas [intro] = steps.intros
lemma steps_append:
  "steps (xs @ tl ys)" if "steps xs" "steps ys" "last xs = hd ys"
  using that by induction (auto 4 4 elim: steps.cases)
lemma steps_append':
  "steps xs" if "steps as" "steps bs" "last as = hd bs" "as @ tl bs = xs"
  using steps_append that by blast
coinductive run where
  "run (x ## y ## xs)" if "E x y" "run (y ## xs)"
lemmas [intro] = run.intros
lemma steps_appendD1:
  "steps xs" if "steps (xs @ ys)" "xs ≠ []"
  using that proof (induction xs)
  case Nil
  then show ?case by auto
next
  case (Cons a xs)
  then show ?case
    by - (cases xs; auto elim: steps.cases)
qed
lemma steps_appendD2:
  "steps ys" if "steps (xs @ ys)" "ys ≠ []"
  using that by (induction xs) (auto elim: steps.cases)
lemma steps_appendD3:
  "steps (xs @ [x]) ∧ E x y" if "steps (xs @ [x, y])"
  using that proof (induction xs)
  case Nil
  then show ?case by (auto elim!: steps.cases)
next
  case prems: (Cons a xs)
  then show ?case by (cases xs) (auto elim: steps.cases)
qed
lemma steps_ConsD:
  "steps xs" if "steps (x # xs)" "xs ≠ []"
  using that by (auto elim: steps.cases)
lemmas stepsD = steps_ConsD steps_appendD1 steps_appendD2
lemma steps_alt_induct[consumes 1, case_names Single Snoc]:
  assumes
    "steps x" "(⋀x. P [x])"
    "⋀y x xs. E y x ⟹ steps (xs @ [y]) ⟹ P (xs @ [y]) ⟹ P (xs @ [y,x])"
  shows "P x"
  using assms(1)
  proof (induction rule: rev_induct)
    case Nil
    then show ?case by (auto elim: steps.cases)
  next
    case prems: (snoc x xs)
    then show ?case by (cases xs rule: rev_cases) (auto intro: assms(2,3) dest!: steps_appendD3)
  qed
lemma steps_appendI:
  "steps (xs @ [x, y])" if "steps (xs @ [x])" "E x y"
  using that
proof (induction xs)
  case Nil
  then show ?case by auto
next
  case (Cons a xs)
  then show ?case by (cases xs; auto elim: steps.cases)
qed
lemma steps_append_single:
  assumes
    "steps xs" "E (last xs) x" "xs ≠ []"
  shows "steps (xs @ [x])"
  using assms(3,1,2) by (induction xs rule: list_nonempty_induct) (auto 4 4 elim: steps.cases)
lemma extend_run:
  assumes
    "steps xs" "E (last xs) x" "run (x ## ys)" "xs ≠ []"
  shows "run (xs @- x ## ys)"
  using assms(4,1-3) by (induction xs rule: list_nonempty_induct) (auto 4 3 elim: steps.cases)
lemma run_cycle:
  assumes "steps xs" "E (last xs) (hd xs)" "xs ≠ []"
  shows "run (cycle xs)"
  using assms proof (coinduction arbitrary: xs)
  case run
  then show ?case
    apply (rewrite at ‹cycle xs› stream.collapse[symmetric])
    apply (rewrite at ‹stl (cycle xs)› stream.collapse[symmetric])
    apply clarsimp
    apply (erule steps.cases)
    subgoal for x
      apply (rule conjI)
       apply (simp; fail)
      apply (rule disjI1)
      apply (inst_existentials xs)
         apply (simp, metis cycle_Cons[of x "[]", simplified])
      by auto
    subgoal for x y xs'
      apply (rule conjI)
       apply (simp; fail)
      apply (rule disjI1)
      apply (inst_existentials "y # xs' @ [x]")
      using steps_append_single[of "y # xs'" x]
         apply (auto elim: steps.cases split: if_split_asm simp: cycle_Cons)
      done
    done
qed
lemma run_stl:
  "run (stl xs)" if "run xs"
  using that by (auto elim: run.cases)
lemma run_sdrop:
  "run (sdrop n xs)" if "run xs"
  using that by (induction n arbitrary: xs) (auto intro: run_stl)
lemma run_reachable':
  assumes "run (x ## xs)" "E⇧*⇧* x⇩0 x"
  shows "pred_stream (λ x. E⇧*⇧* x⇩0 x) xs"
  using assms by (coinduction arbitrary: x xs) (auto 4 3 elim: run.cases)
lemma run_reachable:
  assumes "run (x⇩0 ## xs)"
  shows "pred_stream (λ x. E⇧*⇧* x⇩0 x) xs"
  by (rule run_reachable'[OF assms]) blast
lemma run_decomp:
  assumes "run (xs @- ys)" "xs ≠ []"
  shows "steps xs ∧ run ys ∧ E (last xs) (shd ys)"
using assms(2,1) proof (induction xs rule: list_nonempty_induct)
  case (single x)
  then show ?case by (auto elim: run.cases)
next
  case (cons x xs)
  then show ?case by (cases xs; auto 4 4 elim: run.cases)
qed
lemma steps_decomp:
  assumes "steps (xs @ ys)" "xs ≠ []" "ys ≠ []"
  shows "steps xs ∧ steps ys ∧ E (last xs) (hd ys)"
using assms(2,1,3) proof (induction xs rule: list_nonempty_induct)
  case (single x)
  then show ?case by (auto elim: steps.cases)
next
  case (cons x xs)
  then show ?case by (cases xs; auto 4 4 elim: steps.cases)
qed
lemma steps_rotate:
  assumes "steps (x # xs @ y # ys @ [x])"
  shows "steps (y # ys @ x # xs @ [y])"
proof -
  from steps_decomp[of "x # xs" "y # ys @ [x]"] assms have
    "steps (x # xs)" "steps (y # ys @ [x])" "E (last (x # xs)) y"
    by auto
  then have "steps ((x # xs) @ [y])" by (blast intro: steps_append_single)
  from steps_append[OF ‹steps (y # ys @ [x])› this] show ?thesis by auto
qed
lemma run_shift_coinduct[case_names run_shift, consumes 1]:
  assumes "R w"
      and "⋀ w. R w ⟹ ∃ u v x y. w = u @- x ## y ## v ∧ steps (u @ [x]) ∧ E x y ∧ R (y ## v)"
  shows "run w"
  using assms(2)[OF ‹R w›] proof (coinduction arbitrary: w)
  case (run w)
  then obtain u v x y where "w = u @- x ## y ## v" "steps (u @ [x])" "E x y" "R (y ## v)"
    by auto
  then show ?case
    apply -
    apply (drule assms(2))
    apply (cases u)
     apply force
    subgoal for z zs
      apply (cases zs)
      subgoal
        apply simp
        apply safe
         apply (force elim: steps.cases)
        subgoal for u' v' x' y'
          by (inst_existentials "x # u'") (cases u'; auto)
        done
      subgoal for a as
        apply simp
        apply safe
         apply (force elim: steps.cases)
        subgoal for u' v' x' y'
          apply (inst_existentials "a # as @ x # u'")
          using steps_append[of "a # as @ [x, y]" "u' @ [x']"]
          apply simp
          apply (drule steps_appendI[of "a # as" x, rotated])
          by (cases u'; force elim: steps.cases)+
        done
      done
    done
qed
lemma run_flat_coinduct[case_names run_shift, consumes 1]:
  assumes "R xss"
    and
    "⋀ xs ys xss.
    R (xs ## ys ## xss) ⟹ xs ≠ [] ∧ steps xs ∧ E (last xs) (hd ys) ∧ R (ys ## xss)"
  shows "run (flat xss)"
proof -
  obtain xs ys xss' where "xss = xs ## ys ## xss'" by (metis stream.collapse)
  with assms(2)[OF assms(1)[unfolded this]] show ?thesis
  proof (coinduction arbitrary: xs ys xss' xss rule: run_shift_coinduct)
    case (run_shift xs ys xss' xss)
    from run_shift show ?case
      apply (cases xss')
      apply clarify
      apply (drule assms(2))
      apply (inst_existentials "butlast xs" "tl ys @- flat xss'" "last xs" "hd ys")
         apply (cases ys)
          apply (simp; fail)
      subgoal premises prems for x1 x2 z zs
      proof (cases "xs = []")
        case True
        with prems show ?thesis
          by auto
      next
        case False
        then have "xs = butlast xs @ [last xs]" by auto
        then have "butlast xs @- last xs ## tail = xs @- tail" for tail
          by (metis shift.simps(1,2) shift_append)
        with prems show ?thesis by simp
      qed
        apply (simp; fail)
       apply assumption
      subgoal for ws wss
        by (inst_existentials ys ws wss) (cases ys, auto)
      done
  qed
qed
lemma steps_non_empty[simp]:
  "¬ steps []"
  by (auto elim: steps.cases)
lemma steps_non_empty'[simp]:
  "xs ≠ []" if "steps xs"
  using that by auto
lemma steps_replicate:
  "steps (hd xs # concat (replicate n (tl xs)))" if "last xs = hd xs" "steps xs" "n > 0"
  using that
proof (induction n)
  case 0
  then show ?case by simp
next
  case (Suc n)
  show ?case
  proof (cases n)
    case 0
    with Suc.prems show ?thesis by (cases xs; auto)
  next
    case prems: (Suc nat)
    from Suc.prems have [simp]: "hd xs # tl xs @ ys = xs @ ys" for ys
      by (cases xs; auto)
    from Suc.prems have **: "tl xs @ ys = tl (xs @ ys)" for ys
      by (cases xs; auto)
    from prems Suc show ?thesis
      by (fastforce intro: steps_append')
  qed
qed
notation E (‹_ → _› [100, 100] 40)
abbreviation reaches (‹_ →* _› [100, 100] 40) where "reaches x y ≡ E⇧*⇧* x y"
abbreviation reaches1 (‹_ →⇧+ _› [100, 100] 40) where "reaches1 x y ≡ E⇧+⇧+ x y"
lemma steps_reaches:
  "hd xs →* last xs" if "steps xs"
  using that by (induction xs) auto
lemma steps_reaches':
  "x →* y" if "steps xs" "hd xs = x" "last xs = y"
  using that steps_reaches by auto
lemma reaches_steps:
  "∃ xs. hd xs = x ∧ last xs = y ∧ steps xs" if "x →* y"
  using that
  apply (induction)
   apply force
  apply clarsimp
  subgoal for z xs
    by (inst_existentials "xs @ [z]", (cases xs; simp), auto intro: steps_append_single)
  done
lemma reaches_steps_iff:
  "x →* y ⟷ (∃ xs. hd xs = x ∧ last xs = y ∧ steps xs)"
  using steps_reaches reaches_steps by fast
lemma steps_reaches1:
  "x →⇧+ y" if "steps (x # xs @ [y])"
  by (metis list.sel(1,3) rtranclp_into_tranclp2 snoc_eq_iff_butlast steps.cases steps_reaches that)
lemma stepsI:
  "steps (x # xs)" if "x → hd xs" "steps xs"
  using that by (cases xs) auto
lemma reaches1_steps:
  "∃ xs. steps (x # xs @ [y])" if "x →⇧+ y"
proof -
  from that obtain z where "x → z" "z →* y"
    by atomize_elim (simp add: tranclpD)
  from reaches_steps[OF this(2)] obtain xs where *: "hd xs = z" "last xs = y" "steps xs"
    by auto
  then obtain xs' where [simp]: "xs = xs' @ [y]"
    by atomize_elim (auto 4 3 intro: append_butlast_last_id[symmetric])
  with ‹x → z› * show ?thesis
    by (auto intro: stepsI)
qed
lemma reaches1_steps_iff:
  "x →⇧+ y ⟷ (∃ xs. steps (x # xs @ [y]))"
  using steps_reaches1 reaches1_steps by fast
lemma reaches_steps_iff2:
  "x →* y ⟷ (x = y ∨ (∃vs. steps (x # vs @ [y])))"
  by (simp add: Nitpick.rtranclp_unfold reaches1_steps_iff)
lemma reaches1_reaches_iff1:
  "x →⇧+ y ⟷ (∃ z. x → z ∧ z →* y)"
  by (auto dest: tranclpD)
lemma reaches1_reaches_iff2:
  "x →⇧+ y ⟷ (∃ z. x →* z ∧ z → y)"
  apply safe
   apply (metis Nitpick.rtranclp_unfold tranclp.cases)
  by auto
lemma
  "x →⇧+ z" if "x →* y" "y →⇧+ z"
  using that by auto
lemma
  "x →⇧+ z" if "x →⇧+ y" "y →* z"
  using that by auto
lemma steps_append2:
  "steps (xs @ x # ys)" if "steps (xs @ [x])" "steps (x # ys)"
  using that by (auto dest: steps_append)
lemma reaches1_steps_append:
  assumes "a →⇧+ b" "steps xs" "hd xs = b"
  shows "∃ ys. steps (a # ys @ xs)"
  using assms by (fastforce intro: steps_append' dest: reaches1_steps)
lemma steps_last_step:
  "∃ a. a → last xs" if "steps xs" "length xs > 1"
  using that by induction auto
lemma steps_remove_cycleE:
  assumes "steps (a # xs @ [b])"
  obtains ys where "steps (a # ys @ [b])" "distinct ys" "a ∉ set ys" "b ∉ set ys" "set ys ⊆ set xs"
  using assms
proof (induction "length xs" arbitrary: xs rule: less_induct)
  case less
  note prems = less.prems(2) and intro = less.prems(1) and IH = less.hyps
  consider
    "distinct xs" "a ∉ set xs" "b ∉ set xs" | "a ∈ set xs" | "b ∈ set xs" | "¬ distinct xs"
    by auto
  then consider (goal) ?case
    | (a) as bs where "xs = as @ a # bs" | (b) as bs where "xs = as @ b # bs"
    | (between) x as bs cs where "xs = as @ x # bs @ x # cs"
    using prems by (cases; fastforce dest: not_distinct_decomp simp: split_list intro: intro)
  then show ?case
  proof cases
    case a
    with prems show ?thesis
      by - (rule IH[where xs = bs], auto 4 3 intro: intro dest: stepsD)
  next
    case b
    with prems have "steps (a # as @ b # [] @ (bs @ [b]))"
      by simp
    then have "steps (a # as @ [b])"
      by (metis Cons_eq_appendI Graph_Defs.steps_appendD1 append_eq_appendI neq_Nil_conv)
    with b show ?thesis
      by - (rule IH[where xs = as], auto 4 3 dest: stepsD intro: intro)
  next
    case between
    with prems have "steps (a # as @ x # cs @ [b])"
      by simp (metis
          stepsI append_Cons list.distinct(1) list.sel(1) list.sel(3) steps_append steps_decomp)
    with between show ?thesis
      by - (rule IH[where xs = "as @ x # cs"], auto 4 3 intro: intro dest: stepsD)
  qed
qed
lemma reaches1_stepsE:
  assumes "a →⇧+ b"
  obtains xs where "steps (a # xs @ [b])" "distinct xs" "a ∉ set xs" "b ∉ set xs"
proof -
  from assms obtain xs where "steps (a # xs @ [b])"
    by (auto dest: reaches1_steps)
  then show ?thesis
    by - (erule steps_remove_cycleE, rule that)
qed
lemma reaches_stepsE:
  assumes "a →* b"
  obtains "a = b" | xs where "steps (a # xs @ [b])" "distinct xs" "a ∉ set xs" "b ∉ set xs"
proof -
  from assms consider "a = b" | xs where "a →⇧+ b"
    by (meson rtranclpD)
  then show ?thesis
    by cases ((erule reaches1_stepsE)?; rule that; assumption)+
qed
definition sink where
  "sink a ≡ ∄b. a → b"
lemma sink_or_cycle:
  assumes "finite {b. reaches a b}"
  obtains b where "reaches a b" "sink b" | b where "reaches a b" "reaches1 b b"
proof -
  let ?S = "{b. reaches1 a b}"
  have "?S ⊆ {b. reaches a b}"
    by auto
  then have "finite ?S"
    using assms by (rule finite_subset)
  then show ?thesis
    using that
  proof (induction ?S arbitrary: a rule: finite_psubset_induct)
    case psubset
    consider (empty) "Collect (reaches1 a) = {}" | b where "reaches1 a b"
      by auto
    then show ?case
    proof cases
      case empty
      then have "sink a"
        unfolding sink_def by auto
      with psubset.prems show ?thesis
        by auto
    next
      case 2
      show ?thesis
      proof (cases "reaches b a")
        case True
        with ‹reaches1 a b› have "reaches1 a a"
          by auto
        with psubset.prems show ?thesis
          by auto
      next
        case False
        show ?thesis
        proof (cases "reaches1 b b")
          case True
          with ‹reaches1 a b› psubset.prems show ?thesis
            by (auto intro: tranclp_into_rtranclp)
        next
          case False
          with ‹¬ reaches b a› ‹reaches1 a b› have "Collect (reaches1 b) ⊂ Collect (reaches1 a)"
            by (intro psubsetI) auto
          then show ?thesis
            using ‹reaches1 a b› psubset.prems
            by - (erule psubset.hyps; meson tranclp_into_rtranclp tranclp_rtranclp_tranclp)
        qed
      qed
    qed
  qed
qed
text ‹
  A directed graph where every node has at least one ingoing edge, contains a directed cycle.
›
lemma directed_graph_indegree_ge_1_cycle':
  assumes "finite S" "S ≠ {}" "∀ y ∈ S. ∃ x ∈ S. E x y"
  shows "∃ x ∈ S. ∃ y. E x y ∧ E⇧*⇧* y x"
  using assms
proof (induction arbitrary: E rule: finite_ne_induct)
  case (singleton x)
  then show ?case by auto
next
  case (insert x S E)
  from insert.prems obtain y where "y ∈ insert x S" "E y x"
    by auto
  show ?case
  proof (cases "y = x")
    case True
    with ‹E y x› show ?thesis by auto
  next
    case False
    with ‹y ∈ _› have "y ∈ S" by auto
    define E' where "E' a b ≡ E a b ∨ (a = y ∧ E x b)" for a b
    have E'_E: "∃ c. E a c ∧ E⇧*⇧* c b" if "E' a b" for a b
      using that ‹E y x› unfolding E'_def by auto
    have [intro]: "E⇧*⇧* a b" if "E' a b" for a b
      using that ‹E y x› unfolding E'_def by auto
    have [intro]: "E⇧*⇧* a b" if "E'⇧*⇧* a b" for a b
      using that by (induction; blast intro: rtranclp_trans)
    have "∀y∈S. ∃x∈S. E' x y"
    proof (rule ballI)
      fix b assume "b ∈ S"
      with insert.prems obtain a where "a ∈ insert x S" "E a b"
        by auto
      show "∃a∈S. E' a b"
      proof (cases "a = x")
        case True
        with ‹E a b› have "E' y b" unfolding E'_def by simp
        with ‹y ∈ S› show ?thesis ..
      next
        case False
        with ‹a ∈ _› ‹E a b› show ?thesis unfolding E'_def by auto
      qed
    qed
    from insert.IH[OF this] obtain x y where "x ∈ S" "E' x y" "E'⇧*⇧* y x" by safe
    then show ?thesis by (blast intro: rtranclp_trans dest: E'_E)
    qed
  qed
lemma directed_graph_indegree_ge_1_cycle:
  assumes "finite S" "S ≠ {}" "∀ y ∈ S. ∃ x ∈ S. E x y"
  shows "∃ x ∈ S. ∃ y. x →⇧+ x"
  using directed_graph_indegree_ge_1_cycle'[OF assms] reaches1_reaches_iff1 by blast
text ‹Vertices of a graph›
definition "vertices = {x. ∃y. E x y ∨ E y x}"
lemma reaches1_verts:
  assumes "x →⇧+ y"
  shows "x ∈ vertices" and "y ∈ vertices"
  using assms reaches1_reaches_iff2 reaches1_reaches_iff1 vertices_def by blast+
lemmas graphI =
  steps.intros
  steps_append_single
  steps_reaches'
  stepsI
end 
section ‹Graphs with a Start Node›
locale Graph_Start_Defs = Graph_Defs +
  fixes s⇩0 :: 'a
begin
definition reachable where
  "reachable = E⇧*⇧* s⇩0"
lemma start_reachable[intro!, simp]:
  "reachable s⇩0"
  unfolding reachable_def by auto
lemma reachable_step:
  "reachable b" if "reachable a" "E a b"
  using that unfolding reachable_def by auto
lemma reachable_reaches:
  "reachable b" if "reachable a" "a →* b"
  using that(2,1) by induction (auto intro: reachable_step)
lemma reachable_steps_append:
  assumes "reachable a" "steps xs" "hd xs = a" "last xs = b"
  shows "reachable b"
  using assms by (auto intro: graphI reachable_reaches)
lemmas steps_reachable = reachable_steps_append[of s⇩0, simplified]
lemma reachable_steps_elem:
  "reachable y" if "reachable x" "steps xs" "y ∈ set xs" "hd xs = x"
proof -
  from ‹y ∈ set xs› obtain as bs where [simp]: "xs = as @ y # bs"
    by (auto simp: in_set_conv_decomp)
  show ?thesis
  proof (cases "as = []")
    case True
    with that show ?thesis
      by simp
  next
    case False
    
    from ‹steps xs› have "steps (as @ [y])"
      by (auto intro: stepsD)
    with ‹as ≠ []› ‹hd xs = x› ‹reachable x› show ?thesis
      by (auto 4 3 intro: reachable_reaches graphI)
  qed
qed
lemma reachable_steps:
  "∃ xs. steps xs ∧ hd xs = s⇩0 ∧ last xs = x" if "reachable x"
  using that unfolding reachable_def
proof induction
  case base
  then show ?case by (inst_existentials "[s⇩0]"; force)
next
  case (step y z)
  from step.IH obtain xs where "steps xs" "s⇩0 = hd xs" "y = last xs" by clarsimp
  with step.hyps show ?case
    apply (inst_existentials "xs @ [z]")
    apply (force intro: graphI)
    by (cases xs; auto)+
qed
lemma reachable_cycle_iff:
  "reachable x ∧ x →⇧+ x ⟷ (∃ ws xs. steps (s⇩0 # ws @ [x] @ xs @ [x]))"
proof (safe, goal_cases)
  case (2 ws)
  then show ?case
    by (auto intro: steps_reachable stepsD)
next
  case (3 ws xs)
  then show ?case
    by (auto intro: stepsD steps_reaches1)
next
  case prems: 1
  from ‹reachable x› prems(2) have "s⇩0 →⇧+ x"
    unfolding reachable_def by auto
  with ‹x →⇧+ x› show ?case
    by (fastforce intro: steps_append' dest: reaches1_steps)
qed
lemma reachable_induct[consumes 1, case_names start step, induct pred: reachable]:
  assumes "reachable x"
    and "P s⇩0"
    and "⋀ a b. reachable a ⟹ P a ⟹ a → b ⟹ P b"
  shows "P x"
  using assms(1) unfolding reachable_def
  by induction (auto intro: assms(2-)[unfolded reachable_def])
lemmas graphI_aggressive =
  tranclp_into_rtranclp
  rtranclp.rtrancl_into_rtrancl
  tranclp.trancl_into_trancl
  rtranclp_into_tranclp2
lemmas graphI_aggressive1 =
  graphI_aggressive
  steps_append'
lemmas graphI_aggressive2 =
  graphI_aggressive
  stepsD
  steps_reaches1
  steps_reachable
lemmas graphD =
  reaches1_steps
lemmas graphD_aggressive =
  tranclpD
lemmas graph_startI =
  reachable_reaches
  start_reachable
end 
section ‹Subgraphs›
subsection ‹Edge-induced Subgraphs›
locale Subgraph_Defs = G: Graph_Defs +
  fixes E' :: "'a ⇒ 'a ⇒ bool"
begin
sublocale G': Graph_Defs E' .
end 
locale Subgraph_Start_Defs = G: Graph_Start_Defs +
  fixes E' :: "'a ⇒ 'a ⇒ bool"
begin
sublocale G': Graph_Start_Defs E' s⇩0 .
end 
locale Subgraph = Subgraph_Defs +
  assumes subgraph[intro]: "E' a b ⟹ E a b"
begin
lemma non_subgraph_cycle_decomp:
  "∃ c d. G.reaches a c ∧ E c d ∧ ¬ E' c d ∧ G.reaches d b" if
  "G.reaches1 a b" "¬ G'.reaches1 a b" for a b
    using that
  proof induction
    case (base y)
    then show ?case
      by auto
  next
    case (step y z)
    show ?case
    proof (cases "E' y z")
      case True
      with step have "¬ G'.reaches1 a y"
        by (auto intro: tranclp.trancl_into_trancl) 
      with step obtain c d where
        "G.reaches a c" "E c d" "¬ E' c d" "G.reaches d y"
        by auto
      with ‹E' y z› show ?thesis
        by (blast intro: rtranclp.rtrancl_into_rtrancl) 
    next
      case False
      with step show ?thesis
        by (intro exI conjI) auto
    qed
  qed
lemma reaches:
  "G.reaches a b" if "G'.reaches a b"
  using that by induction (auto intro: rtranclp.intros(2))
lemma reaches1:
  "G.reaches1 a b" if "G'.reaches1 a b"
  using that by induction (auto intro: tranclp.intros(2))
end 
locale Subgraph_Start = Subgraph_Start_Defs + Subgraph
begin
lemma reachable_subgraph[intro]: "G.reachable b" if ‹G.reachable a› ‹G'.reaches a b› for a b
  using that by (auto intro: G.graph_startI mono_rtranclp[rule_format, of E'])
lemma reachable:
  "G.reachable x" if "G'.reachable x"
  using that by (fastforce simp: G.reachable_def G'.reachable_def)
end 
subsection ‹Node-induced Subgraphs›
locale Subgraph_Node_Defs = Graph_Defs +
  fixes V :: "'a ⇒ bool"
begin
definition E' where "E' x y ≡ E x y ∧ V x ∧ V y"
sublocale Subgraph E E' by standard (auto simp: E'_def)
lemma subgraph':
  "E' x y" if "E x y" "V x" "V y"
  using that unfolding E'_def by auto
lemma E'_V1:
  "V x" if "E' x y"
  using that unfolding E'_def by auto
lemma E'_V2:
  "V y" if "E' x y"
  using that unfolding E'_def by auto
lemma G'_reaches_V:
  "V y" if "G'.reaches x y" "V x"
  using that by (cases) (auto intro: E'_V2)
lemma G'_steps_V_all:
  "list_all V xs" if "G'.steps xs" "V (hd xs)"
  using that by induction (auto intro: E'_V2)
lemma G'_steps_V_last:
  "V (last xs)" if "G'.steps xs" "V (hd xs)"
  using that by induction (auto dest: E'_V2)
lemmas subgraphI = E'_V1 E'_V2 G'_reaches_V
lemmas subgraphD = E'_V1 E'_V2 G'_reaches_V
end 
locale Subgraph_Node_Defs_Notation = Subgraph_Node_Defs
begin
no_notation E (‹_ → _› [100, 100] 40)
notation E' (‹_ → _› [100, 100] 40)
no_notation reaches (‹_ →* _› [100, 100] 40)
notation G'.reaches (‹_ →* _› [100, 100] 40)
no_notation reaches1 (‹_ →⇧+ _› [100, 100] 40)
notation G'.reaches1 (‹_ →⇧+ _› [100, 100] 40)
end 
subsection ‹The Reachable Subgraph›
context Graph_Start_Defs
begin
interpretation Subgraph_Node_Defs_Notation E reachable .
sublocale reachable_subgraph: Subgraph_Node_Defs E reachable .
lemma reachable_supgraph:
  "x → y" if "E x y" "reachable x"
  using that unfolding E'_def by (auto intro: graph_startI)
lemma reachable_reaches_equiv: "reaches x y ⟷ x →* y" if "reachable x" for x y
  apply standard
  subgoal premises prems
    using prems ‹reachable x›
    by induction (auto dest: reachable_supgraph intro: graph_startI graphI_aggressive)
  subgoal premises prems
    using prems ‹reachable x›
    by induction (auto dest: subgraph)
  done
lemma reachable_reaches1_equiv: "reaches1 x y ⟷ x →⇧+ y" if "reachable x" for x y
  apply standard
  subgoal premises prems
    using prems ‹reachable x›
    by induction (auto dest: reachable_supgraph intro: graph_startI graphI_aggressive)
  subgoal premises prems
    using prems ‹reachable x›
    by induction (auto dest: subgraph)
  done
lemma reachable_steps_equiv:
  "steps (x # xs) ⟷ G'.steps (x # xs)" if "reachable x"
  apply standard
  subgoal premises prems
    using prems ‹reachable x›
    by (induction "x # xs" arbitrary: x xs) (auto dest: reachable_supgraph intro: graph_startI)
  subgoal premises prems
    using prems by induction auto
  done
end 
section ‹Bundles›
bundle graph_automation
begin
lemmas [intro] = Graph_Defs.graphI Graph_Start_Defs.graph_startI
lemmas [dest]  = Graph_Start_Defs.graphD
end 
bundle reaches_steps_iff =
  Graph_Defs.reaches1_steps_iff [iff]
  Graph_Defs.reaches_steps_iff  [iff]
bundle graph_automation_aggressive
begin
unbundle graph_automation
lemmas [intro] = Graph_Start_Defs.graphI_aggressive
lemmas [dest]  = Graph_Start_Defs.graphD_aggressive
end 
bundle subgraph_automation
begin
unbundle graph_automation
lemmas [intro] = Subgraph_Node_Defs.subgraphI
lemmas [dest]  = Subgraph_Node_Defs.subgraphD
end 
section ‹Directed Acyclic Graphs›
locale DAG = Graph_Defs +
  assumes acyclic: "¬ E⇧+⇧+ x x"
begin
lemma topological_numbering:
  fixes S assumes "finite S"
  shows "∃f :: _ ⇒ nat. inj_on f S ∧ (∀x ∈ S. ∀y ∈ S. E x y ⟶ f x < f y)"
  using assms
proof (induction rule: finite_psubset_induct)
  case (psubset A)
  show ?case
  proof (cases "A = {}")
    case True
    then show ?thesis
      by simp
  next
    case False
    then obtain x where x: "x ∈ A" "∀y ∈ A. ¬E y x"
      using directed_graph_indegree_ge_1_cycle[OF ‹finite A›] acyclic by auto
    let ?A = "A - {x}"
    from ‹x ∈ A› have "?A ⊂ A"
      by auto
    from psubset.IH(1)[OF this] obtain f :: "_ ⇒ nat" where f:
      "inj_on f ?A" "∀x∈?A. ∀y∈?A. x → y ⟶ f x < f y"
      by blast
    let ?f = "λy. if x ≠ y then f y + 1 else 0"
    from ‹x ∈ A› have "A = insert x ?A"
      by auto
    from ‹inj_on f ?A› have "inj_on ?f A"
      by (auto simp: inj_on_def)
    moreover from f(2) x(2) have "∀x∈A. ∀y∈A. x → y ⟶ ?f x < ?f y"
      by auto
    ultimately show ?thesis
      by blast
  qed
qed
end
section ‹Finite Graphs›
locale Finite_Graph = Graph_Defs +
  assumes finite_graph: "finite vertices"
locale Finite_DAG = Finite_Graph + DAG
begin
lemma finite_reachable:
  "finite {y. x →* y}" (is "finite ?S")
proof -
  have "?S ⊆ insert x vertices"
    by (metis insertCI mem_Collect_eq reaches1_verts(2) rtranclpD subsetI)
  also from finite_graph have "finite …" ..
  finally show ?thesis .
qed
end
section ‹Graph Invariants›
locale Graph_Invariant = Graph_Defs +
  fixes P :: "'a ⇒ bool"
  assumes invariant: "P a ⟹ a → b ⟹ P b"
begin
lemma invariant_steps:
  "list_all P as" if "steps (a # as)" "P a"
  using that by (induction "a # as" arbitrary: as a) (auto intro: invariant)
lemma invariant_reaches:
  "P b" if "a →* b" "P a"
  using that by (induction; blast intro: invariant)
lemma invariant_run:
  assumes run: "run (x ## xs)" and P: "P x"
  shows "pred_stream P (x ## xs)"
  using run P by (coinduction arbitrary: x xs) (auto 4 3 elim: invariant run.cases)
text ‹Every graph invariant induces a subgraph.›
sublocale Subgraph_Node_Defs where E = E and V = P .
lemma subgraph':
  assumes "x → y" "P x"
  shows "E' x y"
  using assms by (intro subgraph') (auto intro: invariant)
lemma invariant_steps_iff:
  "G'.steps (v # vs) ⟷ steps (v # vs)" if "P v"
  apply (rule iffI)
  subgoal
    using G'.steps_alt_induct steps_appendI by blast
  subgoal premises prems
    using prems ‹P v› by (induction "v # vs" arbitrary: v vs) (auto intro: subgraph' invariant)
  done
lemma invariant_reaches_iff:
  "G'.reaches u v ⟷ reaches u v" if "P u"
  using that by (simp add: reaches_steps_iff2 G'.reaches_steps_iff2 invariant_steps_iff)
lemma invariant_reaches1_iff:
  "G'.reaches1 u v ⟷ reaches1 u v" if "P u"
  using that by (simp add: reaches1_steps_iff G'.reaches1_steps_iff invariant_steps_iff)
end 
locale Graph_Invariants = Graph_Defs +
  fixes P Q :: "'a ⇒ bool"
  assumes invariant: "P a ⟹ a → b ⟹ Q b" and Q_P: "Q a ⟹ P a"
begin
sublocale Pre: Graph_Invariant E P
  by standard (blast intro: invariant Q_P)
sublocale Post: Graph_Invariant E Q
  by standard (blast intro: invariant Q_P)
lemma invariant_steps:
  "list_all Q as" if "steps (a # as)" "P a"
  using that by (induction "a # as" arbitrary: as a) (auto intro: invariant Q_P)
lemma invariant_run:
  assumes run: "run (x ## xs)" and P: "P x"
  shows "pred_stream Q xs"
  using run P by (coinduction arbitrary: x xs) (auto 4 4 elim: invariant run.cases intro: Q_P)
lemma invariant_reaches1:
  "Q b" if "a →⇧+ b" "P a"
  using that by (induction; blast intro: invariant Q_P)
end 
locale Graph_Invariant_Start = Graph_Start_Defs + Graph_Invariant +
  assumes P_s⇩0: "P s⇩0"
begin
lemma invariant_steps:
  "list_all P as" if "steps (s⇩0 # as)"
  using that P_s⇩0 by (rule invariant_steps)
lemma invariant_reaches:
  "P b" if "s⇩0 →* b"
  using invariant_reaches[OF that P_s⇩0] .
lemmas invariant_run = invariant_run[OF _ P_s⇩0]
end 
locale Graph_Invariant_Strong = Graph_Defs +
  fixes P :: "'a ⇒ bool"
  assumes invariant: "a → b ⟹ P b"
begin
sublocale inv: Graph_Invariant by standard (rule invariant)
lemma P_invariant_steps:
  "list_all P as" if "steps (a # as)"
  using that by (induction "a # as" arbitrary: as a) (auto intro: invariant)
lemma steps_last_invariant:
  "P (last xs)" if "steps (x # xs)" "xs ≠ []"
  using steps_last_step[of "x # xs"] that by (auto intro: invariant)
lemmas invariant_reaches = inv.invariant_reaches
lemma invariant_reaches1:
  "P b" if "a →⇧+ b"
  using that by (induction; blast intro: invariant)
end 
section ‹Simulations and Bisimulations›
locale Simulation_Defs =
  fixes A :: "'a ⇒ 'a ⇒ bool" and B :: "'b ⇒ 'b ⇒ bool"
    and sim :: "'a ⇒ 'b ⇒ bool" (infixr ‹∼› 60)
begin
sublocale A: Graph_Defs A .
sublocale B: Graph_Defs B .
end 
locale Simulation = Simulation_Defs +
  assumes A_B_step: "⋀ a b a'. A a b ⟹ a ∼ a' ⟹ (∃ b'. B a' b' ∧ b ∼ b')"
begin
lemma simulation_reaches:
  "∃ b'. B⇧*⇧* b b' ∧ a' ∼ b'" if "A⇧*⇧* a a'" "a ∼ b"
  using that by (induction rule: rtranclp_induct) (auto intro: rtranclp.intros(2) dest: A_B_step)
lemma simulation_reaches1:
  "∃ b'. B⇧+⇧+ b b' ∧ a' ∼ b'" if "A⇧+⇧+ a a'" "a ∼ b"
  using that by (induction rule: tranclp_induct) (auto 4 3 intro: tranclp.intros(2) dest: A_B_step)
lemma simulation_steps:
  "∃ bs. B.steps (b # bs) ∧ list_all2 (λ a b. a ∼ b) as bs" if "A.steps (a # as)" "a ∼ b"
  using that
  apply (induction "a # as" arbitrary: a b as)
   apply force
  apply (frule A_B_step, auto)
  done
lemma simulation_run:
  "∃ ys. B.run (y ## ys) ∧ stream_all2 (∼) xs ys" if "A.run (x ## xs)" "x ∼ y"
proof -
  let ?ys = "sscan (λ a' b. SOME b'. B b b' ∧ a' ∼ b') xs y"
  have "B.run (y ## ?ys)"
    using that by (coinduction arbitrary: x y xs) (force dest!: someI_ex A_B_step elim: A.run.cases)
  moreover have "stream_all2 (∼) xs ?ys"
    using that by (coinduction arbitrary: x y xs) (force dest!: someI_ex A_B_step elim: A.run.cases)
  ultimately show ?thesis by blast
qed
end 
lemma (in Subgraph) Subgraph_Simulation:
  "Simulation E' E (=)"
  by standard auto
locale Simulation_Invariant = Simulation_Defs +
  fixes PA :: "'a ⇒ bool" and PB :: "'b ⇒ bool"
  assumes A_B_step: "⋀ a b a'. A a b ⟹ PA a ⟹ PB a' ⟹ a ∼ a' ⟹ (∃ b'. B a' b' ∧ b ∼ b')"
  assumes A_invariant[intro]: "⋀ a b. PA a ⟹ A a b ⟹ PA b"
  assumes B_invariant[intro]: "⋀ a b. PB a ⟹ B a b ⟹ PB b"
begin
definition "equiv' ≡ λ a b. a ∼ b ∧ PA a ∧ PB b"
sublocale Simulation A B equiv' by standard (auto dest: A_B_step simp: equiv'_def)
sublocale PA_invariant: Graph_Invariant A PA by standard blast
sublocale PB_invariant: Graph_Invariant B PB by standard blast
lemma simulation_reaches:
  "∃ b'. B⇧*⇧* b b' ∧ a' ∼ b' ∧ PA a' ∧ PB b'" if "A⇧*⇧* a a'" "a ∼ b" "PA a" "PB b"
  using simulation_reaches[of a a' b] that unfolding equiv'_def by simp
lemma simulation_steps:
  "∃ bs. B.steps (b # bs) ∧ list_all2 (λ a b. a ∼ b ∧ PA a ∧ PB b) as bs"
  if "A.steps (a # as)" "a ∼ b" "PA a" "PB b"
  using simulation_steps[of a as b] that unfolding equiv'_def by simp
lemma simulation_steps':
  "∃ bs. B.steps (b # bs) ∧ list_all2 (λ a b. a ∼ b) as bs ∧ list_all PA as ∧ list_all PB bs"
  if "A.steps (a # as)" "a ∼ b" "PA a" "PB b"
  using simulation_steps[OF that]
  by (force dest: list_all2_set1 list_all2_set2 simp: list_all_iff elim: list_all2_mono)
context
  fixes f
  assumes eq: "a ∼ b ⟹ b = f a"
begin
lemma simulation_steps'_map:
  "∃ bs.
    B.steps (b # bs) ∧ bs = map f as
    ∧ list_all2 (λ a b. a ∼ b) as bs
    ∧ list_all PA as ∧ list_all PB bs"
  if "A.steps (a # as)" "a ∼ b" "PA a" "PB b"
proof -
  from simulation_steps'[OF that] obtain bs where guessed:
    "B.steps (b # bs)"
    "list_all2 (∼) as bs"
    "list_all PA as"
    "list_all PB bs"
    by safe
  from this(2) have "bs = map f as"
    by (induction; simp add: eq)
  with guessed show ?thesis
    by auto
qed
end 
end 
locale Simulation_Invariants = Simulation_Defs +
  fixes PA QA :: "'a ⇒ bool" and PB QB :: "'b ⇒ bool"
  assumes A_B_step: "⋀ a b a'. A a b ⟹ PA a ⟹ PB a' ⟹ a ∼ a' ⟹ (∃ b'. B a' b' ∧ b ∼ b')"
  assumes A_invariant[intro]: "⋀ a b. PA a ⟹ A a b ⟹ QA b"
  assumes B_invariant[intro]: "⋀ a b. PB a ⟹ B a b ⟹ QB b"
  assumes PA_QA[intro]: "⋀ a. QA a ⟹ PA a" and PB_QB[intro]: "⋀ a. QB a ⟹ PB a"
begin
sublocale Pre: Simulation_Invariant A B "(∼)" PA PB
  by standard (auto intro: A_B_step)
sublocale Post: Simulation_Invariant A B "(∼)" QA QB
  by standard (auto intro: A_B_step)
sublocale A_invs: Graph_Invariants A PA QA
  by standard auto
sublocale B_invs: Graph_Invariants B PB QB
  by standard auto
lemma simulation_reaches1:
  "∃ b2. B.reaches1 b1 b2 ∧ a2 ∼ b2 ∧ QB b2" if "A.reaches1 a1 a2" "a1 ∼ b1" "PA a1" "PB b1"
  using that
  by - (drule Pre.simulation_reaches1, auto intro: B_invs.invariant_reaches1 simp: Pre.equiv'_def)
lemma reaches1_unique:
  assumes unique: "⋀ b2. a ∼ b2 ⟹ QB b2 ⟹ b2 = b"
    and that: "A.reaches1 a a" "a ∼ b" "PA a" "PB b"
  shows "B.reaches1 b b"
  using that by (auto dest: unique simulation_reaches1)
end 
locale Bisimulation = Simulation_Defs +
  assumes A_B_step: "⋀ a b a'. A a b ⟹ a ∼ a' ⟹ (∃ b'. B a' b' ∧ b ∼ b')"
  assumes B_A_step: "⋀ a a' b'. B a' b' ⟹ a ∼ a' ⟹ (∃ b. A a b ∧ b ∼ b')"
begin
sublocale A_B: Simulation A B "(∼)" by standard (rule A_B_step)
sublocale B_A: Simulation B A "λ x y. y ∼ x" by standard (rule B_A_step)
lemma A_B_reaches:
  "∃ b'. B⇧*⇧* b b' ∧ a' ∼ b'" if "A⇧*⇧* a a'" "a ∼ b"
  using A_B.simulation_reaches[OF that] .
lemma B_A_reaches:
  "∃ b'. A⇧*⇧* b b' ∧ b' ∼ a'" if "B⇧*⇧* a a'" "b ∼ a"
  using B_A.simulation_reaches[OF that] .
end 
locale Bisimulation_Invariant = Simulation_Defs +
  fixes PA :: "'a ⇒ bool" and PB :: "'b ⇒ bool"
  assumes A_B_step: "⋀ a b a'. A a b ⟹ a ∼ a' ⟹ PA a ⟹ PB a' ⟹ (∃ b'. B a' b' ∧ b ∼ b')"
  assumes B_A_step: "⋀ a a' b'. B a' b' ⟹ a ∼ a' ⟹ PA a ⟹ PB a' ⟹ (∃ b. A a b ∧ b ∼ b')"
  assumes A_invariant[intro]: "⋀ a b. PA a ⟹ A a b ⟹ PA b"
  assumes B_invariant[intro]: "⋀ a b. PB a ⟹ B a b ⟹ PB b"
begin
sublocale PA_invariant: Graph_Invariant A PA by standard blast
sublocale PB_invariant: Graph_Invariant B PB by standard blast
lemmas B_steps_invariant[intro] = PB_invariant.invariant_reaches
definition "equiv' ≡ λ a b. a ∼ b ∧ PA a ∧ PB b"
sublocale bisim: Bisimulation A B equiv'
  by standard (clarsimp simp add: equiv'_def, frule A_B_step B_A_step, assumption; auto)+
sublocale A_B: Simulation_Invariant A B "(∼)" PA PB
  by (standard; blast intro: A_B_step B_A_step)
sublocale B_A: Simulation_Invariant B A "λ x y. y ∼ x" PB PA
  by (standard; blast intro: A_B_step B_A_step)
context
  fixes f
  assumes eq: "a ∼ b ⟷ b = f a"
    and inj: "∀ a b. PB (f a) ∧ PA b ∧ f a = f b ⟶ a = b"
begin
lemma list_all2_inj_map_eq:
  "as = bs" if "list_all2 (λa b. a = f b) (map f as) bs" "list_all PB (map f as)" "list_all PA bs"
  using that inj
  by (induction "map f as" bs arbitrary: as rule: list_all2_induct) (auto simp: inj_on_def)
lemma steps_map_equiv:
  "A.steps (a # as) ⟷ B.steps (b # map f as)" if "a ∼ b" "PA a" "PB b"
  using A_B.simulation_steps'_map[of f a as b] B_A.simulation_steps'[of b "map f as" a] that eq
  by (auto dest: list_all2_inj_map_eq)
lemma steps_map:
  "∃ as. bs = map f as" if "B.steps (f a # bs)" "PA a" "PB (f a)"
proof -
  have "a ∼ f a" unfolding eq ..
  from B_A.simulation_steps'[OF that(1) this ‹PB _› ‹PA _›] obtain as where
    "A.steps (a # as)"
    "list_all2 (λa b. b ∼ a) bs as"
    "list_all PB bs"
    "list_all PA as"
    by safe
  from this(2) show ?thesis
    unfolding eq by (inst_existentials as, induction rule: list_all2_induct, auto)
qed
lemma reaches_equiv:
  "A.reaches a a' ⟷ B.reaches (f a) (f a')" if "PA a" "PB (f a)"
  apply safe
   apply (drule A_B.simulation_reaches[of a a' "f a"]; simp add: eq that)
  apply (drule B_A.simulation_reaches)
     defer
     apply (rule that | clarsimp simp: eq | metis inj)+
  done
end 
lemma equiv'_D:
  "a ∼ b" if "A_B.equiv' a b"
  using that unfolding A_B.equiv'_def by auto
lemma equiv'_rotate_1:
  "B_A.equiv' b a" if "A_B.equiv' a b"
  using that by (auto simp: B_A.equiv'_def A_B.equiv'_def)
lemma equiv'_rotate_2:
  "A_B.equiv' a b" if "B_A.equiv' b a"
  using that by (auto simp: B_A.equiv'_def A_B.equiv'_def)
lemma stream_all2_equiv'_D:
  "stream_all2 (∼) xs ys" if "stream_all2 A_B.equiv' xs ys"
  using stream_all2_weaken[OF that equiv'_D] by fast
lemma stream_all2_equiv'_D2:
  "stream_all2 B_A.equiv' ys xs ⟹ stream_all2 ((∼)¯¯) ys xs"
  by (coinduction arbitrary: xs ys) (auto simp: B_A.equiv'_def)
lemma stream_all2_rotate_1:
  "stream_all2 B_A.equiv' ys xs ⟹ stream_all2 A_B.equiv' xs ys"
  by (coinduction arbitrary: xs ys) (auto simp: B_A.equiv'_def A_B.equiv'_def)
lemma stream_all2_rotate_2:
  "stream_all2 A_B.equiv' xs ys ⟹ stream_all2 B_A.equiv' ys xs"
  by (coinduction arbitrary: xs ys) (auto simp: B_A.equiv'_def A_B.equiv'_def)
end 
locale Bisimulation_Invariants = Simulation_Defs +
  fixes PA QA :: "'a ⇒ bool" and PB QB :: "'b ⇒ bool"
  assumes A_B_step: "⋀ a b a'. A a b ⟹ a ∼ a' ⟹ PA a ⟹ PB a' ⟹ (∃ b'. B a' b' ∧ b ∼ b')"
  assumes B_A_step: "⋀ a a' b'. B a' b' ⟹ a ∼ a' ⟹ PA a ⟹ PB a' ⟹ (∃ b. A a b ∧ b ∼ b')"
  assumes A_invariant[intro]: "⋀ a b. PA a ⟹ A a b ⟹ QA b"
  assumes B_invariant[intro]: "⋀ a b. PB a ⟹ B a b ⟹ QB b"
  assumes PA_QA[intro]: "⋀ a. QA a ⟹ PA a" and PB_QB[intro]: "⋀ a. QB a ⟹ PB a"
begin
sublocale PA_invariant: Graph_Invariant A PA by standard blast
sublocale PB_invariant: Graph_Invariant B PB by standard blast
sublocale QA_invariant: Graph_Invariant A QA by standard blast
sublocale QB_invariant: Graph_Invariant B QB by standard blast
sublocale Pre_Bisim: Bisimulation_Invariant A B "(∼)" PA PB
  by standard (auto intro: A_B_step B_A_step)
sublocale Post_Bisim: Bisimulation_Invariant A B "(∼)" QA QB
  by standard (auto intro: A_B_step B_A_step)
sublocale A_B: Simulation_Invariants A B "(∼)" PA QA PB QB
  by standard (blast intro: A_B_step)+
sublocale B_A: Simulation_Invariants B A "λ x y. y ∼ x" PB QB PA QA
  by standard (blast intro: B_A_step)+
context
  fixes f
  assumes eq[simp]: "a ∼ b ⟷ b = f a"
    and inj: "∀ a b. QB (f a) ∧ QA b ∧ f a = f b ⟶ a = b"
begin
lemmas list_all2_inj_map_eq = Post_Bisim.list_all2_inj_map_eq[OF eq inj]
lemmas steps_map_equiv' = Post_Bisim.steps_map_equiv[OF eq inj]
lemma list_all2_inj_map_eq':
  "as = bs" if "list_all2 (λa b. a = f b) (map f as) bs" "list_all QB (map f as)" "list_all QA bs"
  using that by (rule list_all2_inj_map_eq)
lemma steps_map_equiv:
  "A.steps (a # as) ⟷ B.steps (b # map f as)" if "a ∼ b" "PA a" "PB b"
proof
  assume "A.steps (a # as)"
  then show "B.steps (b # map f as)"
  proof cases
    case Single
    then show ?thesis by auto
  next
    case prems: (Cons a' xs)
    from A_B_step[OF ‹A a a'› ‹a ∼ b› ‹PA a› ‹PB b›] obtain b' where "B b b'" "a' ∼ b'"
      by auto
    with steps_map_equiv'[OF ‹a' ∼ b'›, of xs] prems that show ?thesis
      by auto
  qed
next
  assume "B.steps (b # map f as)"
  then show "A.steps (a # as)"
  proof cases
    case Single
    then show ?thesis by auto
  next
    case prems: (Cons b' xs)
    from B_A_step[OF ‹B b b'› ‹a ∼ b› ‹PA a› ‹PB b›] obtain a' where "A a a'" "a' ∼ b'"
      by auto
    with that prems have "QA a'" "QB b'"
      by auto
    with ‹A a a'› ‹a' ∼ b'› steps_map_equiv'[OF ‹a' ∼ b'›, of "tl as"] prems that show ?thesis
      apply clarsimp
      subgoal for z zs
        using inj[rule_format, of z a'] by auto
      done
  qed
qed
lemma steps_map:
  "∃ as. bs = map f as" if "B.steps (f a # bs)" "PA a" "PB (f a)"
  using that proof cases
  case Single
  then show ?thesis by simp
next
  case prems: (Cons b' xs)
  from B_A_step[OF ‹B _ b'› _ ‹PA a› ‹PB (f a)›] obtain a' where "A a a'" "a' ∼ b'"
    by auto
  with that prems have "QA a'" "QB b'"
    by auto
  with Post_Bisim.steps_map[OF eq inj, of a' xs] prems ‹a' ∼ b'› obtain ys where "xs = map f ys"
    by auto
  with ‹bs = _› ‹a' ∼ b'› show ?thesis
    by (inst_existentials "a' # ys") auto
qed
text ‹
  @{thm Post_Bisim.reaches_equiv} cannot be lifted directly:
  injectivity cannot be applied for the reflexive case.
›
lemma reaches1_equiv:
  "A.reaches1 a a' ⟷ B.reaches1 (f a) (f a')" if "PA a" "PB (f a)"
proof safe
  assume "A.reaches1 a a'"
  then obtain a'' where prems: "A a a''" "A.reaches a'' a'"
    including graph_automation_aggressive by blast
  from A_B_step[OF ‹A a _› _ that] obtain b where "B (f a) b" "a'' ∼ b"
    by auto
  with that prems have "QA a''" "QB b"
    by auto
  with Post_Bisim.reaches_equiv[OF eq inj, of a'' a'] prems ‹B (f a) b› ‹a'' ∼ b›
  show "B.reaches1 (f a) (f a')"
    by auto
next
  assume "B.reaches1 (f a) (f a')"
  then obtain b where prems: "B (f a) b" "B.reaches b (f a')"
    including graph_automation_aggressive by blast
  from B_A_step[OF ‹B _ b› _ ‹PA a› ‹PB (f a)›] obtain a'' where "A a a''" "a'' ∼ b"
    by auto
  with that prems have "QA a''" "QB b"
    by auto
  with Post_Bisim.reaches_equiv[OF eq inj, of a'' a'] prems ‹A a a''› ‹a'' ∼ b›
  show "A.reaches1 a a'"
    by auto
qed
end 
end 
lemma Bisimulation_Invariant_composition:
  assumes
    "Bisimulation_Invariant A B sim1 PA PB"
    "Bisimulation_Invariant B C sim2 PB PC"
  shows
    "Bisimulation_Invariant A C (λ a c. ∃ b. PB b ∧ sim1 a b ∧ sim2 b c) PA PC"
proof -
  interpret A: Bisimulation_Invariant A B sim1 PA PB
    by (rule assms(1))
  interpret B: Bisimulation_Invariant B C sim2 PB PC
    by (rule assms(2))
  show ?thesis
    by (standard; (blast dest: A.A_B_step B.A_B_step | blast dest: A.B_A_step B.B_A_step))
qed
lemma Bisimulation_Invariant_filter:
  assumes
    "Bisimulation_Invariant A B sim PA PB"
    "⋀ a b. sim a b ⟹ PA a ⟹ PB b ⟹ FA a ⟷ FB b"
    "⋀ a b. A a b ∧ FA b ⟷ A' a b"
    "⋀ a b. B a b ∧ FB b ⟷ B' a b"
  shows
    "Bisimulation_Invariant A' B' sim PA PB"
proof -
  interpret Bisimulation_Invariant A B sim PA PB
    by (rule assms(1))
  have unfold:
    "A' = (λ a b. A a b ∧ FA b)" "B' = (λ a b. B a b ∧ FB b)"
    using assms(3,4) by auto
  show ?thesis
    unfolding unfold
    apply standard
    using assms(2) apply (blast dest: A_B_step)
    using assms(2) apply (blast dest: B_A_step)
    by blast+
qed
lemma Bisimulation_Invariants_filter:
  assumes
    "Bisimulation_Invariants A B sim PA QA PB QB"
    "⋀ a b. QA a ⟹ QB b ⟹ FA a ⟷ FB b"
    "⋀ a b. A a b ∧ FA b ⟷ A' a b"
    "⋀ a b. B a b ∧ FB b ⟷ B' a b"
  shows
    "Bisimulation_Invariants A' B' sim PA QA PB QB"
proof -
  interpret Bisimulation_Invariants A B sim PA QA PB QB
    by (rule assms(1))
  have unfold:
    "A' = (λ a b. A a b ∧ FA b)" "B' = (λ a b. B a b ∧ FB b)"
    using assms(3,4) by auto
  show ?thesis
    unfolding unfold
    apply standard
    using assms(2) apply (blast dest: A_B_step)
    using assms(2) apply (blast dest: B_A_step)
    by blast+
qed
lemma Bisimulation_Invariants_composition:
  assumes
    "Bisimulation_Invariants A B sim1 PA QA PB QB"
    "Bisimulation_Invariants B C sim2 PB QB PC QC"
  shows
    "Bisimulation_Invariants A C (λ a c. ∃ b. PB b ∧ sim1 a b ∧ sim2 b c) PA QA PC QC"
proof -
  interpret A: Bisimulation_Invariants A B sim1 PA QA PB QB
    by (rule assms(1))
  interpret B: Bisimulation_Invariants B C sim2 PB QB PC QC
    by (rule assms(2))
  show ?thesis
    by (standard, blast dest: A.A_B_step B.A_B_step) (blast dest: A.B_A_step B.B_A_step)+
qed
lemma Bisimulation_Invariant_Invariants_composition:
  assumes
    "Bisimulation_Invariant A B sim1 PA PB"
    "Bisimulation_Invariants B C sim2 PB QB PC QC"
  shows
    "Bisimulation_Invariants A C (λ a c. ∃ b. PB b ∧ sim1 a b ∧ sim2 b c) PA PA PC QC"
proof -
  interpret Bisimulation_Invariant A B sim1 PA PB
    by (rule assms(1))
  interpret B: Bisimulation_Invariants B C sim2 PB QB PC QC
    by (rule assms(2))
  interpret A: Bisimulation_Invariants A B sim1 PA PA PB QB
    by (standard; blast intro: A_B_step B_A_step)+
  show ?thesis
    by (standard; (blast dest: A.A_B_step B.A_B_step | blast dest: A.B_A_step B.B_A_step))
qed
lemma Bisimulation_Invariant_Bisimulation_Invariants:
  assumes "Bisimulation_Invariant A B sim PA PB"
  shows "Bisimulation_Invariants A B sim PA PA PB PB"
proof -
  interpret Bisimulation_Invariant A B sim PA PB
    by (rule assms)
  show ?thesis
    by (standard; blast intro: A_B_step B_A_step)
qed
lemma Bisimulation_Invariant_strengthen_post:
  assumes
    "Bisimulation_Invariant A B sim PA PB"
    "⋀ a b. PA' a ⟹ PA b ⟹ A a b ⟹ PA' b"
    "⋀ a. PA' a ⟹ PA a"
  shows "Bisimulation_Invariant A B sim PA' PB"
proof -
  interpret Bisimulation_Invariant A B sim PA PB
    by (rule assms)
  show ?thesis
    by (standard; blast intro: A_B_step B_A_step assms)
qed
lemma Bisimulation_Invariant_strengthen_post':
  assumes
    "Bisimulation_Invariant A B sim PA PB"
    "⋀ a b. PB' a ⟹ PB b ⟹ B a b ⟹ PB' b"
    "⋀ a. PB' a ⟹ PB a"
  shows "Bisimulation_Invariant A B sim PA PB'"
proof -
  interpret Bisimulation_Invariant A B sim PA PB
    by (rule assms)
  show ?thesis
    by (standard; blast intro: A_B_step B_A_step assms)
qed
lemma Simulation_Invariant_strengthen_post:
  assumes
    "Simulation_Invariant A B sim PA PB"
    "⋀ a b. PA a ⟹ PA b ⟹ A a b ⟹ PA' b"
    "⋀ a. PA' a ⟹ PA a"
  shows "Simulation_Invariant A B sim PA' PB"
proof -
  interpret Simulation_Invariant A B sim PA PB
    by (rule assms)
  show ?thesis
    by (standard; blast intro: A_B_step assms)
qed
lemma Simulation_Invariant_strengthen_post':
  assumes
    "Simulation_Invariant A B sim PA PB"
    "⋀ a b. PB a ⟹ PB b ⟹ B a b ⟹ PB' b"
    "⋀ a. PB' a ⟹ PB a"
  shows "Simulation_Invariant A B sim PA PB'"
proof -
  interpret Simulation_Invariant A B sim PA PB
    by (rule assms)
  show ?thesis
    by (standard; blast intro: A_B_step assms)
qed
lemma Simulation_Invariants_strengthen_post:
  assumes
    "Simulation_Invariants A B sim PA QA PB QB"
    "⋀ a b. PA a ⟹ QA b ⟹ A a b ⟹ QA' b"
    "⋀ a. QA' a ⟹ QA a"
  shows "Simulation_Invariants A B sim PA QA' PB QB"
proof -
  interpret Simulation_Invariants A B sim PA QA PB QB
    by (rule assms)
  show ?thesis
    by (standard; blast intro: A_B_step assms)
qed
lemma Simulation_Invariants_strengthen_post':
  assumes
    "Simulation_Invariants A B sim PA QA PB QB"
    "⋀ a b. PB a ⟹ QB b ⟹ B a b ⟹ QB' b"
    "⋀ a. QB' a ⟹ QB a"
  shows "Simulation_Invariants A B sim PA QA PB QB'"
proof -
  interpret Simulation_Invariants A B sim PA QA PB QB
    by (rule assms)
  show ?thesis
    by (standard; blast intro: A_B_step assms)
qed
lemma Bisimulation_Invariant_sim_replace:
  assumes "Bisimulation_Invariant A B sim PA PB"
      and "⋀ a b. PA a ⟹ PB b ⟹ sim a b ⟷ sim' a b"
    shows "Bisimulation_Invariant A B sim' PA PB"
proof -
  interpret Bisimulation_Invariant A B sim PA PB
    by (rule assms(1))
  show ?thesis
    apply standard
    using assms(2) apply (blast dest: A_B_step)
    using assms(2) apply (blast dest: B_A_step)
    by blast+
qed
end