dd5c3fad96
Cycle 0003: aura-engine's first real content — a Sim that runs a wired DAG of
nodes deterministically, cycle by cycle. First point in the project where
authored nodes execute against data, not just under a hand-fed Ctx.
- `Sim` (aura-engine) — a bootstrapped, frozen root graph: a flat Vec<NodeBox>
(each node owns its input columns, the cycle-0002 shape) + an index edge
table, topologically ordered (Kahn). `bootstrap` sizes every input column from
its node's schema, kind-checks each edge and source target, and rejects
directed cycles — C7's "type check paid once at wiring" generalized to the
whole topology (`BootstrapError::{KindMismatch, BadIndex, Cycle}`).
- `run` — the deterministic loop: per record, forward the source value into its
target slots, evaluate nodes in topo order, capture the observed node's output
at eval time, and forward each `Some` output into its consumers' input columns
(a `None` forwards nothing — the structural seed of sample-and-hold). The loop
destructures `&mut self` into disjoint field borrows and allocates nothing on
the per-cycle path. This is the flat, monomorphized sharpening of RustAst's
reference-counted, interior-mutable observer push graph (C1/C7).
- `Edge` / `Target` (aura-engine) — producer->consumer and source->consumer wiring.
- `Sub` (aura-std) — a 2-input f64-difference node, so the loop is proven on a
real fan-out + join DAG (source -> {SMA(2), SMA(4)} -> Sub), with a determinism
assertion (C1: a second identical run is bit-identical).
Engine library depends only on aura-core; a test-only dev-dependency on aura-std
lets the integration test wire real Sma/Sub nodes. Sim carries a hand-written,
node-opaque `Debug` impl (Box<dyn Node> is not Debug; the impl prints
nodes.len() + the index/topology fields, needed by the bootstrap-rejection tests'
`unwrap_err`).
Deliberately deferred (recorded as decisions): freshness-gated recompute /
sample-and-hold (C5 -> cycle 0004, with the second source that makes it testable);
the explicit monotonic cycle_id counter (its first reader is freshness, 0004);
the Source trait + data-server ingestion + k-way merge (C3/C11); the builder API
(C19); the real sink + run registry (C18/C22).
Gates green: cargo build/test (26: 18 aura-core + 3 aura-std + 5 aura-engine)/
clippy -D warnings clean; surface-purity grep (no dyn-Any / Rc / RefCell) clean.
refs walking-skeleton
63 lines
1.7 KiB
Rust
63 lines
1.7 KiB
Rust
//! `Sub` — two-input f64 difference (input 0 minus input 1), e.g. a fast/slow
|
|
//! spread. The walking skeleton's second worked node: it gives the sim loop a
|
|
//! real fan-out + join to run (two SMAs joining into one node), exercising
|
|
//! multi-input `Ctx` access inside a running graph.
|
|
|
|
use aura_core::{Ctx, InputSpec, Node, NodeSchema, Scalar, ScalarKind};
|
|
|
|
/// Two-input f64 difference: input 0 minus input 1. Emits `None` until both
|
|
/// inputs have a value.
|
|
#[derive(Default)]
|
|
pub struct Sub;
|
|
|
|
impl Sub {
|
|
/// Build a `Sub` node.
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
}
|
|
|
|
impl Node for Sub {
|
|
fn schema(&self) -> NodeSchema {
|
|
NodeSchema {
|
|
inputs: vec![
|
|
InputSpec { kind: ScalarKind::F64, lookback: 1 },
|
|
InputSpec { kind: ScalarKind::F64, lookback: 1 },
|
|
],
|
|
output: ScalarKind::F64,
|
|
}
|
|
}
|
|
|
|
fn eval(&mut self, ctx: Ctx<'_>) -> Option<Scalar> {
|
|
let a = ctx.f64_in(0);
|
|
let b = ctx.f64_in(1);
|
|
if a.is_empty() || b.is_empty() {
|
|
return None;
|
|
}
|
|
Some(Scalar::F64(a[0] - b[0]))
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use aura_core::AnyColumn;
|
|
|
|
#[test]
|
|
fn sub_is_difference_once_both_inputs_present() {
|
|
let mut sub = Sub::new();
|
|
let mut inputs = vec![
|
|
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
|
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
|
];
|
|
|
|
// only input 0 present -> None
|
|
inputs[0].push(Scalar::F64(10.0)).unwrap();
|
|
assert_eq!(sub.eval(Ctx::new(&inputs)), None);
|
|
|
|
// both present -> a - b
|
|
inputs[1].push(Scalar::F64(4.0)).unwrap();
|
|
assert_eq!(sub.eval(Ctx::new(&inputs)), Some(Scalar::F64(6.0)));
|
|
}
|
|
}
|