Skip to content

Commit 4747cfe

Browse files
authored
Remove existence explanations (#333)
1 parent a18666d commit 4747cfe

File tree

4 files changed

+13
-226
lines changed

4 files changed

+13
-226
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changes
22

33
## [Unreleased] - ReleaseDate
4+
- Removed existence explanations from egg (the `explain_existance` function). This feature was buggy and not well supported. Supporting it fully required many changes, and it is incompatible with analysis. See #332 for more details.
45
- Change the API of `make` to have mutable access to the e-graph for some [advanced uses cases](https://github.com/egraphs-good/egg/pull/277).
56
- Fix an e-matching performance regression introduced in [this commit](https://github.com/egraphs-good/egg/commit/ae8af8815231e4aba1b78962f8c07ce837ee1c0e#diff-1d06da761111802c793c6e5ca704bfa0d6336d0becf87fddff02d81548a838ab).
67
- Use `quanta` instead of `instant` crate to provide timing information. This can provide a huge speedup if you have lots of rules, since it avoids some syscalls.

src/egraph.rs

Lines changed: 10 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -509,43 +509,6 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> {
509509
}
510510
}
511511

512-
/// When explanations are enabled, this function
513-
/// produces an [`Explanation`] describing how the given expression came
514-
/// to be in the egraph.
515-
///
516-
/// The [`Explanation`] begins with some expression that was added directly
517-
/// into the egraph and ends with the given `expr`.
518-
/// Note that this function can be called again to explain any intermediate terms
519-
/// used in the output [`Explanation`].
520-
pub fn explain_existance(&mut self, expr: &RecExpr<L>) -> Explanation<L> {
521-
let id = self.add_expr_uncanonical(expr);
522-
self.explain_existance_id(id)
523-
}
524-
525-
/// Equivalent to calling [`explain_existance`](EGraph::explain_existance)`(`[`id_to_expr`](EGraph::id_to_expr)`(id))`
526-
/// but more efficient
527-
fn explain_existance_id(&mut self, id: Id) -> Explanation<L> {
528-
if let Some(explain) = &mut self.explain {
529-
explain.with_nodes(&self.nodes).explain_existance(id)
530-
} else {
531-
panic!("Use runner.with_explanations_enabled() or egraph.with_explanations_enabled() before running to get explanations.")
532-
}
533-
}
534-
535-
/// Return an [`Explanation`] for why a pattern appears in the egraph.
536-
pub fn explain_existance_pattern(
537-
&mut self,
538-
pattern: &PatternAst<L>,
539-
subst: &Subst,
540-
) -> Explanation<L> {
541-
let id = self.add_instantiation_noncanonical(pattern, subst);
542-
if let Some(explain) = &mut self.explain {
543-
explain.with_nodes(&self.nodes).explain_existance(id)
544-
} else {
545-
panic!("Use runner.with_explanations_enabled() or egraph.with_explanations_enabled() before running to get explanations.")
546-
}
547-
}
548-
549512
/// Get an explanation for why an expression matches a pattern.
550513
pub fn explain_matches(
551514
&mut self,
@@ -873,14 +836,6 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> {
873836
} else {
874837
new_node_q.push(false);
875838
}
876-
if let Some(explain) = &mut self.explain {
877-
node.for_each(|child| {
878-
// Set the existance reason for new nodes to their parent node.
879-
if new_node_q[usize::from(child)] {
880-
explain.set_existance_reason(new_ids[usize::from(child)], next_id);
881-
}
882-
});
883-
}
884839
new_ids.push(next_id);
885840
}
886841
*new_ids.last().unwrap()
@@ -919,13 +874,6 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> {
919874
new_node_q.push(false);
920875
}
921876

922-
if let Some(explain) = &mut self.explain {
923-
node.for_each(|child| {
924-
if new_node_q[usize::from(child)] {
925-
explain.set_existance_reason(new_ids[usize::from(child)], next_id);
926-
}
927-
});
928-
}
929877
new_ids.push(next_id);
930878
}
931879
}
@@ -1059,11 +1007,11 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> {
10591007
*existing_explain
10601008
} else {
10611009
let new_id = self.unionfind.make_set();
1062-
explain.add(original.clone(), new_id, new_id);
1010+
explain.add(original.clone(), new_id);
10631011
debug_assert_eq!(Id::from(self.nodes.len()), new_id);
10641012
self.nodes.push(original);
10651013
self.unionfind.union(id, new_id);
1066-
explain.union(existing_id, new_id, Justification::Congruence, true);
1014+
explain.union(existing_id, new_id, Justification::Congruence);
10671015
new_id
10681016
}
10691017
} else {
@@ -1072,7 +1020,7 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> {
10721020
} else {
10731021
let id = self.make_new_eclass(enode, original.clone());
10741022
if let Some(explain) = self.explain.as_mut() {
1075-
explain.add(original, id, id);
1023+
explain.add(original, id);
10761024
}
10771025

10781026
// now that we updated explanations, run the analysis for the new eclass
@@ -1152,16 +1100,9 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> {
11521100
rule_name: impl Into<Symbol>,
11531101
) -> (Id, bool) {
11541102
let id1 = self.add_instantiation_noncanonical(from_pat, subst);
1155-
let size_before = self.unionfind.size();
11561103
let id2 = self.add_instantiation_noncanonical(to_pat, subst);
1157-
let rhs_new = self.unionfind.size() > size_before;
11581104

1159-
let did_union = self.perform_union(
1160-
id1,
1161-
id2,
1162-
Some(Justification::Rule(rule_name.into())),
1163-
rhs_new,
1164-
);
1105+
let did_union = self.perform_union(id1, id2, Some(Justification::Rule(rule_name.into())));
11651106
(self.find(id1), did_union)
11661107
}
11671108

@@ -1171,7 +1112,7 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> {
11711112
/// `Id`s returned by functions like [`add_uncanonical`](EGraph::add_uncanonical) is important
11721113
/// to control explanations
11731114
pub fn union_trusted(&mut self, from: Id, to: Id, reason: impl Into<Symbol>) -> bool {
1174-
self.perform_union(from, to, Some(Justification::Rule(reason.into())), false)
1115+
self.perform_union(from, to, Some(Justification::Rule(reason.into())))
11751116
}
11761117

11771118
/// Unions two eclasses given their ids.
@@ -1194,17 +1135,11 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> {
11941135
let caller = std::panic::Location::caller();
11951136
self.union_trusted(id1, id2, caller.to_string())
11961137
} else {
1197-
self.perform_union(id1, id2, None, false)
1138+
self.perform_union(id1, id2, None)
11981139
}
11991140
}
12001141

1201-
fn perform_union(
1202-
&mut self,
1203-
enode_id1: Id,
1204-
enode_id2: Id,
1205-
rule: Option<Justification>,
1206-
any_new_rhs: bool,
1207-
) -> bool {
1142+
fn perform_union(&mut self, enode_id1: Id, enode_id2: Id, rule: Option<Justification>) -> bool {
12081143
N::pre_union(self, enode_id1, enode_id2, &rule);
12091144

12101145
self.clean = false;
@@ -1226,7 +1161,7 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> {
12261161
}
12271162

12281163
if let Some(explain) = &mut self.explain {
1229-
explain.union(enode_id1, enode_id2, rule.unwrap(), any_new_rhs);
1164+
explain.union(enode_id1, enode_id2, rule.unwrap());
12301165
}
12311166

12321167
// make id1 the new root
@@ -1401,12 +1336,8 @@ impl<L: Language, N: Analysis<L>> EGraph<L, N> {
14011336
let mut node = self.nodes[usize::from(class)].clone();
14021337
node.update_children(|id| self.find_mut(id));
14031338
if let Some(memo_class) = self.memo.insert(node, class) {
1404-
let did_something = self.perform_union(
1405-
memo_class,
1406-
class,
1407-
Some(Justification::Congruence),
1408-
false,
1409-
);
1339+
let did_something =
1340+
self.perform_union(memo_class, class, Some(Justification::Congruence));
14101341
n_unions += did_something as usize;
14111342
}
14121343
}

src/explain.rs

Lines changed: 2 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,6 @@ struct ExplainNode {
4646
// neighbors includes parent connections
4747
neighbors: Vec<Connection>,
4848
parent_connection: Connection,
49-
// it was inserted because of:
50-
// 1) it's parent is inserted (points to parent enode)
51-
// 2) a rewrite instantiated it (points to adjacent enode)
52-
// 3) it was inserted directly (points to itself)
53-
// if 1 is true but it's also adjacent (2) then either works and it picks 2
54-
existance_node: Id,
5549
}
5650

5751
#[derive(Debug, Clone)]
@@ -914,11 +908,7 @@ impl<L: Language> Explain<L> {
914908
}
915909
}
916910

917-
pub(crate) fn set_existance_reason(&mut self, node: Id, existance_node: Id) {
918-
self.explainfind[usize::from(node)].existance_node = existance_node;
919-
}
920-
921-
pub(crate) fn add(&mut self, node: L, set: Id, existance_node: Id) -> Id {
911+
pub(crate) fn add(&mut self, node: L, set: Id) -> Id {
922912
assert_eq!(self.explainfind.len(), usize::from(set));
923913
self.uncanon_memo.insert(node, set);
924914
self.explainfind.push(ExplainNode {
@@ -929,7 +919,6 @@ impl<L: Language> Explain<L> {
929919
next: set,
930920
current: set,
931921
},
932-
existance_node,
933922
});
934923
set
935924
}
@@ -986,19 +975,10 @@ impl<L: Language> Explain<L> {
986975
.insert((node2, node1), (BigUint::one(), node1));
987976
}
988977

989-
pub(crate) fn union(
990-
&mut self,
991-
node1: Id,
992-
node2: Id,
993-
justification: Justification,
994-
new_rhs: bool,
995-
) {
978+
pub(crate) fn union(&mut self, node1: Id, node2: Id, justification: Justification) {
996979
if let Justification::Congruence = justification {
997980
// assert!(self.node(node1).matches(self.node(node2)));
998981
}
999-
if new_rhs {
1000-
self.set_existance_reason(node2, node1)
1001-
}
1002982

1003983
self.make_leader(node1);
1004984
self.explainfind[usize::from(node1)].parent_connection.next = node2;
@@ -1103,21 +1083,6 @@ impl<'x, L: Language> ExplainNodes<'x, L> {
11031083
for i in 0..self.explainfind.len() {
11041084
let explain_node = &self.explainfind[i];
11051085

1106-
// check that explanation reasons never form a cycle
1107-
let mut existance = i;
1108-
let mut seen_existance: HashSet<usize> = Default::default();
1109-
loop {
1110-
seen_existance.insert(existance);
1111-
let next = usize::from(self.explainfind[existance].existance_node);
1112-
if existance == next {
1113-
break;
1114-
}
1115-
existance = next;
1116-
if seen_existance.contains(&existance) {
1117-
panic!("Cycle in existance!");
1118-
}
1119-
}
1120-
11211086
if explain_node.parent_connection.next != Id::from(i) {
11221087
let mut current_explanation = self.node_to_flat_explanation(Id::from(i));
11231088
let mut next_explanation =
@@ -1159,17 +1124,6 @@ impl<'x, L: Language> ExplainNodes<'x, L> {
11591124
Explanation::new(self.explain_enodes(left, right, &mut cache, &mut enode_cache, false))
11601125
}
11611126

1162-
pub(crate) fn explain_existance(&mut self, left: Id) -> Explanation<L> {
1163-
let mut cache = Default::default();
1164-
let mut enode_cache = Default::default();
1165-
Explanation::new(self.explain_enode_existance(
1166-
left,
1167-
self.node_to_explanation(left, &mut enode_cache),
1168-
&mut cache,
1169-
&mut enode_cache,
1170-
))
1171-
}
1172-
11731127
fn common_ancestor(&self, mut left: Id, mut right: Id) -> Id {
11741128
let mut seen_left: HashSet<Id> = Default::default();
11751129
let mut seen_right: HashSet<Id> = Default::default();
@@ -1255,62 +1209,6 @@ impl<'x, L: Language> ExplainNodes<'x, L> {
12551209
(left_connections, right_connections)
12561210
}
12571211

1258-
fn explain_enode_existance(
1259-
&self,
1260-
node: Id,
1261-
rest_of_proof: Rc<TreeTerm<L>>,
1262-
cache: &mut ExplainCache<L>,
1263-
enode_cache: &mut NodeExplanationCache<L>,
1264-
) -> TreeExplanation<L> {
1265-
let graphnode = &self.explainfind[usize::from(node)];
1266-
let existance = graphnode.existance_node;
1267-
let existance_node = &self.explainfind[usize::from(existance)];
1268-
// case 1)
1269-
if existance == node {
1270-
return vec![self.node_to_explanation(node, enode_cache), rest_of_proof];
1271-
}
1272-
1273-
// case 2)
1274-
if graphnode.parent_connection.next == existance
1275-
|| existance_node.parent_connection.next == node
1276-
{
1277-
let mut connection = if graphnode.parent_connection.next == existance {
1278-
graphnode.parent_connection.clone()
1279-
} else {
1280-
existance_node.parent_connection.clone()
1281-
};
1282-
1283-
if graphnode.parent_connection.next == existance {
1284-
connection.is_rewrite_forward = !connection.is_rewrite_forward;
1285-
std::mem::swap(&mut connection.next, &mut connection.current);
1286-
}
1287-
1288-
let adj = self.explain_adjacent(connection, cache, enode_cache, false);
1289-
let mut exp = self.explain_enode_existance(existance, adj, cache, enode_cache);
1290-
exp.push(rest_of_proof);
1291-
return exp;
1292-
}
1293-
1294-
// case 3)
1295-
let mut new_rest_of_proof = (*self.node_to_explanation(existance, enode_cache)).clone();
1296-
let mut index_of_child = 0;
1297-
let mut found = false;
1298-
self.node(existance).for_each(|child| {
1299-
if found {
1300-
return;
1301-
}
1302-
if child == node {
1303-
found = true;
1304-
} else {
1305-
index_of_child += 1;
1306-
}
1307-
});
1308-
assert!(found);
1309-
new_rest_of_proof.child_proofs[index_of_child].push(rest_of_proof);
1310-
1311-
self.explain_enode_existance(existance, Rc::new(new_rest_of_proof), cache, enode_cache)
1312-
}
1313-
13141212
fn explain_enodes(
13151213
&self,
13161214
left: Id,
@@ -2048,35 +1946,6 @@ mod tests {
20481946

20491947
egraph.dot().to_dot("target/foo.dot").unwrap();
20501948
}
2051-
2052-
#[test]
2053-
fn simple_explain_exists() {
2054-
//! Same as previous test, but now I want to make a rewrite add some term and see it exists in
2055-
//! more then one step
2056-
use crate::SymbolLang;
2057-
init_logger();
2058-
2059-
let rws: Vec<Rewrite<SymbolLang, ()>> =
2060-
[rewrite!("makeb"; "a" => "b"), rewrite!("makec"; "b" => "c")].to_vec();
2061-
let mut egraph = Runner::default()
2062-
.with_explanations_enabled()
2063-
.without_explanation_length_optimization()
2064-
.with_expr(&"a".parse().unwrap())
2065-
.run(&rws)
2066-
.egraph;
2067-
egraph.rebuild();
2068-
let _a: Symbol = "a".parse().unwrap();
2069-
let _b: Symbol = "b".parse().unwrap();
2070-
let _c: Symbol = "c".parse().unwrap();
2071-
let mut exp = egraph.explain_existance(&"c".parse().unwrap());
2072-
println!("{:?}", exp.make_flat_explanation());
2073-
assert_eq!(
2074-
exp.make_flat_explanation().len(),
2075-
3,
2076-
"Expected 3 steps, got {:?}",
2077-
exp.make_flat_explanation()
2078-
);
2079-
}
20801949
}
20811950

20821951
#[test]

src/run.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -489,20 +489,6 @@ where
489489
self.egraph.explain_equivalence(left, right)
490490
}
491491

492-
/// Calls [`EGraph::explain_existance`](EGraph::explain_existance()).
493-
pub fn explain_existance(&mut self, expr: &RecExpr<L>) -> Explanation<L> {
494-
self.egraph.explain_existance(expr)
495-
}
496-
497-
/// Calls [EGraph::explain_existance_pattern`](EGraph::explain_existance_pattern()).
498-
pub fn explain_existance_pattern(
499-
&mut self,
500-
pattern: &PatternAst<L>,
501-
subst: &Subst,
502-
) -> Explanation<L> {
503-
self.egraph.explain_existance_pattern(pattern, subst)
504-
}
505-
506492
/// Get an explanation for why an expression matches a pattern.
507493
pub fn explain_matches(
508494
&mut self,

0 commit comments

Comments
 (0)