Theory Assume_Guarantee

(*<*)
theory Assume_Guarantee
imports
  "ConcurrentHOL.ConcurrentHOL"
begin

(*>*)
section‹ Assume/Guarantee rule sets\label{sec:ag} ›

text‹

The rules in theoryConcurrentHOL.Refinement are deficient in various ways:
  redundant stability requirements
  interleaving of program decomposition with stability goals
  insufficiently instantiated

The following are some experimental rules aimed at practical assume/guarantee reasoning.

›


subsection‹ Implicit stabilisation\label{sec:ag-implicit_stabilisation} ›

text‹

We can define a relation ceilr P› to be the largest (weakest assumption) for which P› is stable.
This always yields a preorder (i.e., it is reflexive and transitive). Later we use this to inline
stability side conditions into assume/guarantee rules (\S\ref{sec:ag-iag}).

This relation is not very pleasant to work with: it is not monotonic
and does not have many useful algebraic properties. However it
suffices to defer the checking of assumes (see \S\ref{sec:ag-iag}).

This is a cognate of the ‹strongest guarantee› used by
citet‹Definition~8.31› in "deRoeverEtAl:2001" in their completeness proof for the rely-guarantee
method.

›

definition ceilr :: "'a pred  'a rel" where
  "ceilr P = {r. stable r P}"

lemma ceilr_alt_def:
  shows "ceilr P = {(s, s'). P s  P s'}"
by (auto simp: ceilr_def stable_def monotone_def le_bool_def order_eq_iff Sup_upper)

lemma ceilrE[elim]:
  assumes "(x, y)  ceilr P"
  assumes "P x"
  shows "P y"
using assms by (simp add: ceilr_alt_def)

setup Sign.mandatory_path "ceilr"

named_theorems simps ‹simp rules for const‹ceilr››

lemma bot[ceilr.simps]:
  shows "ceilr  = UNIV"
by (simp_all add: ceilr_alt_def)

lemma top[ceilr.simps]:
  shows "ceilr  = UNIV"
by (simp_all add: ceilr_alt_def)

lemma const[ceilr.simps]:
  shows "ceilr c = UNIV"
    and "ceilr (P  c) = (if c then ceilr P else UNIV)"
    and "ceilr (c  P) = (if c then ceilr P else UNIV)"
    and "ceilr (P  c  P') = (if c then ceilr (P  P') else UNIV)"
by (simp_all add: ceilr_alt_def)

lemma Id_le:
  shows "Id  ceilr P"
by (auto simp: ceilr_alt_def)

lemmas refl[iff] = ceilr.Id_le[folded refl_alt_def]

lemma trans[iff]:
  shows "trans (ceilr P)"
by (simp add: ceilr_alt_def trans_def)

lemma stable[stable.intro]:
  shows "stable (ceilr P) P"
by (simp add: ceilr_def stable.Union_conv)

lemma largest[stable.intro]:
  assumes "stable r P"
  shows "r  ceilr P"
by (simp add: ceilr_def assms Sup_upper)

lemma disj_subseteq: ―‹ Converse does not hold ›
  shows "ceilr (P  Q)  ceilr P  ceilr Q"
by (fastforce simp: ceilr_alt_def)

lemma Ex_subseteq: ―‹ Converse does not hold ›
  shows "ceilr (x. P x)  (x. ceilr (P x))"
by (fastforce simp: ceilr_alt_def)

lemma conj_subseteq: ―‹ Converse does not hold ›
  shows "ceilr P  ceilr Q  ceilr (P  Q)"
by (fastforce simp: ceilr_alt_def)

lemma All_subseteq: ―‹ Converse does not hold ›
  shows "(x. ceilr (P x))  ceilr (x. P x)"
by (fastforce simp: ceilr_alt_def)

lemma const_implies[ceilr.simps]:
  shows "ceilr (P  Q) = (if P then ceilr Q else UNIV)"
