Theory BFS

(*<*)
theory BFS
imports
  "Assume_Guarantee"
begin

(*>*)
section‹ Example: data refinement (search)\label{sec:ex-data_refinement} ›

text‹

We show a very simple example of data refinement: implementing sets with functional queues
for breadth-first search (BFS). The objective here is to transfer a simple correctness property
proven on the abstract level to the concrete level.

Observations:
  there is no concurrency in the BFS: this is just about data refinement
   however arbitrary interference is allowed
  the abstract level does not require the implementation of the pending set to make progress
  the concrete level does not require a representation invariant
  depth optimality is not shown

References:
  queue ADT: 🗏‹$ISABELLE_HOME/src/Doc/Codegen/Introduction.thy›
  BFS verification:
   J. C. Filliâtre 🌐‹http://toccata.lri.fr/gallery/vstte12_bfs.en.html›
   🗏‹$AFP/Refine_Monadic/examples/Breadth_First_Search.thy›
   our model is quite different

›

setup Sign.mandatory_path "pending"

record ('a, 's) interface =
  init :: "('s, unit) prog"
  enq :: "'a  ('s, unit) prog"
  deq :: "('s, 'a option) prog"

type_synonym 'a abstract = "'a set"

definition abstract :: "('a, 'a pending.abstract × 's) pending.interface" where
  "abstract =
     pending.interface.init = prog.write (map_prod {} id)
    , pending.interface.enq = λx. prog.write (map_prod (insert x) id)
    , pending.interface.deq = prog.action ({(None, s, s) |s. fst s = {}}
                                          {(Some x, (insert x X, s), (X, s)) |X s x. True})
    "

type_synonym 'a concrete = "'a list × 'a list" ―‹ a queue ›

fun cdeq_update :: "'a pending.concrete × 's  'a option × 'a pending.concrete × 's" where
  "cdeq_update (([], []), s) = (None, (([], []), s))"
| "cdeq_update ((xs, []), s) = cdeq_update (([], rev xs), s)"
| "cdeq_update ((xs, y # ys), s) = (Some y, ((xs, ys), s))"

