1use crate::ast::FunctionSubtype;
2use crate::termdag::{TermDag, TermId};
3use crate::util::{HashMap, HashSet};
4use crate::*;
5use std::collections::VecDeque;
6
7pub trait CostModel<C: Cost> {
17 fn fold(&self, head: &str, children_cost: &[C], head_cost: C) -> C;
19
20 fn enode_cost(&self, egraph: &EGraph, func: &Function, enode: &Enode<'_>) -> C;
22
23 fn container_cost(
27 &self,
28 egraph: &EGraph,
29 sort: &ArcSort,
30 value: Value,
31 element_costs: &[C],
32 ) -> C {
33 let _egraph = egraph;
34 let _sort = sort;
35 let _value = value;
36 element_costs
37 .iter()
38 .fold(C::identity(), |s, c| s.combine(c))
39 }
40
41 fn base_value_cost(&self, egraph: &EGraph, sort: &ArcSort, value: Value) -> C {
45 let _egraph = egraph;
46 let _sort = sort;
47 let _value = value;
48 C::unit()
49 }
50}
51
52pub trait Cost {
54 fn identity() -> Self;
56
57 fn unit() -> Self;
59
60 fn combine(self, other: &Self) -> Self;
63}
64
65macro_rules! cost_impl_int {
66 ($($cost:ty),*) => {$(
67 impl Cost for $cost {
68 fn identity() -> Self { 0 }
69 fn unit() -> Self { 1 }
70 fn combine(self, other: &Self) -> Self {
71 self.saturating_add(*other)
72 }
73 }
74 )*};
75}
76cost_impl_int!(u8, u16, u32, u64, u128, usize);
77cost_impl_int!(i8, i16, i32, i64, i128, isize);
78
79macro_rules! cost_impl_num {
80 ($($cost:ty),*) => {$(
81 impl Cost for $cost {
82 fn identity() -> Self {
83 use num::Zero;
84 Self::zero()
85 }
86 fn unit() -> Self {
87 use num::One;
88 Self::one()
89 }
90 fn combine(self, other: &Self) -> Self {
91 self + other
92 }
93 }
94 )*};
95}
96cost_impl_num!(num::BigInt, num::BigRational);
97use ordered_float::OrderedFloat;
98cost_impl_num!(f32, f64, OrderedFloat<f32>, OrderedFloat<f64>);
99
100pub type DefaultCost = u64;
101
102#[derive(Default, Clone)]
104pub struct TreeAdditiveCostModel {}
105
106impl CostModel<DefaultCost> for TreeAdditiveCostModel {
107 fn fold(
108 &self,
109 _head: &str,
110 children_cost: &[DefaultCost],
111 head_cost: DefaultCost,
112 ) -> DefaultCost {
113 children_cost.iter().fold(head_cost, |s, c| s.combine(c))
114 }
115
116 fn enode_cost(&self, egraph: &EGraph, func: &Function, _enode: &Enode<'_>) -> DefaultCost {
117 func.extraction_head_cost(egraph)
118 }
119}
120
121pub struct Extractor<C: Cost + Ord + Eq + Clone + Debug> {
128 rootsorts: Vec<ArcSort>,
129 funcs: Vec<String>,
130 cost_model: Box<dyn CostModel<C>>,
131 costs: HashMap<String, HashMap<Value, C>>,
132 topo_rnk_cnt: usize,
133 topo_rnk: HashMap<String, HashMap<Value, usize>>,
134 parent_edge: HashMap<String, HashMap<Value, (String, Vec<Value>)>>,
135}
136
137impl<C: Cost + Ord + Eq + Clone + Debug> Extractor<C> {
138 pub fn compute_costs_from_rootsorts(
145 rootsorts: Option<Vec<ArcSort>>,
146 egraph: &EGraph,
147 cost_model: impl CostModel<C> + 'static,
148 ) -> Self {
149 let extract_all_sorts = rootsorts.is_none();
151
152 let mut rootsorts = rootsorts.unwrap_or_default();
153
154 let mut rev_index: HashMap<String, Vec<String>> = Default::default();
158 for func in egraph.functions.iter() {
159 let unextractable = func.1.decl.unextractable;
160 let hidden = func.1.decl.internal_hidden;
161
162 if !unextractable
166 && !hidden
167 && (func.1.decl.subtype == FunctionSubtype::Constructor
168 || func.1.decl.term_constructor.is_some())
169 {
170 let func_name = func.0.clone();
171 let output_sort_name = func.1.extraction_output_sort().name();
173 if let Some(v) = rev_index.get_mut(output_sort_name) {
174 v.push(func_name);
175 } else {
176 rev_index.insert(output_sort_name.to_owned(), vec![func_name]);
177 if extract_all_sorts {
178 rootsorts.push(func.1.extraction_output_sort().clone());
179 }
180 }
181 }
182 }
183
184 let mut q: VecDeque<ArcSort> = VecDeque::new();
186 let mut seen: HashSet<String> = Default::default();
187 for rootsort in rootsorts.iter() {
188 q.push_back(rootsort.clone());
189 seen.insert(rootsort.name().to_owned());
190 }
191
192 let mut funcs_set: HashSet<String> = Default::default();
193 let mut funcs: Vec<String> = Vec::new();
194 while !q.is_empty() {
195 let sort = q.pop_front().unwrap();
196 if sort.is_container_sort() {
197 let inner_sorts = sort.inner_sorts();
198 for s in inner_sorts {
199 if !seen.contains(s.name()) {
200 q.push_back(s.clone());
201 seen.insert(s.name().to_owned());
202 }
203 }
204 } else if sort.is_eq_sort()
205 && let Some(head_symbols) = rev_index.get(sort.name())
206 {
207 for h in head_symbols {
208 if !funcs_set.contains(h) {
209 let func = egraph.functions.get(h).unwrap();
210 let num_children = func.extraction_num_children();
212 for ch in func.schema.input.iter().take(num_children) {
213 let ch_name = ch.name();
214 if !seen.contains(ch_name) {
215 q.push_back(ch.clone());
216 seen.insert(ch_name.to_owned());
217 }
218 }
219 funcs_set.insert(h.clone());
220 funcs.push(h.clone());
221 }
222 }
223 }
224 }
225
226 let mut costs: HashMap<String, HashMap<Value, C>> = Default::default();
228 let mut topo_rnk: HashMap<String, HashMap<Value, usize>> = Default::default();
229 let mut parent_edge: HashMap<String, HashMap<Value, (String, Vec<Value>)>> =
230 Default::default();
231
232 for func_name in funcs.iter() {
233 let func = egraph.functions.get(func_name).unwrap();
234 let output_sort_name = func.extraction_output_sort().name();
235 if !costs.contains_key(output_sort_name) {
236 costs.insert(output_sort_name.to_owned(), Default::default());
237 topo_rnk.insert(output_sort_name.to_owned(), Default::default());
238 parent_edge.insert(output_sort_name.to_owned(), Default::default());
239 }
240 }
241
242 let mut extractor = Extractor {
243 rootsorts,
244 funcs,
245 cost_model: Box::new(cost_model),
246 costs,
247 topo_rnk_cnt: 0,
248 topo_rnk,
249 parent_edge,
250 };
251
252 extractor.bellman_ford(egraph);
253
254 extractor
255 }
256
257 fn compute_cost_node(&self, egraph: &EGraph, value: Value, sort: &ArcSort) -> Option<C> {
261 if sort.is_container_sort() {
262 let elements = sort.inner_values(egraph.backend.container_values(), value);
263 let mut ch_costs: Vec<C> = Vec::new();
264 for ch in elements.iter() {
265 ch_costs.push(self.compute_cost_node(egraph, ch.1, &ch.0)?);
266 }
267 Some(
268 self.cost_model
269 .container_cost(egraph, sort, value, &ch_costs),
270 )
271 } else if sort.is_eq_sort() {
272 self.costs.get(sort.name())?.get(&value).cloned()
273 } else {
274 Some(self.cost_model.base_value_cost(egraph, sort, value))
276 }
277 }
278
279 fn compute_cost_hyperedge(
281 &self,
282 egraph: &EGraph,
283 row: &egglog_bridge::ScanEntry,
284 func: &Function,
285 ) -> Option<C> {
286 let mut ch_costs: Vec<C> = Vec::new();
287 let sorts = &func.schema.input;
288 let num_children = func.extraction_num_children();
289 for (value, sort) in row.vals.iter().take(num_children).zip(sorts.iter()) {
290 ch_costs.push(self.compute_cost_node(egraph, *value, sort)?);
291 }
292 let head_name = func.extraction_term_name();
293 let output_idx = func.extraction_output_index();
294 let enode = Enode {
295 children: &row.vals[..output_idx],
296 eclass: row.vals[output_idx],
297 subsumed: row.subsumed,
298 };
299 Some(self.cost_model.fold(
300 head_name,
301 &ch_costs,
302 self.cost_model.enode_cost(egraph, func, &enode),
303 ))
304 }
305
306 fn compute_topo_rnk_node(&self, egraph: &EGraph, value: Value, sort: &ArcSort) -> usize {
307 if sort.is_container_sort() {
308 sort.inner_values(egraph.backend.container_values(), value)
309 .iter()
310 .fold(0, |ret, (sort, value)| {
311 usize::max(ret, self.compute_topo_rnk_node(egraph, *value, sort))
312 })
313 } else if sort.is_eq_sort() {
314 if let Some(t) = self.topo_rnk.get(sort.name()) {
315 *t.get(&value).unwrap_or(&usize::MAX)
316 } else {
317 usize::MAX
318 }
319 } else {
320 0
321 }
322 }
323
324 fn compute_topo_rnk_hyperedge(
325 &self,
326 egraph: &EGraph,
327 row: &egglog_bridge::ScanEntry,
328 func: &Function,
329 ) -> usize {
330 let sorts = &func.schema.input;
331 let num_children = func.extraction_num_children();
332 row.vals
333 .iter()
334 .take(num_children)
335 .zip(sorts.iter())
336 .fold(0, |ret, (value, sort)| {
337 usize::max(ret, self.compute_topo_rnk_node(egraph, *value, sort))
338 })
339 }
340
341 fn bellman_ford(&mut self, egraph: &EGraph) {
352 let mut ensure_fixpoint = false;
353
354 let funcs = self.funcs.clone();
355
356 while !ensure_fixpoint {
357 ensure_fixpoint = true;
358
359 for func_name in funcs.iter() {
360 let func = egraph.functions.get(func_name).unwrap();
361 let target_sort = func.extraction_output_sort();
362
363 let output_idx = func.extraction_output_index();
364 let relax_hyperedge = |row: egglog_bridge::ScanEntry| {
365 if !row.subsumed {
366 let target = &row.vals[output_idx];
367 let mut updated = false;
368 if let Some(new_cost) = self.compute_cost_hyperedge(egraph, &row, func) {
369 match self
370 .costs
371 .get_mut(target_sort.name())
372 .unwrap()
373 .entry(*target)
374 {
375 HEntry::Vacant(e) => {
376 updated = true;
377 e.insert(new_cost);
378 }
379 HEntry::Occupied(mut e) => {
380 if new_cost < *(e.get()) {
381 updated = true;
382 e.insert(new_cost);
383 }
384 }
385 }
386 }
387 if updated {
391 ensure_fixpoint = false;
392 self.topo_rnk_cnt += 1;
393 self.topo_rnk
394 .get_mut(target_sort.name())
395 .unwrap()
396 .insert(*target, self.topo_rnk_cnt);
397 }
398 }
399 };
400
401 egraph.backend.for_each(func.backend_id, relax_hyperedge);
402 }
403 }
404
405 for func_name in funcs.iter() {
407 let func = egraph.functions.get(func_name).unwrap();
408 let target_sort = func.extraction_output_sort();
409 let output_idx = func.extraction_output_index();
410
411 let save_best_parent_edge = |row: egglog_bridge::ScanEntry| {
412 if !row.subsumed {
413 let target = &row.vals[output_idx];
414 if let Some(best_cost) = self.costs.get(target_sort.name()).unwrap().get(target)
415 && Some(best_cost.clone())
416 == self.compute_cost_hyperedge(egraph, &row, func)
417 {
418 let target_topo_rnk = *self
420 .topo_rnk
421 .get(target_sort.name())
422 .unwrap()
423 .get(target)
424 .unwrap();
425 if target_topo_rnk > self.compute_topo_rnk_hyperedge(egraph, &row, func) {
426 if let HEntry::Vacant(e) = self
428 .parent_edge
429 .get_mut(target_sort.name())
430 .unwrap()
431 .entry(*target)
432 {
433 e.insert((func.decl.name.clone(), row.vals.to_vec()));
434 }
435 }
436 }
437 }
438 };
439
440 egraph
441 .backend
442 .for_each(func.backend_id, save_best_parent_edge);
443 }
444 }
445
446 fn reconstruct_termdag_node(
448 &self,
449 egraph: &EGraph,
450 termdag: &mut TermDag,
451 value: Value,
452 sort: &ArcSort,
453 ) -> TermId {
454 self.reconstruct_termdag_node_helper(egraph, termdag, value, sort, &mut Default::default())
455 }
456
457 fn reconstruct_termdag_node_helper(
458 &self,
459 egraph: &EGraph,
460 termdag: &mut TermDag,
461 value: Value,
462 sort: &ArcSort,
463 cache: &mut HashMap<(Value, String), TermId>,
464 ) -> TermId {
465 let key = (value, sort.name().to_owned());
466 if let Some(term) = cache.get(&key) {
467 return *term;
468 }
469
470 let term = if sort.is_container_sort() {
471 let elements = sort.inner_values(egraph.backend.container_values(), value);
472 let mut ch_terms: Vec<TermId> = Vec::new();
473 for ch in elements.iter() {
474 ch_terms.push(
475 self.reconstruct_termdag_node_helper(egraph, termdag, ch.1, &ch.0, cache),
476 );
477 }
478 sort.reconstruct_termdag_container(
479 egraph.backend.container_values(),
480 value,
481 termdag,
482 ch_terms,
483 )
484 } else if sort.is_eq_sort() {
485 let (func_name, hyperedge) = self
486 .parent_edge
487 .get(sort.name())
488 .unwrap()
489 .get(&value)
490 .unwrap();
491 let func = egraph.functions.get(func_name).unwrap();
492 let ch_sorts = &func.schema.input;
493
494 let num_children = func.extraction_num_children();
495 let output_name = func.extraction_term_name();
496
497 let mut ch_terms: Vec<TermId> = Vec::new();
498 for (value, sort) in hyperedge.iter().take(num_children).zip(ch_sorts.iter()) {
499 ch_terms.push(
500 self.reconstruct_termdag_node_helper(egraph, termdag, *value, sort, cache),
501 );
502 }
503 termdag.app(output_name.to_string(), ch_terms)
504 } else {
505 sort.reconstruct_termdag_base(egraph.backend.base_values(), value, termdag)
507 };
508
509 cache.insert(key, term);
510 term
511 }
512
513 pub fn extract_best_with_sort(
518 &self,
519 egraph: &EGraph,
520 termdag: &mut TermDag,
521 value: Value,
522 sort: ArcSort,
523 ) -> Option<(C, TermId)> {
524 let canonical_value = self.find_canonical(egraph, value, &sort);
526
527 match self.compute_cost_node(egraph, canonical_value, &sort) {
528 Some(best_cost) => {
529 log::debug!("Best cost for the extract root: {best_cost:?}");
530
531 let term = self.reconstruct_termdag_node(egraph, termdag, canonical_value, &sort);
532
533 Some((best_cost, term))
534 }
535 None => {
536 log::error!("Unextractable root {value:?} with sort {sort:?}",);
537 None
538 }
539 }
540 }
541
542 pub fn extract_best(
546 &self,
547 egraph: &EGraph,
548 termdag: &mut TermDag,
549 value: Value,
550 ) -> Option<(C, TermId)> {
551 assert!(
552 self.rootsorts.len() == 1,
553 "extract_best requires a single rootsort"
554 );
555 self.extract_best_with_sort(
556 egraph,
557 termdag,
558 value,
559 self.rootsorts.first().unwrap().clone(),
560 )
561 }
562
563 fn find_canonical(&self, egraph: &EGraph, value: Value, sort: &ArcSort) -> Value {
567 let Some(uf_name) = egraph.proof_state.uf_parent.get(sort.name()) else {
569 return value;
570 };
571
572 let Some(uf_func) = egraph.functions.get(uf_name) else {
574 return value;
575 };
576
577 let mut canonical = value;
579 egraph
580 .backend
581 .for_each(uf_func.backend_id, |row: egglog_bridge::ScanEntry| {
582 if row.vals[0] == value {
584 canonical = row.vals[1];
585 }
586 });
587
588 canonical
589 }
590
591 pub fn extract_variants_with_sort(
596 &self,
597 egraph: &EGraph,
598 termdag: &mut TermDag,
599 value: Value,
600 nvariants: usize,
601 sort: ArcSort,
602 ) -> Vec<(C, TermId)> {
603 debug_assert!(self.rootsorts.iter().any(|s| { s.name() == sort.name() }));
604
605 if sort.is_eq_sort() {
606 let canonical_value = self.find_canonical(egraph, value, &sort);
608
609 let mut root_variants: Vec<(C, String, Vec<Value>)> = Vec::new();
610
611 let mut root_funcs: Vec<String> = Vec::new();
612
613 for func_name in self.funcs.iter() {
614 if sort.name()
616 == egraph
617 .functions
618 .get(func_name)
619 .unwrap()
620 .extraction_output_sort()
621 .name()
622 {
623 root_funcs.push(func_name.clone());
624 }
625 }
626
627 for func_name in root_funcs.iter() {
628 let func = egraph.functions.get(func_name).unwrap();
629 let output_idx = func.extraction_output_index();
630
631 let find_root_variants = |row: egglog_bridge::ScanEntry| {
632 if !row.subsumed {
633 let target = &row.vals[output_idx];
634 if *target == canonical_value
639 && let Some(cost) = self.compute_cost_hyperedge(egraph, &row, func)
640 {
641 root_variants.push((cost, func_name.clone(), row.vals.to_vec()));
642 }
643 }
644 };
645
646 egraph.backend.for_each(func.backend_id, find_root_variants);
647 }
648
649 let mut res: Vec<(C, TermId)> = Vec::new();
650 let mut cache: HashMap<(Value, String), TermId> = Default::default();
651 root_variants.sort();
652 root_variants.truncate(nvariants);
653 for (cost, func_name, hyperedge) in root_variants {
654 let mut ch_terms: Vec<TermId> = Vec::new();
655 let func = egraph.functions.get(&func_name).unwrap();
656 let ch_sorts = &func.schema.input;
657 let num_children = func.extraction_num_children();
658 for (value, sort) in hyperedge.iter().zip(ch_sorts.iter()).take(num_children) {
660 ch_terms.push(self.reconstruct_termdag_node_helper(
661 egraph, termdag, *value, sort, &mut cache,
662 ));
663 }
664 res.push((
666 cost,
667 termdag.app(func.extraction_term_name().to_string(), ch_terms),
668 ));
669 }
670
671 res
672 } else {
673 log::warn!(
674 "extracting multiple variants for containers or primitives is not implemented, returning a single variant."
675 );
676 if let Some(res) = self.extract_best_with_sort(egraph, termdag, value, sort) {
677 vec![res]
678 } else {
679 vec![]
680 }
681 }
682 }
683
684 pub fn extract_variants(
688 &self,
689 egraph: &EGraph,
690 termdag: &mut TermDag,
691 value: Value,
692 nvariants: usize,
693 ) -> Vec<(C, TermId)> {
694 assert!(
695 self.rootsorts.len() == 1,
696 "extract_variants requires a single rootsort"
697 );
698 self.extract_variants_with_sort(
699 egraph,
700 termdag,
701 value,
702 nvariants,
703 self.rootsorts.first().unwrap().clone(),
704 )
705 }
706}
707
708impl Function {
709 pub(crate) fn extraction_head_cost(&self, egraph: &EGraph) -> DefaultCost {
712 if let Some(term_constructor) = &self.decl.term_constructor {
713 egraph
714 .functions
715 .get(term_constructor)
716 .and_then(|func| func.decl.cost)
717 .unwrap_or(DefaultCost::unit())
718 } else {
719 self.decl.cost.unwrap_or(DefaultCost::unit())
720 }
721 }
722
723 pub(crate) fn extraction_output_sort(&self) -> &ArcSort {
727 if self.decl.term_constructor.is_some() {
728 self.schema.input.last().unwrap()
729 } else {
730 &self.schema.output
731 }
732 }
733
734 pub(crate) fn extraction_num_children(&self) -> usize {
737 if self.decl.term_constructor.is_some() {
738 self.schema.input.len() - 1
739 } else {
740 self.schema.input.len()
741 }
742 }
743
744 pub(crate) fn extraction_term_name(&self) -> &str {
747 self.decl
748 .term_constructor
749 .as_ref()
750 .unwrap_or(&self.decl.name)
751 }
752
753 pub(crate) fn extraction_output_index(&self) -> usize {
757 if self.decl.term_constructor.is_some() {
758 self.schema.input.len() - 1
762 } else {
763 self.schema.input.len()
765 }
766 }
767}
768
769impl EGraph {
770 pub fn extract_value(
773 &self,
774 sort: &ArcSort,
775 value: Value,
776 ) -> Result<(TermDag, TermId, DefaultCost), Error> {
777 self.extract_value_with_cost_model(sort, value, TreeAdditiveCostModel::default())
778 }
779
780 pub fn extract_value_with_cost_model<CM: CostModel<DefaultCost> + 'static>(
784 &self,
785 sort: &ArcSort,
786 value: Value,
787 cost_model: CM,
788 ) -> Result<(TermDag, TermId, DefaultCost), Error> {
789 let extractor =
790 Extractor::compute_costs_from_rootsorts(Some(vec![sort.clone()]), self, cost_model);
791 let mut termdag = TermDag::default();
792 let (cost, term) = extractor
793 .extract_best(self, &mut termdag, value)
794 .ok_or_else(|| {
795 Error::ExtractError(
796 "Unable to find any valid extraction (likely due to subsume or delete)"
797 .to_string(),
798 )
799 })?;
800 Ok((termdag, term, cost))
801 }
802
803 pub fn extract_value_to_string(
806 &self,
807 sort: &ArcSort,
808 value: Value,
809 ) -> Result<(String, DefaultCost), Error> {
810 let (termdag, term, cost) = self.extract_value(sort, value)?;
811 Ok((termdag.to_string(term), cost))
812 }
813
814 pub fn function_to_dag(
816 &self,
817 sym: &str,
818 n: usize,
819 include_output: bool,
820 ) -> Result<(Vec<TermId>, Option<Vec<TermId>>, TermDag), Error> {
821 let func = self
822 .functions
823 .get(sym)
824 .ok_or(TypeError::UnboundFunction(sym.to_owned(), span!()))?;
825 let mut rootsorts = func.schema.input.clone();
826 if include_output {
827 rootsorts.push(func.schema.output.clone());
828 }
829 let extractor = Extractor::compute_costs_from_rootsorts(
830 Some(rootsorts),
831 self,
832 TreeAdditiveCostModel::default(),
833 );
834
835 let mut termdag = TermDag::default();
836 let mut inputs: Vec<TermId> = Vec::new();
837 let mut output: Option<Vec<TermId>> = if include_output {
838 Some(Vec::new())
839 } else {
840 None
841 };
842
843 let extract_row = |row: egglog_bridge::ScanEntry| {
844 if inputs.len() < n {
845 let mut children: Vec<TermId> = Vec::new();
847 for (value, sort) in row.vals.iter().zip(&func.schema.input) {
848 let (_, term_id) = extractor
849 .extract_best_with_sort(self, &mut termdag, *value, sort.clone())
850 .unwrap_or_else(|| (0, termdag.var("Unextractable".into())));
851 children.push(term_id);
852 }
853 inputs.push(termdag.app(sym.to_owned(), children));
854 if include_output {
855 let value = row.vals[func.schema.input.len()];
856 let sort = &func.schema.output;
857 let (_, term) = extractor
858 .extract_best_with_sort(self, &mut termdag, value, sort.clone())
859 .unwrap_or_else(|| (0, termdag.var("Unextractable".into())));
860 output.as_mut().unwrap().push(term);
861 }
862 true
863 } else {
864 false
865 }
866 };
867
868 self.backend.for_each_while(func.backend_id, extract_row);
869
870 Ok((inputs, output, termdag))
871 }
872}