1use std::cmp::Ordering;
20use std::sync::Arc;
21
22use crate::dag::arena::DagArena;
23use crate::dag::node::DagNodeId;
24use crate::dag::symbol::{OpKind, SymbolKind};
25use crate::runtime::{ensure_runtime, parallel_for_each};
26
27#[derive(Default)]
37pub struct FnEvalRegistry {
38 fns: std::collections::HashMap<u32, fn(*const f64) -> f64>,
39}
40
41impl FnEvalRegistry {
42 #[must_use]
44 pub fn new() -> Self {
45 Self {
46 fns: std::collections::HashMap::new(),
47 }
48 }
49
50 pub fn register(&mut self, fn_id: crate::dag::symbol::FnId, f: fn(*const f64) -> f64) {
52 self.fns.insert(fn_id.0, f);
53 }
54
55 #[must_use]
57 pub fn get(&self, fn_id: crate::dag::symbol::FnId) -> Option<fn(*const f64) -> f64> {
58 self.fns.get(&fn_id.0).copied()
59 }
60}
61
62#[derive(Default)]
67pub struct OpEvalRegistry {
68 overrides: std::collections::HashMap<u8, fn(&[f64]) -> f64>,
69}
70
71impl OpEvalRegistry {
72 #[must_use]
74 pub fn new() -> Self {
75 Self {
76 overrides: std::collections::HashMap::new(),
77 }
78 }
79
80 pub fn register(&mut self, op: crate::dag::symbol::OpKind, f: fn(&[f64]) -> f64) {
82 self.overrides.insert(op as u8, f);
83 }
84
85 #[must_use]
87 pub fn get(&self, op: crate::dag::symbol::OpKind) -> Option<fn(&[f64]) -> f64> {
88 self.overrides.get(&(op as u8)).copied()
89 }
90}
91
92#[must_use]
98pub fn evaluate_node_with_overrides(
99 arena: &DagArena,
100 id: DagNodeId,
101 vars: &[f64],
102 fn_registry: &FnEvalRegistry,
103 op_registry: &OpEvalRegistry,
104) -> f64 {
105 if id.is_none() {
106 return 0.0;
107 }
108
109 let mut stack: Vec<Frame> = Vec::with_capacity(64);
110 let mut values: Vec<f64> = Vec::with_capacity(64);
111
112 let root_arity = arena.get(id).map_or(0, |n| n.children.len());
113 stack.push(Frame {
114 id,
115 arity: root_arity,
116 cursor: 0,
117 });
118
119 while let Some(top) = stack.last_mut() {
120 let next_child: Option<DagNodeId> = arena.get(top.id).and_then(|node| {
121 let kids = node.children.as_slice();
122 kids.get(top.cursor).copied()
123 });
124
125 if let Some(child_id) = next_child {
126 top.cursor += 1;
127 let child_arity = arena.get(child_id).map_or(0, |c| c.children.len());
128 stack.push(Frame {
129 id: child_id,
130 arity: child_arity,
131 cursor: 0,
132 });
133 } else {
134 let Some(frame) = stack.pop() else { break };
135 let v = reduce_frame_with_overrides(
136 arena,
137 frame.id,
138 frame.arity,
139 &mut values,
140 vars,
141 fn_registry,
142 op_registry,
143 );
144 values.push(v);
145 }
146 }
147
148 values.pop().unwrap_or(0.0)
149}
150
151fn reduce_frame_with_overrides(
152 arena: &DagArena,
153 id: DagNodeId,
154 arity: usize,
155 values: &mut Vec<f64>,
156 vars: &[f64],
157 fn_registry: &FnEvalRegistry,
158 op_registry: &OpEvalRegistry,
159) -> f64 {
160 let Some(node) = arena.get(id) else {
161 values.truncate(values.len().saturating_sub(arity));
162 return 0.0;
163 };
164
165 match node.kind {
166 SymbolKind::Constant(v) => v,
167 SymbolKind::Variable(sym_id) => vars.get(sym_id.0 as usize).copied().unwrap_or(0.0),
168 SymbolKind::Function(fn_id) => {
169 values.truncate(values.len().saturating_sub(arity));
170 fn_registry.get(fn_id).map_or(0.0, |f| f(vars.as_ptr()))
171 }
172 SymbolKind::Operator(op) => {
173 let split_at = values.len().saturating_sub(arity);
174 let result = op_registry.get(op).map_or_else(
175 || apply_op(op, &values[split_at..]),
176 |override_fn| override_fn(&values[split_at..]),
177 );
178 values.truncate(split_at);
179 result
180 }
181 SymbolKind::ControlFlow(ctrl) => {
182 use crate::dag::symbol::CtrlKind;
183 let split_at = values.len().saturating_sub(arity);
184 let child_vals = &values[split_at..];
185 let result = match ctrl {
186 CtrlKind::Select | CtrlKind::IfElse => {
187 if child_vals.len() == 3 {
188 let cond = child_vals[0];
189 let then_val = child_vals[1];
190 let else_val = child_vals[2];
191 if cond == 0.0 { else_val } else { then_val }
192 } else {
193 0.0
194 }
195 }
196 CtrlKind::ForLoop => {
197 if child_vals.len() == 4 {
198 let init_val = child_vals[0];
199 let limit_val = child_vals[1];
200 let step_val = child_vals[2];
201 let body_val = child_vals[3];
202 let mut acc = init_val;
203 let mut idx = 0.0;
204 while idx.partial_cmp(&limit_val) == Some(Ordering::Less) {
205 acc += body_val;
206 idx += step_val;
207 }
208 acc
209 } else {
210 0.0
211 }
212 }
213 };
214 values.truncate(split_at);
215 result
216 }
217 }
218}
219
220#[must_use]
227pub fn parallel_evaluate(arena: &DagArena, chunks: Vec<Vec<DagNodeId>>, variables: &[f64]) -> f64 {
228 let arc = Arc::new(arena.clone());
229 parallel_evaluate_shared(&arc, chunks, variables)
230}
231
232#[must_use]
239pub fn parallel_evaluate_shared(
240 arena: &Arc<DagArena>,
241 chunks: Vec<Vec<DagNodeId>>,
242 variables: &[f64],
243) -> f64 {
244 if chunks.is_empty() {
245 return 0.0;
246 }
247
248 let gate = ensure_runtime();
249 let vars_arc: Arc<Vec<f64>> = Arc::new(variables.to_vec());
250
251 let tasks: Vec<_> = chunks
252 .into_iter()
253 .map(|chunk| {
254 let arena_local = Arc::clone(arena);
255 let vars_local = Arc::clone(&vars_arc);
256 move || -> f64 {
257 let mut sum = 0.0;
258 for id in chunk {
259 sum += evaluate_node(&arena_local, id, &vars_local);
260 }
261 sum
262 }
263 })
264 .collect();
265
266 let partials = parallel_for_each(gate, tasks);
267 partials.into_iter().flatten().sum()
268}
269
270#[must_use]
283pub fn evaluate_node(arena: &DagArena, id: DagNodeId, vars: &[f64]) -> f64 {
284 if id.is_none() {
285 return 0.0;
286 }
287
288 let mut stack: Vec<Frame> = Vec::with_capacity(64);
289 let mut values: Vec<f64> = Vec::with_capacity(64);
290
291 let root_arity = arena.get(id).map_or(0, |n| n.children.len());
292 stack.push(Frame {
293 id,
294 arity: root_arity,
295 cursor: 0,
296 });
297
298 while let Some(top) = stack.last_mut() {
299 let next_child: Option<DagNodeId> = arena.get(top.id).and_then(|node| {
301 let kids = node.children.as_slice();
302 kids.get(top.cursor).copied()
303 });
304
305 if let Some(child_id) = next_child {
306 top.cursor += 1;
307 let child_arity = arena.get(child_id).map_or(0, |c| c.children.len());
308 stack.push(Frame {
309 id: child_id,
310 arity: child_arity,
311 cursor: 0,
312 });
313 } else {
314 let Some(frame) = stack.pop() else { break };
320 let v = reduce_frame(arena, frame.id, frame.arity, &mut values, vars);
321 values.push(v);
322 }
323 }
324
325 values.pop().unwrap_or(0.0)
326}
327
328struct Frame {
329 id: DagNodeId,
330 arity: usize,
331 cursor: usize,
332}
333
334fn reduce_frame(
335 arena: &DagArena,
336 id: DagNodeId,
337 arity: usize,
338 values: &mut Vec<f64>,
339 vars: &[f64],
340) -> f64 {
341 let Some(node) = arena.get(id) else {
342 values.truncate(values.len().saturating_sub(arity));
344 return 0.0;
345 };
346
347 match node.kind {
348 SymbolKind::Constant(v) => v,
349 SymbolKind::Variable(sym_id) => vars.get(sym_id.0 as usize).copied().unwrap_or(0.0),
350 SymbolKind::Function(_) => {
351 values.truncate(values.len().saturating_sub(arity));
353 0.0
354 }
355 SymbolKind::Operator(op) => {
356 let split_at = values.len().saturating_sub(arity);
357 let result = apply_op(op, &values[split_at..]);
360 values.truncate(split_at);
361 result
362 }
363 SymbolKind::ControlFlow(ctrl) => {
364 use crate::dag::symbol::CtrlKind;
365 let split_at = values.len().saturating_sub(arity);
366 let child_vals = &values[split_at..];
367 let result = match ctrl {
368 CtrlKind::Select | CtrlKind::IfElse => {
369 if child_vals.len() == 3 {
370 let cond = child_vals[0];
371 let then_val = child_vals[1];
372 let else_val = child_vals[2];
373 if cond == 0.0 { else_val } else { then_val }
374 } else {
375 0.0
376 }
377 }
378 CtrlKind::ForLoop => {
379 if child_vals.len() == 4 {
380 let init_val = child_vals[0];
381 let limit_val = child_vals[1];
382 let step_val = child_vals[2];
383 let body_val = child_vals[3];
384 let mut acc = init_val;
385 let mut idx = 0.0;
386 while idx.partial_cmp(&limit_val) == Some(Ordering::Less) {
387 acc += body_val;
388 idx += step_val;
389 }
390 acc
391 } else {
392 0.0
393 }
394 }
395 };
396 values.truncate(split_at);
397 result
398 }
399 }
400}
401
402#[must_use]
410pub fn evaluate_node_with_fns(
411 arena: &DagArena,
412 id: DagNodeId,
413 vars: &[f64],
414 registry: &FnEvalRegistry,
415) -> f64 {
416 if id.is_none() {
417 return 0.0;
418 }
419
420 let mut stack: Vec<Frame> = Vec::with_capacity(64);
421 let mut values: Vec<f64> = Vec::with_capacity(64);
422
423 let root_arity = arena.get(id).map_or(0, |n| n.children.len());
424 stack.push(Frame {
425 id,
426 arity: root_arity,
427 cursor: 0,
428 });
429
430 while let Some(top) = stack.last_mut() {
431 let next_child: Option<DagNodeId> = arena.get(top.id).and_then(|node| {
432 let kids = node.children.as_slice();
433 kids.get(top.cursor).copied()
434 });
435
436 if let Some(child_id) = next_child {
437 top.cursor += 1;
438 let child_arity = arena.get(child_id).map_or(0, |c| c.children.len());
439 stack.push(Frame {
440 id: child_id,
441 arity: child_arity,
442 cursor: 0,
443 });
444 } else {
445 let Some(frame) = stack.pop() else { break };
446 let v =
447 reduce_frame_with_fns(arena, frame.id, frame.arity, &mut values, vars, registry);
448 values.push(v);
449 }
450 }
451
452 values.pop().unwrap_or(0.0)
453}
454
455fn reduce_frame_with_fns(
456 arena: &DagArena,
457 id: DagNodeId,
458 arity: usize,
459 values: &mut Vec<f64>,
460 vars: &[f64],
461 registry: &FnEvalRegistry,
462) -> f64 {
463 let Some(node) = arena.get(id) else {
464 values.truncate(values.len().saturating_sub(arity));
465 return 0.0;
466 };
467
468 match node.kind {
469 SymbolKind::Constant(v) => v,
470 SymbolKind::Variable(sym_id) => vars.get(sym_id.0 as usize).copied().unwrap_or(0.0),
471 SymbolKind::Function(fn_id) => {
472 values.truncate(values.len().saturating_sub(arity));
473 registry.get(fn_id).map_or(0.0, |f| f(vars.as_ptr()))
475 }
476 SymbolKind::Operator(op) => {
477 let split_at = values.len().saturating_sub(arity);
478 let result = apply_op(op, &values[split_at..]);
479 values.truncate(split_at);
480 result
481 }
482 SymbolKind::ControlFlow(ctrl) => {
483 use crate::dag::symbol::CtrlKind;
484 let split_at = values.len().saturating_sub(arity);
485 let child_vals = &values[split_at..];
486 let result = match ctrl {
487 CtrlKind::Select | CtrlKind::IfElse => {
488 if child_vals.len() == 3 {
489 let cond = child_vals[0];
490 let then_val = child_vals[1];
491 let else_val = child_vals[2];
492 if cond == 0.0 { else_val } else { then_val }
493 } else {
494 0.0
495 }
496 }
497 CtrlKind::ForLoop => {
498 if child_vals.len() == 4 {
499 let init_val = child_vals[0];
500 let limit_val = child_vals[1];
501 let step_val = child_vals[2];
502 let body_val = child_vals[3];
503 let mut acc = init_val;
504 let mut idx = 0.0;
505 while idx.partial_cmp(&limit_val) == Some(Ordering::Less) {
506 acc += body_val;
507 idx += step_val;
508 }
509 acc
510 } else {
511 0.0
512 }
513 }
514 };
515 values.truncate(split_at);
516 result
517 }
518 }
519}
520
521fn apply_op(op: OpKind, child_vals: &[f64]) -> f64 {
522 match op {
523 OpKind::Add => child_vals.iter().sum(),
524 OpKind::Sub => {
525 let lhs = child_vals.first().copied().unwrap_or(0.0);
526 let rhs = child_vals.get(1).copied().unwrap_or(0.0);
527 lhs - rhs
528 }
529 OpKind::Mul => child_vals.iter().product(),
530 OpKind::Div => {
531 let lhs = child_vals.first().copied().unwrap_or(0.0);
532 let rhs = child_vals.get(1).copied().unwrap_or(1.0);
533 if rhs == 0.0 { f64::NAN } else { lhs / rhs }
536 }
537 OpKind::Mod => {
538 let lhs = child_vals.first().copied().unwrap_or(0.0);
539 let rhs = child_vals.get(1).copied().unwrap_or(1.0);
540 if rhs == 0.0 { f64::NAN } else { lhs % rhs }
541 }
542 OpKind::Pow => {
543 let base = child_vals.first().copied().unwrap_or(0.0);
544 let exp = child_vals.get(1).copied().unwrap_or(0.0);
545 base.powf(exp)
546 }
547 OpKind::Neg => -child_vals.first().copied().unwrap_or(0.0),
548 }
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554 use crate::dag::builder::DagBuilder;
555
556 #[test]
557 fn iterative_eval_matches_simple_expr() {
558 let mut b = DagBuilder::new();
560 let x = b.variable("x");
561 let y = b.variable("y");
562 let three = b.constant(3.0);
563 let mul = b.mul(three, x);
564 let add = b.add(mul, y);
565 let val = evaluate_node(b.arena(), add, &[2.0, 4.0]);
566 assert!((val - 10.0).abs() < f64::EPSILON);
567 }
568
569 #[test]
570 fn iterative_eval_handles_deep_chain() {
571 let mut b = DagBuilder::new();
573 let x = b.variable("x");
574 let mut acc = x;
575 for _ in 0..5000 {
576 acc = b.add(acc, x);
577 }
578 let val = evaluate_node(b.arena(), acc, &[1.0]);
579 assert!((val - 5001.0).abs() < f64::EPSILON);
581 }
582
583 #[test]
584 fn parallel_evaluate_sums_chunks() {
585 let mut b = DagBuilder::new();
586 let x = b.variable("x");
587 let chunks = vec![vec![x, x], vec![x], vec![x, x]];
588 let total = parallel_evaluate(b.arena(), chunks, &[2.0]);
590 assert!((total - 10.0).abs() < f64::EPSILON);
591 }
592}