//! Example 5 (firing-policy probe) — combine two signals driven by two DIFFERENT //! sources that update on different cycles. //! //! This is the genuinely-heterogeneous combine the north-star reaches for: a //! fast price tick × a slower second feed, summed by LinComb. The interesting //! question — invisible from the public surface — is LinComb's per-input firing //! policy (C6): does a fired-this-cycle leg get combined with the OTHER leg's //! *held* value (mode A, as-of join), or does LinComb only fire when both legs //! are fresh in the same cycle (mode B barrier)? //! //! The rustdoc says only "Emits None until all inputs have a value" — it does //! not state the firing policy. We feed two sources at interleaved timestamps //! and RECORD what LinComb emits, to see which reading the shipped node took. //! //! source A (f64, fast) ─> LinComb slot 0 ┐ //! source B (f64, slow) ─> LinComb slot 1 ┴─> Recorder mod recorder; use std::cell::RefCell; use std::rc::Rc; use aura_core::ScalarKind; use aura_core::{Scalar, Timestamp}; use aura_engine::{Edge, Harness, SourceSpec, Target}; use aura_std::LinComb; use recorder::{Recorder, Tape}; fn main() { const LINCOMB: usize = 0; const REC: usize = 1; let tape: Tape = Rc::new(RefCell::new(Vec::new())); let nodes: Vec> = vec![ Box::new(LinComb::new(vec![1.0, 1.0])), Box::new(Recorder::new(tape.clone())), ]; // Two independent sources, both f64, each feeding one LinComb slot. let sources = vec![ SourceSpec { kind: ScalarKind::F64, targets: vec![Target { node: LINCOMB, slot: 0 }] }, SourceSpec { kind: ScalarKind::F64, targets: vec![Target { node: LINCOMB, slot: 1 }] }, ]; let edges = vec![Edge { from: LINCOMB, to: REC, slot: 0, from_field: 0 }]; let mut harness = Harness::bootstrap(nodes, sources, edges).expect("bootstrap"); // Source A fires at t = 0,1,2,3,4,5; source B fires only at t = 1 and t = 4. // (Distinct timestamps => distinct cycles, C4.) let stream_a: Vec<(Timestamp, Scalar)> = (0..6) .map(|i| (Timestamp(i), Scalar::F64(100.0 + i as f64))) .collect(); let stream_b: Vec<(Timestamp, Scalar)> = vec![ (Timestamp(1), Scalar::F64(10.0)), (Timestamp(4), Scalar::F64(20.0)), ]; harness.run(vec![stream_a, stream_b]); let tape = tape.borrow(); println!("LinComb emitted {} rows from two interleaved sources:", tape.len()); println!(" (source A: a=100+t at t=0..5; source B: b=10 at t=1, b=20 at t=4)"); for (ts, v) in tape.iter() { println!(" t={:>2} combined={:.4}", ts.0, v); } println!(); println!("Interpretation (public surface is SILENT on the firing policy):"); println!(" - mode A (as-of/hold): once both legs seen, every A-fresh cycle re-fires"); println!(" combining a's fresh value with b's HELD value."); println!(" - mode B (barrier): LinComb fires only on cycles where BOTH legs are"); println!(" fresh at the same timestamp -> here only t=1 and t=4 would emit."); println!(" The recorded rows above reveal which reading shipped."); }