use std::sync::atomic::{AtomicU64, Ordering};
use crate::dag::builder::DagBuilder;
use crate::dag::node::DagNodeId;
use crate::heuristic::{HeuristicConfig, HeuristicEngine, SearchStrategy};
#[derive(Debug, Clone, Copy)]
pub struct SimplifyConfig {
pub intermediate_rounds: usize,
pub enable_global_dedup: bool,
}
impl Default for SimplifyConfig {
fn default() -> Self {
Self {
intermediate_rounds: 3,
enable_global_dedup: true,
}
}
}
impl SimplifyConfig {
#[must_use]
pub const fn new() -> Self {
Self {
intermediate_rounds: 0,
enable_global_dedup: true,
}
}
#[must_use]
pub const fn intermediate_rounds(mut self, rounds: usize) -> Self {
self.intermediate_rounds = rounds;
self
}
}
#[derive(Debug)]
pub struct ThreadLocalState {
pub steps_count: AtomicU64,
pub nodes_visited: AtomicU64,
pub rewrites_applied: AtomicU64,
}
impl Default for ThreadLocalState {
fn default() -> Self {
Self {
steps_count: AtomicU64::new(0),
nodes_visited: AtomicU64::new(0),
rewrites_applied: AtomicU64::new(0),
}
}
}
impl ThreadLocalState {
#[must_use]
pub const fn new() -> Self {
Self {
steps_count: AtomicU64::new(0),
nodes_visited: AtomicU64::new(0),
rewrites_applied: AtomicU64::new(0),
}
}
pub fn increment(&self) {
self.steps_count.fetch_add(1, Ordering::Release);
}
#[must_use]
pub fn get_count(&self) -> u64 {
self.steps_count.load(Ordering::Acquire)
}
pub fn record_nodes_visited(&self, n: u64) {
self.nodes_visited.fetch_add(n, Ordering::Relaxed);
}
pub fn record_rewrites(&self, n: u64) {
self.rewrites_applied.fetch_add(n, Ordering::Relaxed);
}
#[must_use]
pub fn get_nodes_visited(&self) -> u64 {
self.nodes_visited.load(Ordering::Relaxed)
}
#[must_use]
pub fn get_rewrites_applied(&self) -> u64 {
self.rewrites_applied.load(Ordering::Relaxed)
}
pub fn reset(&self) {
self.steps_count.store(0, Ordering::Release);
self.nodes_visited.store(0, Ordering::Relaxed);
self.rewrites_applied.store(0, Ordering::Relaxed);
}
}
#[must_use]
pub fn run_staged_simplification(
builder: &mut DagBuilder,
root: DagNodeId,
cfg: SimplifyConfig,
) -> DagNodeId {
if root.is_none() {
return root;
}
let rounds = if cfg.enable_global_dedup {
cfg.intermediate_rounds
} else {
cfg.intermediate_rounds.max(1)
};
if rounds == 0 {
return root;
}
let mut engine = HeuristicEngine::new(HeuristicConfig::default(), SearchStrategy::Greedy);
let mut current = root;
let mut prev = DagNodeId::NONE;
for _ in 0..rounds {
if current == prev {
break;
}
prev = current;
current = engine.simplify(builder, current);
}
current
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_rounds_is_passthrough() {
let mut b = DagBuilder::new();
let x = b.variable("x");
let zero = b.constant(0.0);
let expr = b.add(x, zero);
let cfg = SimplifyConfig {
intermediate_rounds: 0,
enable_global_dedup: true,
};
assert_eq!(run_staged_simplification(&mut b, expr, cfg), expr);
}
#[test]
fn single_round_folds_identity() {
let mut b = DagBuilder::new();
let x = b.variable("x");
let zero = b.constant(0.0);
let expr = b.add(x, zero);
let cfg = SimplifyConfig {
intermediate_rounds: 1,
enable_global_dedup: true,
};
assert_eq!(run_staged_simplification(&mut b, expr, cfg), x);
}
#[test]
fn multiple_rounds_reach_fixpoint() {
let mut b = DagBuilder::new();
let x = b.variable("x");
let zero = b.constant(0.0);
let one = b.constant(1.0);
let inner = b.add(x, zero);
let outer = b.mul(inner, one);
let cfg = SimplifyConfig {
intermediate_rounds: 5,
enable_global_dedup: true,
};
let out = run_staged_simplification(&mut b, outer, cfg);
assert_eq!(out, x);
}
}