definition concrete :: "('a, 'a pending.concrete × 's) pending.interface" where
  "concrete =
     pending.interface.init = prog.write (map_prod ([], []) id)
    , pending.interface.enq = λx. prog.write (map_prod (map_prod ((#) x) id) id)
    , pending.interface.deq = prog.det_action pending.cdeq_update
    "

abbreviation absfn' :: "'a pending.concrete  'a list" where ―‹ queue as a list ›
  "absfn' s  snd s @ rev (fst s)"

definition absfn :: "'a pending.concrete  'a pending.abstract" where
  "absfn s = set (pending.absfn' s)"

setup Sign.mandatory_path "ag"

lemma init:
  fixes Q :: "unit  'a pending.abstract × 's  bool"
  fixes A :: "'s rel"
  assumes "stable (Id ×R A) (Q ())"
  shows "prog.p2s (pending.init pending.abstract)  λs. Q () ({}, snd s)⦄, Id ×R A  UNIV ×R Id, Q"
using assms by (auto intro: ag.prog.action simp: pending.abstract_def stable_def monotone_def)

lemma enq:
  fixes x :: 'a
  fixes Q :: "unit  'a pending.abstract × 's  bool"
  fixes A :: "'s rel"
  assumes "stable (Id ×R A) (Q ())"
  shows "prog.p2s (pending.enq pending.abstract x)  λs. Q () (insert x (fst s), snd s)⦄, Id ×R A  UNIV ×R Id, Q"
using assms by (auto intro: ag.prog.action simp: pending.abstract_def stable_def monotone_def)

lemma deq:
  fixes Q :: "'a option  'a pending.abstract × 's  bool"
  fixes A :: "'s rel"
  assumes "v. stable (Id ×R A) (Q v)"
  shows "prog.p2s (pending.deq pending.abstract)  λs. if fst s = {} then Q None s else (x X. fst s = insert x X  Q (Some x) (X, snd s))⦄, Id ×R A  UNIV ×R Id, Q"
unfolding pending.abstract_def pending.interface.simps
by (rule ag.prog.action)
   (use assms in auto simp: stable_def monotone_def le_bool_def split: if_split_asm)

setup Sign.parent_path

setup Sign.parent_path

setup Sign.mandatory_path "set"

record ('a, 's) interface =
  init :: "('s, unit) prog"
  ins :: "'a  ('s, unit) prog"
  mem :: "'a  ('s, bool) prog"

type_synonym 'a abstract = "'a list" ―‹ model finite sets ›

definition abstract :: "('a, 's × 'a set.abstract × 't) set.interface" where
  "abstract =
     set.interface.init = prog.write (map_prod id (map_prod [] id))
    , set.interface.ins = λx. prog.write (map_prod id (map_prod ((#) x) id))
    , set.interface.mem = λx. prog.read (λs. x  set (fst (snd s)))
    "

setup Sign.mandatory_path "ag"

lemma init:
  fixes Q :: "unit  's × 'a set.abstract × 't  bool"
  fixes A :: "'s rel"
  assumes "stable (A ×R Id ×R B) (Q ())"
  shows "prog.p2s (set.init set.abstract)  λs. Q () (fst s, [], snd (snd s))⦄, A ×R Id ×R B  Id ×R UNIV ×R Id, Q"
using assms by (auto intro: ag.prog.action simp: set.abstract_def stable_def monotone_def)

lemma ins:
  fixes x :: 'a
  fixes Q :: "unit  's × 'a set.abstract × 't  bool"
  fixes A :: "'s rel"
  assumes "stable (A ×R Id ×R B) (Q ())"
  shows "prog.p2s (set.ins set.abstract x)  λs. Q () (fst s, x # fst (snd s), snd (snd s))⦄, A ×R Id ×R B  Id ×R UNIV ×R Id, Q"
using assms by (auto intro: ag.prog.action simp: set.abstract_def stable_def monotone_def)

lemma mem:
  fixes Q :: "bool  's × 'a set.abstract × 't  bool"
  assumes "v. stable (A ×R Id ×R B) (Q v)"
  shows "prog.p2s (set.mem set.abstract x)  λs. Q (x  set (fst (snd s))) s⦄, A ×R Id ×R B  Id ×R UNIV ×R Id, Q"
using assms by (auto intro: ag.prog.action simp: set.abstract_def stable_def monotone_def)

setup Sign.parent_path

setup Sign.parent_path

context
  fixes pending :: "('a, 'p × 'a set.abstract × 's) pending.interface"
  fixes f :: "'a  'a list"
begin

definition loop :: "'a pred  ('p × 'a set.abstract × 's, 'a option) prog" where
  "loop p =
    prog.while (λ_.
      do { aopt  pending.deq pending
         ; case aopt of
             None  prog.return (Inr None)
           | Some x 
               if p x
               then prog.return (Inr (Some x))
               else do { prog.app (λy. do { b  set.mem set.abstract y;
                                            prog.unlessM b (do { set.ins set.abstract y
                                                               ; pending.enq pending y }) })
                                  (f x)
                       ; prog.return (Inl ())
                       }
       }) ()"

definition main :: "'a pred  'a  ('p × 'a set.abstract × 's, 'a option) prog" where
  "main p x =
    do {
      set.init set.abstract
    ; pending.init pending
    ; set.ins set.abstract x
    ; pending.enq pending x
    ; loop p
    }"

definition search :: "'a pred  'a  ('s, 'a option) prog" where
  "search p x = prog.local (prog.local (main p x))"

end

abbreviation (input) "aloop  loop pending.abstract"
abbreviation (input) "amain  main pending.abstract"
abbreviation (input) "asearch  search pending.abstract"
abbreviation (input) "bfs  search pending.concrete"

lemma
  shows pending_g: "UNIV ×R Id  UNIV ×R UNIV ×R Id"
    and set_g: "Id ×R UNIV ×R Id  UNIV ×R UNIV ×R Id"
by fastforce+

context
  fixes f :: "'a  'a list"
  fixes P :: "'a pred"
  fixes x0 :: "'a"
begin

abbreviation (input) step :: "'a rel" where
  "step  {(x, y). y  set (f x)}"

abbreviation (input) path :: "'a rel" where
  "path  step*"

definition aloop_invP :: "'a pending.abstract  'a set.abstract  bool" where
  "aloop_invP q v 
        q  set v
       set v  path `` {x0}
       set v  Collect P  q
       x0  set v"

definition vclosureP :: "'a  'a pending.abstract  'a set.abstract  bool" where
  "vclosureP x q v  (x  set v - q  step `` {x}  set v)"

definition search_postP :: "'a option  bool" where
  "search_postP rv = (case rv of
      None  finite (path `` {x0})  (path `` {x0}  Collect P = {})
    | Some y  (x0, y)  path  P y)"

abbreviation "aloop_inv s  aloop_invP (fst s) (fst (snd s))"
abbreviation "vclosure x s  vclosureP x (fst s) (fst (snd s))"
abbreviation "search_post rv  search_postP rv"

lemma vclosureP_closed:
  assumes "set v  path `` {x0}"
  assumes "y. vclosureP y {} v"
  assumes "x0  set v"
  shows "path `` {x0} = set v"
proof -
  have "y  set v" if "(x0, y)  path" for y
    using that assms(2,3) by induct (auto simp: vclosureP_def)
  with assms(1) show ?thesis
    by fast
qed

lemma vclosureP_app:
  assumes "y. x  y  local.vclosureP y q v"
  assumes "set (f x)  set v"
  shows "vclosureP y q v"
using assms by (fastforce simp: vclosureP_def)

lemma vclosureP_init:
  shows "vclosureP x {x0} [x0]"
by (simp add: vclosureP_def)

lemma vclosureP_step:
  assumes "z. x  z  vclosureP z q v"
  assumes "x  z"
  shows "vclosureP z (insert y q) (y # v)"
using assms by (fastforce simp: vclosureP_def)

lemma vclosureP_dequeue:
  assumes "z. vclosureP z (insert x q) v"
  assumes "x  z"
  shows "vclosureP z q v"
using assms by (fastforce simp: vclosureP_def)

lemma aloop_invPD:
  assumes "aloop_invP q v"
  assumes "x  q"
  shows "(x0, x)  path"
using assms by (auto simp: aloop_invP_def)

lemma aloop_invP_init:
  shows "aloop_invP {x0} [x0]"
by (simp add: aloop_invP_def)

lemma aloop_invP_step:
  assumes "aloop_invP q v"
  assumes "(x0, x)  path"
  assumes "y  set (f x) - set v"
  shows "aloop_invP (insert y q) (y # v)"
using assms by (auto simp: aloop_invP_def elim: rtrancl_into_rtrancl)

lemma aloop_invP_dequeue:
  assumes "aloop_invP (insert x q) v"
  assumes "¬ P x"
  shows "aloop_invP q v"
using assms by (auto simp: aloop_invP_def)

lemma search_postcond_None:
  assumes "aloop_invP {} v"
  assumes "y. vclosureP y {} v"
  shows "search_postP None"
using assms by (auto simp: search_postP_def aloop_invP_def dest: vclosureP_closed)

lemma search_postcond_Some:
  assumes "aloop_invP q v"
  assumes "x  q"
  assumes "P x"
  shows "search_postP (Some x)"
using assms by (auto simp: search_postP_def aloop_invP_def)

lemmas stable_simps =
  prod.sel
  split_def
  sum.simps

lemma ag_aloop:
  shows "prog.p2s (aloop f P)  aloop_inv  (x. vclosure x)⦄, Id ×R Id ×R UNIV  UNIV ×R UNIV ×R Id, search_post"
unfolding loop_def
apply (rule ag.prog.while[OF _ _ stable.Id_fst_fst_snd])
 apply (rule ag.prog.bind)
  apply (rule ag.prog.case_option)
   apply (rule ag.prog.return)
   apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
  apply (rule ag.prog.if)
   apply (rule ag.prog.return)
   apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
  apply (rule ag.prog.bind)
   apply (rule ag.prog.return)
   apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
  apply (rename_tac x)
  apply (rule_tac Q="λ_. aloop_inv  (y. x  y  vclosure y)  (λs. set (f x)  set (fst (snd s))  (x0, x)  path)" in ag.post_imp)
   apply (force elim: vclosureP_app)
  apply (rule ag.prog.app)
   apply (rule ag.prog.bind)
    apply (rule ag.prog.if)
     apply (rule ag.prog.return)
     apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
    apply (rule ag.prog.bind)
     apply (rule ag.pre_g[OF pending.ag.enq pending_g])
     apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
    apply (rule ag.pre_g[OF set.ag.ins set_g])
    apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
   apply (rule ag.pre_pre[OF ag.pre_g[OF set.ag.mem set_g]])
    apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
   apply (force simp: aloop_invP_step vclosureP_step)
  apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
 apply (rule ag.pre_g[OF pending.ag.deq pending_g])
 apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
apply (auto elim: search_postcond_Some search_postcond_None aloop_invP_dequeue
                  aloop_invPD vclosureP_dequeue)
done

lemma ag_amain:
  shows "prog.p2s (amain f P x0)  True⦄, Id ×R Id ×R UNIV  UNIV ×R UNIV ×R Id, search_post"
unfolding main_def
apply (rule ag.pre_pre)
 apply (rule ag.prog.bind)+
     apply (rule ag_aloop)
    apply (rule ag.post_imp[where Q="λ(q, v, _). q = {x0}  v = [x0]"])
     apply (clarsimp simp: aloop_invP_init vclosureP_init; fail)
    apply (rule ag.pre_g[OF pending.ag.enq pending_g])
    apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
   apply (rule ag.pre_g[OF set.ag.ins set_g])
   apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
  apply (rule ag.pre_g[OF pending.ag.init pending_g])
  apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
 apply (rule ag.pre_g[OF set.ag.init set_g])
 apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
apply simp
done

lemma ag_asearch:
  shows "prog.p2s (asearch f P x0 :: ('s, 'a option) prog)  True⦄, UNIV  Id, search_post"
unfolding search_def by (rule ag.prog.local ag_amain)+


paragraph‹ Refinement ›

abbreviation "A  ag.assm (Id ×R Id ×R UNIV)"
abbreviation "absfn c  prog.sinvmap (map_prod pending.absfn id) c"
abbreviation "p2s_absfn c  prog.p2s (absfn c)"

―‹ visited set: reflexive ›
lemma ref_set_init:
  shows "prog.p2s (set.init set.abstract)  λs. True⦄, A  p2s_absfn (set.init set.abstract), λv s. True"
by (auto simp: set.abstract_def intro: rair.prog.action stable.intro)

lemma ref_set_ins:
  shows "prog.p2s (set.ins set.abstract x)  λs. True⦄, A  p2s_absfn (set.ins set.abstract x), λv s. True"
by (auto simp: set.abstract_def intro: rair.prog.action stable.intro)

lemma ref_set_mem:
  shows "prog.p2s (set.mem set.abstract x)  λs. True⦄, A  p2s_absfn (set.mem set.abstract x), λv s. True"
by (auto simp: set.abstract_def intro: rair.prog.action stable.intro)

―‹ queue ›
lemma ref_queue_init:
  shows "prog.p2s (pending.init pending.concrete)  λs. True⦄, A  p2s_absfn (pending.init pending.abstract), λv s. True"
by (auto simp: pending.abstract_def pending.concrete_def pending.absfn_def intro: rair.prog.action stable.intro)

lemma ref_queue_enq:
  shows "prog.p2s (pending.enq pending.concrete x)  λs. True⦄, A  p2s_absfn (pending.enq pending.abstract x), λv s. True"
by (auto simp: pending.abstract_def pending.concrete_def pending.absfn_def intro: rair.prog.action stable.intro)

lemma ref_queue_deq:
  shows "prog.p2s (pending.deq pending.concrete)  λs. True⦄, A  p2s_absfn (pending.deq pending.abstract), λv s. True"
by (auto simp: pending.abstract_def pending.concrete_def pending.absfn_def
        intro: rair.prog.action stable.intro elim!: pending.cdeq_update.elims[OF sym])

―‹ program ›
lemma ref_bfs_loop:
  shows "prog.p2s (loop pending.concrete f P)  λs. True⦄, A  p2s_absfn (loop pending.abstract f P), λv s. True"
apply (simp add: loop_def)
apply (rule rair.prog.while)
  apply (rule rair.prog.rev_bind)
   apply (rule ref_queue_deq)
  apply (rule refinement.pre_pre[OF rair.prog.case_option])
    apply (rule rair.prog.return)
    apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
   apply (rule rair.prog.if)
    apply (rule rair.prog.return)
    apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
   apply (rule rair.prog.rev_bind)
    apply (rule rair.prog.app)
     apply (rule rair.prog.rev_bind)
      apply (rule ref_set_mem)
     apply (rule refinement.pre_pre[OF rair.prog.if])
       apply (rule rair.prog.return)
       apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
      apply (rule rair.prog.rev_bind)
       apply (rule ref_set_ins)
      apply (rule ref_queue_enq)
     apply (simp; fail)
    apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
   apply (rule refinement.pre_pre[OF rair.prog.return])
    apply ((simp only: stable_simps)?; (rule stable.intro)+; fail)
   apply (auto intro: stable.intro split: option.split)
done

lemma ref_bfs_main:
  shows "prog.p2s (main pending.concrete f P x)  True⦄, A  p2s_absfn (amain f P x), λv s. True"
apply (simp add: main_def)
apply (rule rair.prog.rev_bind)
 apply (rule ref_set_init)
apply (rule rair.prog.rev_bind)
 apply (rule ref_queue_init)
apply (rule rair.prog.rev_bind)
 apply (rule ref_set_ins)
apply (rule rair.prog.rev_bind)
 apply (rule ref_queue_enq)
apply (rule ref_bfs_loop)
done

theorem ref_bfs:
  shows "bfs f P x  asearch f P x"
unfolding search_def
apply (intro refinement.prog.leI refinement.prog.data[where sf=id])
apply (simp add: spec.invmap.id spec.localizeA.top)
apply (rule refinement.prog.data[where sf=pending.absfn])
apply (simp flip: prog.p2s.invmap)
apply (rule refinement.pre_a[OF ref_bfs_main])
apply (auto simp: spec.localizeA_def spec.invmap.rel
       simp flip: spec.rel.inf
           intro: spec.rel.mono)
done

theorem bfs_post_le:
  shows "prog.p2s (bfs f P x0)  spec.post (search_post)"
apply (strengthen ord_to_strengthen[OF ref_bfs])
apply (strengthen ord_to_strengthen(1)[OF ag_asearch])
apply (simp add: ag_def spec.rel.UNIV flip: Sigma_Un_distrib1)
done

end
(*<*)

end
(*>*)