feat(aura-core,aura-std,aura-engine,aura-cli): inject a param-set vector at bootstrap (#31)
Cycle B of milestone "The World — parameter-space & sweep". A blueprint is now value-empty: a leaf holds a param-generic recipe, not a built node, and a positional Scalar vector is bound slot-by-slot at bootstrap — so one blueprint bootstraps into many distinct instances under different vectors, with no cdylib rebuild (C12/C19). This is the binding primitive a sweep (#32) drives. Ratified design (brainstorm): value-empty reconstruct-through-new() over mutate-in-place and over a default-bearing variant. The value lives only in the injected vector (no baked default), keeping the blueprint a pure param-generic recipe (C19); every injected value flows through the node's own constructor (the single sizing/validation gate). - aura-core: LeafFactory { name, params, build } — the recipe (params -> sized node through `new`); Scalar::as_i64/as_f64 value accessors. Node trait unchanged. - aura-std: each of the 7 nodes exposes factory() (SMA length:I64, Exposure scale:F64, LinComb arity x weights[i]:F64; Sub/Add/SimBroker/Recorder paramless, capturing their non-param construction args — pip_size, the Recorder channel). - aura-engine: BlueprintNode::Leaf(LeafFactory), From<LeafFactory>; param_space() reads factory.params() pre-build; compile_with_params/bootstrap_with_params build each leaf from its kind-checked slice while lowering (build-then-wire), arity checked up front via param_space().len(); CompileError::{ParamKindMismatch, ParamArity}. compile/inline/edge-rewrite are structurally unchanged, so the compilat stays bit-identical for a given point (the bit-identity and both param_space mirror tests stay green). The vestigial pre-build Composite::schema / BlueprintNode::schema (no live caller — interface resolution is structural on the built flat nodes) are removed. - aura-cli: the blueprint view labels leaves by bare type via LeafFactory::label() (`[SMA]`) — the ascii-dag renderer cannot render wide cluster-sibling labels (see spec); the compiled view still labels valued (`SMA(2)`). The sample supplies its point as a vector; the mis-wiring swap moved to the compiled view. The #34 dual-traversal drift hazard is subsumed: compile_with_params consumes the vector in the same recipe walk param_space() reports, so the two share one traversal. Verified: cargo build/test --workspace green (127 tests, incl. bit-identity, both mirror tests, and 4 new injection tests — different-vector-different-run, kind mismatch, arity, determinism); clippy --workspace --all-targets -D warnings clean; `aura graph` blueprint view renders cleanly. Scope: one vector -> one instance. Deferred: sweep enumeration (#32), domain validation (#32/C20), single-run authoring convenience (#35). closes #31
This commit is contained in:
@@ -57,18 +57,18 @@ pub fn render_blueprint(bp: &Blueprint) -> String {
|
||||
// interior edges (the interior ids are in hand here).
|
||||
for item in bp.nodes() {
|
||||
match item {
|
||||
BlueprintNode::Leaf(node) => {
|
||||
BlueprintNode::Leaf(factory) => {
|
||||
let id = labels.len();
|
||||
labels.push(node.label());
|
||||
labels.push(factory.label());
|
||||
item_display.push(ItemDisplay::Leaf(id));
|
||||
}
|
||||
BlueprintNode::Composite(c) => {
|
||||
let mut interior_ids = Vec::with_capacity(c.nodes().len());
|
||||
for inner in c.nodes() {
|
||||
match inner {
|
||||
BlueprintNode::Leaf(node) => {
|
||||
BlueprintNode::Leaf(factory) => {
|
||||
let id = labels.len();
|
||||
labels.push(node.label());
|
||||
labels.push(factory.label());
|
||||
interior_ids.push(id);
|
||||
}
|
||||
BlueprintNode::Composite(_) => unimplemented!(
|
||||
|
||||
+70
-50
@@ -116,11 +116,12 @@ fn run_sample() -> RunReport {
|
||||
|
||||
/// The SMA-cross signal as a named composite (price -> fast/slow SMA -> spread).
|
||||
/// CLI-local sample builder; the engine ships no sample (the duplication with
|
||||
/// `blueprint.rs`'s test helper is the dedup tracked in #14).
|
||||
fn sma_cross(name: &str, fast: usize, slow: usize) -> Composite {
|
||||
/// `blueprint.rs`'s test helper is the dedup tracked in #14). Value-empty: the SMA
|
||||
/// lengths are injected at compile, not baked here.
|
||||
fn sma_cross(name: &str) -> Composite {
|
||||
Composite::new(
|
||||
name,
|
||||
vec![Sma::new(fast).into(), Sma::new(slow).into(), Sub::new().into()],
|
||||
vec![Sma::factory().into(), Sma::factory().into(), Sub::factory().into()],
|
||||
vec![
|
||||
Edge { from: 0, to: 2, slot: 0, from_field: 0 },
|
||||
Edge { from: 1, to: 2, slot: 1, from_field: 0 },
|
||||
@@ -130,19 +131,20 @@ fn sma_cross(name: &str, fast: usize, slow: usize) -> Composite {
|
||||
)
|
||||
}
|
||||
|
||||
/// The sample signal-quality blueprint, parameterized by the SMA windows so a
|
||||
/// test can author a deliberately swapped variant. Recorders need a channel to
|
||||
/// construct; the receivers are dropped because the render never runs the graph.
|
||||
fn build_sample(fast: usize, slow: usize) -> Blueprint {
|
||||
/// The sample signal-quality blueprint (value-empty): a recipe whose SMA lengths +
|
||||
/// exposure scale are injected at compile via the point vector (see
|
||||
/// `sample_point`). Recorders need a channel to construct; the receivers are
|
||||
/// dropped because the render never runs the graph.
|
||||
fn build_sample() -> Blueprint {
|
||||
let (tx_eq, _rx_eq) = mpsc::channel();
|
||||
let (tx_ex, _rx_ex) = mpsc::channel();
|
||||
Blueprint::new(
|
||||
vec![
|
||||
BlueprintNode::Composite(sma_cross("sma_cross", fast, slow)),
|
||||
Exposure::new(0.5).into(),
|
||||
SimBroker::new(0.0001).into(),
|
||||
Recorder::new(&[ScalarKind::F64], Firing::Any, tx_eq).into(),
|
||||
Recorder::new(&[ScalarKind::F64], Firing::Any, tx_ex).into(),
|
||||
BlueprintNode::Composite(sma_cross("sma_cross")),
|
||||
Exposure::factory().into(),
|
||||
SimBroker::factory(0.0001).into(),
|
||||
Recorder::factory(vec![ScalarKind::F64], Firing::Any, tx_eq).into(),
|
||||
Recorder::factory(vec![ScalarKind::F64], Firing::Any, tx_ex).into(),
|
||||
],
|
||||
vec![SourceSpec {
|
||||
kind: ScalarKind::F64,
|
||||
@@ -162,7 +164,13 @@ fn build_sample(fast: usize, slow: usize) -> Blueprint {
|
||||
|
||||
/// The built-in sample rendered by `aura graph`.
|
||||
fn sample_blueprint() -> Blueprint {
|
||||
build_sample(2, 4)
|
||||
build_sample()
|
||||
}
|
||||
|
||||
/// The point vector injected into the sample blueprint, in `param_space()` slot
|
||||
/// order: `[fast SMA length, slow SMA length, exposure scale]`.
|
||||
fn sample_point() -> Vec<Scalar> {
|
||||
vec![Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)]
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -177,7 +185,8 @@ fn main() {
|
||||
let compiled = args.next().as_deref() == Some("--compiled");
|
||||
let bp = sample_blueprint();
|
||||
let out = if compiled {
|
||||
let (nodes, sources, edges) = bp.compile().expect("valid sample blueprint");
|
||||
let (nodes, sources, edges) =
|
||||
bp.compile_with_params(&sample_point()).expect("valid sample blueprint");
|
||||
graph::render_compilat(&nodes, &sources, &edges)
|
||||
} else {
|
||||
graph::render_blueprint(&bp)
|
||||
@@ -196,21 +205,23 @@ fn main() {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// The sample authored with fast/slow SMA windows swapped — the mis-wiring
|
||||
/// the render must surface. Test-only: nothing outside tests builds it.
|
||||
fn sample_blueprint_swapped() -> Blueprint {
|
||||
build_sample(4, 2)
|
||||
/// The sample's point vector with the fast/slow SMA lengths swapped — the
|
||||
/// mis-wiring the compiled render must surface. The blueprint is param-generic
|
||||
/// and identical for both orderings; only the injected vector differs.
|
||||
fn swapped_point() -> Vec<Scalar> {
|
||||
vec![Scalar::I64(4), Scalar::I64(2), Scalar::F64(0.5)]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blueprint_view_shows_cluster_and_param_labels() {
|
||||
fn blueprint_view_shows_cluster_and_param_generic_labels() {
|
||||
let out = graph::render_blueprint(&sample_blueprint());
|
||||
// the composite renders as a named cluster box
|
||||
assert!(out.contains("sma_cross"), "missing composite name:\n{out}");
|
||||
// param-carrying labels disambiguate the two SMAs
|
||||
assert!(out.contains("SMA(2)"), "missing SMA(2):\n{out}");
|
||||
assert!(out.contains("SMA(4)"), "missing SMA(4):\n{out}");
|
||||
for needle in ["Sub", "Exposure(0.5)", "SimBroker(0.0001)", "Recorder"] {
|
||||
// the value-empty blueprint view labels leaves param-generically by bare
|
||||
// type (no values, and no knob suffix — the ascii-dag layout cannot render
|
||||
// wide cluster-sibling labels); both SMAs render identically as [SMA]
|
||||
assert!(out.contains("[SMA]"), "missing [SMA]:\n{out}");
|
||||
for needle in ["Sub", "Exposure", "SimBroker", "Recorder"] {
|
||||
assert!(out.contains(needle), "missing {needle}:\n{out}");
|
||||
}
|
||||
}
|
||||
@@ -218,7 +229,8 @@ mod tests {
|
||||
#[test]
|
||||
fn compiled_view_dissolves_the_composite_boundary() {
|
||||
let bp = sample_blueprint();
|
||||
let (nodes, sources, edges) = bp.compile().expect("valid sample");
|
||||
let (nodes, sources, edges) =
|
||||
bp.compile_with_params(&sample_point()).expect("valid sample");
|
||||
let out = graph::render_compilat(&nodes, &sources, &edges);
|
||||
// node labels survive inlining...
|
||||
assert!(out.contains("SMA(2)") && out.contains("SMA(4)"), "labels lost:\n{out}");
|
||||
@@ -227,11 +239,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swapped_sma_inputs_render_differently() {
|
||||
fn swapped_param_vector_changes_the_compiled_render() {
|
||||
// the property the cycle exists to buy: a mis-wiring is no longer invisible.
|
||||
let correct = graph::render_blueprint(&sample_blueprint());
|
||||
let swapped = graph::render_blueprint(&sample_blueprint_swapped());
|
||||
assert_ne!(correct, swapped, "a fast/slow SMA swap must change the render");
|
||||
// The blueprint is now param-generic (identical for both orderings), so the
|
||||
// swap is observable only after the vector is injected — in the COMPILED
|
||||
// view, where the flat nodes carry their valued labels (SMA(2)/SMA(4)).
|
||||
let (cn, cs, ce) =
|
||||
sample_blueprint().compile_with_params(&sample_point()).expect("valid sample");
|
||||
let correct = graph::render_compilat(&cn, &cs, &ce);
|
||||
let (sn, ss, se) =
|
||||
sample_blueprint().compile_with_params(&swapped_point()).expect("valid sample");
|
||||
let swapped = graph::render_compilat(&sn, &ss, &se);
|
||||
assert_ne!(correct, swapped, "a fast/slow SMA swap must change the compiled render");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -239,27 +258,27 @@ mod tests {
|
||||
let out = graph::render_blueprint(&sample_blueprint());
|
||||
// ascii-dag's Sugiyama layout is deterministic (no RNG); these are the
|
||||
// exact bytes `aura graph` emits (render() ends in three newlines).
|
||||
let expected = r#" [source:F64]
|
||||
┌─────────└┐────────┐
|
||||
│ │ │
|
||||
╔═════╪══════════╪════╗ │
|
||||
║ sma_cross │ ║ │
|
||||
║ ↓ ↓ ║ │
|
||||
║ [SMA(2)] [SMA(4)] ║ │
|
||||
║ └──┌───────┘ ║ │
|
||||
║ ↓ ┌─╫───┘
|
||||
║ [Sub] │ ║
|
||||
║ │ │ ║
|
||||
╚════════╪══════════╪═╝
|
||||
│ │
|
||||
↓ │
|
||||
[Exposure(0.5)] │
|
||||
┌───────┘────────┐ │
|
||||
↓ ↓─┘
|
||||
[Recorder] [SimBroker(0.0001)]
|
||||
│
|
||||
↓
|
||||
[Recorder]
|
||||
let expected = r#" [source:F64]
|
||||
┌───────└───────┐
|
||||
│ │ │
|
||||
╔═══╪═══════╪═══╗ │
|
||||
║ sma_cross │ ║ │
|
||||
║ ↓ ↓ ║ │
|
||||
║ [SMA] [SMA] ║ │
|
||||
║ └────┌──┘ ║ │
|
||||
║ ↓ ║┌──┘
|
||||
║ [Sub] ║│
|
||||
║ │ ║│
|
||||
╚════════╪══════╝│
|
||||
│ │
|
||||
↓ │
|
||||
[Exposure] │
|
||||
┌───┘───────┼┐
|
||||
↓ └↓
|
||||
[Recorder] [SimBroker]
|
||||
│
|
||||
↓
|
||||
[Recorder]
|
||||
|
||||
|
||||
"#;
|
||||
@@ -269,7 +288,8 @@ mod tests {
|
||||
#[test]
|
||||
fn compiled_view_golden() {
|
||||
let bp = sample_blueprint();
|
||||
let (nodes, sources, edges) = bp.compile().expect("valid sample");
|
||||
let (nodes, sources, edges) =
|
||||
bp.compile_with_params(&sample_point()).expect("valid sample");
|
||||
let out = graph::render_compilat(&nodes, &sources, &edges);
|
||||
let expected = r#" [source:F64]
|
||||
┌────────└─┐──────┐
|
||||
|
||||
Reference in New Issue
Block a user