//! End-to-end coverage for `ListSpace` (cycle 0107 task 2, C12.1): an explicit, //! non-lattice point set — modelled on `random_sweep_e2e.rs`'s worked-author //! pattern — driven through the PUBLIC `sweep` surface with a REAL bootstrapped //! harness (not a fake closure), so the property under test is that `ListSpace` //! integrates with the actual bootstrap -> run -> summarize pipeline the same //! way `GridSpace`/`RandomSpace` do, exercising the exact use case `ListSpace` //! exists for: re-running an arbitrary member subset (e.g. a gate's survivors) //! that no cartesian `GridSpace` can represent. use std::sync::mpsc; use aura_core::{Cell, Firing, ScalarKind, Timestamp}; use aura_engine::{ f64_field, summarize, sweep, BlueprintNode, Composite, Edge, ListSpace, OutField, ParamSpec, Role, RunManifest, RunReport, Scalar, SweepError, Target, VecSource, }; use aura_backtest::SimBroker; use aura_std::{Recorder, Sma, Sub}; use aura_strategy::Bias; /// Seven synthetic F64 ticks — the same fixture shape as `random_sweep_e2e.rs`, /// short enough that small SMA windows warm up deterministically. fn synthetic_prices() -> Vec<(Timestamp, Scalar)> { [ (1_i64, 1.0000_f64), (2, 1.0010), (3, 1.0030), (4, 1.0060), (5, 1.0040), (6, 1.0010), (7, 0.9990), ] .iter() .map(|&(t, p)| (Timestamp(t), Scalar::f64(p))) .collect() } /// The same SMA-cross `[fast: I64, slow: I64, scale: F64]` harness as /// `random_sweep_e2e.rs`, built through the public builder API. #[allow(clippy::type_complexity)] fn sma_cross_harness() -> ( Composite, mpsc::Receiver<(Timestamp, Vec)>, mpsc::Receiver<(Timestamp, Vec)>, ) { let (tx_eq, rx_eq) = mpsc::channel(); let (tx_ex, rx_ex) = mpsc::channel(); let sma_cross = Composite::new( "sma_cross", vec![ Sma::builder().named("fast").into(), Sma::builder().named("slow").into(), Sub::builder().into(), ], vec![ Edge { from: 0, to: 2, slot: 0, from_field: 0 }, Edge { from: 1, to: 2, slot: 1, from_field: 0 }, ], vec![Role { name: "price".into(), targets: vec![Target { node: 0, slot: 0 }, Target { node: 1, slot: 0 }], source: None, }], vec![OutField { node: 2, field: 0, name: "out".into() }], ); let bp = Composite::new( "root", vec![ BlueprintNode::Composite(sma_cross), Bias::builder().into(), SimBroker::builder(0.0001).into(), Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_eq).into(), Recorder::builder(vec![ScalarKind::F64], Firing::Any, tx_ex).into(), ], vec![ Edge { from: 0, to: 1, slot: 0, from_field: 0 }, Edge { from: 1, to: 2, slot: 0, from_field: 0 }, Edge { from: 2, to: 3, slot: 0, from_field: 0 }, Edge { from: 1, to: 4, slot: 0, from_field: 0 }, ], vec![Role { name: "src".into(), targets: vec![Target { node: 0, slot: 0 }, Target { node: 2, slot: 1 }], source: Some(ScalarKind::F64), }], vec![], ); (bp, rx_eq, rx_ex) } /// Build + bootstrap + run + summarize one point into a real `RunReport` — the /// full pipeline `ListSpace` must survive end-to-end, mirroring /// `random_sweep_e2e.rs::run_point`. fn run_point(point: &[Cell]) -> RunReport { let (bp, rx_eq, rx_ex) = sma_cross_harness(); let mut h = bp.bootstrap_with_cells(point).expect("ListSpace validates points before sweep"); h.run(vec![Box::new(VecSource::new(synthetic_prices()))]); let equity = f64_field(&rx_eq.try_iter().collect::>(), 0); let exposure = f64_field(&rx_ex.try_iter().collect::>(), 0); RunReport { manifest: RunManifest { commit: "list-sweep-e2e".to_string(), params: Vec::new(), defaults: Vec::new(), window: (Timestamp(0), Timestamp(0)), seed: 0, broker: "test".to_string(), selection: None, instrument: None, topology_hash: None, project: None, }, metrics: summarize(&equity, &exposure), } } /// Property: `ListSpace` reproduces the exact use case it exists for — an /// arbitrary, non-lattice member subset (e.g. what a gate stage's survivor /// ordinals would pick back out of a prior sweep family) driven through a REAL /// bootstrapped harness produces one finite-metric `RunReport` per given point, /// in the given order — never the cartesian product a `GridSpace` over the same /// values would enumerate. #[test] fn list_space_reruns_an_arbitrary_survivor_subset_through_a_real_harness() { let space = sma_cross_harness().0.param_space(); // a hand-picked, non-lattice subset: point 1 and point 2 share no axis value // pairwise with what a full [fast]x[slow]x[scale] grid over these values // would also produce as *other* combinations — this is a strict subset, not // a grid, and `ListSpace` must run exactly these three, nothing else. let survivors = vec![ vec![Scalar::i64(2), Scalar::i64(4), Scalar::f64(1.0)], vec![Scalar::i64(3), Scalar::i64(5), Scalar::f64(0.5)], vec![Scalar::i64(2), Scalar::i64(5), Scalar::f64(0.75)], ]; let ls = ListSpace::new(&space, survivors.clone()).expect("valid survivor subset"); let family = sweep(&ls, run_point); assert_eq!(family.points.len(), 3, "exactly the given survivor points ran, no more"); for (i, expected) in survivors.iter().enumerate() { let named = family.named_params(i); let got: Vec = named.into_iter().map(|(_, v)| v).collect(); assert_eq!(&got, expected, "point {i} carried through in input order, untouched"); assert!( family.points[i].report.metrics.total_pips.is_finite(), "point {i} ran through the real bootstrap -> run -> summarize pipeline" ); } } /// Property: the typed validation gate on `ListSpace::new` rejects a /// wrong-kind value BEFORE any run — a `F64` value in the harness's `I64` /// `fast` slot returns the public `SweepError::KindMismatch`, never a panic /// and never a run of the malformed point. #[test] fn list_space_rejects_wrong_kind_value_before_any_run() { let space = sma_cross_harness().0.param_space(); let err = ListSpace::new( &space, vec![vec![Scalar::f64(2.0), Scalar::i64(4), Scalar::f64(1.0)]], ) .expect_err("fast slot is I64, not F64"); assert_eq!( err, SweepError::KindMismatch { slot: 0, value_index: 0, expected: ScalarKind::I64, got: ScalarKind::F64, } ); // and the schema type stays reachable even on the rejection path (a real // author inspects it to correct the point) let _: &[ParamSpec] = &space; }