Theory Converter_Rewrite

theory Converter_Rewrite imports
  Converter
begin

section ‹Equivalence of converters restricted by interfaces›

coinductive eq_resource_on :: "'a set  ('a, 'b) resource  ('a, 'b) resource  bool" (‹_ R/ _ / _› [100, 99, 99] 99)
  for A where
    eq_resource_onI: "A R res  res'" if
    "a. a  A  rel_spmf (rel_prod (=) (eq_resource_on A)) (run_resource res a) (run_resource res' a)"

lemma eq_resource_on_coinduct [consumes 1, case_names eq_resource_on, coinduct pred: eq_resource_on]:
  assumes "X res res'"
    and "res res' a.  X res res'; a  A 
       rel_spmf (rel_prod (=) (λres res'. X res res'  A R res  res')) (run_resource res a) (run_resource res' a)"
  shows "A R res  res'"
  using assms(1) by(rule eq_resource_on.coinduct)(auto dest: assms(2))

lemma eq_resource_onD:
  assumes "A R res  res'" "a  A"
  shows "rel_spmf (rel_prod (=) (eq_resource_on A)) (run_resource res a) (run_resource res' a)"
  using assms by(auto elim: eq_resource_on.cases)

lemma eq_resource_on_refl [simp]: "A R res  res"
  by(coinduction arbitrary: res)(auto intro: rel_spmf_reflI)

lemma eq_resource_on_reflI: "res = res'  A R res  res'"
  by(simp add: eq_resource_on_refl)

lemma eq_resource_on_sym: "A R res  res'" if "A R res'  res"
  using that
  by(coinduction arbitrary: res res')
    (drule (1) eq_resource_onD, rewrite in "" conversep_iff[symmetric]
      , auto simp add: spmf_rel_conversep[symmetric] elim!: rel_spmf_mono)

lemma eq_resource_on_trans [trans]: "A R res  res''" if "A R res  res'" "A R res'  res''"
  using that by(coinduction arbitrary: res res' res'')
    ((drule (1) eq_resource_onD)+, drule (1) rel_spmf_OO_trans, auto elim!:rel_spmf_mono) 

lemma eq_resource_on_UNIV_D [simp]: "res = res'" if "UNIV R res  res'"
  using that by(coinduction arbitrary: res res')(auto dest: eq_resource_onD)

lemma eq_resource_on_UNIV_iff: "UNIV R res  res'  res = res'"
  by(auto dest: eq_resource_on_UNIV_D)

lemma eq_resource_on_mono: " A' R res  res'; A  A'   A R res  res'"
  by(coinduction arbitrary: res res')(auto dest: eq_resource_onD elim!: rel_spmf_mono)

lemma eq_resource_on_empty [simp]: "{} R res  res'"
  by(rule eq_resource_onI; simp)

lemma eq_resource_on_resource_of_oracleI:
  includes lifting_syntax 
  fixes S
  assumes sim: "(S ===> eq_on A ===> rel_spmf (rel_prod (=) S)) r1 r2"
    and S: "S s1 s2"
  shows "A R resource_of_oracle r1 s1  resource_of_oracle r2 s2"
  using S  by(coinduction arbitrary: s1 s2)
    (drule sim[THEN rel_funD, THEN rel_funD], simp add: eq_on_def
      , fastforce simp add: eq_on_def spmf_rel_map elim: rel_spmf_mono)

lemma exec_gpv_eq_resource_on:
  assumes "outs_ℐ  R res  res'"
    and " ⊢g gpv "
    and " ⊢res res "
  shows "rel_spmf (rel_prod (=) (eq_resource_on (outs_ℐ ))) (exec_gpv run_resource gpv res) (exec_gpv run_resource gpv res')"
  using assms
proof(induction arbitrary: res res' gpv rule: exec_gpv_fixp_induct)
  case (step exec_gpv')
  have[simp]: "(s, r1)  set_spmf (run_resource res g1); (s, r2)  set_spmf (run_resource res' g1);
    IO g1 g2  set_spmf (the_gpv gpv); outs_ℐ  R r1  r2  rel_spmf (rel_prod (=) (eq_resource_on (outs_ℐ ))) 
        (exec_gpv' (g2 s) r1) (exec_gpv' (g2 s) r2)" for g1 g2 r1 s r2
    by(rule step.IH, simp, rule WT_gpv_ContD[OF step.prems(2)], assumption)
      (auto elim: outs_gpv.IO WT_calleeD[OF run_resource.WT_callee, OF step.prems(3)]
        dest!: WT_resourceD[OF step.prems(3), rotated 1] intro: WT_gpv_outs_gpv[THEN subsetD, OF step.prems(2)])

  show ?case
    by(clarsimp intro!: rel_spmf_bind_reflI step.prems split!: generat.split)
      (rule rel_spmf_bindI', rule eq_resource_onD[OF step.prems(1)]
        , auto elim: outs_gpv.IO intro:  eq_resource_onD[OF step.prems(1)] WT_gpv_outs_gpv[THEN subsetD, OF step.prems(2)])
qed simp_all

inductive eq_ℐ_generat :: "('a  'b  bool)  ('out, 'in)   ('c  'd  bool)  ('a, 'out, 'in  'c) generat  ('b, 'out, 'in  'd) generat  bool"
  for A  D where
    Pure: "eq_ℐ_generat A  D (Pure x) (Pure y)" if "A x y"
  | IO: "eq_ℐ_generat A  D (IO out c) (IO out c')" if "out  outs_ℐ " "input. input  responses_ℐ  out  D (c input) (c' input)"

hide_fact (open) Pure IO

inductive_simps eq_ℐ_generat_simps [simp, code]:
  "eq_ℐ_generat A  D (Pure x) (Pure y)"
  "eq_ℐ_generat A  D (IO out c) (Pure y)"
  "eq_ℐ_generat A  D (Pure x) (IO out' c')"
  "eq_ℐ_generat A  D (IO out c) (IO out' c')"

inductive_simps eq_ℐ_generat_iff1:
  "eq_ℐ_generat A  D (Pure x) g'"
  "eq_ℐ_generat A  D (IO out c) g'"

inductive_simps eq_ℐ_generat_iff2:
  "eq_ℐ_generat A  D g (Pure x)"
  "eq_ℐ_generat A  D g (IO out c)"

lemma eq_ℐ_generat_mono':
  " eq_ℐ_generat A  D x y; x y. A x y  A' x y; x y. D x y  D' x y;   ℐ' 
   eq_ℐ_generat A' ℐ' D' x y"
  by(auto 4 4 elim!: eq_ℐ_generat.cases simp add: le_ℐ_def)

lemma eq_ℐ_generat_mono: "eq_ℐ_generat A  D  eq_ℐ_generat A' ℐ' D'" if "A  A'" "D  D'" "  ℐ'"
  using that by(auto elim!: eq_ℐ_generat_mono' dest: predicate2D)

lemma eq_ℐ_generat_mono'' [mono]:
  " x y. A x y  A' x y; x y. D x y  D' x y 
   eq_ℐ_generat A  D x y  eq_ℐ_generat A'  D' x y"
  by(auto elim: eq_ℐ_generat_mono')

lemma eq_ℐ_generat_conversep: "eq_ℐ_generat A¯¯  D¯¯ = (eq_ℐ_generat A  D)¯¯"
  by(fastforce elim: eq_ℐ_generat.cases)

lemma eq_ℐ_generat_reflI:
  assumes  "x. x  generat_pures generat  A x x"
    and "out c. generat = IO out c  out  outs_ℐ   (inputresponses_ℐ  out. D (c input) (c input))"
  shows "eq_ℐ_generat A  D generat generat"
  using assms by(cases generat) auto

lemma eq_ℐ_generat_relcompp:
  "eq_ℐ_generat A  D OO eq_ℐ_generat A'  D' = eq_ℐ_generat (A OO A')  (D OO D')"
  by(auto 4 3 intro!: ext elim!: eq_ℐ_generat.cases simp add: eq_ℐ_generat_iff1 eq_ℐ_generat_iff2 relcompp.simps) metis

lemma eq_ℐ_generat_map1:
  "eq_ℐ_generat A  D (map_generat f id ((∘) g) generat) generat' 
   eq_ℐ_generat (λx. A (f x))  (λx. D (g x)) generat generat'"
  by(cases generat; cases generat') auto

lemma eq_ℐ_generat_map2:
  "eq_ℐ_generat A  D generat (map_generat f id ((∘) g) generat') 
   eq_ℐ_generat (λx y. A x (f y))  (λx y. D x (g y)) generat generat'"
  by(cases generat; cases generat') auto

lemmas eq_ℐ_generat_map [simp] = 
  eq_ℐ_generat_map1[abs_def] eq_ℐ_generat_map2
  eq_ℐ_generat_map1[where g=id, unfolded fun.map_id0, abs_def] eq_ℐ_generat_map2[where g=id, unfolded fun.map_id0]

lemma eq_ℐ_generat_into_rel_generat:
  "eq_ℐ_generat A ℐ_full D generat generat'  rel_generat A (=) (rel_fun (=) D) generat generat'"
  by(erule eq_ℐ_generat.cases) auto

coinductive eq_ℐ_gpv :: "('a  'b  bool)  ('out, 'in)   ('a, 'out, 'in) gpv  ('b, 'out, 'in) gpv  bool"
  for A  where
    eq_ℐ_gpvI: "eq_ℐ_gpv A  gpv gpv'" 
  if "rel_spmf (eq_ℐ_generat A  (eq_ℐ_gpv A )) (the_gpv gpv) (the_gpv gpv')"

lemma eq_ℐ_gpv_coinduct [consumes 1, case_names eq_ℐ_gpv, coinduct pred: eq_ℐ_gpv]:
  assumes "X gpv gpv'"
    and "gpv gpv'. X gpv gpv'
       rel_spmf (eq_ℐ_generat A  (λgpv gpv'. X gpv gpv'  eq_ℐ_gpv A  gpv gpv')) (the_gpv gpv) (the_gpv gpv')"
  shows "eq_ℐ_gpv A  gpv gpv'"
  using assms(1) by(rule eq_ℐ_gpv.coinduct)(blast dest: assms(2))

lemma eq_ℐ_gpvD:
  "eq_ℐ_gpv A  gpv gpv'  rel_spmf (eq_ℐ_generat A  (eq_ℐ_gpv A )) (the_gpv gpv) (the_gpv gpv')"
  by(blast elim!: eq_ℐ_gpv.cases)

lemma eq_ℐ_gpv_Done [intro!]: "A x y  eq_ℐ_gpv A  (Done x) (Done y)"
  by(rule eq_ℐ_gpvI) simp

lemma eq_ℐ_gpv_Done_iff [simp]: "eq_ℐ_gpv A  (Done x) (Done y)  A x y"
  by(auto dest: eq_ℐ_gpvD)

lemma eq_ℐ_gpv_Pause:
  " out  outs_ℐ ; input. input  responses_ℐ  out  eq_ℐ_gpv A  (rpv input) (rpv' input) 
   eq_ℐ_gpv A  (Pause out rpv) (Pause out rpv')"
  by(rule eq_ℐ_gpvI) simp

lemma eq_ℐ_gpv_mono: "eq_ℐ_gpv A   eq_ℐ_gpv A' ℐ'" if A: "A  A'" "  ℐ'"
proof
  show "eq_ℐ_gpv A' ℐ' gpv gpv'" if "eq_ℐ_gpv A  gpv gpv'" for gpv gpv' using that
    by(coinduction arbitrary: gpv gpv')
      (drule eq_ℐ_gpvD, auto dest: eq_ℐ_gpvD elim: rel_spmf_mono eq_ℐ_generat_mono[OF A(1) _ A(2), THEN predicate2D, rotated -1])
qed

lemma eq_ℐ_gpv_mono':
  " eq_ℐ_gpv A  gpv gpv'; x y. A x y  A' x y;   ℐ'   eq_ℐ_gpv A' ℐ' gpv gpv'"
  by(blast intro: eq_ℐ_gpv_mono[THEN predicate2D])

lemma eq_ℐ_gpv_mono'' [mono]:
  "eq_ℐ_gpv A  gpv gpv'  eq_ℐ_gpv A'  gpv gpv'" if "x y. A x y  A' x y"
  using that by(blast intro: eq_ℐ_gpv_mono')

lemma eq_ℐ_gpv_conversep: "eq_ℐ_gpv A¯¯  = (eq_ℐ_gpv A )¯¯"
proof(intro ext iffI; simp)
  show "eq_ℐ_gpv A  gpv gpv'" if "eq_ℐ_gpv A¯¯  gpv' gpv" for A and gpv gpv' using that
    by(coinduction arbitrary: gpv gpv')
      (drule eq_ℐ_gpvD, rewrite in  conversep_iff[symmetric]
        , auto simp add: pmf.rel_conversep[symmetric] option.rel_conversep[symmetric] eq_ℐ_generat_conversep[symmetric] elim: eq_ℐ_generat_mono' rel_spmf_mono)

  from this[of "conversep A"] show "eq_ℐ_gpv A¯¯  gpv' gpv" if "eq_ℐ_gpv A  gpv gpv'" for gpv gpv'
    using that by simp
qed

lemma eq_ℐ_gpv_reflI:
  " x. x  results_gpv  gpv  A x x;  ⊢g gpv    eq_ℐ_gpv A  gpv gpv"
  by(coinduction arbitrary: gpv)(auto intro!: rel_spmf_reflI eq_ℐ_generat_reflI elim!: generat.set_cases intro: results_gpv.intros dest: WT_gpvD)

lemma eq_ℐ_gpv_into_rel_gpv: "eq_ℐ_gpv A ℐ_full gpv gpv'  rel_gpv A (=) gpv gpv'"
  by(coinduction arbitrary: gpv gpv')
    (drule eq_ℐ_gpvD, auto elim: spmf_rel_mono_strong generat.rel_mono_strong dest: eq_ℐ_generat_into_rel_generat )

lemma eq_ℐ_gpv_relcompp: "eq_ℐ_gpv (A OO A')  = eq_ℐ_gpv A  OO eq_ℐ_gpv A' " (is "?lhs = ?rhs")
proof(intro ext iffI relcomppI; (elim relcomppE)?)
  fix gpv gpv''
  assume *: "?lhs gpv gpv''"
  define middle where "middle = corec_gpv (λ(gpv, gpv'').
    map_spmf (map_generat (relcompp_witness A A') (relcompp_witness (=) (=)) ((∘) Inr  rel_witness_fun (=) (=))  
              rel_witness_generat)
    (rel_witness_spmf (eq_ℐ_generat (A OO A')  (eq_ℐ_gpv (A OO A') )) (the_gpv gpv, the_gpv gpv'')))"
  have middle_sel [simp]: "the_gpv (middle (gpv, gpv'')) = 
     map_spmf (map_generat (relcompp_witness A A') (relcompp_witness (=) (=)) ((∘) middle  rel_witness_fun (=) (=))  
              rel_witness_generat)
    (rel_witness_spmf (eq_ℐ_generat (A OO A')  (eq_ℐ_gpv (A OO A') )) (the_gpv gpv, the_gpv gpv''))"
    for gpv gpv'' by(auto simp add: middle_def spmf.map_comp o_def generat.map_comp)
  show "eq_ℐ_gpv A  gpv (middle (gpv, gpv''))" using *
    by(coinduction arbitrary: gpv gpv'')
      (drule eq_ℐ_gpvD, simp add: spmf_rel_map, erule rel_witness_spmf1[THEN rel_spmf_mono]
        , auto 4 3 del: relcomppE elim!: relcompp_witness eq_ℐ_generat.cases)

  show "eq_ℐ_gpv A'  (middle (gpv, gpv'')) gpv''" using *
    by(coinduction arbitrary: gpv gpv'')
      (drule eq_ℐ_gpvD, simp add: spmf_rel_map, erule rel_witness_spmf2[THEN rel_spmf_mono]
        , auto 4 3 del: relcomppE elim: rel_witness_spmf2[THEN rel_spmf_mono] elim!: relcompp_witness eq_ℐ_generat.cases)
next
  show "?lhs gpv gpv''" if "eq_ℐ_gpv A  gpv gpv'" and "eq_ℐ_gpv A'  gpv' gpv''" for gpv gpv' gpv'' using that
    by(coinduction arbitrary: gpv gpv' gpv'')
      ((drule eq_ℐ_gpvD)+, simp, drule (1) rel_spmf_OO_trans, erule rel_spmf_mono
        , auto simp add: eq_ℐ_generat_relcompp elim: eq_ℐ_generat_mono')
qed

lemma eq_ℐ_gpv_map_gpv1: "eq_ℐ_gpv A  (map_gpv f id gpv) gpv'  eq_ℐ_gpv (λx. A (f x))  gpv gpv'" (is "?lhs  ?rhs")
proof
  show ?rhs if ?lhs using that
    by(coinduction arbitrary: gpv gpv')
      (drule eq_ℐ_gpvD, auto simp add: gpv.map_sel spmf_rel_map elim!: rel_spmf_mono eq_ℐ_generat_mono')
  show ?lhs if ?rhs using that
    by(coinduction arbitrary: gpv gpv')
      (drule eq_ℐ_gpvD, auto simp add: gpv.map_sel spmf_rel_map elim!: rel_spmf_mono eq_ℐ_generat_mono')
qed

lemma eq_ℐ_gpv_map_gpv2: "eq_ℐ_gpv A  gpv (map_gpv f id gpv') = eq_ℐ_gpv (λx y. A x (f y))  gpv gpv'"
  using eq_ℐ_gpv_map_gpv1[of "conversep A"  f gpv' gpv]
  by(rewrite in "_ = " conversep_iff[symmetric] , simp add: eq_ℐ_gpv_conversep[symmetric])
    (subst (asm) eq_ℐ_gpv_conversep , simp add: conversep_iff[abs_def])

lemmas eq_ℐ_gpv_map_gpv [simp] = eq_ℐ_gpv_map_gpv1[abs_def] eq_ℐ_gpv_map_gpv2

lemma (in callee_invariant_on) eq_ℐ_exec_gpv:
  " eq_ℐ_gpv A  gpv gpv'; I s   rel_spmf (rel_prod A (eq_onp I)) (exec_gpv callee gpv s) (exec_gpv callee gpv' s)"
proof(induction arbitrary: s gpv gpv' rule: parallel_fixp_induct_2_2[OF partial_function_definitions_spmf partial_function_definitions_spmf exec_gpv.mono exec_gpv.mono exec_gpv_def exec_gpv_def, unfolded lub_spmf_empty, case_names adm bottom step])
  case adm show ?case by simp
  case bottom show ?case by simp
  case (step exec_gpv' exec_gpv'')
  show ?case using step.prems
    by - (drule eq_ℐ_gpvD, erule rel_spmf_bindI
        , auto split!: generat.split simp add: eq_onp_same_args 
        intro: WT_callee[THEN WT_calleeD] callee_invariant step.IH intro!: rel_spmf_bind_reflI)
qed

lemma eq_ℐ_gpv_coinduct_bind [consumes 1, case_names eq_ℐ_gpv]:
  fixes gpv :: "('a, 'out, 'in) gpv" and gpv' :: "('a', 'out, 'in) gpv"
  assumes X: "X gpv gpv'"
    and step: "gpv gpv'. X gpv gpv'
       rel_spmf (eq_ℐ_generat A  (λgpv gpv'. X gpv gpv'  eq_ℐ_gpv A  gpv gpv'  
      (gpv'' gpv''' (B :: 'b  'b'  bool) f g. gpv = bind_gpv gpv'' f  gpv' = bind_gpv gpv''' g  eq_ℐ_gpv B  gpv'' gpv'''  (rel_fun B X) f g))) (the_gpv gpv) (the_gpv gpv')"
  shows "eq_ℐ_gpv A  gpv gpv'"
proof -
  fix x y
  define gpv'' :: "('b, 'out, 'in) gpv" where "gpv''  Done x"
  define f :: "'b  ('a, 'out, 'in) gpv" where "f  λ_. gpv"
  define gpv''' :: "('b', 'out, 'in) gpv" where "gpv'''  Done y"
  define g :: "'b'  ('a', 'out, 'in) gpv" where "g  λ_. gpv'"
  have "eq_ℐ_gpv (λx y. X (f x) (g y))  gpv'' gpv'''" using X
    by(simp add: f_def g_def gpv''_def gpv'''_def)
  then have "eq_ℐ_gpv A  (bind_gpv gpv'' f) (bind_gpv gpv''' g)"
    by(coinduction arbitrary: gpv'' f gpv''' g)
      (drule eq_ℐ_gpvD, (clarsimp simp add: bind_gpv.sel spmf_rel_map simp del: bind_gpv_sel' elim!: rel_spmf_bindI split!: generat.split dest!: step)
        , erule rel_spmf_mono, (erule eq_ℐ_generat.cases; clarsimp), (erule meta_allE, erule (1) meta_impE)
        , (fastforce | force intro: exI[where x="Done _"] elim!: eq_ℐ_gpv_mono' dest: rel_funD)+)

  then show ?thesis unfolding gpv''_def gpv'''_def f_def g_def by simp
qed

context
  fixes S :: "'s1  's2  bool"
    and callee1 :: "'s1  'out  ('in × 's1, 'out', 'in') gpv"
    and callee2 :: "'s2  'out  ('in × 's2, 'out', 'in') gpv"
    and  :: "('out, 'in) "
    and ℐ' :: "('out', 'in') "
  assumes callee: "s1 s2 q.  S s1 s2; q  outs_ℐ    eq_ℐ_gpv (rel_prod (eq_onp (λr. r  responses_ℐ  q)) S) ℐ' (callee1 s1 q) (callee2 s2 q)"
begin

lemma eq_ℐ_gpv_inline1:
  includes lifting_syntax
  assumes "S s1 s2" "eq_ℐ_gpv A  gpv1 gpv2"
  shows "rel_spmf (rel_sum (rel_prod A S) 
      (λ(q, rpv1, rpv2) (q', rpv1', rpv2'). q = q'  q'  outs_ℐ ℐ'  (q''  outs_ℐ . 
          (r  responses_ℐ ℐ' q'. eq_ℐ_gpv (rel_prod (eq_onp (λr'. r'  responses_ℐ  q'')) S) ℐ' (rpv1 r) (rpv1' r))  
          (r'  responses_ℐ  q''. eq_ℐ_gpv A  (rpv2 r') (rpv2' r')))))
     (inline1 callee1 gpv1 s1) (inline1 callee2 gpv2 s2)"
  using assms
proof(induction arbitrary: gpv1 gpv2 s1 s2 rule: parallel_fixp_induct_2_2[OF partial_function_definitions_spmf partial_function_definitions_spmf inline1.mono inline1.mono inline1_def inline1_def, unfolded lub_spmf_empty, case_names adm bottom step])
  case adm show ?case by simp
  case bottom show ?case by simp
  case (step inline1' inline1'')
  from step.prems show ?case
    by - (erule eq_ℐ_gpvD[THEN rel_spmf_bindI]
        , clarsimp split!: generat.split
        , erule eq_ℐ_gpvD[OF callee(1), THEN rel_spmf_bindI]
        , auto simp add: eq_onp_def intro: step.IH[THEN rel_spmf_mono] elim: eq_ℐ_gpvD[OF callee(1), THEN rel_spmf_bindI] split!: generat.split)
qed

lemma eq_ℐ_gpv_inline:
  assumes S: "S s1 s2"
    and gpv: "eq_ℐ_gpv A  gpv1 gpv2"
  shows "eq_ℐ_gpv (rel_prod A S) ℐ' (inline callee1 gpv1 s1) (inline callee2 gpv2 s2)"
  using S gpv
  by (coinduction arbitrary: gpv1 gpv2 s1 s2 rule: eq_ℐ_gpv_coinduct_bind)
    (clarsimp simp add: inline_sel spmf_rel_map, drule (1) eq_ℐ_gpv_inline1
      , fastforce split!: generat.split sum.split del: disjCI intro!: disjI2 rel_funI elim: rel_spmf_mono simp add: eq_onp_def)

end

lemma eq_ℐ_gpv_left_gpv_cong:
  "eq_ℐ_gpv A  gpv gpv'  eq_ℐ_gpv A (  ℐ') (left_gpv gpv) (left_gpv gpv')"
  by(coinduction arbitrary: gpv gpv')
    (drule eq_ℐ_gpvD, auto 4 4 simp add: spmf_rel_map elim!: rel_spmf_mono eq_ℐ_generat.cases)

lemma eq_ℐ_gpv_right_gpv_cong:
  "eq_ℐ_gpv A ℐ' gpv gpv'  eq_ℐ_gpv A (  ℐ') (right_gpv gpv) (right_gpv gpv')"
  by(coinduction arbitrary: gpv gpv')
    (drule eq_ℐ_gpvD, auto 4 4 simp add: spmf_rel_map elim!: rel_spmf_mono eq_ℐ_generat.cases)

lemma eq_ℐ_gpvD_WT1: " eq_ℐ_gpv A  gpv gpv';  ⊢g gpv     ⊢g gpv' "
  by(coinduction arbitrary: gpv gpv')(fastforce simp add: eq_ℐ_generat_iff2 dest: WT_gpv_ContD eq_ℐ_gpvD dest!: rel_setD2 set_spmf_parametric[THEN rel_funD])

lemma eq_ℐ_gpvD_results_gpv2: 
  assumes "eq_ℐ_gpv A  gpv gpv'" "y  results_gpv  gpv'"
  shows "x  results_gpv  gpv. A x y"
  using assms(2,1)
  by(induction arbitrary: gpv)
    (fastforce dest!: set_spmf_parametric[THEN rel_funD] rel_setD2 dest: eq_ℐ_gpvD simp add: eq_ℐ_generat_iff2 intro: results_gpv.intros)+


coinductive eq_ℐ_converter :: "('a, 'b)   ('out, 'in)   ('a, 'b, 'out, 'in) converter  ('a, 'b, 'out, 'in) converter  bool"
  (‹_,_ C/ _ / _› [100, 0, 99, 99] 99)
  for  ℐ' where
    eq_ℐ_converterI: ", ℐ' C conv  conv'" if
    "q. q  outs_ℐ   eq_ℐ_gpv (rel_prod (eq_onp (λr. r  responses_ℐ  q)) (eq_ℐ_converter  ℐ')) ℐ' (run_converter conv q) (run_converter conv' q)"

lemma eq_ℐ_converter_coinduct [consumes 1, case_names eq_ℐ_converter, coinduct pred: eq_ℐ_converter]:
  assumes "X conv conv'"
    and "conv conv' q.  X conv conv'; q  outs_ℐ  
      eq_ℐ_gpv (rel_prod (eq_onp (λr. r  responses_ℐ  q)) (λconv conv'. X conv conv'  , ℐ' C conv  conv')) ℐ'
           (run_converter conv q) (run_converter conv' q)"
  shows ", ℐ' C conv  conv'"
  using assms(1) by(rule eq_ℐ_converter.coinduct)(blast dest: assms(2))

lemma eq_ℐ_converterD: 
  "eq_ℐ_gpv (rel_prod (eq_onp (λr. r  responses_ℐ  q)) (eq_ℐ_converter  ℐ')) ℐ' (run_converter conv q) (run_converter conv' q)"
  if ", ℐ' C conv  conv'" "q  outs_ℐ "
  using that by(blast elim: eq_ℐ_converter.cases)

lemma eq_ℐ_converter_reflI: ", ℐ' C conv  conv" if ", ℐ' C conv "
  using that by(coinduction arbitrary: conv)(auto intro!: eq_ℐ_gpv_reflI dest: WT_converterD simp add: eq_onp_same_args)

lemma eq_ℐ_converter_sym [sym]: ", ℐ' C conv  conv'" if ", ℐ' C conv'  conv"
  using that
  by(coinduction arbitrary: conv conv')
    (drule (1) eq_ℐ_converterD, rewrite in  conversep_iff[symmetric]
      ,  auto simp add: eq_ℐ_gpv_conversep[symmetric] eq_onp_def elim: eq_ℐ_gpv_mono')

lemma eq_ℐ_converter_trans [trans]:
  " , ℐ' C conv  conv'; , ℐ' C conv'  conv''   , ℐ' C conv  conv''"
  by(coinduction arbitrary: conv conv' conv'')
    ((drule (1) eq_ℐ_converterD)+, drule (1) eq_ℐ_gpv_relcompp[THEN fun_cong, THEN fun_cong, THEN iffD2, OF relcomppI]
      , auto simp add: eq_OO prod.rel_compp[symmetric] eq_onp_def elim!: eq_ℐ_gpv_mono')

lemma eq_ℐ_converter_mono:
  assumes *: "ℐ1, ℐ2 C conv  conv'"
    and le: "ℐ1'  ℐ1" "ℐ2  ℐ2'"
  shows "ℐ1', ℐ2' C conv  conv'"
  using *
  by(coinduction arbitrary: conv conv')
    (auto simp add: eq_onp_def dest!:eq_ℐ_converterD  intro: responses_ℐ_mono[THEN subsetD, OF le(1)] 
      elim!: eq_ℐ_gpv_mono'[OF _ _ le(2)] outs_ℐ_mono[THEN subsetD, OF le(1)])

lemma eq_ℐ_converter_eq: "conv1 = conv2" if "ℐ_full, ℐ_full C conv1  conv2"
  using that
  by(coinduction arbitrary: conv1 conv2)
    (auto simp add: eq_ℐ_gpv_into_rel_gpv eq_onp_def intro!: rel_funI elim!: gpv.rel_mono_strong eq_ℐ_gpv_into_rel_gpv dest:eq_ℐ_converterD)

lemma eq_ℐ_attach_on: (* TODO: generalise to eq_resource_on *)
  assumes "ℐ' ⊢res res " "ℐ_uniform A UNIV, ℐ' C conv  conv'"
  shows "A R attach conv res  attach conv' res"
  using assms
  by(coinduction arbitrary: conv conv' res)
    (auto 4 4 simp add: eq_onp_def spmf_rel_map dest: eq_ℐ_converterD intro!: rel_funI run_resource.eq_ℐ_exec_gpv[THEN rel_spmf_mono])

lemma eq_ℐ_attach_on':
  assumes "ℐ' ⊢res res " ", ℐ' C conv  conv'" "A  outs_ℐ "
  shows "A R attach conv res  attach conv' res"
  using assms(1) assms(2)[THEN eq_ℐ_converter_mono]
  by(rule eq_ℐ_attach_on)(use assms(3) in auto simp add: le_ℐ_def)

lemma eq_ℐ_attach:
  " ℐ' ⊢res res ; ℐ_full, ℐ' C conv  conv'   attach conv res = attach conv' res"
  by(rule eq_resource_on_UNIV_D)(simp add: eq_ℐ_attach_on)

lemma eq_ℐ_comp_cong:
  " ℐ1, ℐ2 C conv1  conv1'; ℐ2, ℐ3 C conv2  conv2' 
   ℐ1, ℐ3 C comp_converter conv1 conv2  comp_converter conv1' conv2'"
  by(coinduction arbitrary: conv1 conv2 conv1' conv2')
    (clarsimp, rule eq_ℐ_gpv_mono'[OF eq_ℐ_gpv_inline[where S="eq_ℐ_converter ℐ2 ℐ3"]]
      , (fastforce elim!: eq_ℐ_converterD)+)

lemma comp_converter_cong: "comp_converter conv1 conv2 = comp_converter conv1' conv2'"
  if "ℐ_full,  C conv1  conv1'" ", ℐ_full C conv2  conv2'"
  by(rule eq_ℐ_converter_eq)(rule eq_ℐ_comp_cong[OF that])

lemma parallel_converter2_id_id: 
  "ℐ1  ℐ2, ℐ1  ℐ2 C parallel_converter2 id_converter id_converter  id_converter"
  by(coinduction)(auto split: sum.split intro!: eq_ℐ_gpv_Pause simp add: eq_onp_same_args)

lemma parallel_converter2_eq_ℐ_cong:
  " ℐ1, ℐ1' C conv1  conv1'; ℐ2, ℐ2' C conv2  conv2' 
   ℐ1  ℐ2, ℐ1'  ℐ2' C parallel_converter2 conv1 conv2  parallel_converter2 conv1' conv2'"
  by(coinduction arbitrary: conv1 conv2 conv1' conv2')
    (fastforce intro!: eq_ℐ_gpv_left_gpv_cong eq_ℐ_gpv_right_gpv_cong dest: eq_ℐ_converterD elim!: eq_ℐ_gpv_mono' simp add: eq_onp_def)

lemma id_converter_eq_self: ",ℐ' C id_converter  id_converter" if "  ℐ'"
  by(rule eq_ℐ_converter_mono[OF _ order_refl that])(rule eq_ℐ_converter_reflI[OF WT_converter_id])

lemma eq_ℐ_converterD_WT1:
  assumes ",ℐ' C conv1  conv2" and ",ℐ' C conv1 "
  shows ",ℐ' C conv2 "
  using assms
  by(coinduction arbitrary: conv1 conv2)
    (drule (1) eq_ℐ_converterD, auto 4 3 dest: eq_ℐ_converterD eq_ℐ_gpvD_WT1 WT_converterD_WT WT_converterD_results eq_ℐ_gpvD_results_gpv2 simp add: eq_onp_def)

lemma eq_ℐ_converterD_WT:
  assumes ",ℐ' C conv1  conv2"
  shows ",ℐ' C conv1   ,ℐ' C conv2 "
proof(rule iffI, goal_cases)
  case 1 then show ?case using assms by (auto intro: eq_ℐ_converterD_WT1) 
next
  case 2 then show ?case using assms[symmetric] by (auto intro: eq_ℐ_converterD_WT1)
qed

lemma eq_ℐ_gpv_Fail [simp]: "eq_ℐ_gpv A  Fail Fail"
  by(rule eq_ℐ_gpv.intros) simp

lemma eq_ℐ_restrict_gpv:
  assumes "eq_ℐ_gpv A  gpv gpv'"
  shows "eq_ℐ_gpv A  (restrict_gpv  gpv) gpv'"
  using assms
  by(coinduction arbitrary: gpv gpv')
    (fastforce dest: eq_ℐ_gpvD simp add: spmf_rel_map pmf.rel_map option_rel_Some1 eq_ℐ_generat_iff1 elim!: pmf.rel_mono_strong eq_ℐ_generat_mono' split: option.split generat.split)

lemma eq_ℐ_restrict_converter:
  assumes ",ℐ' C cnv "
    and "outs_ℐ   A"
  shows ",ℐ' C restrict_converter A ℐ' cnv  cnv"
  using assms(1)
  by(coinduction arbitrary: cnv)
    (use assms(2) in auto intro!: eq_ℐ_gpv_reflI eq_ℐ_restrict_gpv simp add: eq_onp_def dest: WT_converterD)

lemma eq_ℐ_restrict_gpv_full:
  "eq_ℐ_gpv A ℐ_full (restrict_gpv  gpv) (restrict_gpv  gpv')"
  if "eq_ℐ_gpv A  gpv gpv'"
  using that
  by(coinduction arbitrary: gpv gpv')
    (fastforce dest: eq_ℐ_gpvD simp add: pmf.rel_map in_set_spmf[symmetric] elim!: pmf.rel_mono_strong split!: option.split generat.split)

lemma eq_ℐ_restrict_converter_cong:
  assumes ",ℐ' C cnv  cnv'"
    and "A  outs_ℐ "
  shows "restrict_converter A ℐ' cnv = restrict_converter A ℐ' cnv'"
  using assms
  by(coinduction arbitrary: cnv cnv')
    (fastforce intro: eq_ℐ_gpv_into_rel_gpv eq_ℐ_restrict_gpv_full elim!: eq_ℐ_gpv_mono' simp add: eq_onp_def rel_fun_def gpv.rel_map dest: eq_ℐ_converterD)

end