by (simp add: ceilr.simps)

lemma Id_proj_on:
  shows "(c. ceilr (c = f)) = Id⇘f⇙"
    and "(c. ceilr (f = c)) = Id⇘f⇙"
by (fastforce simp: ceilr_alt_def)+

setup Sign.parent_path

setup Sign.mandatory_path "stable"

lemma Inter_ceilr:
  shows "stable (v. ceilr (Q v)) (Q v)"
by (rule antimonoD[where y="ceilr (Q v)", OF stable.antimono_rel, unfolded le_bool_def, rule_format, rotated])
   (auto simp: ceilr.stable)

setup Sign.parent_path

text‹

We can internalize the stability conditions; see \S\ref{sec:ag-iag} for further discussion.

›

setup Sign.mandatory_path "ag.prog"

lemma p2s_s2p_ag_ceilr:
  shows "prog.p2s (prog.s2p (P⦄, ceilr P  (v. ceilr (Q v))  G, Q))
       = P⦄, ceilr P  (v. ceilr (Q v))  G, Q"
by (simp add: ag.prog.p2s_s2p_ag_stable ceilr.stable stable.Inter_ceilr stable.infI)

setup Sign.parent_path


subsubsection‹ Assume/guarantee rules using implicit stability\label{sec:ag-iag} ›

text‹

We use constceilr to incorporate stability side conditions directly into the assume/guarantee
rules. In other words, instead of working with arbitrary relations, we work with the largest
(most general) ‹assume› that makes the relevant predicates conststable.

In practice this allows us to defer all stability obligations to the end of a proof, which may be
in any convenient context (typically a function). This approach could be considered a semantic
version of how citet"ZakowskiEtAl:2019" split sequential and assume/guarantee reasoning.
See citet‹\S4› in "Vafeiadis:2008" for a discussion on when to check stability.

We defer the ‹guarantee› proofs by incorporating them into preconditions. This also allows
control flow context to be accumulated.

These are backchaining (``weakest precondition'') rules: the guarantee and post condition
need to be instantiated and the rules instantiate assume and pre condition schematics.

Note that the rule for constprog.bind duplicates stability goals.

See \S\ref{sec:ex_findP} for an example of using these rules.

›

setup Sign.mandatory_path "iag"

named_theorems intro ‹safe backchaining intro rules›

