egg/tutorials/
_03_explanations.rs

1// -*- mode: markdown;  markdown-fontify-code-block-default-mode: rustic-mode; evil-shift-width: 2; -*-
2/*!
3
4# Explanations
5
6It's often useful to know exactly why two terms are equivalent in
7  the egraph.
8For example, if you are trying to debug incorrect rules,
9  it would be useful to have a trace of rewrites showing how an example
10  given bad equivalence was found.
11`egg` uses an algorithm adapted from
12  [Proof-Producing Congruence Closure](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.76.1716&rep=rep1&type=pdf)
13  in order to generate such [`Explanation`]s between two given terms.
14
15Consider this program, which prints a [`FlatExplanation`] showing how
16  `(/ (* (/ 2 3) (/ 3 2)) 1)` can be simplified to `1`:
17```
18use egg::{*, rewrite as rw};
19let rules: &[Rewrite<SymbolLang, ()>] = &[
20    rw!("div-one"; "?x" => "(/ ?x 1)"),
21    rw!("unsafe-invert-division"; "(/ ?a ?b)" => "(/ 1 (/ ?b ?a))"),
22    rw!("simplify-frac"; "(/ ?a (/ ?b ?c))" => "(/ (* ?a ?c) (* (/ ?b ?c) ?c))"),
23    rw!("cancel-denominator"; "(* (/ ?a ?b) ?b)" => "?a"),
24    rw!("times-zero"; "(* ?a 0)" => "0"),
25];
26
27let start = "(/ (* (/ 2 3) (/ 3 2)) 1)".parse().unwrap();
28let end = "1".parse().unwrap();
29let mut runner = Runner::default().with_explanations_enabled().with_expr(&start).run(rules);
30
31println!("{}", runner.explain_equivalence(&start, &end).get_flat_string());
32```
33
34The output of the program is a series of s-expressions annotated
35  with the rewrite being performed:
36```text
37(/ (* (/ 2 3) (/ 3 2)) 1)
38(Rewrite<= div-one (* (/ 2 3) (/ 3 2)))
39(* (Rewrite=> unsafe-invert-division (/ 1 (/ 3 2))) (/ 3 2))
40(Rewrite=> cancel-denominator 1)
41```
42At each step, the part of the term being rewritten is annotated
43  with the rule being applied.
44Each term besides the first term has exactly one rewrite annotation.
45`Rewrite=>` indicates that the previous term is rewritten to the current term
46  and `Rewrite<=` indicates that the current term is rewritten to the previous term.
47
48It turns out that these rules can easily lead to undesirable results in the egraph.
49For example, with just `0` as the starting term, the egraph finds that `0` is equivalent
50  to `1` within a few iterations.
51Here's the flattened explanation that `egg` generates:
52```text
530
54(Rewrite<= times-zero (* (/ 1 0) 0))
55(Rewrite=> cancel-denominator 1)
56```
57
58This tells you how the egraph got from `0` to `1`, but it's not clear why.
59In fact, normally the rules `times-zero` and `cancel-denominator` are perfectly
60  reasonable.
61However, in the presence of a division by zero, they lead to arbitrary unions in the egraph.
62So the true problem is the presense of the term `(/ 1 0)`.
63For these kinds of questions, `egg` provides the `explain_existance` function which can be used to get an explanation
64  of why a term exists in the egraph in the first place.
65
66
67# Explanation Trees
68
69So far we have looked at the [`FlatExplanation`] represenation of explanations because
70  they are the most human-readable.
71But explanations can also be used for automatic testing or translation validation of egraph results,
72  so the flat representation is not always necessary.
73In fact, the flattened representation misses the opportunity to share parts of the explanation
74  among several different terms.
75Egraphs tend to generate explanations with a large amount of duplication of explanations
76  from one term to another, making explanation-sharing very important.
77To solve this problem, `egg` provides the [`TreeExplanation`] representation.
78
79Here's an example [`TreeExplanation`] in string form:
80```text
81(+ 1 (- a (* (- 2 1) a)))
82 (+
83    1
84    (Explanation
85      (- a (* (- 2 1) a))
86      (-
87        a
88        (Explanation
89          (* (- 2 1) a)
90          (* (Explanation (- 2 1) (Rewrite=> constant_fold 1)) a)
91          (Rewrite=> comm-mul (* a 1))
92          (Rewrite<= mul-one a)))
93      (Rewrite=> cancel-sub 0)))
94 (Rewrite=> constant_fold 1)
95```
96
97The big difference between [`FlatExplanation`] and [`TreeExplanation`] is that now
98  children of terms can contain explanations themselves.
99So a [`TreeTerm`] can have have each of their children be rewritten from an initial term
100  to a final term, making the representation more compact.
101In addition, the string format supports let bindings in order to allow sharing of explantions:
102
103```text
104(let
105  (v_0 (- 2 1))
106  (let
107    (v_1 (- 2 (Explanation v_0 (Rewrite=> constant_fold 1))))
108    (Explanation
109      (* (- 2 (- 2 1)) (- 2 (- 2 1)))
110      (*
111        (Explanation (- 2 (- 2 1)) v_1 (Rewrite=> constant_fold 1))
112        (Explanation (- 2 (- 2 1)) v_1 (Rewrite=> constant_fold 1)))
113      (Rewrite=> constant_fold 1))))
114```
115As you can see, the let binding allows for sharing the term `v_1`.
116There are other duplicate expressions that could be let bound, but are not because
117  `egg` only binds shared sub-terms found during the explanation generation process.
118
119Besides the string forms, [`TreeExplanation`] and [`FlatExplanation`] encode the same information
120  as Rust objects.
121For proof sharing, each `Rc<TreeTerm>` in the [`TreeExplanation`] can be checked for pointer
122  equality with other terms.
123
124
125[`EGraph`]: super::super::EGraph
126[`Runner`]: super::super::Runner
127[`Explanation`]: super::super::Explanation
128[`TreeExplanation`]: super::super::TreeExplanation
129[`FlatExplanation`]: super::super::FlatExplanation
130[`TreeTerm`]: super::super::TreeTerm
131[`with_explanations_enabled`]: super::super::Runner::with_explanations_enabled
132
133*/