Theory TD_plain
section ‹The plain Top-Down Solver›
text ‹\label{sec:td_plain}›
text‹TD\_plain is a simplified version of the original TD which only keeps track of already called
unknowns to avoid infinite descend in case of recursive dependencies. In contrast to the TD, it
does, however, not track stable unknowns and the dependencies between unknowns. Instead, it
re-iterates every unknown when queried again.
›
theory TD_plain
imports Basics
begin
locale TD_plain = Solver D T
for D :: "'d :: bot"
and T :: "'x ⇒ ('x, 'd) strategy_tree"
begin
subsection ‹Definition of the Solver Algorithm›
text ‹The recursively descending solver algorithm is defined with three mutual recursive functions.
Initially, the function @{term iterate} is called from the top-level @{term solve} function for
the requested unknown. @{term iterate} keeps evaluating the right-hand side by calling the
function @{term eval} and updates the value mapping @{term σ} until the value stabilizes. The
function @{term eval} walks through a strategy tree and chooses the path based on the result for
queried unknowns. These queries are delegated to the third mutual recursive function @{term query}
which checks that the unknown is not already being evaluated and iterates it otherwise. The
function keyword is used for the definition, since, without further assumptions, the solver may
not terminate.›
function (domintros)
query :: "'x ⇒ 'x ⇒ 'x set ⇒ ('x, 'd) map ⇒ 'd × ('x, 'd) map" and
iterate :: "'x ⇒ 'x set ⇒ ('x, 'd) map ⇒ 'd × ('x, 'd) map" and
eval :: "'x ⇒ ('x, 'd) strategy_tree ⇒ 'x set ⇒ ('x, 'd) map ⇒ 'd × ('x, 'd) map" where
"query x y c σ = (
if y ∈ c then
(mlup σ y, σ)
else
iterate y (insert y c) σ)"
| "iterate x c σ = (
let (d_new, σ) = eval x (T x) c σ in
if d_new = mlup σ x then
(d_new, σ)
else
iterate x c (σ(x ↦ d_new)))"
| "eval x t c σ = (case t of
Answer d ⇒ (d, σ)
| Query y g ⇒ (let (yd, σ) = query x y c σ in eval x (g yd) c σ))"
by pat_completeness auto
definition solve :: "'x ⇒ ('x, 'd) map" where
"solve x = (let (_, σ) = iterate x {x} Map.empty in σ)"
definition query_dom where
"query_dom x y c σ = query_iterate_eval_dom (Inl (x, y, c, σ))"
declare query_dom_def [simp]
definition iterate_dom where
"iterate_dom x c σ = query_iterate_eval_dom (Inr (Inl (x, c, σ)))"
declare iterate_dom_def [simp]
definition eval_dom where
"eval_dom x t c σ = query_iterate_eval_dom (Inr (Inr (x, t, c, σ)))"
declare eval_dom_def [simp]
definition solve_dom where
"solve_dom x = iterate_dom x {x} Map.empty"
lemmas dom_defs = query_dom_def iterate_dom_def eval_dom_def
subsection ‹Refinement of Auto-Generated Rules›
text ‹The auto-generated \texttt{pinduct} rule contains a redundant assumption. This lemma removes
this redundant assumption for easier instantiation and assigns each case a comprehensible name.›
lemmas query_iterate_eval_pinduct[consumes 1, case_names Query Iterate Eval]
= query_iterate_eval.pinduct(1)[
folded query_dom_def iterate_dom_def eval_dom_def,
of x y c σ for x y c σ
]
query_iterate_eval.pinduct(2)[
folded query_dom_def iterate_dom_def eval_dom_def,
of x c σ for x c σ
]
query_iterate_eval.pinduct(3)[
folded query_dom_def iterate_dom_def eval_dom_def,
of x t c σ for x t c σ
]
lemmas iterate_pinduct[consumes 1, case_names Iterate]
= query_iterate_eval_pinduct(2)[where ?P="λx y c σ. True" and ?R="λx t c σ. True",
simplified (no_asm_use), folded query_dom_def iterate_dom_def eval_dom_def]
declare query.psimps [simp]
declare iterate.psimps [simp]
declare eval.psimps [simp]
subsection ‹Domain Lemmas›
lemma dom_backwards_pinduct:
shows "query_dom x y c σ
⟹ y ∉ c ⟹ iterate_dom y (insert y c) σ"
and "iterate_dom x c σ
⟹ (eval_dom x (T x) c σ ∧
(eval x (T x) c σ = (xd_new, σ')
⟶ mlup σ' x = xd_old ⟶ xd_new ≠ xd_old ⟶
iterate_dom x c (σ'(x ↦ xd_new))))"
and "eval_dom x (Query y g) c σ
⟹ (query_dom x y c σ ∧ (query x y c σ = (yd, σ') ⟶ eval_dom x (g yd) c σ'))"
proof (induction x y c σ and x c σ and x "Query y g" c σ
arbitrary: and xd_new xd_old σ' and y g yd σ'
rule: query_iterate_eval_pinduct)
case (Query x c σ)
then show ?case
using query_iterate_eval.domintros(2) by fastforce
next
case (Iterate x c σ)
then show ?case
using query_iterate_eval.domintros(2,3)[folded eval_dom_def iterate_dom_def query_dom_def]
by metis
next
case (Eval c σ)
then show ?case
using query_iterate_eval.domintros(1,3) by simp
qed
subsection ‹Case Rules›
lemma iterate_continue_fixpoint_cases[consumes 3]:
assumes "iterate_dom x c σ"
and "iterate x c σ = (xd, σ')"
and "x ∈ c"
obtains (Fixpoint) "eval_dom x (T x) c σ"
and "eval x (T x) c σ = (xd, σ')"
and "mlup σ' x = xd"
| (Continue) σ1 xd_new
where "eval_dom x (T x) c σ"
and "eval x (T x) c σ = (xd_new, σ1)"
and "mlup σ1 x ≠ xd_new"
and "iterate_dom x c (σ1(x ↦ xd_new))"
and "iterate x c (σ1(x ↦ xd_new)) = (xd, σ')"
proof -
obtain xd_new σ1
where "eval x (T x) c σ = (xd_new, σ1)"
by (cases "eval x (T x) c σ")
then show ?thesis
using assms that dom_backwards_pinduct(2)
by (cases "mlup σ1 x = xd_new"; simp)
qed
lemma iterate_fmlookup:
assumes "iterate_dom x c σ"
and "iterate x c σ = (xd, σ')"
and "x ∈ c"
shows "mlup σ' x = xd"
using assms
proof(induction rule: iterate_pinduct)
case (Iterate x c σ)
show ?case
using Iterate.hyps Iterate.prems
proof (cases rule: iterate_continue_fixpoint_cases)
case (Continue σ1 xd_new)
then show ?thesis
using Iterate.prems(2) Iterate.IH
by fastforce
qed simp
qed
corollary query_fmlookup:
assumes "query_dom x y c σ"
and "query x y c σ = (yd, σ')"
shows "mlup σ' y = yd"
using assms iterate_fmlookup dom_backwards_pinduct(1)[of x y c σ]
by (auto split: if_splits)
lemma query_iterate_lookup_cases [consumes 2]:
assumes "query_dom x y c σ"
and "query x y c σ = (yd, σ')"
obtains (Iterate)
"iterate_dom y (insert y c) σ"
and "iterate y (insert y c) σ = (yd, σ')"
and "mlup σ' y = yd"
and "y ∉ c"
| (Lookup) "mlup σ y = yd"
and "σ = σ'"
and "y ∈ c"
using assms that dom_backwards_pinduct(1) query_fmlookup[of x y c σ yd σ']
by (cases "y ∈ c"; auto)
lemma eval_query_answer_cases [consumes 2]:
assumes "eval_dom x t c σ"
and "eval x t c σ = (d, σ')"
obtains (Query) y g yd σ1
where "t = Query y g"
and "query_dom x y c σ"
and "query x y c σ = (yd, σ1)"
and "eval_dom x (g yd) c σ1"
and "eval x (g yd) c σ1 = (d, σ')"
and "mlup σ1 y = yd"
| (Answer) "t = Answer d"
and "σ = σ'"
using assms dom_backwards_pinduct(3) that query_fmlookup
by (cases t; auto split: prod.splits)
subsection ‹Predicate for Valid Input States›
text‹We define a predicate for valid input solver states. @{term c} is the set of called unknowns,
i.e., the unknowns currently being evaluated and @{term σ} is an unknown-to-value mapping. Both
are data structures maintained by the solver. In contrast, the parameter @{term s} describing a
set of unknowns, for which a partial solution has already been computed or which are currently
being evaluated, is introduced for the proof. Although it is similar to the set @{term stabl}
maintained by the original TD, it is only an under-approximation of it.
A valid solver state is one, where @{term σ} is a partial solution for all truly stable unknowns,
i.e., unknowns in @{term "s - c"}, and where these truly stable unknowns only depend on unknowns
which are also truly stable or currently being evaluated.
A substantial part of the partial correctness proof is to show that this property about the
solver's state is preserved during a solver's run.›
definition invariant where
"invariant s c σ ≡ (∀ξ∈s - c. dep σ ξ ⊆ s) ∧ part_solution σ (s - c)"
lemma invariant_simp:
assumes "x ∈ c"
and "invariant s (c - {x}) σ"
shows "invariant (insert x s) c σ"
using assms
proof -
have "c - {x} ⊆ s ≡ c ⊆ insert x s"
using assms(1)
by (simp add: subset_insert_iff)
moreover have "s - (c - {x}) ⊇ insert x s - c"
using assms(1)
by auto
ultimately show ?thesis
using assms(2)
unfolding invariant_def
by fastforce
qed
lemma invariant_continue:
assumes "x ∉ s"
and "invariant s c σ"
and "∀y∈s. mlup σ y = mlup σ1 y"
shows "invariant s c (σ1(x ↦ xd))"
proof -
show ?thesis
using assms mlup_eq_mupd_set[OF assms(1,3)] unfolding invariant_def
proof(intro conjI, goal_cases)
case 1 then show ?case using dep_eq by blast
next
case 2 then show ?case using part_solution_coinciding_sigma_called
by (metis DiffD1 solution_sufficient subsetD)
qed
qed
subsection ‹Partial Correctness Proofs›
lemma x_not_stable:
assumes "eq x σ ≠ mlup σ x"
and "part_solution σ s"
shows "x ∉ s"
using assms by auto
text‹With the following lemma we establish, that whenever the solver is called for an unknown
in @{term s} and where the solver state and @{term s} fulfill the invariant, the output value
mapping is unchanged compared to the input value mapping.›
lemma already_solution:
shows "query_dom x y c σ
⟹ query x y c σ = (yd, σ')
⟹ y ∈ s
⟹ invariant s c σ
⟹ σ = σ'"
and "iterate_dom x c σ
⟹ iterate x c σ = (xd, σ')
⟹ x ∈ c
⟹ x ∈ s
⟹ invariant s (c - {x}) σ
⟹ σ = σ'"
and "eval_dom x t c σ
⟹ eval x t c σ = (xd, σ')
⟹ dep_aux σ t ⊆ s
⟹ invariant s c σ
⟹ traverse_rhs t σ' = xd ∧ σ = σ'"
proof(induction arbitrary: yd s σ' and xd s σ' and xd s σ' rule: query_iterate_eval_pinduct)
case (Query x y c σ)
show ?case using Query.IH(1) Query.prems Query.IH(2)
by (cases rule: query_iterate_lookup_cases; simp)
next
case (Iterate x c σ)
show ?case using Iterate.IH(1) Iterate.prems(1,2)
proof(cases rule: iterate_continue_fixpoint_cases)
case Fixpoint
then show ?thesis
using Iterate.prems(3,4) Iterate.IH(2)[of _ _ "insert x s"]
invariant_simp[OF Iterate.prems(2,4)]
unfolding dep_def invariant_def by auto
next
case (Continue σ1 xd')
show ?thesis
proof(rule ccontr)
have IH: "eq x σ1 = xd' ∧ σ = σ1"
using Iterate.prems(2-4) Iterate.IH(2)[OF Continue(2), of s]
invariant_simp[OF Iterate.prems(2,4)] unfolding dep_def invariant_def by auto
then show False
using Iterate.prems(2-4) Continue(3) unfolding invariant_def by simp
qed
qed
next
case (Eval x t c σ)
show ?case using Eval.IH(1) Eval.prems(1)
proof(cases rule: eval_query_answer_cases)
case (Query y g yd σ1)
then show ?thesis using Eval.prems(1-3) Eval.IH(1) Eval.IH(2)[OF Query(1,3)]
Eval.IH(3)[OF Query(1) Query(3)[symmetric] _ Query(5)]
by auto
qed simp
qed
text‹Furthermore, we show that whenever the solver is called with a valid solver state, the valid
solver state invariant also holds for its output state and the set of stable unknowns increases by
the set @{term [source = true] reach_cap} of the current unknown.›
lemma partial_correctness_ind:
shows "query_dom x y c σ
⟹ query x y c σ = (yd, σ')
⟹ invariant s c σ
⟹ invariant (s ∪ reach_cap σ' c y) c σ'
∧ (∀ξ ∈ s. mlup σ ξ = mlup σ' ξ)"
and "iterate_dom x c σ
⟹ iterate x c σ = (xd, σ')
⟹ x ∈ c
⟹ invariant s (c - {x}) σ
⟹ invariant (s ∪ (reach_cap σ' (c - {x}) x)) (c - {x}) σ'
∧ (∀ξ ∈ s. mlup σ ξ = mlup σ' ξ)"
and "eval_dom x t c σ
⟹ eval x t c σ = (xd, σ')
⟹ invariant s c σ
⟹ invariant (s ∪ reach_cap_tree σ' c t) c σ'
∧ (∀ξ ∈ s. mlup σ ξ = mlup σ' ξ)
∧ traverse_rhs t σ' = xd"
proof(induction arbitrary: yd s σ' and xd s σ' and xd s σ' rule: query_iterate_eval_pinduct)
case (Query x y c σ)
show ?case
using Query.IH(1) Query.prems(1)
proof (cases rule: query_iterate_lookup_cases)
case Iterate
note IH = Query.IH(2)[simplified, OF Iterate(4,2) Query.prems(2)]
then show ?thesis
using Iterate(4) by simp
next
case Lookup
then show ?thesis
using Query.prems(2) unfolding invariant_def by auto
qed
next
case (Iterate x c σ)
show ?case
using Iterate.IH(1) Iterate.prems(1,2)
proof(cases rule: iterate_continue_fixpoint_cases)
case Fixpoint
note IH = Iterate.IH(2)[OF Fixpoint(2) invariant_simp[OF Iterate.prems(2,3)], folded eq_def]
then show ?thesis
using Fixpoint(3) Iterate.prems(2) reach_cap_tree_simp2[of x "c - {x}"]
dep_subset_reach_cap_tree[of σ' "T x", folded dep_def]
unfolding invariant_def
by (auto simp add: insert_absorb)
next
case (Continue σ1 xd')
note IH = Iterate.IH(2)[OF Continue(2) invariant_simp[OF Iterate.prems(2,3)]]
have "part_solution σ1 (s - (c - {x}))"
using part_solution_coinciding_sigma_called[of s "c - {x}" σ σ1] IH Iterate.prems(3)
unfolding invariant_def
by simp
then have x_not_stable: "x ∉ s"
using x_not_stable[of x σ1 s] IH Continue(3)
by auto
then have inv: "invariant s (c - {x}) (σ1(x ↦ xd'))"
using IH invariant_continue[OF x_not_stable Iterate.prems(3)] by blast
note ih = Iterate.IH(3)[OF Continue(2)[symmetric] _ Continue(3)[symmetric] Continue(5)
Iterate.prems(2) inv, simplified]
then show ?thesis
using IH mlup_eq_mupd_set[OF x_not_stable, of σ]
unfolding mlup_def
by auto
qed
next
case (Eval x t c σ)
show ?case using Eval.IH(1) Eval.prems(1)
proof(cases rule: eval_query_answer_cases)
case (Query y g yd σ1)
note IH = Eval.IH(2)[OF Query(1,3) Eval.prems(2)]
note ih = Eval.IH(3)[OF Query(1) Query(3)[symmetric] _ Query(5) conjunct1[OF IH], simplified]
show ?thesis
using Query IH ih reach_cap_tree_step reach_cap_tree_eq[of σ1 "insert y c" "T y" σ']
by (auto simp add: Un_assoc)
next
case Answer
then show ?thesis
using Eval.prems(2) by simp
qed
qed
text‹Since the initial solver state fulfills the valid solver state predicate, we can conclude from
the above lemma, that the solve function returns a partial solution for the queried unknown
@{term x} and all unknowns on which it transitively depends.›
corollary partial_correctness:
assumes "solve_dom x"
and "solve x = σ"
shows "part_solution σ (reach σ x)"
proof -
obtain xd where "iterate x {x} Map.empty = (xd, σ)"
using assms(2) unfolding solve_def by (auto split: prod.splits)
then show ?thesis
using assms(1) partial_correctness_ind(2)[of x "{x}" Map.empty xd σ "{}"] reach_empty_capped
unfolding solve_dom_def invariant_def by simp
qed
subsection ‹Termination of TD\_plain for Stable Unknowns›
text‹In the equivalence proof of the TD and the TD\_plain, we need to show that when the TD
trivially terminates because the queried unknown is already stable and its value is only looked
up, the evaluation of this unknown @{term x} with TD\_plain also terminates. For this, we exploit
that the set of stable unknowns is always finite during a terminating solver's run and provide the
following lemma:›
lemma td1_terminates_for_stabl:
assumes "x ∈ s"
and "invariant s (c - {x}) σ"
and "mlup σ x = xd"
and "finite s"
and "x ∈ c"
shows "iterate_dom x c σ" and "iterate x c σ = (xd, σ)"
proof(goal_cases)
have "reach_cap σ (c - {x}) x ⊆ s"
using assms(1,2) dep_closed_implies_reach_cap_tree_closed unfolding invariant_def by simp
from finite_subset[OF this] have "finite (reach_cap σ (c - {x}) x - (c - {x}))"
using assms(4) by simp+
then have goal: "iterate_dom x c σ ∧ iterate x c σ = (xd, σ)" using assms(1-3,5)
proof(induction "reach_cap σ (c - {x}) x - (c - {x})"
arbitrary: x c xd rule: finite_psubset_induct)
case psubset
have "eval_dom x t c σ ∧ (traverse_rhs t σ, σ) = eval x t c σ" if "t ∈ subt σ x" for t
using that
proof(induction t)
case (Answer _)
then show ?case
using query_iterate_eval.domintros(3)[folded query_dom_def iterate_dom_def eval_dom_def]
by fastforce
next
case (Query y g)
have "reach_cap_tree σ (insert x (c - {x})) (T x) ⊆ s"
using dep_closed_implies_reach_cap_tree_closed[OF psubset.prems(1), of c σ]
psubset.prems(2)[unfolded invariant_def]
by auto
then have y_stable: "y ∈ s"
using dep_subset_reach_cap_tree subt_implies_dep[OF Query(2)[unfolded subt_def]]
by blast
show ?case
proof(cases "y ∈ c" rule: case_split[case_names called not_called])
case called
then have dom: "query_dom x y c σ"
using query_iterate_eval.domintros(1)[folded query_dom_def] by auto
moreover have query_val: "(mlup σ y, σ) = query x y c σ"
using called already_solution(1) partial_correctness_ind(1)
by (metis query.psimps query_iterate_eval.domintros(1))
ultimately have "eval_dom x (Query y g) c σ"
using Query.IH[of "g (mlup σ y)"]
query_iterate_eval.domintros(3)[folded dom_defs, of "Query y g" x c σ] Query.prems
subt_aux.step subt_def
by fastforce
have "g (mlup σ y) ∈ subt_aux σ (T x)"
using Query.prems subt_aux.step subt_def by blast
then have "eval_dom x (g (mlup σ y)) c σ"
and "(traverse_rhs (g (mlup σ y)) σ, σ) = eval x (g (mlup σ y)) c σ"
using Query.IH unfolding subt_def by auto
then show ?thesis
using ‹eval_dom x (Query y g) c σ› query_val
by (auto split: strategy_tree.split prod.split)
next
case not_called
then obtain yd where lupy: "mlup σ y = yd" and eqy: "eq y σ = yd"
using y_stable psubset.prems(2) unfolding invariant_def by auto
have ih: "eval_dom x (g (mlup σ y)) c σ"
and "(traverse_rhs (g (mlup σ y)) σ, σ) = eval x (g (mlup σ y)) c σ"
using Query.IH[of "g (mlup σ y)"] Query.prems subt_aux.step subt_def by auto
moreover have "reach_cap σ c y ⊆ reach_cap σ (c - {x}) x"
using not_called psubset.prems(4) reach_cap_tree_step[of σ y yd c g, OF lupy]
reach_cap_tree_subset_subt[of "Query y g" σ "T x" c, folded subt_def, OF Query.prems]
by (simp add: insert_absorb subset_insertI2)
then have f_def: "reach_cap σ c y - c ⊂ reach_cap σ (c - {x}) x - (c - {x})"
using psubset.prems(4)
by blast
have "invariant s (c - {y}) σ"
using psubset.prems(2) not_called psubset.prems(1) invariant_simp
by (metis Diff_empty Diff_insert0 insert_absorb)
then have IH: "iterate_dom y (insert y c) σ ∧ iterate y (insert y c) σ = (yd, σ)"
using f_def y_stable not_called lupy psubset.hyps(2)[of y "c - {y}" yd] psubset.hyps(2)
by (metis Diff_idemp Diff_insert_absorb insertCI )
then have "query_dom x y c σ ∧ (mlup σ y, σ) = query x y c σ"
using not_called lupy query_iterate_eval.domintros(1)[folded dom_defs, of y c σ]
by simp
ultimately show ?thesis
using query_iterate_eval.domintros(3)[folded dom_defs, of "Query y g" x c σ] by fastforce
qed
qed
note IH = this[of "T x", folded eq_def, OF subt_aux.base[of "T x" σ, folded subt_def]]
moreover have "eq x σ = mlup σ x" using psubset.prems(1,2) unfolding invariant_def by auto
moreover have "iterate_dom x c σ"
using query_iterate_eval.domintros(2)[folded dom_defs, of x c σ] IH ‹eq x σ = mlup σ x›
by (metis Pair_inject)
ultimately show ?case
using iterate.psimps[folded dom_defs, of x c σ] psubset.prems(3)
by (cases "eval x (T x) c σ") auto
qed
case 1 show ?case using goal ..
case 2 show ?case using goal ..
qed
subsection ‹Program Refinement for Code Generation›
text‹For code generation, we define a refined version of the solver function using the
partial\_function keyword with the option attribute.›