lemma init:
  assumes "c  P⦄, A  G, Q"
  assumes "s. P' s  P s"
  assumes "A'  A" ―‹ these rules use constceilr which always yields a reflexive relation (@{thm [source] "ceilr.refl"}
  shows "c  P'⦄, A'  G, Q"
using assms(2,3) by (auto intro: ag.mono order.trans[OF assms(1)])

lemmas mono = ag.mono

lemmas gen_asm = ag.gen_asm

lemmas pre = ag.pre
lemmas pre_pre = ag.pre_pre
lemmas pre_post = ag.pre_post
lemmas pre_ag = ag.pre_ag
lemmas pre_a = ag.pre_a
lemmas pre_g = ag.pre_g

lemmas post_imp = ag.post_imp

lemmas conj_lift = ag.conj_lift
lemmas disj_lift = ag.disj_lift
lemmas all_lift = ag.all_lift

lemmas augment_a = ag.augment_a
lemmas augment_post = ag.augment_post
lemmas augment_post_imp = ag.augment_post_imp

lemmas stable_augment_base = ag.stable_augment_base
lemmas stable_augment = ag.stable_augment
lemmas stable_augment_post = ag.stable_augment_post
lemmas stable_augment_frame = ag.stable_augment_frame

lemma bind[iag.intro]:
  assumes "v. prog.p2s (g v)  Q' v⦄, A2 v  G, Q"
  assumes "prog.p2s f  P⦄, A1  G, Q'"
  shows "prog.p2s (f  g)  P⦄, A1  (v. A2 v)  G, Q"
by (auto simp: prog.p2s.simps intro: assms ag.spec.bind ag.pre)

lemmas rev_bind = iag.bind[rotated]

lemma read[iag.intro]:
  shows "prog.p2s (prog.read F)  λs. Q (F s) s⦄, ceilr (λs. Q (F s) s)  (s. ceilr (Q (F s)))  G, Q"
by (rule ag.pre[OF ag.prog.action[where P="λs. Q (F s) s"
                                    and Q=Q
                                    and A="ceilr (λs. Q (F s) s)  (s. ceilr (Q (F s)))"
                                    and G=G]];
    fastforce intro: stable.intro stable.Inter_ceilr stable.infI)

lemma return[iag.intro]:
  shows "prog.p2s (prog.return v)  Q v⦄, ceilr (Q v)  G, Q"
unfolding prog.return_alt_def by (rule iag.init[OF iag.read]; fastforce)

lemma "write"[iag.intro]: ―‹ this is where ‹guarantee› obligations arise ›
  shows "prog.p2s (prog.write F)
       (λs. Q () (F s)  (s, F s)  G)⦄, ceilr (λs. Q () (F s)  (s, F s)  G)  ceilr (Q ())  G, Q"
by (rule ag.prog.action; fastforce intro: stable.intro stable.Inter_ceilr stable.infI1 stable.infI2)

lemma parallel: ―‹ not in the iag› format; instantiate the first two assumptions ›
  assumes "prog.p2s c1  P1⦄, A1  G1, Q1"
  assumes "prog.p2s c2  P2⦄, A2  G2, Q2"
  assumes "s. Q1 () s; Q2 () s  Q () s"
  assumes "G2  A1"
  assumes "G1  A2"
  assumes "G1  G2  G"
  shows "prog.p2s (prog.parallel c1 c2)  P1  P2⦄, A1  A2  G, Q"
by (rule order.trans[OF ag.prog.parallel[OF iag.pre_a[OF assms(1)] iag.pre_a[OF assms(2)],
                                         where A="A1  A2", simplified, OF G2  A1 G1  A2]];
    use assms(3,6) in force intro: iag.mono)

lemmas local = ag.prog.local ―‹ not in the iag› format ›

lemma "if"[iag.intro]:
  assumes "b  prog.p2s c1  P1⦄, A1  G, Q"
  assumes "¬b  prog.p2s c2  P2⦄, A2  G, Q"
  shows "prog.p2s (if b then c1 else c2)  if b then P1 else P2⦄, A1  A2  G, Q"
using assms by (fastforce intro: ag.pre_ag)

lemma case_option[iag.intro]:
  assumes "x = None  prog.p2s none  Pn⦄, An  G, Q"
  assumes "v. x = Some v  prog.p2s (some v)  Ps v⦄, As v  G, Q"
  shows "prog.p2s (case_option none some x)  case x of None  Pn | Some v  Ps v⦄, case_option An As x  G, Q"
using assms by (fastforce intro: ag.pre_ag split: option.split)

lemma case_sum[iag.intro]:
  assumes "v. x = Inl v  prog.p2s (left v)  Pl v⦄, Al v  G, Q"
  assumes "v. x = Inr v  prog.p2s (right v)  Pr v⦄, Ar v  G, Q"
  shows "prog.p2s (case_sum left right x)  case_sum Pl Pr x⦄, case_sum Al Ar x  G, Q"
using assms by (fastforce intro: ag.pre_ag split: sum.split)

lemma case_list[iag.intro]:
  assumes "x = []  prog.p2s nil  Pn⦄, An  G, Q"
  assumes "v vs. x = v # vs  prog.p2s (cons v vs)  Pc v vs⦄, Ac v vs  G, Q"
  shows "prog.p2s (case_list nil cons x)  case_list Pn Pc x⦄, case_list An Ac x  G, Q"
using assms by (fastforce intro: ag.pre_ag split: list.split)

lemma while:
  fixes c :: "'k  ('s, 'k + 'v) prog"
  assumes c: "k. prog.p2s (c k)  P k⦄, A  G, case_sum I Q"
  shows "prog.p2s (prog.while c k)  v s. I v s  P v s  I k⦄, A  (v. ceilr (Q v))  G, Q"
by (rule iag.gen_asm)
   (rule ag.prog.while[OF ag.pre_a[OF c]]; blast intro: stable.Inter_ceilr stable.infI2)

lemmas whenM = iag.if[where c1=c and A1=A and P1=P, OF _ iag.return[where v="()"]] for A c P

setup Sign.parent_path


subsection‹ Refinement with relational assumes\label{sec:ag-rar} ›

text‹

Two sets of refinement rules:
  relational assumes
  relational assumes and prog.sinvmap› (inverse state abstraction)

›

setup Sign.mandatory_path "rar.prog"

lemma bind:
  assumes "v. prog.p2s (g v)  Q' v⦄, ag.assm A  prog.p2s (g' v), Q"
  assumes "prog.p2s f  P⦄, ag.assm A  prog.p2s f', Q'"
  shows "prog.p2s (f  g)  P⦄, ag.assm A  prog.p2s (f'  g'), Q"
by (rule refinement.prog.bind[OF refinement.pre_a[OF assms(1) refinement.spec.bind.res.rel_le[OF order.refl]]])
   (simp add: spec.term.all.rel assms(2))

lemmas rev_bind = rar.prog.bind[rotated]

lemma action:
  fixes F :: "('v × 's × 's) set"
  fixes F' :: "('v × 's × 's) set"
  assumes Q: "v s s'. P s; (v, s, s')  F  Q v s'"
  assumes F': "v s s'. P s; (v, s, s')  F  (v, s, s')  F'"
  assumes sP: "stable A P"
  assumes sQ: "v s s'. P s; (v, s, s')  F  stable A (Q v)"
  shows "prog.p2s (prog.action F)  P⦄, ag.assm A  prog.p2s (prog.action F'), Q"
