c8dc57e2aa
Second half of the #281 clock-enable set. CumSum is the generic run-length accumulator with O(1) Neumaier-compensated state — the branching Neumaier variant, not Sma's plain Kahan, so it stays exact when a large running total absorbs a tiny sample (the standalone accumulator has no windowed-magnitude guarantee; module doc explains why the helper is not reused). When is the clock-enable driver: forwards value iff gate, else a quiet cycle (eval->None), so every stateful reducer composes gated unmodified — SMA(When(x,g),N) is the gated SMA. Rosters CumSum and When (both count pins 31->33) and ships two engine E2E fixtures: When forwarding/quiet-downstream through the real GraphBuilder->compile->run seam, and a quiet When leg suppressing a whole Resample Barrier(0) group (the deliberate stall semantics). refs #281
55 lines
2.6 KiB
Rust
55 lines
2.6 KiB
Rust
//! End-to-end coverage for the `CumSum` standard cell (#281): the generic
|
|
//! running-total accumulator.
|
|
//!
|
|
//! The in-module tests in `aura-std/src/cumsum.rs` drive `eval` directly —
|
|
//! they never go through a `PrimitiveBuilder` schema, a `GraphBuilder`-resolved
|
|
//! wiring, or a compiled+bootstrapped `FlatGraph`. That is the real seam a
|
|
//! data-authored op-script or a Rust builder actually exercises (C24); this
|
|
//! file proves `CumSum` is reachable from the public `aura_engine`/`aura_std`
|
|
//! construction surface, wires cleanly by declared port name/kind, and
|
|
//! accumulates the right running total once actually driven cycle-by-cycle
|
|
//! through a real `Harness::run` — the same bar the Sign/Select pair set in
|
|
//! `select_sign_e2e.rs`.
|
|
|
|
use std::sync::mpsc;
|
|
|
|
use aura_core::{Firing, Scalar, ScalarKind, Timestamp};
|
|
use aura_engine::{GraphBuilder, VecSource};
|
|
use aura_std::{CumSum, Recorder};
|
|
|
|
/// A deterministic tick stream, no timestamps/randomness beyond a fixed
|
|
/// literal sequence.
|
|
fn ticks(values: &[f64]) -> Vec<(Timestamp, Scalar)> {
|
|
values.iter().enumerate().map(|(i, &v)| (Timestamp(i as i64 + 1), Scalar::f64(v))).collect()
|
|
}
|
|
|
|
/// Property: **`CumSum` correctly registers its `PrimitiveBuilder` schema
|
|
/// (one `f64` input, one `f64` output) and dispatches through a
|
|
/// compiled+bootstrapped `FlatGraph`, accumulating the running total across
|
|
/// real engine cycles** — not merely via a direct `eval` call (the in-module
|
|
/// `cumsum.rs` unit tests' seam). A schema/wiring regression (wrong port
|
|
/// kind, wrong output arity) would fail at `GraphBuilder::build` or
|
|
/// `compile_with_params`, before a single cycle runs; a dispatch or
|
|
/// state-carry regression would show up in the recorded running totals below.
|
|
#[test]
|
|
fn cumsum_dispatches_through_a_compiled_graph_and_accumulates() {
|
|
let (tx, rx) = mpsc::channel();
|
|
let mut g = GraphBuilder::new("root");
|
|
let series = g.source_role("series", ScalarKind::F64);
|
|
let cumsum = g.add(CumSum::builder());
|
|
let rec = g.add(Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx));
|
|
|
|
g.feed(series, [cumsum.input("series")]);
|
|
g.connect(cumsum.output("value"), rec.input("col[0]"));
|
|
|
|
let mut h = g.build().expect("resolves by name").bootstrap_with_params(vec![]).expect("bootstraps");
|
|
h.run(vec![Box::new(VecSource::new(ticks(&[2.0, -0.5, 3.5, 0.0])))]);
|
|
let trace: Vec<f64> = rx.try_iter().map(|(_, row)| row[0].as_f64()).collect();
|
|
|
|
assert_eq!(
|
|
trace,
|
|
vec![2.0, 1.5, 5.0, 5.0],
|
|
"running total accumulated across real engine cycles, dispatched through a compiled FlatGraph"
|
|
);
|
|
}
|