use crate::domain::{EvaluationDomain, PowfExtension};
use crate::error::Result;
use crate::evaluator::ExpressionEvaluator;
pub struct StreamingEvaluator<'a, T: EvaluationDomain> {
evaluator: &'a ExpressionEvaluator<T>,
params: Vec<T>,
stack: Vec<T>,
results: Vec<T>,
}
impl<T: EvaluationDomain + PowfExtension> StreamingEvaluator<'_, T> {
pub fn new(evaluator: &ExpressionEvaluator<T>) -> StreamingEvaluator<'_, T> {
StreamingEvaluator {
evaluator,
params: Vec::with_capacity(evaluator.param_count()),
stack: Vec::with_capacity(evaluator.stack_size()),
results: Vec::with_capacity(evaluator.result_count()),
}
}
pub fn for_each<I, S, F>(&mut self, rows: I, mut sink: F) -> Result<usize>
where
I: IntoIterator<Item = S>,
S: AsRef<[T]>,
F: FnMut(&[T]),
{
let mut count = 0usize;
for row in rows {
let row = row.as_ref();
self.params.clear();
self.params.extend(row.iter().cloned());
self.evaluator
.evaluate_with_stack(&self.params, &mut self.stack, &mut self.results)?;
sink(&self.results);
count += 1;
}
Ok(count)
}
pub fn evaluate_chunk<S: AsRef<[T]>>(&mut self, rows: &[S]) -> Result<Vec<Vec<T>>> {
let mut out = Vec::with_capacity(rows.len());
self.for_each(rows, |results| out.push(results.to_vec()))?;
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use ocas_atom::AtomArena;
use ocas_core::arena::Arena;
fn build_eval() -> (Arena, ExpressionEvaluator<f64>) {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let sum = ctx.add(&[ctx.var("x"), ctx.var("y")]);
let prod = ctx.mul(&[ctx.var("x"), ctx.var("y")]);
let eval = ExpressionEvaluator::compile_multi(&[sum, prod]).unwrap();
(arena, eval)
}
#[test]
fn streaming_multi_output() {
let (_arena, eval) = build_eval();
let mut stream = StreamingEvaluator::new(&eval);
let rows: Vec<[f64; 2]> = (0..100).map(|i| [i as f64, 2.0]).collect();
let mut seen = Vec::new();
let n = stream
.for_each(&rows, |results| seen.push((results[0], results[1])))
.unwrap();
assert_eq!(n, 100);
for (i, &(sum, prod)) in seen.iter().enumerate() {
assert!((sum - (i as f64 + 2.0)).abs() < 1e-10);
assert!((prod - (i as f64 * 2.0)).abs() < 1e-10);
}
}
#[test]
fn streaming_constant_memory_million_rows() {
let (_arena, eval) = build_eval();
let mut stream = StreamingEvaluator::new(&eval);
let warm: Vec<[f64; 2]> = vec![[1.0, 2.0]; 10];
stream.for_each(&warm, |_| {}).unwrap();
let stack_cap = stream.stack.capacity();
let results_cap = stream.results.capacity();
let params_cap = stream.params.capacity();
let rows = (0..1_000_000u64).map(|i| [i as f64 % 100.0, 3.0]);
let mut count = 0usize;
let mut checksum = 0.0f64;
let n = stream
.for_each(rows, |results| {
count += 1;
checksum += results[0];
})
.unwrap();
assert_eq!(n, 1_000_000);
assert_eq!(count, 1_000_000);
assert!(checksum > 0.0);
assert_eq!(stream.stack.capacity(), stack_cap);
assert_eq!(stream.results.capacity(), results_cap);
assert_eq!(stream.params.capacity(), params_cap);
}
#[test]
fn streaming_wrong_arity_errors() {
let (_arena, eval) = build_eval();
let mut stream = StreamingEvaluator::new(&eval);
let rows: Vec<Vec<f64>> = vec![vec![1.0], vec![2.0, 3.0]];
assert!(stream.for_each(&rows, |_| {}).is_err());
}
#[test]
fn streaming_evaluate_chunk() {
let (_arena, eval) = build_eval();
let mut stream = StreamingEvaluator::new(&eval);
let rows: Vec<[f64; 2]> = vec![[1.0, 2.0], [3.0, 4.0]];
let out = stream.evaluate_chunk(&rows).unwrap();
assert_eq!(out.len(), 2);
assert!((out[0][0] - 3.0).abs() < 1e-10);
assert!((out[0][1] - 2.0).abs() < 1e-10);
assert!((out[1][0] - 7.0).abs() < 1e-10);
assert!((out[1][1] - 12.0).abs() < 1e-10);
}
}