by (rule refinement.prog.action)
   (use assms in simp_all add: spec.steps.rel stable_def monotone_def)

lemma return:
  assumes sQ: "stable A (Q v)"
  shows "prog.p2s (prog.return v)  Q v⦄, ag.assm A  prog.p2s (prog.return v), Q"
using assms by (simp add: refinement.prog.return spec.steps.rel stable_def monotone_def)

lemma parallel_refinement:
  assumes c1: "prog.p2s c1  P1⦄, ag.assm (A  G2)  prog.p2s (c1'  prog.rel G1), Q1"
  assumes c2: "prog.p2s c2  P2⦄, ag.assm (A  G1)  prog.p2s (c2'  prog.rel G2), Q2"
  shows "prog.p2s (c1  c2)  P1  P2⦄, ag.assm A  prog.p2s (c1'  prog.rel G1  c2'  prog.rel G2), λv. Q1 v  Q2 v"
apply (rule refinement.prog.parallel[OF refinement.pre_a[OF c1] refinement.pre_a[OF c2]])
 apply (rule order.trans[OF refinement.spec.env_hyp.mono[OF order.refl] ag.spec.Parallel_assm[where a=True and as=UNIV and G="λa. if a then G1 else G2", simplified]];
        simp add: ac_simps prog.p2s.simps; fail)
apply (rule order.trans[OF refinement.spec.env_hyp.mono[OF order.refl] ag.spec.Parallel_assm[where a=False and as=UNIV and G="λa. if a then G1 else G2", simplified]];
       simp add: ac_simps prog.p2s.simps; fail)
done

lemma parallel:
  assumes "prog.p2s c1  P1⦄, ag.assm (A  G2)  prog.p2s c1', Q1"
  assumes "prog.p2s c1  P1⦄, A  G2  G1, "
  assumes "prog.p2s c2  P2⦄, ag.assm (A  G1)  prog.p2s c2', Q2"
  assumes "prog.p2s c2  P2⦄, A  G1  G2, "
  shows "prog.p2s (c1  c2)  P1  P2⦄, ag.assm A  prog.p2s (c1'  c2'), λv. Q1 v  Q2 v"
