use std::sync::Arc;
use crate::dag::arena::DagArena;
use crate::dag::node::DagNodeId;
use crate::dag::symbol::{OpKind, SymbolKind};
use crate::runtime::{ensure_runtime, parallel_for_each};
#[derive(Default)]
pub struct FnEvalRegistry {
fns: std::collections::HashMap<u32, fn(*const f64) -> f64>,
}
impl FnEvalRegistry {
#[must_use]
pub fn new() -> Self {
Self {
fns: std::collections::HashMap::new(),
}
}
pub fn register(&mut self, fn_id: crate::dag::symbol::FnId, f: fn(*const f64) -> f64) {
self.fns.insert(fn_id.0, f);
}
#[must_use]
pub fn get(&self, fn_id: crate::dag::symbol::FnId) -> Option<fn(*const f64) -> f64> {
self.fns.get(&fn_id.0).copied()
}
}
#[derive(Default)]
pub struct OpEvalRegistry {
overrides: std::collections::HashMap<u8, fn(&[f64]) -> f64>,
}
impl OpEvalRegistry {
#[must_use]
pub fn new() -> Self {
Self {
overrides: std::collections::HashMap::new(),
}
}
pub fn register(&mut self, op: crate::dag::symbol::OpKind, f: fn(&[f64]) -> f64) {
self.overrides.insert(op as u8, f);
}
#[must_use]
pub fn get(&self, op: crate::dag::symbol::OpKind) -> Option<fn(&[f64]) -> f64> {
self.overrides.get(&(op as u8)).copied()
}
}
#[must_use]
pub fn evaluate_node_with_overrides(
arena: &DagArena,
id: DagNodeId,
vars: &[f64],
fn_registry: &FnEvalRegistry,
op_registry: &OpEvalRegistry,
) -> f64 {
if id.is_none() {
return 0.0;
}
let mut stack: Vec<Frame> = Vec::with_capacity(64);
let mut values: Vec<f64> = Vec::with_capacity(64);
let root_arity = arena.get(id).map_or(0, |n| n.children.len());
stack.push(Frame {
id,
arity: root_arity,
cursor: 0,
});
while let Some(top) = stack.last_mut() {
let next_child: Option<DagNodeId> = arena.get(top.id).and_then(|node| {
let kids = node.children.as_slice();
kids.get(top.cursor).copied()
});
if let Some(child_id) = next_child {
top.cursor += 1;
let child_arity = arena.get(child_id).map_or(0, |c| c.children.len());
stack.push(Frame {
id: child_id,
arity: child_arity,
cursor: 0,
});
} else {
let Some(frame) = stack.pop() else { break };
let v = reduce_frame_with_overrides(
arena,
frame.id,
frame.arity,
&mut values,
vars,
fn_registry,
op_registry,
);
values.push(v);
}
}
values.pop().unwrap_or(0.0)
}
fn reduce_frame_with_overrides(
arena: &DagArena,
id: DagNodeId,
arity: usize,
values: &mut Vec<f64>,
vars: &[f64],
fn_registry: &FnEvalRegistry,
op_registry: &OpEvalRegistry,
) -> f64 {
let Some(node) = arena.get(id) else {
values.truncate(values.len().saturating_sub(arity));
return 0.0;
};
match node.kind {
SymbolKind::Constant(v) => v,
SymbolKind::Variable(sym_id) => vars.get(sym_id.0 as usize).copied().unwrap_or(0.0),
SymbolKind::Function(fn_id) => {
values.truncate(values.len().saturating_sub(arity));
fn_registry.get(fn_id).map_or(0.0, |f| f(vars.as_ptr()))
}
SymbolKind::Operator(op) => {
let split_at = values.len().saturating_sub(arity);
let result = op_registry.get(op).map_or_else(
|| apply_op(op, &values[split_at..]),
|override_fn| override_fn(&values[split_at..]),
);
values.truncate(split_at);
result
}
}
}
#[must_use]
pub fn parallel_evaluate(arena: &DagArena, chunks: Vec<Vec<DagNodeId>>, variables: &[f64]) -> f64 {
let arc = Arc::new(arena.clone());
parallel_evaluate_shared(&arc, chunks, variables)
}
#[must_use]
pub fn parallel_evaluate_shared(
arena: &Arc<DagArena>,
chunks: Vec<Vec<DagNodeId>>,
variables: &[f64],
) -> f64 {
if chunks.is_empty() {
return 0.0;
}
let gate = ensure_runtime();
let vars_arc: Arc<Vec<f64>> = Arc::new(variables.to_vec());
let tasks: Vec<_> = chunks
.into_iter()
.map(|chunk| {
let arena_local = Arc::clone(arena);
let vars_local = Arc::clone(&vars_arc);
move || -> f64 {
let mut sum = 0.0;
for id in chunk {
sum += evaluate_node(&arena_local, id, &vars_local);
}
sum
}
})
.collect();
let partials = parallel_for_each(gate, tasks);
partials.into_iter().flatten().sum()
}
#[must_use]
pub fn evaluate_node(arena: &DagArena, id: DagNodeId, vars: &[f64]) -> f64 {
if id.is_none() {
return 0.0;
}
let mut stack: Vec<Frame> = Vec::with_capacity(64);
let mut values: Vec<f64> = Vec::with_capacity(64);
let root_arity = arena.get(id).map_or(0, |n| n.children.len());
stack.push(Frame {
id,
arity: root_arity,
cursor: 0,
});
while let Some(top) = stack.last_mut() {
let next_child: Option<DagNodeId> = arena.get(top.id).and_then(|node| {
let kids = node.children.as_slice();
kids.get(top.cursor).copied()
});
if let Some(child_id) = next_child {
top.cursor += 1;
let child_arity = arena.get(child_id).map_or(0, |c| c.children.len());
stack.push(Frame {
id: child_id,
arity: child_arity,
cursor: 0,
});
} else {
let Some(frame) = stack.pop() else { break };
let v = reduce_frame(arena, frame.id, frame.arity, &mut values, vars);
values.push(v);
}
}
values.pop().unwrap_or(0.0)
}
struct Frame {
id: DagNodeId,
arity: usize,
cursor: usize,
}
fn reduce_frame(
arena: &DagArena,
id: DagNodeId,
arity: usize,
values: &mut Vec<f64>,
vars: &[f64],
) -> f64 {
let Some(node) = arena.get(id) else {
values.truncate(values.len().saturating_sub(arity));
return 0.0;
};
match node.kind {
SymbolKind::Constant(v) => v,
SymbolKind::Variable(sym_id) => vars.get(sym_id.0 as usize).copied().unwrap_or(0.0),
SymbolKind::Function(_) => {
values.truncate(values.len().saturating_sub(arity));
0.0
}
SymbolKind::Operator(op) => {
let split_at = values.len().saturating_sub(arity);
let result = apply_op(op, &values[split_at..]);
values.truncate(split_at);
result
}
}
}
#[must_use]
pub fn evaluate_node_with_fns(
arena: &DagArena,
id: DagNodeId,
vars: &[f64],
registry: &FnEvalRegistry,
) -> f64 {
if id.is_none() {
return 0.0;
}
let mut stack: Vec<Frame> = Vec::with_capacity(64);
let mut values: Vec<f64> = Vec::with_capacity(64);
let root_arity = arena.get(id).map_or(0, |n| n.children.len());
stack.push(Frame {
id,
arity: root_arity,
cursor: 0,
});
while let Some(top) = stack.last_mut() {
let next_child: Option<DagNodeId> = arena.get(top.id).and_then(|node| {
let kids = node.children.as_slice();
kids.get(top.cursor).copied()
});
if let Some(child_id) = next_child {
top.cursor += 1;
let child_arity = arena.get(child_id).map_or(0, |c| c.children.len());
stack.push(Frame {
id: child_id,
arity: child_arity,
cursor: 0,
});
} else {
let Some(frame) = stack.pop() else { break };
let v =
reduce_frame_with_fns(arena, frame.id, frame.arity, &mut values, vars, registry);
values.push(v);
}
}
values.pop().unwrap_or(0.0)
}
fn reduce_frame_with_fns(
arena: &DagArena,
id: DagNodeId,
arity: usize,
values: &mut Vec<f64>,
vars: &[f64],
registry: &FnEvalRegistry,
) -> f64 {
let Some(node) = arena.get(id) else {
values.truncate(values.len().saturating_sub(arity));
return 0.0;
};
match node.kind {
SymbolKind::Constant(v) => v,
SymbolKind::Variable(sym_id) => vars.get(sym_id.0 as usize).copied().unwrap_or(0.0),
SymbolKind::Function(fn_id) => {
values.truncate(values.len().saturating_sub(arity));
registry.get(fn_id).map_or(0.0, |f| f(vars.as_ptr()))
}
SymbolKind::Operator(op) => {
let split_at = values.len().saturating_sub(arity);
let result = apply_op(op, &values[split_at..]);
values.truncate(split_at);
result
}
}
}
fn apply_op(op: OpKind, child_vals: &[f64]) -> f64 {
match op {
OpKind::Add => child_vals.iter().sum(),
OpKind::Sub => {
let lhs = child_vals.first().copied().unwrap_or(0.0);
let rhs = child_vals.get(1).copied().unwrap_or(0.0);
lhs - rhs
}
OpKind::Mul => child_vals.iter().product(),
OpKind::Div => {
let lhs = child_vals.first().copied().unwrap_or(0.0);
let rhs = child_vals.get(1).copied().unwrap_or(1.0);
if rhs == 0.0 { f64::NAN } else { lhs / rhs }
}
OpKind::Mod => {
let lhs = child_vals.first().copied().unwrap_or(0.0);
let rhs = child_vals.get(1).copied().unwrap_or(1.0);
if rhs == 0.0 { f64::NAN } else { lhs % rhs }
}
OpKind::Pow => {
let base = child_vals.first().copied().unwrap_or(0.0);
let exp = child_vals.get(1).copied().unwrap_or(0.0);
base.powf(exp)
}
OpKind::Neg => -child_vals.first().copied().unwrap_or(0.0),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dag::builder::DagBuilder;
#[test]
fn iterative_eval_matches_simple_expr() {
let mut b = DagBuilder::new();
let x = b.variable("x");
let y = b.variable("y");
let three = b.constant(3.0);
let mul = b.mul(three, x);
let add = b.add(mul, y);
let val = evaluate_node(b.arena(), add, &[2.0, 4.0]);
assert!((val - 10.0).abs() < f64::EPSILON);
}
#[test]
fn iterative_eval_handles_deep_chain() {
let mut b = DagBuilder::new();
let x = b.variable("x");
let mut acc = x;
for _ in 0..5000 {
acc = b.add(acc, x);
}
let val = evaluate_node(b.arena(), acc, &[1.0]);
assert!((val - 5001.0).abs() < f64::EPSILON);
}
#[test]
fn parallel_evaluate_sums_chunks() {
let mut b = DagBuilder::new();
let x = b.variable("x");
let chunks = vec![vec![x, x], vec![x], vec![x, x]];
let total = parallel_evaluate(b.arena(), chunks, &[2.0]);
assert!((total - 10.0).abs() < f64::EPSILON);
}
}