1use std::collections::HashMap;
29
30use crate::term::{Literal, Term};
31
32type TermId = usize;
33
34pub use logicaffeine_base::union_find::UnionFind;
37
38#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47pub enum ENode {
48 Lit(i64),
50 Var(i64),
52 Name(String),
54 App {
56 func: TermId,
58 arg: TermId,
60 },
61}
62
63pub struct EGraph {
68 nodes: Vec<ENode>,
70 uf: UnionFind,
72 node_map: HashMap<ENode, TermId>,
74 pending: Vec<(TermId, TermId)>,
76 use_list: Vec<Vec<TermId>>,
79}
80
81impl EGraph {
82 pub fn new() -> Self {
83 EGraph {
84 nodes: Vec::new(),
85 uf: UnionFind::new(),
86 node_map: HashMap::new(),
87 pending: Vec::new(),
88 use_list: Vec::new(),
89 }
90 }
91
92 pub fn add(&mut self, node: ENode) -> TermId {
94 if let Some(&id) = self.node_map.get(&node) {
96 return id;
97 }
98
99 let id = self.nodes.len();
100 self.nodes.push(node.clone());
101 self.node_map.insert(node.clone(), id);
102 self.uf.make_set();
103 self.use_list.push(Vec::new());
104
105 if let ENode::App { func, arg } = &node {
107 self.use_list[*func].push(id);
108 self.use_list[*arg].push(id);
109 }
110
111 id
112 }
113
114 pub fn merge(&mut self, a: TermId, b: TermId) {
116 self.pending.push((a, b));
117 self.propagate();
118 }
119
120 fn propagate(&mut self) {
122 while let Some((a, b)) = self.pending.pop() {
123 let ra = self.uf.find(a);
124 let rb = self.uf.find(b);
125 if ra == rb {
126 continue;
127 }
128
129 let uses_a: Vec<TermId> = self.use_list[ra].clone();
131 let uses_b: Vec<TermId> = self.use_list[rb].clone();
132
133 self.uf.union(ra, rb);
135 let new_root = self.uf.find(ra);
136
137 for &ua in &uses_a {
140 for &ub in &uses_b {
141 if self.congruent(ua, ub) {
142 self.pending.push((ua, ub));
143 }
144 }
145 }
146
147 if new_root == ra {
149 for u in uses_b {
150 self.use_list[ra].push(u);
151 }
152 } else {
153 for u in uses_a {
154 self.use_list[rb].push(u);
155 }
156 }
157 }
158 }
159
160 fn congruent(&mut self, a: TermId, b: TermId) -> bool {
162 match (&self.nodes[a].clone(), &self.nodes[b].clone()) {
163 (ENode::App { func: f1, arg: a1 }, ENode::App { func: f2, arg: a2 }) => {
164 self.uf.find(*f1) == self.uf.find(*f2) && self.uf.find(*a1) == self.uf.find(*a2)
165 }
166 _ => false,
167 }
168 }
169
170 pub fn equivalent(&mut self, a: TermId, b: TermId) -> bool {
172 self.uf.find(a) == self.uf.find(b)
173 }
174}
175
176pub fn reify(egraph: &mut EGraph, term: &Term) -> Option<TermId> {
190 if let Some(n) = extract_slit(term) {
192 return Some(egraph.add(ENode::Lit(n)));
193 }
194
195 if let Some(i) = extract_svar(term) {
197 return Some(egraph.add(ENode::Var(i)));
198 }
199
200 if let Some(name) = extract_sname(term) {
202 return Some(egraph.add(ENode::Name(name)));
203 }
204
205 if let Some((func_term, arg_term)) = extract_sapp(term) {
207 let func = reify(egraph, &func_term)?;
208 let arg = reify(egraph, &arg_term)?;
209 return Some(egraph.add(ENode::App { func, arg }));
210 }
211
212 None
213}
214
215pub fn decompose_goal(goal: &Term) -> (Vec<(Term, Term)>, Term) {
233 let mut hypotheses = Vec::new();
234 let mut current = goal.clone();
235
236 while let Some((hyp, rest)) = extract_implication(¤t) {
238 if let Some((lhs, rhs)) = extract_equality(&hyp) {
239 hypotheses.push((lhs, rhs));
240 }
241 current = rest;
242 }
243
244 (hypotheses, current)
245}
246
247pub fn check_goal(goal: &Term) -> bool {
263 let (hypotheses, conclusion) = decompose_goal(goal);
264
265 let (lhs, rhs) = match extract_equality(&conclusion) {
267 Some(eq) => eq,
268 None => return false,
269 };
270
271 let mut egraph = EGraph::new();
272
273 let lhs_id = match reify(&mut egraph, &lhs) {
277 Some(id) => id,
278 None => return false,
279 };
280
281 let rhs_id = match reify(&mut egraph, &rhs) {
282 Some(id) => id,
283 None => return false,
284 };
285
286 for (h_lhs, h_rhs) in &hypotheses {
289 let h_lhs_id = match reify(&mut egraph, h_lhs) {
290 Some(id) => id,
291 None => return false,
292 };
293 let h_rhs_id = match reify(&mut egraph, h_rhs) {
294 Some(id) => id,
295 None => return false,
296 };
297 egraph.merge(h_lhs_id, h_rhs_id);
298 }
299
300 egraph.equivalent(lhs_id, rhs_id)
302}
303
304fn extract_slit(term: &Term) -> Option<i64> {
310 if let Term::App(ctor, arg) = term {
311 if let Term::Global(name) = ctor.as_ref() {
312 if name == "SLit" {
313 if let Term::Lit(Literal::Int(n)) = arg.as_ref() {
314 return Some(*n);
315 }
316 }
317 }
318 }
319 None
320}
321
322fn extract_svar(term: &Term) -> Option<i64> {
324 if let Term::App(ctor, arg) = term {
325 if let Term::Global(name) = ctor.as_ref() {
326 if name == "SVar" {
327 if let Term::Lit(Literal::Int(i)) = arg.as_ref() {
328 return Some(*i);
329 }
330 }
331 }
332 }
333 None
334}
335
336fn extract_sname(term: &Term) -> Option<String> {
338 if let Term::App(ctor, arg) = term {
339 if let Term::Global(name) = ctor.as_ref() {
340 if name == "SName" {
341 if let Term::Lit(Literal::Text(s)) = arg.as_ref() {
342 return Some(s.clone());
343 }
344 }
345 }
346 }
347 None
348}
349
350fn extract_sapp(term: &Term) -> Option<(Term, Term)> {
352 if let Term::App(outer, arg) = term {
353 if let Term::App(sapp, func) = outer.as_ref() {
354 if let Term::Global(ctor) = sapp.as_ref() {
355 if ctor == "SApp" {
356 return Some((func.as_ref().clone(), arg.as_ref().clone()));
357 }
358 }
359 }
360 }
361 None
362}
363
364fn extract_implication(term: &Term) -> Option<(Term, Term)> {
366 if let Some((op, hyp, concl)) = extract_binary_app(term) {
367 if op == "implies" {
368 return Some((hyp, concl));
369 }
370 }
371 None
372}
373
374fn extract_equality(term: &Term) -> Option<(Term, Term)> {
376 if let Some((op, lhs, rhs)) = extract_binary_app(term) {
377 if op == "Eq" || op == "eq" {
378 return Some((lhs, rhs));
379 }
380 }
381 None
382}
383
384fn extract_binary_app(term: &Term) -> Option<(String, Term, Term)> {
386 if let Term::App(outer, b) = term {
387 if let Term::App(sapp_outer, inner) = outer.as_ref() {
388 if let Term::Global(ctor) = sapp_outer.as_ref() {
389 if ctor == "SApp" {
390 if let Term::App(partial, a) = inner.as_ref() {
391 if let Term::App(sapp_inner, op_term) = partial.as_ref() {
392 if let Term::Global(ctor2) = sapp_inner.as_ref() {
393 if ctor2 == "SApp" {
394 if let Some(op) = extract_sname(op_term) {
395 return Some((
396 op,
397 a.as_ref().clone(),
398 b.as_ref().clone(),
399 ));
400 }
401 }
402 }
403 }
404 }
405 }
406 }
407 }
408 }
409 None
410}
411
412#[cfg(test)]
417mod tests {
418 use super::*;
419
420 #[test]
421 fn test_union_find_basic() {
422 let mut uf = UnionFind::new();
423 let a = uf.make_set();
424 let b = uf.make_set();
425 assert_ne!(uf.find(a), uf.find(b));
426 uf.union(a, b);
427 assert_eq!(uf.find(a), uf.find(b));
428 }
429
430 #[test]
431 fn test_union_find_transitivity() {
432 let mut uf = UnionFind::new();
433 let a = uf.make_set();
434 let b = uf.make_set();
435 let c = uf.make_set();
436 uf.union(a, b);
437 uf.union(b, c);
438 assert_eq!(uf.find(a), uf.find(c));
439 }
440
441 #[test]
442 fn test_egraph_reflexive() {
443 let mut eg = EGraph::new();
444 let x = eg.add(ENode::Var(0));
445 assert!(eg.equivalent(x, x));
446 }
447
448 #[test]
449 fn test_egraph_congruence() {
450 let mut eg = EGraph::new();
451 let x = eg.add(ENode::Var(0));
452 let y = eg.add(ENode::Var(1));
453 let f = eg.add(ENode::Name("f".to_string()));
454 let fx = eg.add(ENode::App { func: f, arg: x });
455 let fy = eg.add(ENode::App { func: f, arg: y });
456
457 assert!(!eg.equivalent(fx, fy));
459
460 eg.merge(x, y);
462 assert!(eg.equivalent(fx, fy));
463 }
464
465 #[test]
466 fn test_egraph_nested_congruence() {
467 let mut eg = EGraph::new();
468 let a = eg.add(ENode::Var(0));
469 let b = eg.add(ENode::Var(1));
470 let c = eg.add(ENode::Var(2));
471 let f = eg.add(ENode::Name("f".to_string()));
472
473 let fa = eg.add(ENode::App { func: f, arg: a });
474 let fc = eg.add(ENode::App { func: f, arg: c });
475 let ffa = eg.add(ENode::App { func: f, arg: fa });
476 let ffc = eg.add(ENode::App { func: f, arg: fc });
477
478 eg.merge(a, b);
480 eg.merge(b, c);
481 assert!(eg.equivalent(ffa, ffc));
482 }
483
484 #[test]
485 fn test_egraph_binary_congruence() {
486 let mut eg = EGraph::new();
487 let a = eg.add(ENode::Var(0));
488 let b = eg.add(ENode::Var(1));
489 let c = eg.add(ENode::Var(2));
490 let add = eg.add(ENode::Name("add".to_string()));
491
492 let add_a = eg.add(ENode::App { func: add, arg: a });
494 let add_b = eg.add(ENode::App { func: add, arg: b });
495 let add_a_c = eg.add(ENode::App { func: add_a, arg: c });
496 let add_b_c = eg.add(ENode::App { func: add_b, arg: c });
497
498 assert!(!eg.equivalent(add_a_c, add_b_c));
499 eg.merge(a, b);
500 assert!(eg.equivalent(add_a_c, add_b_c));
501 }
502
503 fn make_sname(s: &str) -> Term {
509 Term::App(
510 Box::new(Term::Global("SName".to_string())),
511 Box::new(Term::Lit(Literal::Text(s.to_string()))),
512 )
513 }
514
515 fn make_svar(i: i64) -> Term {
517 Term::App(
518 Box::new(Term::Global("SVar".to_string())),
519 Box::new(Term::Lit(Literal::Int(i))),
520 )
521 }
522
523 fn make_sapp(f: Term, a: Term) -> Term {
525 Term::App(
526 Box::new(Term::App(
527 Box::new(Term::Global("SApp".to_string())),
528 Box::new(f),
529 )),
530 Box::new(a),
531 )
532 }
533
534 #[test]
535 fn test_extract_sname() {
536 let term = make_sname("f");
537 assert_eq!(extract_sname(&term), Some("f".to_string()));
538 }
539
540 #[test]
541 fn test_extract_svar() {
542 let term = make_svar(0);
543 assert_eq!(extract_svar(&term), Some(0));
544 }
545
546 #[test]
547 fn test_extract_sapp() {
548 let term = make_sapp(make_sname("f"), make_svar(0));
550 let result = extract_sapp(&term);
551 assert!(result.is_some());
552 let (func, arg) = result.unwrap();
553 assert_eq!(extract_sname(&func), Some("f".to_string()));
554 assert_eq!(extract_svar(&arg), Some(0));
555 }
556
557 #[test]
558 fn test_extract_binary_app() {
559 let term = make_sapp(make_sapp(make_sname("Eq"), make_svar(0)), make_svar(1));
561 let result = extract_binary_app(&term);
562 assert!(result.is_some(), "Should extract binary app");
563 let (op, a, b) = result.unwrap();
564 assert_eq!(op, "Eq");
565 assert_eq!(extract_svar(&a), Some(0));
566 assert_eq!(extract_svar(&b), Some(1));
567 }
568
569 #[test]
570 fn test_extract_equality() {
571 let term = make_sapp(make_sapp(make_sname("Eq"), make_svar(0)), make_svar(1));
573 let result = extract_equality(&term);
574 assert!(result.is_some(), "Should extract equality");
575 let (lhs, rhs) = result.unwrap();
576 assert_eq!(extract_svar(&lhs), Some(0));
577 assert_eq!(extract_svar(&rhs), Some(1));
578 }
579
580 #[test]
581 fn test_extract_implication() {
582 let x = make_svar(0);
586 let y = make_svar(1);
587 let hyp = make_sapp(make_sapp(make_sname("Eq"), x.clone()), y.clone());
588
589 let f = make_sname("f");
590 let fx = make_sapp(f.clone(), x);
591 let fy = make_sapp(f, y);
592 let concl = make_sapp(make_sapp(make_sname("Eq"), fx), fy);
593
594 let goal = make_sapp(make_sapp(make_sname("implies"), hyp.clone()), concl.clone());
595
596 let result = extract_implication(&goal);
597 assert!(result.is_some(), "Should extract implication");
598 let (hyp_extracted, concl_extracted) = result.unwrap();
599
600 let hyp_eq = extract_equality(&hyp_extracted);
602 assert!(hyp_eq.is_some(), "Hypothesis should be equality");
603 let (h_lhs, h_rhs) = hyp_eq.unwrap();
604 assert_eq!(extract_svar(&h_lhs), Some(0));
605 assert_eq!(extract_svar(&h_rhs), Some(1));
606
607 let concl_eq = extract_equality(&concl_extracted);
609 assert!(concl_eq.is_some(), "Conclusion should be equality");
610 }
611
612 #[test]
613 fn test_decompose_goal_with_hypothesis() {
614 let x = make_svar(0);
616 let y = make_svar(1);
617 let hyp = make_sapp(make_sapp(make_sname("Eq"), x.clone()), y.clone());
618
619 let f = make_sname("f");
620 let fx = make_sapp(f.clone(), x);
621 let fy = make_sapp(f, y);
622 let concl = make_sapp(make_sapp(make_sname("Eq"), fx), fy);
623
624 let goal = make_sapp(make_sapp(make_sname("implies"), hyp), concl);
625
626 let (hypotheses, conclusion) = decompose_goal(&goal);
627 assert_eq!(hypotheses.len(), 1, "Should have 1 hypothesis");
628
629 let concl_eq = extract_equality(&conclusion);
631 assert!(concl_eq.is_some(), "Conclusion should be equality");
632 }
633
634 #[test]
635 fn test_check_goal_with_hypothesis() {
636 let x = make_svar(0);
639 let y = make_svar(1);
640 let hyp = make_sapp(make_sapp(make_sname("Eq"), x.clone()), y.clone());
641
642 let f = make_sname("f");
643 let fx = make_sapp(f.clone(), x.clone());
644 let fy = make_sapp(f.clone(), y.clone());
645 let concl = make_sapp(make_sapp(make_sname("Eq"), fx), fy);
646
647 let goal = make_sapp(make_sapp(make_sname("implies"), hyp), concl);
648
649 assert!(check_goal(&goal), "CC should prove x=y → f(x)=f(y)");
650 }
651}