Theory Acyclic_EGraph_Extraction
section ‹Acyclic e-graph extraction›
theory Acyclic_EGraph_Extraction
imports Extraction_Certificates
begin
text ‹
A finite acyclic e-graph is represented by a list of e-classes in
topological order. An e-node consists of a function symbol and indexes of
child classes. Every child index must be smaller than the index of the
containing class. Thus the list representation is both finite and a
certificate of acyclicity. E-graph expressions are ground terms; atoms such
as source-language variables are represented by nullary function symbols, as
in egg's recursive-expression format.
The represented terms of a class include every e-node in that class and
every combination of terms represented by its child classes. Extraction
below is a bottom-up dynamic program: after finding an optimum for each
earlier class, it instantiates every e-node with those optima and retains a
minimum-cost result.
›
type_synonym 'f = "'f × nat list"
type_synonym 'f = "'f dag_enode list"
type_synonym 'f = "'f dag_eclass list"
:: "'f acyclic_egraph ⇒ bool" where
"wf_acyclic_egraph G ⟷
(∀i < length G.
G ! i ≠ [] ∧
(∀node ∈ set (G ! i). ∀j ∈ set (snd node). j < i))"
::
"'f acyclic_egraph ⇒ nat ⇒ ('f, unit) term ⇒ bool"
for G where
:
"i < length G ⟹
(f, children) ∈ set (G ! i) ⟹
list_all2 (represents_eclass G) children ts ⟹
represents_eclass G i (Fun f ts)"
::
"('f, unit) term list ⇒ 'f dag_enode ⇒ ('f, unit) term"
where
"instantiate_enode memo node =
Fun (fst node) (map ((!) memo) (snd node))"
::
"('f ⇒ nat) ⇒ ('f, unit) term list ⇒
'f dag_eclass ⇒ ('f, unit) term"
where
"best_eclass_term w memo cls =
instantiate_enode memo
(arg_min_list (term_cost w ∘ instantiate_enode memo) cls)"
::
"('f ⇒ nat) ⇒ 'f acyclic_egraph ⇒ nat ⇒
('f, unit) term list"
where
"extract_prefix w G 0 = []"
| "extract_prefix w G (Suc i) =
(let memo = extract_prefix w G i
in memo @ [best_eclass_term w memo (G ! i)])"
::
"('f ⇒ nat) ⇒ 'f acyclic_egraph ⇒
('f, unit) term list option"
where
"extract_egraph w G =
(if wf_acyclic_egraph G
then Some (extract_prefix w G (length G))
else None)"
::
"('f ⇒ nat) ⇒ 'f acyclic_egraph ⇒ nat ⇒
('f, unit) term option"
where
"extract_eclass w G i =
(if wf_acyclic_egraph G ∧ i < length G
then Some (extract_prefix w G (length G) ! i)
else None)"
lemma [simp]:
"length (extract_prefix w G n) = n"
by (induction n) (simp_all add: Let_def)
lemma :
assumes "j < n"
shows "extract_prefix w G n ! j = extract_prefix w G (Suc j) ! j"
using assms
proof (induction n)
case 0
then show ?case by simp
next
case (Suc n)
show ?case
proof (cases "j = n")
case True
then show ?thesis by simp
next
case False
with Suc.prems have "j < n" by simp
then have "j < length (extract_prefix w G n)" by simp
then have "extract_prefix w G (Suc n) ! j = extract_prefix w G n ! j"
unfolding extract_prefix.simps Let_def
by (rule nth_append_left)
also have "… = extract_prefix w G (Suc j) ! j"
by (rule Suc.IH[OF ‹j < n›])
finally show ?thesis .
qed
qed
lemma :
fixes score :: "'a ⇒ nat"
assumes "xs ≠ []" and "x ∈ set xs"
shows "score (arg_min_list score xs) ≤ score x"
proof -
have "score (arg_min_list score xs) = Min (score ` set xs)"
by (rule f_arg_min_list_f[OF assms(1)])
also have "… ≤ score x"
by (rule Min_le) (use assms in auto)
finally show ?thesis .
qed
lemma :
fixes xs ys :: "nat list"
assumes "list_all2 (≤) xs ys"
shows "sum_list xs ≤ sum_list ys"
using assms by (induction rule: list_all2_induct) simp_all
lemma :
assumes "∀x ∈ set xs. finite {y. P x y}"
shows "finite {ys. list_all2 P xs ys}"
using assms
proof (induction xs)
case Nil
then show ?case by simp
next
case (Cons x xs)
have "{ys. list_all2 P (x # xs) ys} =
(λ(y, ys). y # ys) ` ({y. P x y} × {ys. list_all2 P xs ys})"
by (auto simp: list_all2_Cons1)
moreover have "finite ({y. P x y} × {ys. list_all2 P xs ys})"
using Cons by auto
ultimately show ?case by simp
qed
theorem :
assumes wf: "wf_acyclic_egraph G" and i: "i < length G"
shows "finite {t. represents_eclass G i t}"
proof -
have finite_at:
"k < length G ⟹ finite {t. represents_eclass G k t}" for k
proof (induction k rule: less_induct)
case (less k)
have class_wf:
"G ! k ≠ [] ∧
(∀node ∈ set (G ! k). ∀j ∈ set (snd node). j < k)"
using wf less.prems unfolding wf_acyclic_egraph_def by blast
have fibres:
"finite {ts. list_all2 (represents_eclass G) children ts}"
if node: "(f, children) ∈ set (G ! k)" for f children
proof (rule finite_list_all2_fibres)
show "∀j ∈ set children. finite {t. represents_eclass G j t}"
proof (intro ballI)
fix j
assume "j ∈ set children"
with class_wf node have jk: "j < k" by auto
with less.prems have "j < length G" by simp
from less.IH[OF jk this] show "finite {t. represents_eclass G j t}" .
qed
qed
have represented_union:
"{t. represents_eclass G k t} =
(⋃(f, children) ∈ set (G ! k).
Fun f ` {ts. list_all2 (represents_eclass G) children ts})"
proof (rule set_eqI, rule iffI)
fix t
assume "t ∈ {t. represents_eclass G k t}"
then have rep: "represents_eclass G k t" by simp
from rep obtain f children ts where
t: "t = Fun f ts" and node: "(f, children) ∈ set (G ! k)" and
related: "list_all2 (represents_eclass G) children ts"
by (cases rule: represents_eclass.cases) auto
from t node related show
"t ∈ (⋃(f, children) ∈ set (G ! k).
Fun f ` {ts. list_all2 (represents_eclass G) children ts})"
by auto
next
fix t
assume
"t ∈ (⋃(f, children) ∈ set (G ! k).
Fun f ` {ts. list_all2 (represents_eclass G) children ts})"
then obtain f children ts where
node: "(f, children) ∈ set (G ! k)" and
related: "list_all2 (represents_eclass G) children ts" and
t: "t = Fun f ts"
by auto
show "t ∈ {t. represents_eclass G k t}"
using represents_eclass.represents_node[OF less.prems node related] t
by simp
qed
show ?case unfolding represented_union using fibres by auto
qed
from finite_at[OF i] show ?thesis .
qed
lemma :
assumes "cls ≠ []"
shows "best_eclass_term w memo cls
= instantiate_enode memo
(arg_min_list (term_cost w ∘ instantiate_enode memo) cls)"
and "arg_min_list (term_cost w ∘ instantiate_enode memo) cls
∈ set cls"
using assms unfolding best_eclass_term_def
by (simp_all add: arg_min_list_in)
theorem :
assumes wf: "wf_acyclic_egraph G" and i: "i < length G"
shows
"represents_eclass G i (extract_prefix w G (Suc i) ! i)"
"⋀t. represents_eclass G i t ⟹
term_cost w (extract_prefix w G (Suc i) ! i) ≤ term_cost w t"
proof -
have correct:
"k < length G ⟹
represents_eclass G k (extract_prefix w G (Suc k) ! k) ∧
(∀t. represents_eclass G k t ⟶
term_cost w (extract_prefix w G (Suc k) ! k) ≤ term_cost w t)"
for k
proof (induction k rule: less_induct)
case (less k)
let ?memo = "extract_prefix w G k"
let ?cls = "G ! k"
let ?score = "term_cost w ∘ instantiate_enode ?memo"
let ?best = "arg_min_list ?score ?cls"
have class_wf:
"?cls ≠ [] ∧
(∀node ∈ set ?cls. ∀j ∈ set (snd node). j < k)"
using wf less.prems unfolding wf_acyclic_egraph_def by blast
have best_mem: "?best ∈ set ?cls"
by (rule arg_min_list_in[OF class_wf[THEN conjunct1]])
obtain f children where best: "?best = (f, children)"
by (cases ?best)
have children_lt: "∀j ∈ set children. j < k"
using class_wf best_mem best by auto
have child_rep:
"list_all2 (represents_eclass G) children
(map ((!) ?memo) children)"
proof (rule list_all2_all_nthI)
show "length children = length (map ((!) ?memo) children)" by simp
fix n
assume n: "n < length children"
let ?j = "children ! n"
have j_mem: "?j ∈ set children" using n by simp
with children_lt have jk: "?j < k" by blast
with less.prems have jG: "?j < length G" by simp
have rep:
"represents_eclass G ?j (extract_prefix w G (Suc ?j) ! ?j)"
using less.IH[OF jk jG] by blast
have "?memo ! ?j = extract_prefix w G (Suc ?j) ! ?j"
by (rule extract_prefix_nth_stable[OF jk])
with rep show
"represents_eclass G (children ! n)
(map ((!) ?memo) children ! n)"
using n by simp
qed
have chosen:
"extract_prefix w G (Suc k) ! k = instantiate_enode ?memo ?best"
proof -
have len: "k = length ?memo" by simp
have "(?memo @ [instantiate_enode ?memo ?best]) ! k =
instantiate_enode ?memo ?best"
using len by (metis nth_append_length)
then show ?thesis by (simp add: Let_def best_eclass_term_def)
qed
have rep:
"represents_eclass G k (extract_prefix w G (Suc k) ! k)"
proof -
have node_best: "(f, children) ∈ set ?cls"
using best_mem best by simp
have "represents_eclass G k
(Fun f (map ((!) ?memo) children))"
by (rule represents_eclass.represents_node[
OF less.prems node_best child_rep])
then show ?thesis
using chosen best unfolding instantiate_enode_def by simp
qed
have minimal:
"term_cost w (extract_prefix w G (Suc k) ! k) ≤ term_cost w t"
if t: "represents_eclass G k t" for t
proof -
from t obtain g cs ts where
t_eq: "t = Fun g ts" and node: "(g, cs) ∈ set ?cls" and
related: "list_all2 (represents_eclass G) cs ts"
using less.prems by (cases rule: represents_eclass.cases) auto
have candidate_le:
"term_cost w (instantiate_enode ?memo ?best)
≤ term_cost w (instantiate_enode ?memo (g, cs))"
using arg_min_list_le[OF class_wf[THEN conjunct1] node, of ?score]
by simp
have child_costs:
"list_all2 (≤)
(map (term_cost w) (map ((!) ?memo) cs))
(map (term_cost w) ts)"
proof (rule list_all2_all_nthI)
show "length (map (term_cost w) (map ((!) ?memo) cs))
= length (map (term_cost w) ts)"
using list_all2_lengthD[OF related] by simp
fix n
assume n: "n < length (map (term_cost w) (map ((!) ?memo) cs))"
then have ncs: "n < length cs" by simp
let ?j = "cs ! n"
have j_mem: "?j ∈ set cs" using ncs by simp
from class_wf node j_mem have jk: "?j < k" by auto
with less.prems have jG: "?j < length G" by simp
have rep_child: "represents_eclass G ?j (ts ! n)"
using list_all2_nthD[OF related ncs] .
have le:
"term_cost w (extract_prefix w G (Suc ?j) ! ?j)
≤ term_cost w (ts ! n)"
using less.IH[OF jk jG] rep_child by blast
have "?memo ! ?j = extract_prefix w G (Suc ?j) ! ?j"
by (rule extract_prefix_nth_stable[OF jk])
with le show
"map (term_cost w) (map ((!) ?memo) cs) ! n
≤ map (term_cost w) ts ! n"
using ncs list_all2_lengthD[OF related] by simp
qed
have instantiated_le:
"term_cost w (instantiate_enode ?memo (g, cs)) ≤ term_cost w t"
using sum_list_mono_list_all2[OF child_costs]
unfolding instantiate_enode_def t_eq by simp
from candidate_le instantiated_le show ?thesis
unfolding chosen by simp
qed
show ?case using rep minimal by blast
qed
from correct[OF i] show
"represents_eclass G i (extract_prefix w G (Suc i) ! i)" by blast
from correct[OF i] show
"term_cost w (extract_prefix w G (Suc i) ! i) ≤ term_cost w t"
if "represents_eclass G i t" for t
using that by blast
qed
theorem :
assumes wf: "wf_acyclic_egraph G" and i: "i < length G"
obtains t where
"extract_eclass w G i = Some t"
"represents_eclass G i t"
"⋀u. represents_eclass G i u ⟹
term_cost w t ≤ term_cost w u"
proof -
let ?t = "extract_prefix w G (length G) ! i"
have stable:
"?t = extract_prefix w G (Suc i) ! i"
by (rule extract_prefix_nth_stable[OF i])
have extracted: "extract_eclass w G i = Some ?t"
using wf i unfolding extract_eclass_def by simp
have represented: "represents_eclass G i ?t"
unfolding stable by (rule extract_prefix_optimal(1)[OF wf i])
have minimal:
"term_cost w ?t ≤ term_cost w u"
if "represents_eclass G i u" for u
unfolding stable by (rule extract_prefix_optimal(2)[OF wf i that])
from that[OF extracted represented minimal] show thesis .
qed
end