Theory Strategy_Program_Fold
theory Strategy_Program_Fold
imports Strategy_Tree_Program
begin
section ‹Folding a right-hand side from contribution programs›
text ‹
An equation's right-hand side is assembled from a list of contributions ---
one per incoming contribution, such as an intra predecessor, a call return, or
a routed activation hook --- joined into one answer. Each contribution is a
∗‹program›, not a finished tree: the fold sequences them with ‹⤜›, joining
each answer into a running accumulator, and the result is again a program.
Nothing is compiled here. A generator compiles once, at the end, where the
solver takes over.
That is why the characterizations below can speak one vocabulary on both
sides. ‹traverse_program›, ‹sides_of_program› and ‹dep_program› ask a
contribution the same questions they ask the fold, and each hypothesis
‹∀p ∈ set ps. sp_wf p› is what makes asking them of a program well posed at
all: an arbitrary program may ignore or duplicate its continuation, and then
what it publishes would depend on what follows it.
The fold never emits a \<^const>‹Side› itself: a fold of Side-free
contributions stays Side-free, which is what lets a generator publish once,
after the whole fold, instead of once per contribution --- several updates to
one key during a single right-hand-side evaluation otherwise change what the
vendored solver's per-origin update rule widens against.
What a caller varies is ∗‹how much of each answer accumulates›. The fold
therefore takes a projection ‹prj› into the accumulator and an embedding
‹emb› back out. At the identity pair the whole answer accumulates. A D/G
instance instead projects the local half into the accumulator and embeds the
result as ‹DG d bot›; the global half of a contribution simply does not reach
the node's answer, and a contribution that has one to share must publish
it itself through \<^const>‹Side›. Nothing here makes that happen.
The two parameters are used as a projection and a final embedding, but the
fold assumes no inverse or homomorphism law relating them. Order preservation
is required only where it is actually used, by the environment-monotonicity
results; the publication and dependency characterizations hold for arbitrary
‹prj› and ‹emb›.
›
subsection ‹Join-folds over a list›
text ‹
‹⨆x←xs. f x› joins ‹f x› over the list ‹xs›; the empty join is
\<^const>‹bot›. It is built like HOL's \<^const>‹sum_list›: ‹join_list› is the
list fold of the commutative monoid ‹(⊔, ⊥)›, and the binder maps
before it folds.
›
context bounded_semilattice_sup_bot
begin
sublocale join_list: comm_monoid_list sup bot
defines join_list = join_list.F ..
end
open_bundle join_list_syntax
begin
syntax
"_join_list" :: "pttrn ⇒ 'a list ⇒ 'b ⇒ 'b"
(‹(‹indent=3 notation=‹binder ⨆››⨆_←_. _)› [0, 51, 10] 10)
syntax_consts
"_join_list" == join_list
translations
"⨆x←xs. b" == "CONST join_list (CONST map (λx. b) xs)"
end
text ‹
Every characterization below states the fold as a list join over
already-computed values, so its consumers keep needing the same handful of
facts about that: the seed of a right fold can be moved out, a bound against
the join is a bound against every element, one element is below the join,
only the element set matters, a pointwise bound lifts, two lists that agree
elementwise agree, and an all-\<^const>‹bot› join is \<^const>‹bot›.
They are collected here, ahead of the fold they serve, rather than restated
per generator. ‹foldr_sup_seed_swap› is the one with content --- it needs
only ‹semilattice_sup› --- and ‹foldr_join_list› and ‹foldr_join_seed_out›
read a right fold with a \<^const>‹bot› or an arbitrary seed as a list join.
›
lemma foldr_sup_seed_swap:
fixes h :: "'a ⇒ 'b::semilattice_sup"
shows "foldr (λt acc'. h t ⊔ acc') ts (acc ⊔ x) = x ⊔ foldr (λt acc'. h t ⊔ acc') ts acc"
by (induction ts arbitrary: acc) (simp_all add: sup_assoc sup_commute sup_left_commute)
lemma foldr_join_list:
fixes h :: "'a ⇒ 'b::bounded_semilattice_sup_bot"
shows "foldr (λt acc'. h t ⊔ acc') ts bot = (⨆t←ts. h t)"
by (induction ts) simp_all
lemma foldr_join_seed_out:
fixes h :: "'a ⇒ 'b::bounded_semilattice_sup_bot"
shows "foldr (λt acc'. h t ⊔ acc') ts a = a ⊔ (⨆t←ts. h t)"
using foldr_sup_seed_swap[of h ts bot a] by (simp add: foldr_join_list)
lemma foldr_sup_le_iff [simp]:
fixes h :: "'a ⇒ 'b::bounded_semilattice_sup_bot"
shows "(⨆t←xs. h t) ≤ y ⟷ (∀x ∈ set xs. h x ≤ y)"
by (induction xs) auto
lemma foldr_sup_member_le [intro]:
fixes h :: "'a ⇒ 'b::bounded_semilattice_sup_bot"
assumes "x ∈ set xs"
shows "h x ≤ (⨆t←xs. h t)"
using assms by (induction xs) (auto intro: le_supI2)
lemma foldr_sup_mono:
fixes f g :: "'a ⇒ 'b::bounded_semilattice_sup_bot"
assumes "⋀x. x ∈ set xs ⟹ f x ≤ g x"
shows "(⨆t←xs. f t) ≤ (⨆t←xs. g t)"
using assms by (auto intro: order_trans[OF _ foldr_sup_member_le])
lemma foldr_sup_set_cong:
fixes h :: "'a ⇒ 'b::bounded_semilattice_sup_bot"
assumes eq: "set xs = set ys"
shows "(⨆t←xs. h t) = (⨆t←ys. h t)"
by (rule order_antisym) (auto simp: eq)
text ‹Two lists whose elements agree under their respective observations have
the same join. This is what lets a correspondence proof relate two
generators' contribution lists elementwise instead of reasoning about the
folds themselves.›
lemma foldr_sup_list_all2_cong:
fixes f :: "'a ⇒ 'c::bounded_semilattice_sup_bot"
assumes "list_all2 (λx y. f x = g y) xs ys"
shows "(⨆x←xs. f x) = (⨆y←ys. g y)"
using assms by (induction rule: list_all2_induct) simp_all
text ‹The two ways such a correspondence is built and consumed: establishing
it between two maps over one list, and reading it as an equality of unions.
Neither mentions a fold, a tree, or a graph.›
lemma list_all2_map_diag:
"(⋀x. x ∈ set xs ⟹ P (f x) (g x)) ⟹ list_all2 P (map f xs) (map g xs)"
by (induction xs) simp_all
lemma list_all2_Union_eq:
assumes "list_all2 (λa b. f a = g b) xs ys"
shows "(⋃x∈set xs. f x) = (⋃y∈set ys. g y)"
using assms by (induction rule: list_all2_induct) auto
lemma foldr_sup_bot_of_all_bot:
fixes L :: "'a list" and h :: "'a ⇒ 'b::bounded_semilattice_sup_bot"
assumes "⋀x. x ∈ set L ⟹ h x = bot"
shows "(⨆x←L. h x) = bot"
using assms by (induction L) simp_all
subsection ‹Folding contributions into one right-hand side›
text ‹Several contribution programs, one right-hand side: run each in list order, join its
answer into the accumulator, and answer with the accumulator. ‹prj› and ‹emb› keep the
fold generic in which part of an answer accumulates -- that is what later lets the D/G
layer join the local half while the global half travels as a side effect.›
fun fold_rhs_program_projected ::
"('a ⇒ 'b) ⇒ ('b ⇒ 'a) ⇒ 'b::semilattice_sup
⇒ ('k, 'g, 'a, 'a) strategy_program list
⇒ ('k, 'g, 'a, 'a) strategy_program"
where
"fold_rhs_program_projected prj emb acc [] = sp_return (emb acc)"
| "fold_rhs_program_projected prj emb acc (p # ps) =
do {
res ← p;
fold_rhs_program_projected prj emb (acc ⊔ prj res) ps
}"
text ‹The accumulator read declaratively off a valuation, rather than run.
Every answer characterization below is stated through it.›
fun fold_acc_projected ::
"('a::bounded_semilattice_sup_bot ⇒ 'b::semilattice_sup) ⇒ 'b
⇒ ('k + 'g ⇒ 'a) ⇒ ('k, 'g, 'a, 'a) strategy_program list ⇒ 'b"
where
"fold_acc_projected prj acc σ [] = acc"
| "fold_acc_projected prj acc σ (p # ps) =
fold_acc_projected prj (acc ⊔ prj (traverse_program p σ)) σ ps"
subsection ‹Declarative characterizations›
text ‹
Contributions are traversed in list order: each answer joins the running
accumulator before the next program runs. The publication and dependency
characterizations forget that order, the underlying operation being
commutative --- a set union for \<^const>‹dep_aux›, a join seeded at ‹bot› for
\<^const>‹sides_of_rhs› --- and neither sees ‹prj› or ‹emb› at all: what a fold
publishes and reads is its contributions' business, not its accumulator's.
That is also why both are independent of the accumulator it starts from.
›
theorem traverse_fold_rhs_program_projected_char:
assumes "∀p ∈ set ps. sp_wf p"
shows "traverse_program (fold_rhs_program_projected prj emb acc ps) σ
= emb (fold_acc_projected prj acc σ ps)"
using assms
by (induction ps arbitrary: acc)
(simp_all add: sp_compile_def sp_compile_with_bind traverse_rhs_sp_wf)
theorem sides_of_program_fold_rhs_program_projected_char:
fixes ps :: "('x, 'g, 'd::bounded_semilattice_sup_bot, 'd) strategy_program list"
assumes "∀p ∈ set ps. sp_wf p"
shows "sides_of_program (fold_rhs_program_projected prj emb acc ps) σ z
= (⨆p←ps. sides_of_program p σ z)"
using assms
by (induction ps arbitrary: acc)
(auto simp add: sp_compile_def sp_compile_with_bind sides_of_rhs_sp_wf traverse_rhs_sp_wf)
theorem dep_program_fold_rhs_program_projected_char:
assumes "∀p ∈ set ps. sp_wf p"
shows "dep_program σ (fold_rhs_program_projected prj emb acc ps)
= (⋃p∈set ps. dep_program σ p)"
using assms
by (induction ps arbitrary: acc)
(auto simp add: sp_compile_def sp_compile_with_bind dep_aux_sp_wf traverse_rhs_sp_wf)
lemma fold_acc_projected_as_foldr:
fixes prj :: "'a::bounded_semilattice_sup_bot ⇒ 'b::bounded_semilattice_sup_bot"
shows "fold_acc_projected prj acc σ ps
= acc ⊔ (⨆p←ps. prj (traverse_program p σ))"
by (induction ps arbitrary: acc) (simp_all add: sup_assoc)
lemma fold_acc_projected_acc_mono:
fixes prj :: "'a::bounded_semilattice_sup_bot ⇒ 'b::bounded_semilattice_sup_bot"
assumes "acc1 ≤ acc2"
shows "fold_acc_projected prj acc1 σ ts ≤ fold_acc_projected prj acc2 σ ts"
using assms by (simp add: fold_acc_projected_as_foldr le_supI1)
text ‹
Monotonicity in the ∗‹environment› rather than the accumulator. This is
where ‹prj› and ‹emb› stop being arbitrary: a fold inherits its
contributions' monotonicity only if the two also preserve order. Publications
and dependencies need no such hypothesis, since neither passes through them.
›
lemma fold_acc_projected_env_mono:
fixes prj :: "'a::bounded_semilattice_sup_bot ⇒ 'b::bounded_semilattice_sup_bot"
assumes prj_mono: "mono prj"
and progs_mono: "⋀p. p ∈ set ps ⟹ traverse_program p σ1 ≤ traverse_program p σ2"
shows "fold_acc_projected prj acc σ1 ps ≤ fold_acc_projected prj acc σ2 ps"
unfolding fold_acc_projected_as_foldr
by (rule sup_mono[OF order_refl])
(rule foldr_sup_mono, rule monoD[OF prj_mono, OF progs_mono])
lemma traverse_fold_rhs_program_projected_mono:
fixes prj :: "'a::bounded_semilattice_sup_bot ⇒ 'b::bounded_semilattice_sup_bot"
assumes wf: "∀p ∈ set ps. sp_wf p"
and prj_mono: "mono prj"
and emb_mono: "mono emb"
and progs_mono: "⋀p. p ∈ set ps ⟹ traverse_program p σ1 ≤ traverse_program p σ2"
shows "traverse_program (fold_rhs_program_projected prj emb acc ps) σ1
≤ traverse_program (fold_rhs_program_projected prj emb acc ps) σ2"
unfolding traverse_fold_rhs_program_projected_char[OF wf]
by (rule monoD[OF emb_mono])
(rule fold_acc_projected_env_mono[OF prj_mono progs_mono])
lemma sides_of_program_fold_rhs_program_projected_mono:
fixes ps :: "('x, 'g, 'd::bounded_semilattice_sup_bot, 'd) strategy_program list"
assumes wf: "∀p ∈ set ps. sp_wf p"
and progs_mono: "⋀p. p ∈ set ps ⟹ sides_of_program p σ1 ≤ sides_of_program p σ2"
shows "sides_of_program (fold_rhs_program_projected prj emb acc ps) σ1
≤ sides_of_program (fold_rhs_program_projected prj emb acc ps) σ2"
unfolding le_fun_def sides_of_program_fold_rhs_program_projected_char[OF wf]
using progs_mono
by (fastforce simp: le_fun_def
intro: order_trans[OF _ foldr_sup_member_le])
lemma sides_of_program_fold_rhs_program_projected_acc_indep:
fixes ps :: "('x, 'g, 'd::bounded_semilattice_sup_bot, 'd) strategy_program list"
assumes wf: "∀p ∈ set ps. sp_wf p"
shows "sides_of_program (fold_rhs_program_projected prj emb acc1 ps) σ
= sides_of_program (fold_rhs_program_projected prj emb acc2 ps) σ"
by (simp add: fun_eq_iff sides_of_program_fold_rhs_program_projected_char[OF wf])
lemma dep_program_fold_rhs_program_projected_acc_indep:
assumes wf: "∀p ∈ set ps. sp_wf p"
shows "dep_program σ (fold_rhs_program_projected prj emb acc1 ps)
= dep_program σ (fold_rhs_program_projected prj emb acc2 ps)"
by (simp add: dep_program_fold_rhs_program_projected_char[OF wf])
subsection ‹Accumulating the whole answer›
text ‹The identity instance: nothing is projected away, so the accumulator has
the answer type and every contribution joins into the node's answer whole.›
lemma sp_wf_fold_rhs_program_projected [intro]:
"∀p ∈ set ps. sp_wf p ⟹ sp_wf (fold_rhs_program_projected prj emb acc ps)"
by (induction ps arbitrary: acc) auto
definition fold_rhs_program ::
"'a::semilattice_sup
⇒ ('k, 'g, 'a, 'a) strategy_program list
⇒ ('k, 'g, 'a, 'a) strategy_program"
where
"fold_rhs_program acc ps = fold_rhs_program_projected id id acc ps"
lemma sp_wf_fold_rhs_program [intro]:
"∀p ∈ set ps. sp_wf p ⟹ sp_wf (fold_rhs_program acc ps)"
unfolding fold_rhs_program_def by (rule sp_wf_fold_rhs_program_projected)
lemma fold_rhs_program_simps [simp, code]:
"fold_rhs_program acc [] = sp_return acc"
"fold_rhs_program acc (p # ps) =
do { res ← p; fold_rhs_program (acc ⊔ res) ps }"
by (simp_all add: fold_rhs_program_def)
text ‹The answer as a right fold. \<^const>‹fold_rhs_program› threads the
accumulator in evaluation order, which is a left fold;
proofs about a node's contributions are usually already phrased as a right
fold, and \<^const>‹traverse_rhs› only ever joins, so the two agree.›
theorem traverse_fold_rhs_program_char_foldr:
assumes "∀p ∈ set ps. sp_wf p"
shows "traverse_program (fold_rhs_program acc ps) σ
= foldr (λp acc'. traverse_program p σ ⊔ acc') ps acc"
using assms
by (induction ps arbitrary: acc)
(simp_all add: sp_compile_def sp_compile_with_bind foldr_sup_seed_swap
traverse_rhs_sp_wf)
theorem sides_of_program_fold_rhs_program_char:
fixes ps :: "('x,'g,'d::bounded_semilattice_sup_bot,'d) strategy_program list"
assumes wf: "∀p ∈ set ps. sp_wf p"
shows "sides_of_program (fold_rhs_program acc ps) σ z
= (⨆p←ps. sides_of_program p σ z)"
unfolding fold_rhs_program_def
by (rule sides_of_program_fold_rhs_program_projected_char[OF wf])
theorem dep_program_fold_rhs_program_char:
assumes wf: "∀p ∈ set ps. sp_wf p"
shows "dep_program σ (fold_rhs_program acc ps) = (⋃p∈set ps. dep_program σ p)"
unfolding fold_rhs_program_def
by (rule dep_program_fold_rhs_program_projected_char[OF wf])
subsection ‹Dependency-property preservation›
text ‹
A fold of programs whose query set is (respectively environment-independent,
monotone in the environment) is itself environment-independent (monotone) --
the list-level analogue of \<^const>‹env_indep_deps›/\<^const>‹mono_tree_deps›'s
own single-tree closure facts, and what lets a generator's whole-node
dependency obligation reduce to a per-hook one.
Both read straight off ‹dep_program_fold_rhs_program_projected_char›: a fold's
dependencies are the union of its contributions', so whatever closure
property each contribution has, the union inherits.
›
lemma env_indep_deps_fold_rhs_program_projected:
assumes wf: "∀p ∈ set ps. sp_wf p"
and "⋀p. p ∈ set ps ⟹ env_indep_deps (sp_compile p)"
shows "env_indep_deps (sp_compile (fold_rhs_program_projected prj emb acc ps))"
using assms
unfolding env_indep_deps_def
by (auto simp add: dep_program_fold_rhs_program_projected_char[OF wf]; blast)
lemma mono_tree_deps_fold_rhs_program_projected:
assumes wf: "∀p ∈ set ps. sp_wf p"
and "⋀p. p ∈ set ps ⟹ mono_tree_deps (sp_compile p)"
shows "mono_tree_deps (sp_compile (fold_rhs_program_projected prj emb acc ps))"
using assms
unfolding mono_tree_deps_def
by (fastforce simp: dep_program_fold_rhs_program_projected_char[OF wf])
end