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