90e298d3b4
First fieldtest of the sum combinators. A standalone downstream-consumer crate
(fieldtests/cycle-0008-sum-combinators/) path-depends on the engine crates and
exercises the post-0008 surface from the public interface only (rustdoc + ledger
+ glossary + project-layout, never crates/*/src).
Primary axis empirically met: the north-star two-signal combine move now uses the
shipped Add (Add::new() dropped in exactly where the 0007 fixture hand-authored
an Add2) — the 0007 boilerplate is retired, end to end through SimBroker to a
deterministic recorded pip curve.
Findings: 0 bugs, 1 friction, 2 spec_gaps, 3 working.
- working ×3: Add2 retired; LinComb weighted/variadic sums exact (<1e-12);
warm-up barrier + empty-weights panic as documented.
- spec_gap: Add/LinComb per-input firing policy (Firing::Any / mode-A as-of
join) absent from the public surface — same class as the 0007 SimBroker gap.
- spec_gap: a fired combinator emits one row per *cycle*, so a heterogeneous
same-timestamp multi-source trace can carry >1 row per timestamp (correct per
C4/C5, undocumented at this surface).
- friction: nested consumer crate still needs the empty [workspace] table
(carried from 0006/0007; #9 closed the docs half; folds into aura new).
Spec feeds the next plan as reference.
99 lines
3.2 KiB
Rust
99 lines
3.2 KiB
Rust
//! Example 4 — edge behaviour of the new combinators.
|
|
//!
|
|
//! (a) Warm-up barrier: `LinComb` withholds output (`None`) until EVERY leg has
|
|
//! a value. With two legs that warm up at different times (Sma(2) at t=1,
|
|
//! Sma(5) at t=4), the combined output must first appear at t=4, never
|
|
//! folding a cold leg in as 0.0.
|
|
//!
|
|
//! (b) The empty-weights build error: `LinComb::new(vec![])` panics (a build-time
|
|
//! param error, documented on the public surface), caught here and reported.
|
|
|
|
mod recorder;
|
|
|
|
use std::cell::RefCell;
|
|
use std::panic;
|
|
use std::rc::Rc;
|
|
|
|
use aura_core::ScalarKind;
|
|
use aura_core::{Scalar, Timestamp};
|
|
use aura_engine::{Edge, Harness, SourceSpec, Target};
|
|
use aura_std::{LinComb, Sma};
|
|
|
|
use recorder::{Recorder, Tape};
|
|
|
|
fn warm_up_barrier() {
|
|
const SMA2: usize = 0;
|
|
const SMA5: usize = 1;
|
|
const LINCOMB: usize = 2;
|
|
const REC: usize = 3;
|
|
|
|
let tape: Tape = Rc::new(RefCell::new(Vec::new()));
|
|
|
|
let nodes: Vec<Box<dyn aura_core::Node>> = vec![
|
|
Box::new(Sma::new(2)),
|
|
Box::new(Sma::new(5)),
|
|
Box::new(LinComb::new(vec![1.0, 1.0])),
|
|
Box::new(Recorder::new(tape.clone())),
|
|
];
|
|
|
|
let sources = vec![SourceSpec {
|
|
kind: ScalarKind::F64,
|
|
targets: vec![
|
|
Target { node: SMA2, slot: 0 },
|
|
Target { node: SMA5, slot: 0 },
|
|
],
|
|
}];
|
|
|
|
let edges = vec![
|
|
Edge { from: SMA2, to: LINCOMB, slot: 0, from_field: 0 },
|
|
Edge { from: SMA5, to: LINCOMB, slot: 1, from_field: 0 },
|
|
Edge { from: LINCOMB, to: REC, slot: 0, from_field: 0 },
|
|
];
|
|
|
|
let mut harness = Harness::bootstrap(nodes, sources, edges).expect("bootstrap");
|
|
|
|
let prices: &[f64] = &[10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0];
|
|
let stream: Vec<(Timestamp, Scalar)> = prices
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, &p)| (Timestamp(i as i64), Scalar::F64(p)))
|
|
.collect();
|
|
|
|
harness.run(vec![stream]);
|
|
|
|
let tape = tape.borrow();
|
|
let first = tape.first().map(|&(t, _)| t.0);
|
|
println!("(a) warm-up: LinComb first fires at t={:?} (expect t=4, when Sma(5) warms up)", first);
|
|
for (ts, v) in tape.iter() {
|
|
println!(" t={:>2} combined={:.4}", ts.0, v);
|
|
}
|
|
assert_eq!(first, Some(4), "barrier: no output until the slower leg (Sma(5)) is present");
|
|
// No leading rows that would betray a cold leg silently read as 0.0.
|
|
assert!(
|
|
tape.iter().all(|&(t, _)| t.0 >= 4),
|
|
"no row before both legs are warm (no implicit cold-leg 0.0)"
|
|
);
|
|
println!(" -> no cold-leg-as-0.0 rows; barrier honoured = OK");
|
|
}
|
|
|
|
fn empty_weights_panic() {
|
|
print!("(b) empty weights: LinComb::new(vec![]) ... ");
|
|
let prev = panic::take_hook();
|
|
panic::set_hook(Box::new(|_| {})); // silence the default panic print
|
|
let result = panic::catch_unwind(|| {
|
|
let _ = LinComb::new(vec![]);
|
|
});
|
|
panic::set_hook(prev);
|
|
match result {
|
|
Ok(_) => println!("NO PANIC (surface says it should panic!) -- unexpected"),
|
|
Err(_) => println!("panicked as documented = OK"),
|
|
}
|
|
assert!(result.is_err(), "LinComb::new(vec![]) must panic per the public surface");
|
|
}
|
|
|
|
fn main() {
|
|
warm_up_barrier();
|
|
println!();
|
|
empty_weights_panic();
|
|
}
|