by (rule order.trans[OF rar.prog.parallel_refinement
                        refinement.mono[OF order.refl order.refl prog.p2s.mono[OF prog.parallel.mono[OF inf.cobounded1 inf.cobounded1]] order.refl]])
   (force simp: prog.p2s.simps refinement.infR_conv[where Q2=, simplified]
         simp flip: ag.refinement
             intro: assms)+

lemma while:
  fixes c :: "'k  ('s, 'k + 'v) prog"
  fixes c' :: "'k  ('s, 'k + 'v) prog"
  assumes c: "k. prog.p2s (c k)  P k⦄, ag.assm A  prog.p2s (c' k), case_sum I Q"
  assumes IP: "s v. I v s  P v s"
  assumes sQ: "v. stable A (Q v)"
  shows "prog.p2s (prog.while c k)  I k⦄, ag.assm A  prog.p2s (prog.while c' k), Q"
proof -
  have "k. prog.p2s (prog.while c k)  P k⦄, ag.assm A  prog.p2s (prog.while c' k), Q"
  proof(induct rule: prog.while.fixp_induct[where P="λR. k. prog.p2s (R c k)  P k⦄, ag.assm A  prog.p2s (prog.while c' k), Q", case_names adm bot step])
    case (step R) from sQ show ?case
      apply (subst prog.while.simps)
      apply (intro allI rar.prog.rev_bind[OF c]
                   refinement.pre_pre[OF refinement.prog.case_sum[OF step[rule_format]
                                                                     rar.prog.return[OF sQ]]])
      apply (simp add: IP split: sum.splits)
      done
  qed simp_all
  then show ?thesis
    by (meson IP refinement.pre_pre)
qed

lemma app:
  fixes xs :: "'a list"
  fixes f :: "'a  ('s, unit) prog"
  fixes P :: "'a list  's pred"
  assumes "x ys zs. xs = ys @ x # zs  prog.p2s (f x)  P ys⦄, ag.assm A  prog.p2s (f' x), λ_. P (ys @ [x])"
  assumes "ys. prefix ys xs  stable A (P ys)"
  shows "prog.p2s (prog.app f xs)  P []⦄, ag.assm A  prog.p2s (prog.app f' xs), λ_. P xs"
using assms
by (induct xs rule: rev_induct;
    force intro: rar.prog.return
           simp: prog.app.append prog.app.simps spec.steps.rel prog.bind.return rar.prog.rev_bind)

lemmas "if" = refinement.prog.if[where A="ag.assm A" for A]
lemmas case_option = refinement.prog.case_option[where A="ag.assm A" for A]

setup Sign.parent_path

setup Sign.mandatory_path "rair.prog"

abbreviation (input) "absfn sf c  prog.p2s (prog.sinvmap sf c)"

lemma bind:
  assumes "v. prog.p2s (g v)  Q' v⦄, ag.assm A  rair.prog.absfn sf (g' v), Q"
  assumes "prog.p2s f  P⦄, ag.assm A  rair.prog.absfn sf f', Q'"
  shows "prog.p2s (f  g)  P⦄, ag.assm A  rair.prog.absfn sf (f'  g'), Q"
by (simp add: prog.invmap.bind rar.prog.bind[OF assms])

lemmas rev_bind = rair.prog.bind[rotated]

lemma action:
  fixes F :: "('v × 's × 's) set"
  fixes F' :: "('v × 't × 't) set"
  fixes sf :: "'s  't"
  assumes Q: "v s s'. P s; (v, s, s')  F  Q v s'"
  assumes F': "v s s'. P s; (v, s, s')  F  (v, sf s, sf s')  F'"
  assumes sP: "stable A P"
  assumes sQ: "v s s'. P s; (v, s, s')  F  stable A (Q v)"
  shows "prog.p2s (prog.action F)  P⦄, ag.assm A  rair.prog.absfn sf (prog.action F'), Q"
