Skip to main content

oxilean_kernel/congruence/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::{Expr, Name};
6use std::collections::{HashMap, HashSet};
7
8use super::functions::{mk_congr_theorem, TermIdx};
9
10/// A flat list of substitution pairs `(from, to)`.
11#[allow(dead_code)]
12pub struct FlatSubstitution {
13    pairs: Vec<(String, String)>,
14}
15#[allow(dead_code)]
16impl FlatSubstitution {
17    /// Creates an empty substitution.
18    pub fn new() -> Self {
19        Self { pairs: Vec::new() }
20    }
21    /// Adds a pair.
22    pub fn add(&mut self, from: impl Into<String>, to: impl Into<String>) {
23        self.pairs.push((from.into(), to.into()));
24    }
25    /// Applies all substitutions to `s` (leftmost-first order).
26    pub fn apply(&self, s: &str) -> String {
27        let mut result = s.to_string();
28        for (from, to) in &self.pairs {
29            result = result.replace(from.as_str(), to.as_str());
30        }
31        result
32    }
33    /// Returns the number of pairs.
34    pub fn len(&self) -> usize {
35        self.pairs.len()
36    }
37    /// Returns `true` if empty.
38    pub fn is_empty(&self) -> bool {
39        self.pairs.is_empty()
40    }
41}
42/// A congruence closure augmented with statistics collection.
43pub struct InstrumentedCC {
44    /// Inner closure.
45    pub cc: CongruenceClosure,
46    /// Collected statistics.
47    pub stats: CongrClosureStats,
48}
49impl InstrumentedCC {
50    /// Create a new instrumented congruence closure.
51    pub fn new() -> Self {
52        Self {
53            cc: CongruenceClosure::new(),
54            stats: CongrClosureStats::new(),
55        }
56    }
57    /// Add an equality and record the operation.
58    pub fn add_equality(&mut self, e1: Expr, e2: Expr) {
59        let before = self.cc.num_classes();
60        self.cc.add_equality(e1, e2);
61        let after = self.cc.num_classes();
62        if after < before {
63            self.stats.unions += 1;
64        }
65        self.stats.equalities_added += 1;
66        self.stats.apps_tracked = self.cc.apps.len();
67    }
68    /// Check equality (delegates to inner CC).
69    pub fn are_equal(&mut self, e1: &Expr, e2: &Expr) -> bool {
70        self.cc.are_equal(e1, e2)
71    }
72    /// Reset everything.
73    pub fn reset(&mut self) {
74        self.cc.clear();
75        self.stats = CongrClosureStats::new();
76    }
77    /// Number of equivalence classes.
78    pub fn num_classes(&mut self) -> usize {
79        self.cc.num_classes()
80    }
81}
82/// Extended equality graph for congruence closure with E-node tracking.
83///
84/// Provides richer structure than `CongruenceClosure` for proof reconstruction.
85pub struct EGraph {
86    /// E-nodes, each representing one equivalence class.
87    nodes: Vec<ENode>,
88}
89impl EGraph {
90    /// Create an empty E-graph.
91    pub fn new() -> Self {
92        Self { nodes: Vec::new() }
93    }
94    /// Add an expression to the E-graph (creating a new singleton class if not present).
95    pub fn add_expr(&mut self, expr: Expr) -> usize {
96        for (i, node) in self.nodes.iter().enumerate() {
97            if node.contains(&expr) {
98                return i;
99            }
100        }
101        self.nodes.push(ENode::singleton(expr));
102        self.nodes.len() - 1
103    }
104    /// Get the class ID for an expression, if present.
105    pub fn find_class(&self, expr: &Expr) -> Option<usize> {
106        self.nodes.iter().position(|n| n.contains(expr))
107    }
108    /// Merge two equivalence classes.
109    pub fn merge_classes(&mut self, id1: usize, id2: usize, proof: Option<Expr>) {
110        if id1 == id2 {
111            return;
112        }
113        let (small_id, large_id) = if id1 < id2 { (id2, id1) } else { (id1, id2) };
114        let small = self.nodes.remove(small_id);
115        for (m, p) in small.members.into_iter().zip(small.proofs.into_iter()) {
116            let proof_for = proof.clone().or(p);
117            self.nodes[large_id].add_member(m, proof_for);
118        }
119    }
120    /// Check if two expressions are in the same class.
121    pub fn are_equal(&self, e1: &Expr, e2: &Expr) -> bool {
122        match (self.find_class(e1), self.find_class(e2)) {
123            (Some(c1), Some(c2)) => c1 == c2,
124            _ => false,
125        }
126    }
127    /// Add an equality e1 = e2 with optional proof.
128    pub fn add_equality(&mut self, e1: Expr, e2: Expr, proof: Option<Expr>) {
129        let c1 = self.add_expr(e1);
130        let c2 = self.add_expr(e2);
131        if c1 != c2 {
132            self.merge_classes(c1, c2, proof);
133        }
134    }
135    /// Get the number of equivalence classes.
136    pub fn num_classes(&self) -> usize {
137        self.nodes.len()
138    }
139    /// Get an E-node by class ID.
140    pub fn get_class(&self, id: usize) -> Option<&ENode> {
141        self.nodes.get(id)
142    }
143    /// Get the representative of the class containing `expr`.
144    pub fn representative(&self, expr: &Expr) -> Option<&Expr> {
145        self.find_class(expr)
146            .and_then(|id| self.nodes.get(id))
147            .map(|n| &n.repr)
148    }
149    /// Clear the E-graph.
150    pub fn clear(&mut self) {
151        self.nodes.clear();
152    }
153}
154/// A proof of an equality in the congruence closure.
155#[derive(Debug, Clone)]
156pub enum CongrProof {
157    /// `refl a`: a = a.
158    Refl(Expr),
159    /// `symm p`: p proves a = b, so symm proves b = a.
160    Symm(Box<CongrProof>),
161    /// `trans p q`: p proves a = b and q proves b = c.
162    Trans(Box<CongrProof>, Box<CongrProof>),
163    /// `congr p q`: p proves f = g and q proves a = b, so (f a) = (g b).
164    Congr(Box<CongrProof>, Box<CongrProof>),
165    /// `hyp name`: an equality hypothesis.
166    Hyp(Name),
167}
168impl CongrProof {
169    /// Compute the depth of the proof term.
170    pub fn depth(&self) -> usize {
171        match self {
172            CongrProof::Refl(_) | CongrProof::Hyp(_) => 0,
173            CongrProof::Symm(p) => 1 + p.depth(),
174            CongrProof::Trans(p, q) | CongrProof::Congr(p, q) => 1 + p.depth().max(q.depth()),
175        }
176    }
177    /// Check if the proof is a trivial reflexivity.
178    pub fn is_refl(&self) -> bool {
179        matches!(self, CongrProof::Refl(_))
180    }
181    /// Count the number of hypothesis uses.
182    pub fn hypothesis_count(&self) -> usize {
183        match self {
184            CongrProof::Hyp(_) => 1,
185            CongrProof::Refl(_) => 0,
186            CongrProof::Symm(p) => p.hypothesis_count(),
187            CongrProof::Trans(p, q) | CongrProof::Congr(p, q) => {
188                p.hypothesis_count() + q.hypothesis_count()
189            }
190        }
191    }
192    /// Simplify double negations (`symm (symm p)` → `p`).
193    pub fn simplify(self) -> Self {
194        match self {
195            CongrProof::Symm(inner) => match *inner {
196                CongrProof::Symm(p) => p.simplify(),
197                other => CongrProof::Symm(Box::new(other.simplify())),
198            },
199            CongrProof::Trans(p, q) => {
200                CongrProof::Trans(Box::new(p.simplify()), Box::new(q.simplify()))
201            }
202            CongrProof::Congr(p, q) => {
203                CongrProof::Congr(Box::new(p.simplify()), Box::new(q.simplify()))
204            }
205            other => other,
206        }
207    }
208}
209/// A window iterator that yields overlapping windows of size `n`.
210#[allow(dead_code)]
211pub struct WindowIterator<'a, T> {
212    pub(super) data: &'a [T],
213    pub(super) pos: usize,
214    pub(super) window: usize,
215}
216#[allow(dead_code)]
217impl<'a, T> WindowIterator<'a, T> {
218    /// Creates a new window iterator.
219    pub fn new(data: &'a [T], window: usize) -> Self {
220        Self {
221            data,
222            pos: 0,
223            window,
224        }
225    }
226}
227/// Represents a rewrite rule `lhs → rhs`.
228#[allow(dead_code)]
229#[allow(missing_docs)]
230pub struct RewriteRule {
231    /// The name of the rule.
232    pub name: String,
233    /// A string representation of the LHS pattern.
234    pub lhs: String,
235    /// A string representation of the RHS.
236    pub rhs: String,
237    /// Whether this is a conditional rule (has side conditions).
238    pub conditional: bool,
239}
240#[allow(dead_code)]
241impl RewriteRule {
242    /// Creates an unconditional rewrite rule.
243    pub fn unconditional(
244        name: impl Into<String>,
245        lhs: impl Into<String>,
246        rhs: impl Into<String>,
247    ) -> Self {
248        Self {
249            name: name.into(),
250            lhs: lhs.into(),
251            rhs: rhs.into(),
252            conditional: false,
253        }
254    }
255    /// Creates a conditional rewrite rule.
256    pub fn conditional(
257        name: impl Into<String>,
258        lhs: impl Into<String>,
259        rhs: impl Into<String>,
260    ) -> Self {
261        Self {
262            name: name.into(),
263            lhs: lhs.into(),
264            rhs: rhs.into(),
265            conditional: true,
266        }
267    }
268    /// Returns a textual representation.
269    pub fn display(&self) -> String {
270        format!("{}: {} → {}", self.name, self.lhs, self.rhs)
271    }
272}
273/// A trie-based prefix counter.
274#[allow(dead_code)]
275pub struct PrefixCounter {
276    children: std::collections::HashMap<char, PrefixCounter>,
277    count: usize,
278}
279#[allow(dead_code)]
280impl PrefixCounter {
281    /// Creates an empty prefix counter.
282    pub fn new() -> Self {
283        Self {
284            children: std::collections::HashMap::new(),
285            count: 0,
286        }
287    }
288    /// Records a string.
289    pub fn record(&mut self, s: &str) {
290        self.count += 1;
291        let mut node = self;
292        for c in s.chars() {
293            node = node.children.entry(c).or_default();
294            node.count += 1;
295        }
296    }
297    /// Returns how many strings have been recorded that start with `prefix`.
298    pub fn count_with_prefix(&self, prefix: &str) -> usize {
299        let mut node = self;
300        for c in prefix.chars() {
301            match node.children.get(&c) {
302                Some(n) => node = n,
303                None => return 0,
304            }
305        }
306        node.count
307    }
308}
309/// A non-empty list (at least one element guaranteed).
310#[allow(dead_code)]
311pub struct NonEmptyVec<T> {
312    head: T,
313    tail: Vec<T>,
314}
315#[allow(dead_code)]
316impl<T> NonEmptyVec<T> {
317    /// Creates a non-empty vec with a single element.
318    pub fn singleton(val: T) -> Self {
319        Self {
320            head: val,
321            tail: Vec::new(),
322        }
323    }
324    /// Pushes an element.
325    pub fn push(&mut self, val: T) {
326        self.tail.push(val);
327    }
328    /// Returns a reference to the first element.
329    pub fn first(&self) -> &T {
330        &self.head
331    }
332    /// Returns a reference to the last element.
333    pub fn last(&self) -> &T {
334        self.tail.last().unwrap_or(&self.head)
335    }
336    /// Returns the number of elements.
337    pub fn len(&self) -> usize {
338        1 + self.tail.len()
339    }
340    /// Returns whether the collection is empty.
341    pub fn is_empty(&self) -> bool {
342        self.len() == 0
343    }
344    /// Returns all elements as a Vec.
345    pub fn to_vec(&self) -> Vec<&T> {
346        let mut v = vec![&self.head];
347        v.extend(self.tail.iter());
348        v
349    }
350}
351/// A fixed-size sliding window that computes a running sum.
352#[allow(dead_code)]
353pub struct SlidingSum {
354    window: Vec<f64>,
355    capacity: usize,
356    pos: usize,
357    sum: f64,
358    count: usize,
359}
360#[allow(dead_code)]
361impl SlidingSum {
362    /// Creates a sliding sum with the given window size.
363    pub fn new(capacity: usize) -> Self {
364        Self {
365            window: vec![0.0; capacity],
366            capacity,
367            pos: 0,
368            sum: 0.0,
369            count: 0,
370        }
371    }
372    /// Adds a value to the window, removing the oldest if full.
373    pub fn push(&mut self, val: f64) {
374        let oldest = self.window[self.pos];
375        self.sum -= oldest;
376        self.sum += val;
377        self.window[self.pos] = val;
378        self.pos = (self.pos + 1) % self.capacity;
379        if self.count < self.capacity {
380            self.count += 1;
381        }
382    }
383    /// Returns the current window sum.
384    pub fn sum(&self) -> f64 {
385        self.sum
386    }
387    /// Returns the window mean, or `None` if empty.
388    pub fn mean(&self) -> Option<f64> {
389        if self.count == 0 {
390            None
391        } else {
392            Some(self.sum / self.count as f64)
393        }
394    }
395    /// Returns the current window size (number of valid elements).
396    pub fn count(&self) -> usize {
397        self.count
398    }
399}
400/// A set of rewrite rules.
401#[allow(dead_code)]
402pub struct RewriteRuleSet {
403    rules: Vec<RewriteRule>,
404}
405#[allow(dead_code)]
406impl RewriteRuleSet {
407    /// Creates an empty rule set.
408    pub fn new() -> Self {
409        Self { rules: Vec::new() }
410    }
411    /// Adds a rule.
412    pub fn add(&mut self, rule: RewriteRule) {
413        self.rules.push(rule);
414    }
415    /// Returns the number of rules.
416    pub fn len(&self) -> usize {
417        self.rules.len()
418    }
419    /// Returns `true` if the set is empty.
420    pub fn is_empty(&self) -> bool {
421        self.rules.is_empty()
422    }
423    /// Returns all conditional rules.
424    pub fn conditional_rules(&self) -> Vec<&RewriteRule> {
425        self.rules.iter().filter(|r| r.conditional).collect()
426    }
427    /// Returns all unconditional rules.
428    pub fn unconditional_rules(&self) -> Vec<&RewriteRule> {
429        self.rules.iter().filter(|r| !r.conditional).collect()
430    }
431    /// Looks up a rule by name.
432    pub fn get(&self, name: &str) -> Option<&RewriteRule> {
433        self.rules.iter().find(|r| r.name == name)
434    }
435}
436/// A reusable scratch buffer for path computations.
437#[allow(dead_code)]
438pub struct PathBuf {
439    components: Vec<String>,
440}
441#[allow(dead_code)]
442impl PathBuf {
443    /// Creates a new empty path buffer.
444    pub fn new() -> Self {
445        Self {
446            components: Vec::new(),
447        }
448    }
449    /// Pushes a component.
450    pub fn push(&mut self, comp: impl Into<String>) {
451        self.components.push(comp.into());
452    }
453    /// Pops the last component.
454    pub fn pop(&mut self) {
455        self.components.pop();
456    }
457    /// Returns the current path as a `/`-separated string.
458    pub fn as_str(&self) -> String {
459        self.components.join("/")
460    }
461    /// Returns the depth of the path.
462    pub fn depth(&self) -> usize {
463        self.components.len()
464    }
465    /// Clears the path.
466    pub fn clear(&mut self) {
467        self.components.clear();
468    }
469}
470/// A hierarchical configuration tree.
471#[allow(dead_code)]
472pub struct ConfigNode {
473    key: String,
474    value: Option<String>,
475    children: Vec<ConfigNode>,
476}
477#[allow(dead_code)]
478impl ConfigNode {
479    /// Creates a leaf config node with a value.
480    pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
481        Self {
482            key: key.into(),
483            value: Some(value.into()),
484            children: Vec::new(),
485        }
486    }
487    /// Creates a section node with children.
488    pub fn section(key: impl Into<String>) -> Self {
489        Self {
490            key: key.into(),
491            value: None,
492            children: Vec::new(),
493        }
494    }
495    /// Adds a child node.
496    pub fn add_child(&mut self, child: ConfigNode) {
497        self.children.push(child);
498    }
499    /// Returns the key.
500    pub fn key(&self) -> &str {
501        &self.key
502    }
503    /// Returns the value, or `None` for section nodes.
504    pub fn value(&self) -> Option<&str> {
505        self.value.as_deref()
506    }
507    /// Returns the number of children.
508    pub fn num_children(&self) -> usize {
509        self.children.len()
510    }
511    /// Looks up a dot-separated path.
512    pub fn lookup(&self, path: &str) -> Option<&str> {
513        let mut parts = path.splitn(2, '.');
514        let head = parts.next()?;
515        let tail = parts.next();
516        if head != self.key {
517            return None;
518        }
519        match tail {
520            None => self.value.as_deref(),
521            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
522        }
523    }
524    fn lookup_relative(&self, path: &str) -> Option<&str> {
525        let mut parts = path.splitn(2, '.');
526        let head = parts.next()?;
527        let tail = parts.next();
528        if head != self.key {
529            return None;
530        }
531        match tail {
532            None => self.value.as_deref(),
533            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
534        }
535    }
536}
537/// A token bucket rate limiter.
538#[allow(dead_code)]
539pub struct TokenBucket {
540    capacity: u64,
541    tokens: u64,
542    refill_per_ms: u64,
543    last_refill: std::time::Instant,
544}
545#[allow(dead_code)]
546impl TokenBucket {
547    /// Creates a new token bucket.
548    pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
549        Self {
550            capacity,
551            tokens: capacity,
552            refill_per_ms,
553            last_refill: std::time::Instant::now(),
554        }
555    }
556    /// Attempts to consume `n` tokens.  Returns `true` on success.
557    pub fn try_consume(&mut self, n: u64) -> bool {
558        self.refill();
559        if self.tokens >= n {
560            self.tokens -= n;
561            true
562        } else {
563            false
564        }
565    }
566    fn refill(&mut self) {
567        let now = std::time::Instant::now();
568        let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
569        if elapsed_ms > 0 {
570            let new_tokens = elapsed_ms * self.refill_per_ms;
571            self.tokens = (self.tokens + new_tokens).min(self.capacity);
572            self.last_refill = now;
573        }
574    }
575    /// Returns the number of currently available tokens.
576    pub fn available(&self) -> u64 {
577        self.tokens
578    }
579    /// Returns the bucket capacity.
580    pub fn capacity(&self) -> u64 {
581        self.capacity
582    }
583}
584/// A versioned record that stores a history of values.
585#[allow(dead_code)]
586pub struct VersionedRecord<T: Clone> {
587    history: Vec<T>,
588}
589#[allow(dead_code)]
590impl<T: Clone> VersionedRecord<T> {
591    /// Creates a new record with an initial value.
592    pub fn new(initial: T) -> Self {
593        Self {
594            history: vec![initial],
595        }
596    }
597    /// Updates the record with a new version.
598    pub fn update(&mut self, val: T) {
599        self.history.push(val);
600    }
601    /// Returns the current (latest) value.
602    pub fn current(&self) -> &T {
603        self.history
604            .last()
605            .expect("VersionedRecord history is always non-empty after construction")
606    }
607    /// Returns the value at version `n` (0-indexed), or `None`.
608    pub fn at_version(&self, n: usize) -> Option<&T> {
609        self.history.get(n)
610    }
611    /// Returns the version number of the current value.
612    pub fn version(&self) -> usize {
613        self.history.len() - 1
614    }
615    /// Returns `true` if more than one version exists.
616    pub fn has_history(&self) -> bool {
617        self.history.len() > 1
618    }
619}
620/// A pair of `StatSummary` values tracking before/after a transformation.
621#[allow(dead_code)]
622pub struct TransformStat {
623    before: StatSummary,
624    after: StatSummary,
625}
626#[allow(dead_code)]
627impl TransformStat {
628    /// Creates a new transform stat recorder.
629    pub fn new() -> Self {
630        Self {
631            before: StatSummary::new(),
632            after: StatSummary::new(),
633        }
634    }
635    /// Records a before value.
636    pub fn record_before(&mut self, v: f64) {
637        self.before.record(v);
638    }
639    /// Records an after value.
640    pub fn record_after(&mut self, v: f64) {
641        self.after.record(v);
642    }
643    /// Returns the mean reduction ratio (after/before).
644    pub fn mean_ratio(&self) -> Option<f64> {
645        let b = self.before.mean()?;
646        let a = self.after.mean()?;
647        if b.abs() < f64::EPSILON {
648            return None;
649        }
650        Some(a / b)
651    }
652}
653/// Cache for congruence lemmas to avoid regenerating them.
654#[derive(Debug, Default)]
655pub struct CongrLemmaCache {
656    entries: std::collections::HashMap<(Name, usize), CongruenceTheorem>,
657}
658impl CongrLemmaCache {
659    /// Create an empty cache.
660    pub fn new() -> Self {
661        Self::default()
662    }
663    /// Look up a cached lemma.
664    pub fn get(&self, name: &Name, num_args: usize) -> Option<&CongruenceTheorem> {
665        self.entries.get(&(name.clone(), num_args))
666    }
667    /// Insert a lemma into the cache.
668    pub fn insert(&mut self, thm: CongruenceTheorem) {
669        let key = (thm.fn_name.clone(), thm.num_args);
670        self.entries.insert(key, thm);
671    }
672    /// Get or compute a basic congruence theorem.
673    pub fn get_or_compute(&mut self, name: Name, num_args: usize) -> &CongruenceTheorem {
674        let key = (name.clone(), num_args);
675        self.entries
676            .entry(key)
677            .or_insert_with(|| mk_congr_theorem(name, num_args))
678    }
679    /// Number of cached lemmas.
680    pub fn len(&self) -> usize {
681        self.entries.len()
682    }
683    /// Clear the cache.
684    pub fn clear(&mut self) {
685        self.entries.clear();
686    }
687    /// Check if the cache is empty.
688    pub fn is_empty(&self) -> bool {
689        self.entries.is_empty()
690    }
691}
692/// A generic counter that tracks min/max/sum for statistical summaries.
693#[allow(dead_code)]
694pub struct StatSummary {
695    count: u64,
696    sum: f64,
697    min: f64,
698    max: f64,
699}
700#[allow(dead_code)]
701impl StatSummary {
702    /// Creates an empty summary.
703    pub fn new() -> Self {
704        Self {
705            count: 0,
706            sum: 0.0,
707            min: f64::INFINITY,
708            max: f64::NEG_INFINITY,
709        }
710    }
711    /// Records a sample.
712    pub fn record(&mut self, val: f64) {
713        self.count += 1;
714        self.sum += val;
715        if val < self.min {
716            self.min = val;
717        }
718        if val > self.max {
719            self.max = val;
720        }
721    }
722    /// Returns the mean, or `None` if no samples.
723    pub fn mean(&self) -> Option<f64> {
724        if self.count == 0 {
725            None
726        } else {
727            Some(self.sum / self.count as f64)
728        }
729    }
730    /// Returns the minimum, or `None` if no samples.
731    pub fn min(&self) -> Option<f64> {
732        if self.count == 0 {
733            None
734        } else {
735            Some(self.min)
736        }
737    }
738    /// Returns the maximum, or `None` if no samples.
739    pub fn max(&self) -> Option<f64> {
740        if self.count == 0 {
741            None
742        } else {
743            Some(self.max)
744        }
745    }
746    /// Returns the count of recorded samples.
747    pub fn count(&self) -> u64 {
748        self.count
749    }
750}
751/// A counter that can measure elapsed time between snapshots.
752#[allow(dead_code)]
753pub struct Stopwatch {
754    start: std::time::Instant,
755    splits: Vec<f64>,
756}
757#[allow(dead_code)]
758impl Stopwatch {
759    /// Creates and starts a new stopwatch.
760    pub fn start() -> Self {
761        Self {
762            start: std::time::Instant::now(),
763            splits: Vec::new(),
764        }
765    }
766    /// Records a split time (elapsed since start).
767    pub fn split(&mut self) {
768        self.splits.push(self.elapsed_ms());
769    }
770    /// Returns total elapsed milliseconds since start.
771    pub fn elapsed_ms(&self) -> f64 {
772        self.start.elapsed().as_secs_f64() * 1000.0
773    }
774    /// Returns all recorded split times.
775    pub fn splits(&self) -> &[f64] {
776        &self.splits
777    }
778    /// Returns the number of splits.
779    pub fn num_splits(&self) -> usize {
780        self.splits.len()
781    }
782}
783/// A tagged union for representing a simple two-case discriminated union.
784#[allow(dead_code)]
785pub enum Either2<A, B> {
786    /// The first alternative.
787    First(A),
788    /// The second alternative.
789    Second(B),
790}
791#[allow(dead_code)]
792impl<A, B> Either2<A, B> {
793    /// Returns `true` if this is the first alternative.
794    pub fn is_first(&self) -> bool {
795        matches!(self, Either2::First(_))
796    }
797    /// Returns `true` if this is the second alternative.
798    pub fn is_second(&self) -> bool {
799        matches!(self, Either2::Second(_))
800    }
801    /// Returns the first value if present.
802    pub fn first(self) -> Option<A> {
803        match self {
804            Either2::First(a) => Some(a),
805            _ => None,
806        }
807    }
808    /// Returns the second value if present.
809    pub fn second(self) -> Option<B> {
810        match self {
811            Either2::Second(b) => Some(b),
812            _ => None,
813        }
814    }
815    /// Maps over the first alternative.
816    pub fn map_first<C, F: FnOnce(A) -> C>(self, f: F) -> Either2<C, B> {
817        match self {
818            Either2::First(a) => Either2::First(f(a)),
819            Either2::Second(b) => Either2::Second(b),
820        }
821    }
822}
823/// A write-once cell.
824#[allow(dead_code)]
825pub struct WriteOnce<T> {
826    value: std::cell::Cell<Option<T>>,
827}
828#[allow(dead_code)]
829impl<T: Copy> WriteOnce<T> {
830    /// Creates an empty write-once cell.
831    pub fn new() -> Self {
832        Self {
833            value: std::cell::Cell::new(None),
834        }
835    }
836    /// Writes a value.  Returns `false` if already written.
837    pub fn write(&self, val: T) -> bool {
838        if self.value.get().is_some() {
839            return false;
840        }
841        self.value.set(Some(val));
842        true
843    }
844    /// Returns the value if written.
845    pub fn read(&self) -> Option<T> {
846        self.value.get()
847    }
848    /// Returns `true` if the value has been written.
849    pub fn is_written(&self) -> bool {
850        self.value.get().is_some()
851    }
852}
853/// A simple decision tree node for rule dispatching.
854#[allow(dead_code)]
855#[allow(missing_docs)]
856pub enum DecisionNode {
857    /// A leaf with an action string.
858    Leaf(String),
859    /// An interior node: check `key` equals `val` → `yes_branch`, else `no_branch`.
860    Branch {
861        key: String,
862        val: String,
863        yes_branch: Box<DecisionNode>,
864        no_branch: Box<DecisionNode>,
865    },
866}
867#[allow(dead_code)]
868impl DecisionNode {
869    /// Evaluates the decision tree with the given context.
870    pub fn evaluate(&self, ctx: &std::collections::HashMap<String, String>) -> &str {
871        match self {
872            DecisionNode::Leaf(action) => action.as_str(),
873            DecisionNode::Branch {
874                key,
875                val,
876                yes_branch,
877                no_branch,
878            } => {
879                let actual = ctx.get(key).map(|s| s.as_str()).unwrap_or("");
880                if actual == val.as_str() {
881                    yes_branch.evaluate(ctx)
882                } else {
883                    no_branch.evaluate(ctx)
884                }
885            }
886        }
887    }
888    /// Returns the depth of the decision tree.
889    pub fn depth(&self) -> usize {
890        match self {
891            DecisionNode::Leaf(_) => 0,
892            DecisionNode::Branch {
893                yes_branch,
894                no_branch,
895                ..
896            } => 1 + yes_branch.depth().max(no_branch.depth()),
897        }
898    }
899}
900/// A dependency closure builder (transitive closure via BFS).
901#[allow(dead_code)]
902pub struct TransitiveClosure {
903    adj: Vec<Vec<usize>>,
904    n: usize,
905}
906#[allow(dead_code)]
907impl TransitiveClosure {
908    /// Creates a transitive closure builder for `n` nodes.
909    pub fn new(n: usize) -> Self {
910        Self {
911            adj: vec![Vec::new(); n],
912            n,
913        }
914    }
915    /// Adds a direct edge.
916    pub fn add_edge(&mut self, from: usize, to: usize) {
917        if from < self.n {
918            self.adj[from].push(to);
919        }
920    }
921    /// Computes all nodes reachable from `start` (including `start`).
922    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
923        let mut visited = vec![false; self.n];
924        let mut queue = std::collections::VecDeque::new();
925        queue.push_back(start);
926        while let Some(node) = queue.pop_front() {
927            if node >= self.n || visited[node] {
928                continue;
929            }
930            visited[node] = true;
931            for &next in &self.adj[node] {
932                queue.push_back(next);
933            }
934        }
935        (0..self.n).filter(|&i| visited[i]).collect()
936    }
937    /// Returns `true` if `from` can transitively reach `to`.
938    pub fn can_reach(&self, from: usize, to: usize) -> bool {
939        self.reachable_from(from).contains(&to)
940    }
941}
942/// A simple directed acyclic graph.
943#[allow(dead_code)]
944pub struct SimpleDag {
945    /// `edges[i]` is the list of direct successors of node `i`.
946    edges: Vec<Vec<usize>>,
947}
948#[allow(dead_code)]
949impl SimpleDag {
950    /// Creates a DAG with `n` nodes and no edges.
951    pub fn new(n: usize) -> Self {
952        Self {
953            edges: vec![Vec::new(); n],
954        }
955    }
956    /// Adds an edge from `from` to `to`.
957    pub fn add_edge(&mut self, from: usize, to: usize) {
958        if from < self.edges.len() {
959            self.edges[from].push(to);
960        }
961    }
962    /// Returns the successors of `node`.
963    pub fn successors(&self, node: usize) -> &[usize] {
964        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
965    }
966    /// Returns `true` if `from` can reach `to` via DFS.
967    pub fn can_reach(&self, from: usize, to: usize) -> bool {
968        let mut visited = vec![false; self.edges.len()];
969        self.dfs(from, to, &mut visited)
970    }
971    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
972        if cur == target {
973            return true;
974        }
975        if cur >= visited.len() || visited[cur] {
976            return false;
977        }
978        visited[cur] = true;
979        for &next in self.successors(cur) {
980            if self.dfs(next, target, visited) {
981                return true;
982            }
983        }
984        false
985    }
986    /// Returns the topological order of nodes, or `None` if a cycle is detected.
987    pub fn topological_sort(&self) -> Option<Vec<usize>> {
988        let n = self.edges.len();
989        let mut in_degree = vec![0usize; n];
990        for succs in &self.edges {
991            for &s in succs {
992                if s < n {
993                    in_degree[s] += 1;
994                }
995            }
996        }
997        let mut queue: std::collections::VecDeque<usize> =
998            (0..n).filter(|&i| in_degree[i] == 0).collect();
999        let mut order = Vec::new();
1000        while let Some(node) = queue.pop_front() {
1001            order.push(node);
1002            for &s in self.successors(node) {
1003                if s < n {
1004                    in_degree[s] -= 1;
1005                    if in_degree[s] == 0 {
1006                        queue.push_back(s);
1007                    }
1008                }
1009            }
1010        }
1011        if order.len() == n {
1012            Some(order)
1013        } else {
1014            None
1015        }
1016    }
1017    /// Returns the number of nodes.
1018    pub fn num_nodes(&self) -> usize {
1019        self.edges.len()
1020    }
1021}
1022/// Kind of congruence argument.
1023///
1024/// When generating a congruence lemma for `f a₁ a₂ ... aₙ`,
1025/// each argument `aᵢ` falls into one of these categories.
1026#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1027pub enum CongrArgKind {
1028    /// Fixed: this argument is the same on both sides (e.g., type parameters).
1029    Fixed,
1030    /// Eq: this argument requires an equality proof `aᵢ = bᵢ`.
1031    Eq,
1032    /// HEq: this argument requires a heterogeneous equality `HEq aᵢ bᵢ`.
1033    HEq,
1034    /// Cast: this argument is obtained by casting via a previous equality.
1035    Cast,
1036    /// Subsingletonality: this argument is in a subsingleton type (at most one element),
1037    /// so any two values are equal.
1038    Subsingle,
1039}
1040/// Congruence closure for tracking equalities.
1041///
1042/// Uses union-find with path compression for efficient equivalence
1043/// class management, plus congruence propagation for f(a) = f(b)
1044/// when a = b.
1045pub struct CongruenceClosure {
1046    /// Parent pointers for union-find
1047    parent: HashMap<Expr, Expr>,
1048    /// Rank for union by rank
1049    rank: HashMap<Expr, u32>,
1050    /// Pending equalities to process
1051    pending: Vec<(Expr, Expr)>,
1052    /// All App expressions we've seen, as (fn, arg) pairs
1053    apps: HashSet<(Expr, Expr)>,
1054    /// Proof terms for equalities (for proof reconstruction)
1055    proofs: HashMap<(Expr, Expr), Expr>,
1056}
1057impl CongruenceClosure {
1058    /// Create a new congruence closure.
1059    pub fn new() -> Self {
1060        Self {
1061            parent: HashMap::new(),
1062            rank: HashMap::new(),
1063            pending: Vec::new(),
1064            apps: HashSet::new(),
1065            proofs: HashMap::new(),
1066        }
1067    }
1068    /// Find the representative of an expression (with path compression).
1069    pub fn find(&mut self, expr: &Expr) -> Expr {
1070        if !self.parent.contains_key(expr) {
1071            self.parent.insert(expr.clone(), expr.clone());
1072            self.rank.insert(expr.clone(), 0);
1073            return expr.clone();
1074        }
1075        let parent = self
1076            .parent
1077            .get(expr)
1078            .cloned()
1079            .expect("expr must have a parent in the union-find structure");
1080        if &parent == expr {
1081            return expr.clone();
1082        }
1083        let root = self.find(&parent);
1084        self.parent.insert(expr.clone(), root.clone());
1085        root
1086    }
1087    /// Merge two equivalence classes (union by rank).
1088    fn union(&mut self, e1: &Expr, e2: &Expr) {
1089        let r1 = self.find(e1);
1090        let r2 = self.find(e2);
1091        if r1 == r2 {
1092            return;
1093        }
1094        let rank1 = *self.rank.get(&r1).unwrap_or(&0);
1095        let rank2 = *self.rank.get(&r2).unwrap_or(&0);
1096        if rank1 < rank2 {
1097            self.parent.insert(r1, r2);
1098        } else if rank1 > rank2 {
1099            self.parent.insert(r2, r1);
1100        } else {
1101            self.parent.insert(r2, r1.clone());
1102            self.rank.insert(r1, rank1 + 1);
1103        }
1104    }
1105    /// Register an expression and its subexpressions.
1106    fn register(&mut self, expr: &Expr) {
1107        if !self.parent.contains_key(expr) {
1108            self.parent.insert(expr.clone(), expr.clone());
1109            self.rank.insert(expr.clone(), 0);
1110        }
1111        if let Expr::App(f, a) = expr {
1112            self.apps.insert(((**f).clone(), (**a).clone()));
1113            self.register(f);
1114            self.register(a);
1115        }
1116    }
1117    /// Add an equality with an optional proof term.
1118    pub fn add_equality(&mut self, e1: Expr, e2: Expr) {
1119        self.register(&e1);
1120        self.register(&e2);
1121        self.pending.push((e1, e2));
1122        self.process_pending();
1123    }
1124    /// Add an equality with a proof term.
1125    pub fn add_equality_with_proof(&mut self, e1: Expr, e2: Expr, proof: Expr) {
1126        self.proofs.insert((e1.clone(), e2.clone()), proof);
1127        self.add_equality(e1, e2);
1128    }
1129    /// Process pending equalities and propagate congruences.
1130    fn process_pending(&mut self) {
1131        while let Some((e1, e2)) = self.pending.pop() {
1132            let r1 = self.find(&e1);
1133            let r2 = self.find(&e2);
1134            if r1 == r2 {
1135                continue;
1136            }
1137            self.union(&e1, &e2);
1138            let apps_vec: Vec<_> = self.apps.iter().cloned().collect();
1139            for i in 0..apps_vec.len() {
1140                for j in (i + 1)..apps_vec.len() {
1141                    let (f1, a1) = &apps_vec[i];
1142                    let (f2, a2) = &apps_vec[j];
1143                    let rf1 = self.find(f1);
1144                    let rf2 = self.find(f2);
1145                    let ra1 = self.find(a1);
1146                    let ra2 = self.find(a2);
1147                    if rf1 == rf2 && ra1 == ra2 {
1148                        let app1 = Expr::App(Box::new(f1.clone()), Box::new(a1.clone()));
1149                        let app2 = Expr::App(Box::new(f2.clone()), Box::new(a2.clone()));
1150                        if self.find(&app1) != self.find(&app2) {
1151                            self.pending.push((app1, app2));
1152                        }
1153                    }
1154                }
1155            }
1156        }
1157    }
1158    /// Check if two expressions are equal.
1159    pub fn are_equal(&mut self, e1: &Expr, e2: &Expr) -> bool {
1160        if self.find(e1) == self.find(e2) {
1161            return true;
1162        }
1163        match (e1, e2) {
1164            (Expr::App(f1, a1), Expr::App(f2, a2)) => {
1165                self.are_equal(f1, f2) && self.are_equal(a1, a2)
1166            }
1167            _ => false,
1168        }
1169    }
1170    /// Get all expressions in the same equivalence class as `expr`.
1171    pub fn get_class(&mut self, expr: &Expr) -> Vec<Expr> {
1172        let root = self.find(expr);
1173        let keys: Vec<Expr> = self.parent.keys().cloned().collect();
1174        keys.into_iter().filter(|e| self.find(e) == root).collect()
1175    }
1176    /// Get the number of equivalence classes.
1177    pub fn num_classes(&mut self) -> usize {
1178        let keys: Vec<Expr> = self.parent.keys().cloned().collect();
1179        let mut roots = HashSet::new();
1180        for k in &keys {
1181            roots.insert(self.find(k));
1182        }
1183        roots.len()
1184    }
1185    /// Get a proof that two expressions are equal (if available).
1186    pub fn get_proof(&self, e1: &Expr, e2: &Expr) -> Option<&Expr> {
1187        self.proofs
1188            .get(&(e1.clone(), e2.clone()))
1189            .or_else(|| self.proofs.get(&(e2.clone(), e1.clone())))
1190    }
1191    /// Clear all state.
1192    pub fn clear(&mut self) {
1193        self.parent.clear();
1194        self.rank.clear();
1195        self.pending.clear();
1196        self.apps.clear();
1197        self.proofs.clear();
1198    }
1199}
1200/// A flat application: `(fn_idx, arg_idx) → result_idx`.
1201#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1202pub struct FlatApp {
1203    /// Index of the function term.
1204    pub fn_idx: TermIdx,
1205    /// Index of the argument term.
1206    pub arg_idx: TermIdx,
1207    /// Index of the result term (the node representing the application).
1208    /// `None` if not tracked (result congruence propagation is skipped).
1209    pub result_idx: Option<TermIdx>,
1210}
1211/// A pool of reusable string buffers.
1212#[allow(dead_code)]
1213pub struct StringPool {
1214    free: Vec<String>,
1215}
1216#[allow(dead_code)]
1217impl StringPool {
1218    /// Creates a new empty string pool.
1219    pub fn new() -> Self {
1220        Self { free: Vec::new() }
1221    }
1222    /// Takes a string from the pool (may be empty).
1223    pub fn take(&mut self) -> String {
1224        self.free.pop().unwrap_or_default()
1225    }
1226    /// Returns a string to the pool.
1227    pub fn give(&mut self, mut s: String) {
1228        s.clear();
1229        self.free.push(s);
1230    }
1231    /// Returns the number of free strings in the pool.
1232    pub fn free_count(&self) -> usize {
1233        self.free.len()
1234    }
1235}
1236/// A sparse vector: stores only non-default elements.
1237#[allow(dead_code)]
1238pub struct SparseVec<T: Default + Clone + PartialEq> {
1239    entries: std::collections::HashMap<usize, T>,
1240    default_: T,
1241    logical_len: usize,
1242}
1243#[allow(dead_code)]
1244impl<T: Default + Clone + PartialEq> SparseVec<T> {
1245    /// Creates a new sparse vector with logical length `len`.
1246    pub fn new(len: usize) -> Self {
1247        Self {
1248            entries: std::collections::HashMap::new(),
1249            default_: T::default(),
1250            logical_len: len,
1251        }
1252    }
1253    /// Sets element at `idx`.
1254    pub fn set(&mut self, idx: usize, val: T) {
1255        if val == self.default_ {
1256            self.entries.remove(&idx);
1257        } else {
1258            self.entries.insert(idx, val);
1259        }
1260    }
1261    /// Gets element at `idx`.
1262    pub fn get(&self, idx: usize) -> &T {
1263        self.entries.get(&idx).unwrap_or(&self.default_)
1264    }
1265    /// Returns the logical length.
1266    pub fn len(&self) -> usize {
1267        self.logical_len
1268    }
1269    /// Returns whether the collection is empty.
1270    pub fn is_empty(&self) -> bool {
1271        self.len() == 0
1272    }
1273    /// Returns the number of non-default elements.
1274    pub fn nnz(&self) -> usize {
1275        self.entries.len()
1276    }
1277}
1278/// A simple stack-based calculator for arithmetic expressions.
1279#[allow(dead_code)]
1280pub struct StackCalc {
1281    stack: Vec<i64>,
1282}
1283#[allow(dead_code)]
1284impl StackCalc {
1285    /// Creates a new empty calculator.
1286    pub fn new() -> Self {
1287        Self { stack: Vec::new() }
1288    }
1289    /// Pushes an integer literal.
1290    pub fn push(&mut self, n: i64) {
1291        self.stack.push(n);
1292    }
1293    /// Adds the top two values.  Panics if fewer than two values.
1294    pub fn add(&mut self) {
1295        let b = self
1296            .stack
1297            .pop()
1298            .expect("stack must have at least two values for add");
1299        let a = self
1300            .stack
1301            .pop()
1302            .expect("stack must have at least two values for add");
1303        self.stack.push(a + b);
1304    }
1305    /// Subtracts top from second.
1306    pub fn sub(&mut self) {
1307        let b = self
1308            .stack
1309            .pop()
1310            .expect("stack must have at least two values for sub");
1311        let a = self
1312            .stack
1313            .pop()
1314            .expect("stack must have at least two values for sub");
1315        self.stack.push(a - b);
1316    }
1317    /// Multiplies the top two values.
1318    pub fn mul(&mut self) {
1319        let b = self
1320            .stack
1321            .pop()
1322            .expect("stack must have at least two values for mul");
1323        let a = self
1324            .stack
1325            .pop()
1326            .expect("stack must have at least two values for mul");
1327        self.stack.push(a * b);
1328    }
1329    /// Peeks the top value.
1330    pub fn peek(&self) -> Option<i64> {
1331        self.stack.last().copied()
1332    }
1333    /// Returns the stack depth.
1334    pub fn depth(&self) -> usize {
1335        self.stack.len()
1336    }
1337}
1338/// A congruence theorem for a specific function.
1339#[derive(Debug, Clone)]
1340pub struct CongruenceTheorem {
1341    /// The function this theorem is about
1342    pub fn_name: Name,
1343    /// The number of arguments
1344    pub num_args: usize,
1345    /// The kind of each argument
1346    pub arg_kinds: Vec<CongrArgKind>,
1347    /// The proof term (if generated)
1348    pub proof: Option<Expr>,
1349    /// The type of the congruence lemma
1350    pub ty: Option<Expr>,
1351}
1352impl CongruenceTheorem {
1353    /// Create a new congruence theorem description.
1354    pub fn new(fn_name: Name, arg_kinds: Vec<CongrArgKind>) -> Self {
1355        let num_args = arg_kinds.len();
1356        Self {
1357            fn_name,
1358            num_args,
1359            arg_kinds,
1360            proof: None,
1361            ty: None,
1362        }
1363    }
1364    /// Check if any argument needs an equality proof.
1365    pub fn has_eq_args(&self) -> bool {
1366        self.arg_kinds
1367            .iter()
1368            .any(|k| matches!(k, CongrArgKind::Eq | CongrArgKind::HEq))
1369    }
1370    /// Get the number of equality hypotheses needed.
1371    pub fn num_eq_hypotheses(&self) -> usize {
1372        self.arg_kinds
1373            .iter()
1374            .filter(|k| matches!(k, CongrArgKind::Eq | CongrArgKind::HEq))
1375            .count()
1376    }
1377}
1378/// A mutable reference stack for tracking the current "focus" in a tree traversal.
1379#[allow(dead_code)]
1380pub struct FocusStack<T> {
1381    items: Vec<T>,
1382}
1383#[allow(dead_code)]
1384impl<T> FocusStack<T> {
1385    /// Creates an empty focus stack.
1386    pub fn new() -> Self {
1387        Self { items: Vec::new() }
1388    }
1389    /// Focuses on `item`.
1390    pub fn focus(&mut self, item: T) {
1391        self.items.push(item);
1392    }
1393    /// Blurs (pops) the current focus.
1394    pub fn blur(&mut self) -> Option<T> {
1395        self.items.pop()
1396    }
1397    /// Returns the current focus, or `None`.
1398    pub fn current(&self) -> Option<&T> {
1399        self.items.last()
1400    }
1401    /// Returns the focus depth.
1402    pub fn depth(&self) -> usize {
1403        self.items.len()
1404    }
1405    /// Returns `true` if there is no current focus.
1406    pub fn is_empty(&self) -> bool {
1407        self.items.is_empty()
1408    }
1409}
1410/// A simple mutable key-value store for test fixtures.
1411#[allow(dead_code)]
1412pub struct Fixture {
1413    data: std::collections::HashMap<String, String>,
1414}
1415#[allow(dead_code)]
1416impl Fixture {
1417    /// Creates an empty fixture.
1418    pub fn new() -> Self {
1419        Self {
1420            data: std::collections::HashMap::new(),
1421        }
1422    }
1423    /// Sets a key.
1424    pub fn set(&mut self, key: impl Into<String>, val: impl Into<String>) {
1425        self.data.insert(key.into(), val.into());
1426    }
1427    /// Gets a value.
1428    pub fn get(&self, key: &str) -> Option<&str> {
1429        self.data.get(key).map(|s| s.as_str())
1430    }
1431    /// Returns the number of entries.
1432    pub fn len(&self) -> usize {
1433        self.data.len()
1434    }
1435    /// Returns whether the collection is empty.
1436    pub fn is_empty(&self) -> bool {
1437        self.len() == 0
1438    }
1439}
1440/// A hypothesis in a congruence lemma.
1441///
1442/// Records the type of an equality hypothesis that must be provided.
1443#[derive(Debug, Clone)]
1444pub struct CongrHypothesis {
1445    /// Left-hand side of the equality.
1446    pub lhs: Expr,
1447    /// Right-hand side of the equality.
1448    pub rhs: Expr,
1449    /// Whether this is a heterogeneous equality.
1450    pub is_heq: bool,
1451}
1452impl CongrHypothesis {
1453    /// Create a homogeneous equality hypothesis `lhs = rhs`.
1454    pub fn eq(lhs: Expr, rhs: Expr) -> Self {
1455        Self {
1456            lhs,
1457            rhs,
1458            is_heq: false,
1459        }
1460    }
1461    /// Create a heterogeneous equality hypothesis `HEq lhs rhs`.
1462    pub fn heq(lhs: Expr, rhs: Expr) -> Self {
1463        Self {
1464            lhs,
1465            rhs,
1466            is_heq: true,
1467        }
1468    }
1469    /// Check if this is a trivial hypothesis (`lhs = lhs`).
1470    pub fn is_trivial(&self) -> bool {
1471        self.lhs == self.rhs
1472    }
1473}
1474/// Flat union-find for congruence closure on term indices.
1475///
1476/// This is a simpler, more cache-friendly representation than
1477/// the `HashMap`-based approach above.
1478pub struct FlatCC {
1479    /// Parent pointers (by index).
1480    parent: Vec<TermIdx>,
1481    /// Rank for union by rank.
1482    rank: Vec<u32>,
1483    /// All registered applications.
1484    apps: Vec<FlatApp>,
1485}
1486impl FlatCC {
1487    /// Create a new flat CC with `n` initial singleton classes.
1488    pub fn new(n: usize) -> Self {
1489        Self {
1490            parent: (0..n).collect(),
1491            rank: vec![0; n],
1492            apps: Vec::new(),
1493        }
1494    }
1495    /// Find the root of the equivalence class containing `i`.
1496    pub fn find(&mut self, i: TermIdx) -> TermIdx {
1497        if self.parent[i] != i {
1498            self.parent[i] = self.find(self.parent[i]);
1499        }
1500        self.parent[i]
1501    }
1502    /// Merge the classes of `i` and `j`.
1503    pub fn union(&mut self, i: TermIdx, j: TermIdx) {
1504        let ri = self.find(i);
1505        let rj = self.find(j);
1506        if ri == rj {
1507            return;
1508        }
1509        if self.rank[ri] < self.rank[rj] {
1510            self.parent[ri] = rj;
1511        } else if self.rank[ri] > self.rank[rj] {
1512            self.parent[rj] = ri;
1513        } else {
1514            self.parent[rj] = ri;
1515            self.rank[ri] += 1;
1516        }
1517    }
1518    /// Add a new term node, returning its index.
1519    pub fn add_node(&mut self) -> TermIdx {
1520        let idx = self.parent.len();
1521        self.parent.push(idx);
1522        self.rank.push(0);
1523        idx
1524    }
1525    /// Register an application.
1526    pub fn add_app(&mut self, app: FlatApp) {
1527        self.apps.push(app);
1528    }
1529    /// Check if `i` and `j` are in the same class.
1530    pub fn are_equal(&mut self, i: TermIdx, j: TermIdx) -> bool {
1531        self.find(i) == self.find(j)
1532    }
1533    /// Get the total number of term nodes.
1534    pub fn num_nodes(&self) -> usize {
1535        self.parent.len()
1536    }
1537    /// Get the number of registered applications.
1538    pub fn num_apps(&self) -> usize {
1539        self.apps.len()
1540    }
1541    /// Propagate congruences: if fn(i) == fn(j) and arg(i) == arg(j),
1542    /// then result(i) == result(j).
1543    ///
1544    /// Merges the result classes of congruent applications so that downstream
1545    /// `are_equal` queries reflect these equalities.
1546    pub fn propagate_congruences(&mut self) {
1547        let len = self.apps.len();
1548        for i in 0..len {
1549            for j in (i + 1)..len {
1550                let a = self.apps[i];
1551                let b = self.apps[j];
1552                if self.are_equal(a.fn_idx, b.fn_idx) && self.are_equal(a.arg_idx, b.arg_idx) {
1553                    if let (Some(ra), Some(rb)) = (a.result_idx, b.result_idx) {
1554                        self.union(ra, rb);
1555                    }
1556                }
1557            }
1558        }
1559    }
1560}
1561/// A node in the E-graph, representing an equivalence class.
1562#[derive(Debug, Clone)]
1563pub struct ENode {
1564    /// Representative of this equivalence class.
1565    pub repr: Expr,
1566    /// All members of this equivalence class.
1567    pub members: Vec<Expr>,
1568    /// Proof terms linking members to the representative (if available).
1569    pub proofs: Vec<Option<Expr>>,
1570}
1571impl ENode {
1572    /// Create a new E-node with a single expression.
1573    pub fn singleton(expr: Expr) -> Self {
1574        Self {
1575            repr: expr.clone(),
1576            members: vec![expr],
1577            proofs: vec![None],
1578        }
1579    }
1580    /// Add an expression to this equivalence class.
1581    pub fn add_member(&mut self, expr: Expr, proof: Option<Expr>) {
1582        if !self.members.contains(&expr) {
1583            self.members.push(expr);
1584            self.proofs.push(proof);
1585        }
1586    }
1587    /// Check if an expression is in this class.
1588    pub fn contains(&self, expr: &Expr) -> bool {
1589        self.members.contains(expr)
1590    }
1591    /// Size of the equivalence class.
1592    pub fn size(&self) -> usize {
1593        self.members.len()
1594    }
1595}
1596/// A type-erased function pointer with arity tracking.
1597#[allow(dead_code)]
1598pub struct RawFnPtr {
1599    /// The raw function pointer (stored as usize for type erasure).
1600    ptr: usize,
1601    arity: usize,
1602    name: String,
1603}
1604#[allow(dead_code)]
1605impl RawFnPtr {
1606    /// Creates a new raw function pointer descriptor.
1607    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
1608        Self {
1609            ptr,
1610            arity,
1611            name: name.into(),
1612        }
1613    }
1614    /// Returns the arity.
1615    pub fn arity(&self) -> usize {
1616        self.arity
1617    }
1618    /// Returns the name.
1619    pub fn name(&self) -> &str {
1620        &self.name
1621    }
1622    /// Returns the raw pointer value.
1623    pub fn raw(&self) -> usize {
1624        self.ptr
1625    }
1626}
1627/// A label set for a graph node.
1628#[allow(dead_code)]
1629pub struct LabelSet {
1630    labels: Vec<String>,
1631}
1632#[allow(dead_code)]
1633impl LabelSet {
1634    /// Creates a new empty label set.
1635    pub fn new() -> Self {
1636        Self { labels: Vec::new() }
1637    }
1638    /// Adds a label (deduplicates).
1639    pub fn add(&mut self, label: impl Into<String>) {
1640        let s = label.into();
1641        if !self.labels.contains(&s) {
1642            self.labels.push(s);
1643        }
1644    }
1645    /// Returns `true` if `label` is present.
1646    pub fn has(&self, label: &str) -> bool {
1647        self.labels.iter().any(|l| l == label)
1648    }
1649    /// Returns the count of labels.
1650    pub fn count(&self) -> usize {
1651        self.labels.len()
1652    }
1653    /// Returns all labels.
1654    pub fn all(&self) -> &[String] {
1655        &self.labels
1656    }
1657}
1658/// A min-heap implemented as a binary heap.
1659#[allow(dead_code)]
1660pub struct MinHeap<T: Ord> {
1661    data: Vec<T>,
1662}
1663#[allow(dead_code)]
1664impl<T: Ord> MinHeap<T> {
1665    /// Creates a new empty min-heap.
1666    pub fn new() -> Self {
1667        Self { data: Vec::new() }
1668    }
1669    /// Inserts an element.
1670    pub fn push(&mut self, val: T) {
1671        self.data.push(val);
1672        self.sift_up(self.data.len() - 1);
1673    }
1674    /// Removes and returns the minimum element.
1675    pub fn pop(&mut self) -> Option<T> {
1676        if self.data.is_empty() {
1677            return None;
1678        }
1679        let n = self.data.len();
1680        self.data.swap(0, n - 1);
1681        let min = self.data.pop();
1682        if !self.data.is_empty() {
1683            self.sift_down(0);
1684        }
1685        min
1686    }
1687    /// Returns a reference to the minimum element.
1688    pub fn peek(&self) -> Option<&T> {
1689        self.data.first()
1690    }
1691    /// Returns the number of elements.
1692    pub fn len(&self) -> usize {
1693        self.data.len()
1694    }
1695    /// Returns `true` if empty.
1696    pub fn is_empty(&self) -> bool {
1697        self.data.is_empty()
1698    }
1699    fn sift_up(&mut self, mut i: usize) {
1700        while i > 0 {
1701            let parent = (i - 1) / 2;
1702            if self.data[i] < self.data[parent] {
1703                self.data.swap(i, parent);
1704                i = parent;
1705            } else {
1706                break;
1707            }
1708        }
1709    }
1710    fn sift_down(&mut self, mut i: usize) {
1711        let n = self.data.len();
1712        loop {
1713            let left = 2 * i + 1;
1714            let right = 2 * i + 2;
1715            let mut smallest = i;
1716            if left < n && self.data[left] < self.data[smallest] {
1717                smallest = left;
1718            }
1719            if right < n && self.data[right] < self.data[smallest] {
1720                smallest = right;
1721            }
1722            if smallest == i {
1723                break;
1724            }
1725            self.data.swap(i, smallest);
1726            i = smallest;
1727        }
1728    }
1729}
1730/// Statistics gathered by the congruence closure algorithm.
1731#[derive(Debug, Clone, Default)]
1732pub struct CongrClosureStats {
1733    /// Number of equalities added.
1734    pub equalities_added: usize,
1735    /// Number of congruence steps propagated.
1736    pub congruences_propagated: usize,
1737    /// Number of union operations performed.
1738    pub unions: usize,
1739    /// Number of app expressions tracked.
1740    pub apps_tracked: usize,
1741}
1742impl CongrClosureStats {
1743    /// Create empty stats.
1744    pub fn new() -> Self {
1745        Self::default()
1746    }
1747}
1748/// A simple key-value store backed by a sorted Vec for small maps.
1749#[allow(dead_code)]
1750pub struct SmallMap<K: Ord + Clone, V: Clone> {
1751    entries: Vec<(K, V)>,
1752}
1753#[allow(dead_code)]
1754impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
1755    /// Creates a new empty small map.
1756    pub fn new() -> Self {
1757        Self {
1758            entries: Vec::new(),
1759        }
1760    }
1761    /// Inserts or replaces the value for `key`.
1762    pub fn insert(&mut self, key: K, val: V) {
1763        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
1764            Ok(i) => self.entries[i].1 = val,
1765            Err(i) => self.entries.insert(i, (key, val)),
1766        }
1767    }
1768    /// Returns the value for `key`, or `None`.
1769    pub fn get(&self, key: &K) -> Option<&V> {
1770        self.entries
1771            .binary_search_by_key(&key, |(k, _)| k)
1772            .ok()
1773            .map(|i| &self.entries[i].1)
1774    }
1775    /// Returns the number of entries.
1776    pub fn len(&self) -> usize {
1777        self.entries.len()
1778    }
1779    /// Returns `true` if empty.
1780    pub fn is_empty(&self) -> bool {
1781        self.entries.is_empty()
1782    }
1783    /// Returns all keys.
1784    pub fn keys(&self) -> Vec<&K> {
1785        self.entries.iter().map(|(k, _)| k).collect()
1786    }
1787    /// Returns all values.
1788    pub fn values(&self) -> Vec<&V> {
1789        self.entries.iter().map(|(_, v)| v).collect()
1790    }
1791}