use super::functions::*;
use crate::Expr;
use std::collections::{HashMap, HashSet, VecDeque};
#[allow(dead_code)]
pub struct EquivalenceTable {
hash_to_id: std::collections::HashMap<u64, usize>,
uf: UnionFind,
next_id: usize,
}
#[allow(dead_code)]
impl EquivalenceTable {
pub fn new(capacity: usize) -> Self {
Self {
hash_to_id: std::collections::HashMap::with_capacity(capacity),
uf: UnionFind::new(capacity),
next_id: 0,
}
}
pub fn register(&mut self, hash: u64) -> usize {
if let Some(&id) = self.hash_to_id.get(&hash) {
return id;
}
let id = self.next_id;
self.next_id += 1;
self.hash_to_id.insert(hash, id);
id
}
pub fn merge(&mut self, h1: u64, h2: u64) {
let id1 = self.register(h1);
let id2 = self.register(h2);
self.uf.union(id1, id2);
}
pub fn equiv(&mut self, h1: u64, h2: u64) -> bool {
let id1 = self.register(h1);
let id2 = self.register(h2);
self.uf.same(id1, id2)
}
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Timestamp(u64);
#[allow(dead_code)]
impl Timestamp {
pub const fn from_us(us: u64) -> Self {
Self(us)
}
pub fn as_us(self) -> u64 {
self.0
}
pub fn elapsed_since(self, earlier: Timestamp) -> u64 {
self.0.saturating_sub(earlier.0)
}
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Generation(u32);
#[allow(dead_code)]
impl Generation {
pub const INITIAL: Generation = Generation(0);
pub fn advance(self) -> Generation {
Generation(self.0 + 1)
}
pub fn number(self) -> u32 {
self.0
}
}
#[allow(dead_code)]
pub struct LoopClock {
start: std::time::Instant,
iters: u64,
}
#[allow(dead_code)]
impl LoopClock {
pub fn start() -> Self {
Self {
start: std::time::Instant::now(),
iters: 0,
}
}
pub fn tick(&mut self) {
self.iters += 1;
}
pub fn elapsed_us(&self) -> f64 {
self.start.elapsed().as_secs_f64() * 1e6
}
pub fn avg_us_per_iter(&self) -> f64 {
if self.iters == 0 {
return 0.0;
}
self.elapsed_us() / self.iters as f64
}
pub fn iters(&self) -> u64 {
self.iters
}
}
#[allow(dead_code)]
pub struct BiMap<A: std::hash::Hash + Eq + Clone, B: std::hash::Hash + Eq + Clone> {
forward: std::collections::HashMap<A, B>,
backward: std::collections::HashMap<B, A>,
}
#[allow(dead_code)]
impl<A: std::hash::Hash + Eq + Clone, B: std::hash::Hash + Eq + Clone> BiMap<A, B> {
pub fn new() -> Self {
Self {
forward: std::collections::HashMap::new(),
backward: std::collections::HashMap::new(),
}
}
pub fn insert(&mut self, a: A, b: B) {
self.forward.insert(a.clone(), b.clone());
self.backward.insert(b, a);
}
pub fn get_b(&self, a: &A) -> Option<&B> {
self.forward.get(a)
}
pub fn get_a(&self, b: &B) -> Option<&A> {
self.backward.get(b)
}
pub fn len(&self) -> usize {
self.forward.len()
}
pub fn is_empty(&self) -> bool {
self.forward.is_empty()
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub struct BeforeAfter<T> {
pub before: T,
pub after: T,
}
#[allow(dead_code)]
impl<T: PartialEq> BeforeAfter<T> {
pub fn new(before: T, after: T) -> Self {
Self { before, after }
}
pub fn unchanged(&self) -> bool {
self.before == self.after
}
}
#[allow(dead_code)]
pub struct SimpleLruCache<K: std::hash::Hash + Eq + Clone, V: Clone> {
capacity: usize,
map: std::collections::HashMap<K, usize>,
keys: Vec<K>,
vals: Vec<V>,
order: Vec<usize>,
}
#[allow(dead_code)]
impl<K: std::hash::Hash + Eq + Clone, V: Clone> SimpleLruCache<K, V> {
pub fn new(capacity: usize) -> Self {
assert!(capacity > 0);
Self {
capacity,
map: std::collections::HashMap::new(),
keys: Vec::new(),
vals: Vec::new(),
order: Vec::new(),
}
}
pub fn put(&mut self, key: K, val: V) {
if let Some(&idx) = self.map.get(&key) {
self.vals[idx] = val;
self.order.retain(|&x| x != idx);
self.order.insert(0, idx);
return;
}
if self.keys.len() >= self.capacity {
let evict_idx = *self
.order
.last()
.expect("order list must be non-empty before eviction");
self.map.remove(&self.keys[evict_idx]);
self.order.pop();
self.keys[evict_idx] = key.clone();
self.vals[evict_idx] = val;
self.map.insert(key, evict_idx);
self.order.insert(0, evict_idx);
} else {
let idx = self.keys.len();
self.keys.push(key.clone());
self.vals.push(val);
self.map.insert(key, idx);
self.order.insert(0, idx);
}
}
pub fn get(&mut self, key: &K) -> Option<&V> {
let idx = *self.map.get(key)?;
self.order.retain(|&x| x != idx);
self.order.insert(0, idx);
Some(&self.vals[idx])
}
pub fn len(&self) -> usize {
self.keys.len()
}
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
}
#[allow(dead_code)]
pub struct SparseBitSet {
words: Vec<u64>,
}
#[allow(dead_code)]
impl SparseBitSet {
pub fn new(capacity: usize) -> Self {
let words = (capacity + 63) / 64;
Self {
words: vec![0u64; words],
}
}
pub fn set(&mut self, i: usize) {
let word = i / 64;
let bit = i % 64;
if word < self.words.len() {
self.words[word] |= 1u64 << bit;
}
}
pub fn clear(&mut self, i: usize) {
let word = i / 64;
let bit = i % 64;
if word < self.words.len() {
self.words[word] &= !(1u64 << bit);
}
}
pub fn get(&self, i: usize) -> bool {
let word = i / 64;
let bit = i % 64;
self.words.get(word).is_some_and(|w| w & (1u64 << bit) != 0)
}
pub fn count_ones(&self) -> u32 {
self.words.iter().map(|w| w.count_ones()).sum()
}
pub fn union(&self, other: &SparseBitSet) -> SparseBitSet {
let len = self.words.len().max(other.words.len());
let mut result = SparseBitSet {
words: vec![0u64; len],
};
for i in 0..self.words.len() {
result.words[i] |= self.words[i];
}
for i in 0..other.words.len() {
result.words[i] |= other.words[i];
}
result
}
}
#[allow(dead_code)]
pub struct FrequencyTable<T: std::hash::Hash + Eq + Clone> {
counts: std::collections::HashMap<T, u64>,
}
#[allow(dead_code)]
impl<T: std::hash::Hash + Eq + Clone> FrequencyTable<T> {
pub fn new() -> Self {
Self {
counts: std::collections::HashMap::new(),
}
}
pub fn record(&mut self, item: T) {
*self.counts.entry(item).or_insert(0) += 1;
}
pub fn freq(&self, item: &T) -> u64 {
self.counts.get(item).copied().unwrap_or(0)
}
pub fn most_frequent(&self) -> Option<(&T, u64)> {
self.counts
.iter()
.max_by_key(|(_, &v)| v)
.map(|(k, &v)| (k, v))
}
pub fn total(&self) -> u64 {
self.counts.values().sum()
}
pub fn distinct(&self) -> usize {
self.counts.len()
}
}
#[allow(dead_code)]
pub struct WorkStack<T> {
items: Vec<T>,
}
#[allow(dead_code)]
impl<T> WorkStack<T> {
pub fn new() -> Self {
Self { items: Vec::new() }
}
pub fn push(&mut self, item: T) {
self.items.push(item);
}
pub fn pop(&mut self) -> Option<T> {
self.items.pop()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn len(&self) -> usize {
self.items.len()
}
}
#[allow(dead_code)]
pub struct SymmetricRelation {
pairs: std::collections::HashSet<(u64, u64)>,
}
#[allow(dead_code)]
impl SymmetricRelation {
pub fn new() -> Self {
Self {
pairs: std::collections::HashSet::new(),
}
}
fn key(a: u64, b: u64) -> (u64, u64) {
if a <= b {
(a, b)
} else {
(b, a)
}
}
pub fn add(&mut self, a: u64, b: u64) {
self.pairs.insert(Self::key(a, b));
}
pub fn contains(&self, a: u64, b: u64) -> bool {
self.pairs.contains(&Self::key(a, b))
}
pub fn len(&self) -> usize {
self.pairs.len()
}
pub fn is_empty(&self) -> bool {
self.pairs.is_empty()
}
}
#[allow(dead_code)]
pub struct MemoSlot<T: Clone> {
cached: Option<T>,
}
#[allow(dead_code)]
impl<T: Clone> MemoSlot<T> {
pub fn new() -> Self {
Self { cached: None }
}
pub fn get_or_compute(&mut self, f: impl FnOnce() -> T) -> &T {
if self.cached.is_none() {
self.cached = Some(f());
}
self.cached
.as_ref()
.expect("cached value must be initialized before access")
}
pub fn invalidate(&mut self) {
self.cached = None;
}
pub fn is_cached(&self) -> bool {
self.cached.is_some()
}
}
#[allow(dead_code)]
pub struct CheckedEquiv {
lhs: u64,
rhs: u64,
proof: EquivProofTerm,
}
#[allow(dead_code)]
impl CheckedEquiv {
pub fn refl(id: u64) -> Self {
Self {
lhs: id,
rhs: id,
proof: EquivProofTerm::Refl(id),
}
}
pub fn by_axiom(lhs: u64, rhs: u64, name: impl Into<String>) -> Self {
Self {
lhs,
rhs,
proof: EquivProofTerm::Axiom(name.into()),
}
}
pub fn lhs(&self) -> u64 {
self.lhs
}
pub fn rhs(&self) -> u64 {
self.rhs
}
pub fn proof(&self) -> &EquivProofTerm {
&self.proof
}
pub fn is_trivial(&self) -> bool {
self.lhs == self.rhs
}
}
#[allow(dead_code)]
pub struct EventCounter {
counts: std::collections::HashMap<String, u64>,
}
#[allow(dead_code)]
impl EventCounter {
pub fn new() -> Self {
Self {
counts: std::collections::HashMap::new(),
}
}
pub fn inc(&mut self, event: &str) {
*self.counts.entry(event.to_string()).or_insert(0) += 1;
}
pub fn add(&mut self, event: &str, n: u64) {
*self.counts.entry(event.to_string()).or_insert(0) += n;
}
pub fn get(&self, event: &str) -> u64 {
self.counts.get(event).copied().unwrap_or(0)
}
pub fn total(&self) -> u64 {
self.counts.values().sum()
}
pub fn reset(&mut self) {
self.counts.clear();
}
pub fn event_names(&self) -> Vec<&str> {
self.counts.keys().map(|s| s.as_str()).collect()
}
}
#[allow(dead_code)]
pub struct AnnotationTable {
map: std::collections::HashMap<String, Vec<String>>,
}
#[allow(dead_code)]
impl AnnotationTable {
pub fn new() -> Self {
Self {
map: std::collections::HashMap::new(),
}
}
pub fn annotate(&mut self, key: impl Into<String>, val: impl Into<String>) {
self.map.entry(key.into()).or_default().push(val.into());
}
pub fn get_all(&self, key: &str) -> &[String] {
self.map.get(key).map(|v| v.as_slice()).unwrap_or(&[])
}
pub fn num_keys(&self) -> usize {
self.map.len()
}
pub fn has(&self, key: &str) -> bool {
self.map.contains_key(key)
}
}
#[allow(dead_code)]
pub struct WorkQueue<T> {
items: std::collections::VecDeque<T>,
}
#[allow(dead_code)]
impl<T> WorkQueue<T> {
pub fn new() -> Self {
Self {
items: std::collections::VecDeque::new(),
}
}
pub fn enqueue(&mut self, item: T) {
self.items.push_back(item);
}
pub fn dequeue(&mut self) -> Option<T> {
self.items.pop_front()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn len(&self) -> usize {
self.items.len()
}
}
#[allow(dead_code)]
pub struct IdDispenser<T> {
next: u32,
_phantom: std::marker::PhantomData<T>,
}
#[allow(dead_code)]
impl<T> IdDispenser<T> {
pub fn new() -> Self {
Self {
next: 0,
_phantom: std::marker::PhantomData,
}
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> TypedId<T> {
let id = TypedId::new(self.next);
self.next += 1;
id
}
pub fn count(&self) -> u32 {
self.next
}
}
#[allow(dead_code)]
pub struct Slot<T> {
inner: Option<T>,
}
#[allow(dead_code)]
impl<T> Slot<T> {
pub fn empty() -> Self {
Self { inner: None }
}
pub fn fill(&mut self, val: T) {
assert!(self.inner.is_none(), "Slot: already filled");
self.inner = Some(val);
}
pub fn get(&self) -> Option<&T> {
self.inner.as_ref()
}
pub fn is_filled(&self) -> bool {
self.inner.is_some()
}
pub fn take(&mut self) -> Option<T> {
self.inner.take()
}
pub fn get_or_fill_with(&mut self, f: impl FnOnce() -> T) -> &T {
if self.inner.is_none() {
self.inner = Some(f());
}
self.inner
.as_ref()
.expect("inner value must be initialized before access")
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub struct EquivClass {
pub repr: usize,
pub members: Vec<usize>,
}
#[allow(dead_code)]
impl EquivClass {
pub fn singleton(id: usize) -> Self {
Self {
repr: id,
members: vec![id],
}
}
pub fn contains(&self, id: usize) -> bool {
self.members.contains(&id)
}
pub fn size(&self) -> usize {
self.members.len()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum EquivProofTerm {
Refl(u64),
Symm(Box<EquivProofTerm>),
Trans(Box<EquivProofTerm>, Box<EquivProofTerm>),
Axiom(String),
}
#[allow(dead_code)]
impl EquivProofTerm {
pub fn depth(&self) -> usize {
match self {
EquivProofTerm::Refl(_) => 0,
EquivProofTerm::Axiom(_) => 0,
EquivProofTerm::Symm(p) => 1 + p.depth(),
EquivProofTerm::Trans(p, q) => 1 + p.depth().max(q.depth()),
}
}
pub fn is_refl(&self) -> bool {
matches!(self, EquivProofTerm::Refl(_))
}
}
#[derive(Debug)]
struct UnionFindEntry {
parent: usize,
rank: u32,
}
#[derive(Clone, Debug, Default)]
pub struct EquivStats {
pub total_queries: u64,
pub equiv_hits: u64,
pub failure_hits: u64,
pub cache_misses: u64,
pub equiv_additions: u64,
pub failure_additions: u64,
}
impl EquivStats {
pub fn new() -> Self {
Self::default()
}
pub fn record_equiv_hit(&mut self) {
self.total_queries += 1;
self.equiv_hits += 1;
}
pub fn record_failure_hit(&mut self) {
self.total_queries += 1;
self.failure_hits += 1;
}
pub fn record_miss(&mut self) {
self.total_queries += 1;
self.cache_misses += 1;
}
pub fn record_equiv_addition(&mut self) {
self.equiv_additions += 1;
}
pub fn record_failure_addition(&mut self) {
self.failure_additions += 1;
}
pub fn hit_rate(&self) -> f64 {
if self.total_queries == 0 {
return 1.0;
}
(self.equiv_hits + self.failure_hits) as f64 / self.total_queries as f64
}
}
#[allow(dead_code)]
pub struct IntervalSet {
intervals: Vec<(i64, i64)>,
}
#[allow(dead_code)]
impl IntervalSet {
pub fn new() -> Self {
Self {
intervals: Vec::new(),
}
}
pub fn add(&mut self, lo: i64, hi: i64) {
if lo > hi {
return;
}
let mut new_lo = lo;
let mut new_hi = hi;
let mut i = 0;
while i < self.intervals.len() {
let (il, ih) = self.intervals[i];
if ih < new_lo - 1 {
i += 1;
continue;
}
if il > new_hi + 1 {
break;
}
new_lo = new_lo.min(il);
new_hi = new_hi.max(ih);
self.intervals.remove(i);
}
self.intervals.insert(i, (new_lo, new_hi));
}
pub fn contains(&self, x: i64) -> bool {
self.intervals.iter().any(|&(lo, hi)| x >= lo && x <= hi)
}
pub fn num_intervals(&self) -> usize {
self.intervals.len()
}
pub fn cardinality(&self) -> i64 {
self.intervals.iter().map(|&(lo, hi)| hi - lo + 1).sum()
}
}
#[derive(Debug)]
pub struct EquivManager {
equiv_set: HashSet<(Expr, Expr)>,
failure_set: HashSet<(Expr, Expr)>,
expr_to_idx: HashMap<Expr, usize>,
entries: Vec<UnionFindEntry>,
}
impl EquivManager {
pub fn new() -> Self {
EquivManager {
equiv_set: HashSet::new(),
failure_set: HashSet::new(),
expr_to_idx: HashMap::new(),
entries: Vec::new(),
}
}
pub fn add_equiv(&mut self, a: &Expr, b: &Expr) {
if a == b {
return;
}
let pair = canonicalize_pair(a.clone(), b.clone());
self.equiv_set.insert(pair);
let idx_a = self.get_or_create_idx(a);
let idx_b = self.get_or_create_idx(b);
self.union(idx_a, idx_b);
}
pub fn is_equiv(&mut self, a: &Expr, b: &Expr) -> bool {
if a == b {
return true;
}
let pair = canonicalize_pair(a.clone(), b.clone());
if self.equiv_set.contains(&pair) {
return true;
}
if let (Some(&idx_a), Some(&idx_b)) = (self.expr_to_idx.get(a), self.expr_to_idx.get(b)) {
return self.find(idx_a) == self.find(idx_b);
}
false
}
pub fn add_failure(&mut self, a: &Expr, b: &Expr) {
let pair = canonicalize_pair(a.clone(), b.clone());
self.failure_set.insert(pair);
}
pub fn is_failure(&self, a: &Expr, b: &Expr) -> bool {
let pair = canonicalize_pair(a.clone(), b.clone());
self.failure_set.contains(&pair)
}
pub fn clear(&mut self) {
self.equiv_set.clear();
self.failure_set.clear();
self.expr_to_idx.clear();
self.entries.clear();
}
pub fn num_equiv(&self) -> usize {
self.equiv_set.len()
}
pub fn num_failures(&self) -> usize {
self.failure_set.len()
}
fn get_or_create_idx(&mut self, e: &Expr) -> usize {
if let Some(&idx) = self.expr_to_idx.get(e) {
return idx;
}
let idx = self.entries.len();
self.entries.push(UnionFindEntry {
parent: idx,
rank: 0,
});
self.expr_to_idx.insert(e.clone(), idx);
idx
}
fn find(&mut self, mut x: usize) -> usize {
while self.entries[x].parent != x {
let grandparent = self.entries[self.entries[x].parent].parent;
self.entries[x].parent = grandparent;
x = grandparent;
}
x
}
fn union(&mut self, x: usize, y: usize) {
let rx = self.find(x);
let ry = self.find(y);
if rx == ry {
return;
}
match self.entries[rx].rank.cmp(&self.entries[ry].rank) {
std::cmp::Ordering::Less => {
self.entries[rx].parent = ry;
}
std::cmp::Ordering::Greater => {
self.entries[ry].parent = rx;
}
std::cmp::Ordering::Equal => {
self.entries[ry].parent = rx;
self.entries[rx].rank += 1;
}
}
}
}
#[derive(Clone, Debug, Default)]
pub struct IndexEquivManager {
parent: Vec<usize>,
rank: Vec<u32>,
}
impl IndexEquivManager {
pub fn new(n: usize) -> Self {
Self {
parent: (0..n).collect(),
rank: vec![0; n],
}
}
pub fn find(&mut self, x: usize) -> usize {
if self.parent[x] != x {
self.parent[x] = self.find(self.parent[x]);
}
self.parent[x]
}
pub fn union(&mut self, x: usize, y: usize) {
let rx = self.find(x);
let ry = self.find(y);
if rx == ry {
return;
}
match self.rank[rx].cmp(&self.rank[ry]) {
std::cmp::Ordering::Less => self.parent[rx] = ry,
std::cmp::Ordering::Greater => self.parent[ry] = rx,
std::cmp::Ordering::Equal => {
self.parent[ry] = rx;
self.rank[rx] += 1;
}
}
}
pub fn same_class(&mut self, x: usize, y: usize) -> bool {
self.find(x) == self.find(y)
}
pub fn num_classes(&mut self) -> usize {
let n = self.parent.len();
let roots: std::collections::HashSet<usize> = (0..n).map(|i| self.find(i)).collect();
roots.len()
}
}
#[allow(dead_code)]
pub struct DiagMeta {
pub(super) entries: Vec<(String, String)>,
}
#[allow(dead_code)]
impl DiagMeta {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn add(&mut self, key: impl Into<String>, val: impl Into<String>) {
self.entries.push((key.into(), val.into()));
}
pub fn get(&self, key: &str) -> Option<&str> {
self.entries
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TypedId<T> {
pub(super) id: u32,
_phantom: std::marker::PhantomData<T>,
}
#[allow(dead_code)]
impl<T> TypedId<T> {
pub const fn new(id: u32) -> Self {
Self {
id,
_phantom: std::marker::PhantomData,
}
}
pub fn raw(&self) -> u32 {
self.id
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub struct StatCache<K: std::hash::Hash + Eq + Clone, V: Clone> {
pub inner: SimpleLruCache<K, V>,
pub hits: u64,
pub misses: u64,
}
#[allow(dead_code)]
impl<K: std::hash::Hash + Eq + Clone, V: Clone> StatCache<K, V> {
pub fn new(capacity: usize) -> Self {
Self {
inner: SimpleLruCache::new(capacity),
hits: 0,
misses: 0,
}
}
pub fn get(&mut self, key: &K) -> Option<&V> {
let result = self.inner.get(key);
if result.is_some() {
self.hits += 1;
} else {
self.misses += 1;
}
None
}
pub fn put(&mut self, key: K, val: V) {
self.inner.put(key, val);
}
pub fn hit_rate(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
return 0.0;
}
self.hits as f64 / total as f64
}
}
#[allow(dead_code)]
pub struct ScopeStack {
names: Vec<String>,
}
#[allow(dead_code)]
impl ScopeStack {
pub fn new() -> Self {
Self { names: Vec::new() }
}
pub fn push(&mut self, name: impl Into<String>) {
self.names.push(name.into());
}
pub fn pop(&mut self) -> Option<String> {
self.names.pop()
}
pub fn current(&self) -> Option<&str> {
self.names.last().map(|s| s.as_str())
}
pub fn depth(&self) -> usize {
self.names.len()
}
pub fn is_empty(&self) -> bool {
self.names.is_empty()
}
pub fn path(&self) -> String {
self.names.join(".")
}
}
#[allow(dead_code)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SeqNum(u64);
#[allow(dead_code)]
impl SeqNum {
pub const ZERO: SeqNum = SeqNum(0);
pub fn next(self) -> SeqNum {
SeqNum(self.0 + 1)
}
pub fn value(self) -> u64 {
self.0
}
}
#[derive(Debug, Default)]
pub struct InstrumentedEquivManager {
inner: EquivManager,
stats: EquivStats,
}
impl InstrumentedEquivManager {
pub fn new() -> Self {
Self::default()
}
pub fn add_equiv(&mut self, a: &Expr, b: &Expr) {
self.stats.record_equiv_addition();
self.inner.add_equiv(a, b);
}
pub fn add_failure(&mut self, a: &Expr, b: &Expr) {
self.stats.record_failure_addition();
self.inner.add_failure(a, b);
}
pub fn is_equiv(&mut self, a: &Expr, b: &Expr) -> bool {
let result = self.inner.is_equiv(a, b);
if result {
self.stats.record_equiv_hit();
} else {
self.stats.record_miss();
}
result
}
pub fn is_failure(&self, a: &Expr, b: &Expr) -> bool {
self.inner.is_failure(a, b)
}
pub fn stats(&self) -> &EquivStats {
&self.stats
}
pub fn clear(&mut self) {
self.inner.clear();
self.stats = EquivStats::new();
}
}
#[allow(dead_code)]
#[allow(missing_docs)]
pub struct EquivManagerStats {
pub checks: u64,
pub hits: u64,
pub merges: u64,
pub classes: usize,
}
#[allow(dead_code)]
impl EquivManagerStats {
pub fn new() -> Self {
Self {
checks: 0,
hits: 0,
merges: 0,
classes: 0,
}
}
pub fn hit_rate(&self) -> f64 {
if self.checks == 0 {
return 0.0;
}
self.hits as f64 / self.checks as f64
}
}
pub struct EquivQuery<'a> {
manager: &'a EquivManager,
}
impl<'a> EquivQuery<'a> {
pub fn new(manager: &'a EquivManager) -> Self {
Self { manager }
}
pub fn is_known_failure(&self, a: &Expr, b: &Expr) -> bool {
self.manager.is_failure(a, b)
}
pub fn num_equiv(&self) -> usize {
self.manager.num_equiv()
}
pub fn num_failures(&self) -> usize {
self.manager.num_failures()
}
}
#[derive(Clone, Debug, Default)]
pub struct PersistentEquivManager {
pairs: Vec<(String, String)>,
}
impl PersistentEquivManager {
pub fn new() -> Self {
Self::default()
}
pub fn add_equiv(&mut self, a: &Expr, b: &Expr) {
let ka = format!("{:?}", a);
let kb = format!("{:?}", b);
let pair = if ka <= kb { (ka, kb) } else { (kb, ka) };
if !self.pairs.contains(&pair) {
self.pairs.push(pair);
self.pairs.sort();
}
}
pub fn is_equiv(&self, a: &Expr, b: &Expr) -> bool {
if a == b {
return true;
}
let ka = format!("{:?}", a);
let kb = format!("{:?}", b);
let pair = if ka <= kb {
(ka.clone(), kb.clone())
} else {
(kb.clone(), ka.clone())
};
self.pairs.contains(&pair)
}
pub fn len(&self) -> usize {
self.pairs.len()
}
pub fn is_empty(&self) -> bool {
self.pairs.is_empty()
}
pub fn serialize(&self) -> Vec<(String, String)> {
self.pairs.clone()
}
}
#[allow(dead_code)]
pub struct ExprEquivCache {
proven: std::collections::HashSet<(u64, u64)>,
disproven: std::collections::HashSet<(u64, u64)>,
}
#[allow(dead_code)]
impl ExprEquivCache {
pub fn new() -> Self {
Self {
proven: std::collections::HashSet::new(),
disproven: std::collections::HashSet::new(),
}
}
pub fn mark_equal(&mut self, a: u64, b: u64) {
let key = if a <= b { (a, b) } else { (b, a) };
self.proven.insert(key);
}
pub fn mark_unequal(&mut self, a: u64, b: u64) {
let key = if a <= b { (a, b) } else { (b, a) };
self.disproven.insert(key);
}
pub fn query(&self, a: u64, b: u64) -> Option<bool> {
let key = if a <= b { (a, b) } else { (b, a) };
if self.proven.contains(&key) {
return Some(true);
}
if self.disproven.contains(&key) {
return Some(false);
}
None
}
pub fn proven_count(&self) -> usize {
self.proven.len()
}
}
#[allow(dead_code)]
pub struct StringInterner {
strings: Vec<String>,
map: std::collections::HashMap<String, u32>,
}
#[allow(dead_code)]
impl StringInterner {
pub fn new() -> Self {
Self {
strings: Vec::new(),
map: std::collections::HashMap::new(),
}
}
pub fn intern(&mut self, s: &str) -> u32 {
if let Some(&id) = self.map.get(s) {
return id;
}
let id = self.strings.len() as u32;
self.strings.push(s.to_string());
self.map.insert(s.to_string(), id);
id
}
pub fn get(&self, id: u32) -> Option<&str> {
self.strings.get(id as usize).map(|s| s.as_str())
}
pub fn len(&self) -> usize {
self.strings.len()
}
pub fn is_empty(&self) -> bool {
self.strings.is_empty()
}
}
#[allow(dead_code)]
pub struct UnionFind {
parent: Vec<usize>,
rank: Vec<usize>,
count: usize,
}
#[allow(dead_code)]
impl UnionFind {
pub fn new(n: usize) -> Self {
Self {
parent: (0..n).collect(),
rank: vec![0; n],
count: n,
}
}
pub fn find(&mut self, x: usize) -> usize {
if self.parent[x] != x {
self.parent[x] = self.find(self.parent[x]);
}
self.parent[x]
}
pub fn union(&mut self, x: usize, y: usize) -> bool {
let rx = self.find(x);
let ry = self.find(y);
if rx == ry {
return false;
}
if self.rank[rx] < self.rank[ry] {
self.parent[rx] = ry;
} else if self.rank[rx] > self.rank[ry] {
self.parent[ry] = rx;
} else {
self.parent[ry] = rx;
self.rank[rx] += 1;
}
self.count -= 1;
true
}
pub fn same(&mut self, x: usize, y: usize) -> bool {
self.find(x) == self.find(y)
}
pub fn num_classes(&self) -> usize {
self.count
}
pub fn num_elements(&self) -> usize {
self.parent.len()
}
}
#[derive(Debug, Default)]
pub struct ScopedEquivManager {
scopes: Vec<Vec<(Expr, Expr)>>,
inner: EquivManager,
}
impl ScopedEquivManager {
pub fn new() -> Self {
Self {
scopes: vec![Vec::new()],
inner: EquivManager::new(),
}
}
pub fn push_scope(&mut self) {
self.scopes.push(Vec::new());
}
pub fn pop_scope(&mut self) {
if self.scopes.len() > 1 {
self.scopes.pop();
self.inner.clear();
for scope in &self.scopes {
for (a, b) in scope {
self.inner.add_equiv(a, b);
}
}
}
}
pub fn add_equiv(&mut self, a: &Expr, b: &Expr) {
if let Some(scope) = self.scopes.last_mut() {
scope.push((a.clone(), b.clone()));
}
self.inner.add_equiv(a, b);
}
pub fn is_equiv(&mut self, a: &Expr, b: &Expr) -> bool {
self.inner.is_equiv(a, b)
}
pub fn scope_depth(&self) -> usize {
self.scopes.len()
}
pub fn total_equivs(&self) -> usize {
self.scopes.iter().map(|s| s.len()).sum()
}
}
#[allow(dead_code)]
pub struct RingBuffer<T> {
data: Vec<Option<T>>,
head: usize,
tail: usize,
count: usize,
capacity: usize,
}
#[allow(dead_code)]
impl<T> RingBuffer<T> {
pub fn new(capacity: usize) -> Self {
let mut data = Vec::with_capacity(capacity);
for _ in 0..capacity {
data.push(None);
}
Self {
data,
head: 0,
tail: 0,
count: 0,
capacity,
}
}
pub fn push(&mut self, val: T) {
if self.count == self.capacity {
self.data[self.head] = Some(val);
self.head = (self.head + 1) % self.capacity;
self.tail = (self.tail + 1) % self.capacity;
} else {
self.data[self.tail] = Some(val);
self.tail = (self.tail + 1) % self.capacity;
self.count += 1;
}
}
pub fn pop(&mut self) -> Option<T> {
if self.count == 0 {
return None;
}
let val = self.data[self.head].take();
self.head = (self.head + 1) % self.capacity;
self.count -= 1;
val
}
pub fn len(&self) -> usize {
self.count
}
pub fn is_empty(&self) -> bool {
self.count == 0
}
pub fn is_full(&self) -> bool {
self.count == self.capacity
}
}