by (strengthen ord_to_strengthen(2)[OF prog.action.invmap_le])
   (simp add: rar.prog.action assms)

lemma return:
  assumes sQ: "stable A (Q v)"
  shows "prog.p2s (prog.return v)  Q v⦄, ag.assm A  rair.prog.absfn sf (prog.return v), Q"
using assms by (simp add: refinement.prog.invmap_return spec.steps.rel stable_def monotone_def)

lemma parallel:
  fixes sf :: "'s  't"
  assumes "prog.p2s c1  P1⦄, ag.assm (A  G2)  rair.prog.absfn sf c1', Q1"
  assumes "prog.p2s c1  P1⦄, A  G2  G1, "
  assumes "prog.p2s c2  P2⦄, ag.assm (A  G1)  rair.prog.absfn sf c2', Q2"
  assumes "prog.p2s c2  P2⦄, A  G1  G2, "
  shows "prog.p2s (c1  c2)  P1  P2⦄, ag.assm A  rair.prog.absfn sf (c1'  c2'), λv. Q1 v  Q2 v"
unfolding prog.invmap.parallel by (rule rar.prog.parallel[OF assms])

lemma while:
  fixes c :: "'k  ('s, 'k + 'v) prog"
  fixes c' :: "'k  ('t, 'k + 'v) prog"
  fixes sf :: "'s  't"
  assumes c: "k. prog.p2s (c k)  P k⦄, ag.assm A  rair.prog.absfn sf (c' k), case_sum I Q"
  assumes IP: "s v. I v s  P v s"
  assumes sQ: "v. stable A (Q v)"
  shows "prog.p2s (prog.while c k)  I k⦄, ag.assm A  rair.prog.absfn sf (prog.while c' k), Q"
by (strengthen ord_to_strengthen(2)[OF prog.while.invmap_le])
   (simp add: assms map_sum.id rar.prog.while[OF c])

lemma app:
  fixes xs :: "'a list"
  fixes f :: "'a  ('s, unit) prog"
  fixes P :: "'a list  's pred"
  assumes "x ys zs. xs = ys @ x # zs  prog.p2s (f x)  P ys⦄, ag.assm A  rair.prog.absfn sf (f' x), λ_. P (ys @ [x])"
  assumes "ys. prefix ys xs  stable A (P ys)"
  shows "prog.p2s (prog.app f xs)  P []⦄, ag.assm A  rair.prog.absfn sf (prog.app f' xs), λ_. P xs"
by (strengthen ord_to_strengthen(2)[OF prog.sinvmap.app_le])
   (simp add: rar.prog.app assms)

lemma "if":
  assumes "i  prog.p2s t  P⦄, ag.assm A  rair.prog.absfn sf t', Q"
  assumes "¬i  prog.p2s e  P'⦄, ag.assm A  rair.prog.absfn sf e', Q"
  shows "prog.p2s (if i then t else e)  if i then P else P'⦄, ag.assm A  rair.prog.absfn sf (if i then t' else e'), Q"
using assms by fastforce

lemma case_option:
  assumes "opt = None  prog.p2s none  Pn⦄, ag.assm A  rair.prog.absfn sf none', Q"
  assumes "v. opt = Some v  prog.p2s (some v)  Ps v⦄, ag.assm A  rair.prog.absfn sf (some' v), Q"
  shows "prog.p2s (case_option none some opt)  case opt of None  Pn | Some v  Ps v⦄, ag.assm A  rair.prog.absfn sf (case_option none' some' opt), Q"
using assms by (simp add: option.case_eq_if)

setup Sign.parent_path

setup Sign.parent_path
(*<*)

end
(*>*)