refactor(aura-core): split Scalar into a tag-free Cell + ScalarKind
Motivation
----------
`Scalar` was a tagged enum (I64/F64/Bool/Ts), so every scalar value
physically carried its own kind tag. But the kind is already known from
the schema/port/column the value flows through (C7: the type is a
property of the column, not of the value — the hot path is already
columnar `Column<T>`, and `AnyColumn::get` *reconstructs* the tag from
the column on the way out). The per-value tag was therefore redundant
with the kind the surrounding context already holds.
That redundancy had three costs:
* It baked an implicit `match` (a branch) into every function that read
a Scalar payload — even where the caller statically knew the type.
The tag could never be exploited away.
* Size: a tagged enum is tag + payload = 16 bytes (f64/i64 alignment),
twice the 8 bytes the value needs. A `Column<Scalar>` would be double
the memory and half the cache utilisation.
* It is the shared root of several downstream papercuts we keep hitting
— the lossy f64 manifest field, the `unreachable!` panic on a
non-numeric param, the serde-tag question — all symptoms of "the type
is baked into the value".
Change
------
Introduce `Cell`: a type-erased 64-bit word (`struct Cell(u64)`) that is
not readable without external type context. It is constructed per base
type (`from_i64/from_f64/from_bool/from_ts`) and read only by naming the
type at the call site (`i64()/f64()/bool()/ts()`) — each a branch-free
bit-cast. The hot path resolves the kind once at the boundary (from the
schema) and then reads natively, with no per-value branch. `Cell` knows
nothing of `Scalar` or `ScalarKind`; the dependency is strictly one-way,
and it lives in its own `cell.rs` (more is planned on top of it).
`Scalar` becomes `struct { kind: ScalarKind, cell: Cell }` — the
self-describing form for the dynamic boundaries (builder binding,
serialization, rendering), built on top of `Cell`. Its `as_*` accessors
now `debug_assert` the kind and return the native value (free in
release); calling the wrong accessor is a caller bug, not a checked
`Option`. The variant constructors `Scalar::I64(..)` become associated
fns `Scalar::i64(..)`.
`PartialEq` is hand-written (not derived) to preserve the former enum's
value semantics: kinds must match, then native payloads compare, so f64
keeps IEEE-754 behaviour (`NaN != NaN`, `+0.0 == -0.0`) and a kind
mismatch is never equal even when the raw words coincide. A fixture
(`scalar_eq_is_value_not_bitwise`) pins exactly the cases where bit- and
value-equality diverge, so it can't silently regress. `Cell`'s own
`Eq`/`Hash` stay bitwise — correct for a raw word.
The change is behaviour-preserving: Scalar's observable behaviour is
identical to the pre-Cell enum (the value-equality fixture proves it);
only the internal representation changed. The ~440 call sites across the
workspace are a mechanical constructor rename plus ~12 destructuring
sites (match-arms / `let`-patterns) rewritten to `kind()` + `as_*`.
Verified: cargo build --workspace --all-targets, cargo clippy --workspace
--all-targets -- -D warnings, cargo test --workspace — all green.
This commit is contained in:
@@ -827,16 +827,16 @@ mod tests {
|
||||
ParamSpec { name: "scale".into(), kind: ScalarKind::F64 },
|
||||
];
|
||||
let axes = vec![
|
||||
("scale".to_string(), vec![Scalar::F64(0.5)]),
|
||||
("sma_cross.fast".to_string(), vec![Scalar::I64(2), Scalar::I64(3)]),
|
||||
("sma_cross.slow".to_string(), vec![Scalar::I64(4), Scalar::I64(5)]),
|
||||
("scale".to_string(), vec![Scalar::f64(0.5)]),
|
||||
("sma_cross.fast".to_string(), vec![Scalar::i64(2), Scalar::i64(3)]),
|
||||
("sma_cross.slow".to_string(), vec![Scalar::i64(4), Scalar::i64(5)]),
|
||||
];
|
||||
assert_eq!(
|
||||
resolve_axes(&space, &axes),
|
||||
Ok(vec![
|
||||
vec![Scalar::I64(2), Scalar::I64(3)],
|
||||
vec![Scalar::I64(4), Scalar::I64(5)],
|
||||
vec![Scalar::F64(0.5)],
|
||||
vec![Scalar::i64(2), Scalar::i64(3)],
|
||||
vec![Scalar::i64(4), Scalar::i64(5)],
|
||||
vec![Scalar::f64(0.5)],
|
||||
]),
|
||||
);
|
||||
}
|
||||
@@ -857,7 +857,7 @@ mod tests {
|
||||
ParamSpec { name: "b".into(), kind: ScalarKind::I64 },
|
||||
];
|
||||
assert_eq!(
|
||||
resolve_axes(&space, &[("a".to_string(), vec![Scalar::I64(1)])]),
|
||||
resolve_axes(&space, &[("a".to_string(), vec![Scalar::i64(1)])]),
|
||||
Err(BindError::MissingKnob("b".to_string())),
|
||||
);
|
||||
}
|
||||
@@ -867,7 +867,7 @@ mod tests {
|
||||
// a MIXED-kind axis: the second element mismatches; per-element check must
|
||||
// catch it (a first-element-only check would pass it through to a panic).
|
||||
let space = vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }];
|
||||
let axes = vec![("scale".to_string(), vec![Scalar::F64(0.5), Scalar::I64(1)])];
|
||||
let axes = vec![("scale".to_string(), vec![Scalar::f64(0.5), Scalar::i64(1)])];
|
||||
assert_eq!(
|
||||
resolve_axes(&space, &axes),
|
||||
Err(BindError::KindMismatch {
|
||||
@@ -907,16 +907,16 @@ mod tests {
|
||||
let named = resolve_axes(
|
||||
&space,
|
||||
&[
|
||||
("sma_cross.fast.length".to_string(), vec![Scalar::I64(2), Scalar::I64(3)]),
|
||||
("sma_cross.slow.length".to_string(), vec![Scalar::I64(4), Scalar::I64(5)]),
|
||||
("exposure.scale".to_string(), vec![Scalar::F64(0.5)]),
|
||||
("sma_cross.fast.length".to_string(), vec![Scalar::i64(2), Scalar::i64(3)]),
|
||||
("sma_cross.slow.length".to_string(), vec![Scalar::i64(4), Scalar::i64(5)]),
|
||||
("exposure.scale".to_string(), vec![Scalar::f64(0.5)]),
|
||||
],
|
||||
)
|
||||
.expect("named axes resolve");
|
||||
let positional = vec![
|
||||
vec![Scalar::I64(2), Scalar::I64(3)],
|
||||
vec![Scalar::I64(4), Scalar::I64(5)],
|
||||
vec![Scalar::F64(0.5)],
|
||||
vec![Scalar::i64(2), Scalar::i64(3)],
|
||||
vec![Scalar::i64(4), Scalar::i64(5)],
|
||||
vec![Scalar::f64(0.5)],
|
||||
];
|
||||
let named_pts = GridSpace::new(&space, named).expect("named grid").points();
|
||||
let pos_pts = GridSpace::new(&space, positional).expect("positional grid").points();
|
||||
@@ -932,13 +932,13 @@ mod tests {
|
||||
ParamSpec { name: "scale".into(), kind: ScalarKind::F64 },
|
||||
];
|
||||
let bound = vec![
|
||||
("scale".to_string(), Scalar::F64(0.5)),
|
||||
("sma_cross.fast".to_string(), Scalar::I64(2)),
|
||||
("sma_cross.slow".to_string(), Scalar::I64(4)),
|
||||
("scale".to_string(), Scalar::f64(0.5)),
|
||||
("sma_cross.fast".to_string(), Scalar::i64(2)),
|
||||
("sma_cross.slow".to_string(), Scalar::i64(4)),
|
||||
];
|
||||
assert_eq!(
|
||||
resolve(&space, &bound),
|
||||
Ok(vec![Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)]),
|
||||
Ok(vec![Scalar::i64(2), Scalar::i64(4), Scalar::f64(0.5)]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -946,7 +946,7 @@ mod tests {
|
||||
fn resolve_unknown_knob() {
|
||||
let space = vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }];
|
||||
assert_eq!(
|
||||
resolve(&space, &[("nope".to_string(), Scalar::F64(0.5))]),
|
||||
resolve(&space, &[("nope".to_string(), Scalar::f64(0.5))]),
|
||||
Err(BindError::UnknownKnob("nope".to_string())),
|
||||
);
|
||||
}
|
||||
@@ -958,7 +958,7 @@ mod tests {
|
||||
ParamSpec { name: "b".into(), kind: ScalarKind::I64 },
|
||||
];
|
||||
assert_eq!(
|
||||
resolve(&space, &[("a".to_string(), Scalar::I64(1))]),
|
||||
resolve(&space, &[("a".to_string(), Scalar::i64(1))]),
|
||||
Err(BindError::MissingKnob("b".to_string())),
|
||||
);
|
||||
}
|
||||
@@ -967,7 +967,7 @@ mod tests {
|
||||
fn resolve_kind_mismatch() {
|
||||
let space = vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }];
|
||||
assert_eq!(
|
||||
resolve(&space, &[("scale".to_string(), Scalar::I64(2))]),
|
||||
resolve(&space, &[("scale".to_string(), Scalar::i64(2))]),
|
||||
Err(BindError::KindMismatch {
|
||||
knob: "scale".to_string(),
|
||||
expected: ScalarKind::F64,
|
||||
@@ -982,7 +982,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
resolve(
|
||||
&space,
|
||||
&[("a".to_string(), Scalar::I64(1)), ("a".to_string(), Scalar::I64(2))],
|
||||
&[("a".to_string(), Scalar::i64(1)), ("a".to_string(), Scalar::i64(2))],
|
||||
),
|
||||
Err(BindError::DuplicateBinding("a".to_string())),
|
||||
);
|
||||
@@ -993,8 +993,8 @@ mod tests {
|
||||
// Phase-1 (unknown) wins over Phase-2 (kind mismatch)
|
||||
let space = vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }];
|
||||
let bound = vec![
|
||||
("typo".to_string(), Scalar::I64(9)),
|
||||
("scale".to_string(), Scalar::I64(2)),
|
||||
("typo".to_string(), Scalar::i64(9)),
|
||||
("scale".to_string(), Scalar::i64(2)),
|
||||
];
|
||||
assert_eq!(resolve(&space, &bound), Err(BindError::UnknownKnob("typo".to_string())));
|
||||
}
|
||||
@@ -1004,8 +1004,8 @@ mod tests {
|
||||
// intra-binding: check a (unknown) precedes check d (duplicate)
|
||||
let space = vec![ParamSpec { name: "a".into(), kind: ScalarKind::I64 }];
|
||||
let bound = vec![
|
||||
("typo".to_string(), Scalar::I64(1)),
|
||||
("typo".to_string(), Scalar::I64(2)),
|
||||
("typo".to_string(), Scalar::i64(1)),
|
||||
("typo".to_string(), Scalar::i64(2)),
|
||||
];
|
||||
assert_eq!(resolve(&space, &bound), Err(BindError::UnknownKnob("typo".to_string())));
|
||||
}
|
||||
@@ -1027,7 +1027,7 @@ mod tests {
|
||||
|
||||
let (bp2, pos_eq, pos_ex) = composite_sma_cross_harness();
|
||||
let mut positional = bp2
|
||||
.bootstrap_with_params(vec![Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)])
|
||||
.bootstrap_with_params(vec![Scalar::i64(2), Scalar::i64(4), Scalar::f64(0.5)])
|
||||
.expect("positional bootstrap");
|
||||
positional.run(vec![Box::new(VecSource::new(synthetic_prices()))]);
|
||||
let pos_eq_v = pos_eq.try_iter().collect::<Vec<_>>();
|
||||
@@ -1066,7 +1066,7 @@ mod tests {
|
||||
if a.is_empty() || b.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.out[0] = Scalar::F64(a[0] + b[0]);
|
||||
self.out[0] = Scalar::f64(a[0] + b[0]);
|
||||
Some(&self.out)
|
||||
}
|
||||
}
|
||||
@@ -1084,7 +1084,7 @@ mod tests {
|
||||
if w.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.out[0] = Scalar::F64(w[0]);
|
||||
self.out[0] = Scalar::f64(w[0]);
|
||||
Some(&self.out)
|
||||
}
|
||||
}
|
||||
@@ -1116,7 +1116,7 @@ mod tests {
|
||||
PrimitiveBuilder::new(
|
||||
"Pass1",
|
||||
NodeSchema { inputs: vec![f64_any()], output: out_v(), params: vec![] },
|
||||
|_| Box::new(Pass1 { out: [Scalar::F64(0.0)] }),
|
||||
|_| Box::new(Pass1 { out: [Scalar::f64(0.0)] }),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
@@ -1124,7 +1124,7 @@ mod tests {
|
||||
PrimitiveBuilder::new(
|
||||
"Join2",
|
||||
NodeSchema { inputs: vec![f64_any(), f64_any()], output: out_v(), params: vec![] },
|
||||
|_| Box::new(Join2 { out: [Scalar::F64(0.0)] }),
|
||||
|_| Box::new(Join2 { out: [Scalar::f64(0.0)] }),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
@@ -1236,7 +1236,7 @@ mod tests {
|
||||
// distinct node names -> fan-in distinguishable -> compiles (the two SMA
|
||||
// length params are supplied so the only thing under test is the fan-in)
|
||||
let bp = sma_cross_under_root(true);
|
||||
assert!(bp.compile_with_params(&[Scalar::I64(2), Scalar::I64(4)]).is_ok());
|
||||
assert!(bp.compile_with_params(&[Scalar::i64(2), Scalar::i64(4)]).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1745,7 +1745,7 @@ mod tests {
|
||||
// (b) the same graph authored as a composite blueprint, compiled
|
||||
let (bp, comp_eq, comp_ex) = composite_sma_cross_harness();
|
||||
let mut composed = bp
|
||||
.bootstrap_with_params(vec![Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)])
|
||||
.bootstrap_with_params(vec![Scalar::i64(2), Scalar::i64(4), Scalar::f64(0.5)])
|
||||
.expect("composite blueprint compiles");
|
||||
composed.run(vec![Box::new(VecSource::new(prices))]);
|
||||
|
||||
@@ -1808,7 +1808,7 @@ mod tests {
|
||||
vec![], // output
|
||||
);
|
||||
let mut h = bp
|
||||
.bootstrap_with_params(vec![Scalar::I64(2), Scalar::I64(4)])
|
||||
.bootstrap_with_params(vec![Scalar::i64(2), Scalar::i64(4)])
|
||||
.expect("multi-output composite bootstraps");
|
||||
h.run(vec![Box::new(VecSource::new(prices))]);
|
||||
|
||||
@@ -1825,13 +1825,13 @@ mod tests {
|
||||
fn injecting_a_different_vector_changes_the_run() {
|
||||
let prices = synthetic_prices();
|
||||
let (bp, eq, _ex) = composite_sma_cross_harness();
|
||||
let mut a = bp.bootstrap_with_params(vec![Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)])
|
||||
let mut a = bp.bootstrap_with_params(vec![Scalar::i64(2), Scalar::i64(4), Scalar::f64(0.5)])
|
||||
.expect("compiles");
|
||||
a.run(vec![Box::new(VecSource::new(prices.clone()))]);
|
||||
let a_eq = eq.try_iter().collect::<Vec<_>>();
|
||||
|
||||
let (bp2, eq2, _ex2) = composite_sma_cross_harness();
|
||||
let mut b = bp2.bootstrap_with_params(vec![Scalar::I64(5), Scalar::I64(20), Scalar::F64(1.0)])
|
||||
let mut b = bp2.bootstrap_with_params(vec![Scalar::i64(5), Scalar::i64(20), Scalar::f64(1.0)])
|
||||
.expect("compiles");
|
||||
b.run(vec![Box::new(VecSource::new(prices))]);
|
||||
let b_eq = eq2.try_iter().collect::<Vec<_>>();
|
||||
@@ -1844,7 +1844,7 @@ mod tests {
|
||||
fn wrong_kind_is_a_param_kind_mismatch() {
|
||||
let (bp, _eq, _ex) = composite_sma_cross_harness();
|
||||
// slot 0 is I64 (an SMA length); inject F64 there
|
||||
let err = bp.bootstrap_with_params(vec![Scalar::F64(2.0), Scalar::I64(4), Scalar::F64(0.5)])
|
||||
let err = bp.bootstrap_with_params(vec![Scalar::f64(2.0), Scalar::i64(4), Scalar::f64(0.5)])
|
||||
.unwrap_err();
|
||||
assert!(matches!(err, CompileError::ParamKindMismatch { slot: 0, .. }));
|
||||
}
|
||||
@@ -1853,13 +1853,13 @@ mod tests {
|
||||
fn wrong_arity_is_a_param_arity_error() {
|
||||
let (short, _e1, _x1) = composite_sma_cross_harness();
|
||||
assert!(matches!(
|
||||
short.bootstrap_with_params(vec![Scalar::I64(2)]).unwrap_err(),
|
||||
short.bootstrap_with_params(vec![Scalar::i64(2)]).unwrap_err(),
|
||||
CompileError::ParamArity { expected: 3, got: 1 }
|
||||
));
|
||||
let (long, _e2, _x2) = composite_sma_cross_harness();
|
||||
assert!(matches!(
|
||||
long.bootstrap_with_params(
|
||||
vec![Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5), Scalar::F64(0.0)]
|
||||
vec![Scalar::i64(2), Scalar::i64(4), Scalar::f64(0.5), Scalar::f64(0.0)]
|
||||
).unwrap_err(),
|
||||
CompileError::ParamArity { expected: 3, got: 4 }
|
||||
));
|
||||
@@ -1869,11 +1869,11 @@ mod tests {
|
||||
fn same_vector_bootstraps_identically() {
|
||||
let prices = synthetic_prices();
|
||||
let (bp, eq, _ex) = composite_sma_cross_harness();
|
||||
let mut a = bp.bootstrap_with_params(vec![Scalar::I64(3), Scalar::I64(9), Scalar::F64(0.7)])
|
||||
let mut a = bp.bootstrap_with_params(vec![Scalar::i64(3), Scalar::i64(9), Scalar::f64(0.7)])
|
||||
.expect("compiles");
|
||||
a.run(vec![Box::new(VecSource::new(prices.clone()))]);
|
||||
let (bp2, eq2, _ex2) = composite_sma_cross_harness();
|
||||
let mut b = bp2.bootstrap_with_params(vec![Scalar::I64(3), Scalar::I64(9), Scalar::F64(0.7)])
|
||||
let mut b = bp2.bootstrap_with_params(vec![Scalar::i64(3), Scalar::i64(9), Scalar::f64(0.7)])
|
||||
.expect("compiles");
|
||||
b.run(vec![Box::new(VecSource::new(prices))]);
|
||||
assert_eq!(eq.try_iter().collect::<Vec<_>>(), eq2.try_iter().collect::<Vec<_>>());
|
||||
@@ -1896,7 +1896,7 @@ mod tests {
|
||||
|
||||
// the same blueprint, actually compiled to its flat node array; each flat
|
||||
// node's own declared params, concatenated in flat-node order
|
||||
let flat = bp.compile_with_params(&[Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)]).expect("harness compiles");
|
||||
let flat = bp.compile_with_params(&[Scalar::i64(2), Scalar::i64(4), Scalar::f64(0.5)]).expect("harness compiles");
|
||||
let from_flat: Vec<ParamSpec> =
|
||||
flat.signatures.iter().flat_map(|s| s.params.clone()).collect();
|
||||
|
||||
@@ -1981,7 +1981,7 @@ mod tests {
|
||||
let strat = Composite::new(
|
||||
"sma2_entry",
|
||||
vec![
|
||||
Sma::builder().named("bias").bind("length", Scalar::I64(2)).into(),
|
||||
Sma::builder().named("bias").bind("length", Scalar::i64(2)).into(),
|
||||
Exposure::builder().named("exp").into(),
|
||||
],
|
||||
vec![], // edges — irrelevant to param_space()
|
||||
@@ -2055,7 +2055,7 @@ mod tests {
|
||||
|
||||
// the same blueprint, compiled to its flat node array; each flat node's
|
||||
// own declared params, concatenated in flat-node order
|
||||
let flat = bp.compile_with_params(&[Scalar::I64(2), Scalar::I64(4), Scalar::F64(1.0), Scalar::F64(-1.0)]).expect("nested composite compiles");
|
||||
let flat = bp.compile_with_params(&[Scalar::i64(2), Scalar::i64(4), Scalar::f64(1.0), Scalar::f64(-1.0)]).expect("nested composite compiles");
|
||||
let from_flat: Vec<ParamSpec> =
|
||||
flat.signatures.iter().flat_map(|s| s.params.clone()).collect();
|
||||
|
||||
@@ -2190,7 +2190,7 @@ mod tests {
|
||||
vec![Role { name: "price".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64) }],
|
||||
vec![],
|
||||
);
|
||||
let err = root.compile_with_params(&[Scalar::I64(3)]);
|
||||
let err = root.compile_with_params(&[Scalar::i64(3)]);
|
||||
// kind fault caught pre-build (no panic) — same variant bootstrap would give
|
||||
assert!(matches!(
|
||||
err,
|
||||
@@ -2209,7 +2209,7 @@ mod tests {
|
||||
);
|
||||
// the Ok arm holds a FlatGraph (not Debug), so assert via the Err arm.
|
||||
assert_eq!(
|
||||
root.compile_with_params(&[Scalar::I64(3)]).err(),
|
||||
root.compile_with_params(&[Scalar::i64(3)]).err(),
|
||||
Some(CompileError::UnboundRootRole { role: 0 })
|
||||
);
|
||||
}
|
||||
@@ -2262,7 +2262,7 @@ mod tests {
|
||||
// two params declared (one per SMA); supply both so the only failure under
|
||||
// test is the duplicate path (green today, DuplicateParamPath after).
|
||||
assert_eq!(
|
||||
bp.compile_with_params(&[Scalar::I64(2), Scalar::I64(3)]).err(),
|
||||
bp.compile_with_params(&[Scalar::i64(2), Scalar::i64(3)]).err(),
|
||||
Some(CompileError::DuplicateParamPath("dup.sma.length".to_string()))
|
||||
);
|
||||
}
|
||||
@@ -2280,7 +2280,7 @@ mod tests {
|
||||
let paramless_sma = PrimitiveBuilder::new(
|
||||
"Pass1",
|
||||
NodeSchema { inputs: vec![f64_any()], output: out_v(), params: vec![] },
|
||||
|_| Box::new(Pass1 { out: [Scalar::F64(0.0)] }),
|
||||
|_| Box::new(Pass1 { out: [Scalar::f64(0.0)] }),
|
||||
)
|
||||
.named("sma");
|
||||
let asym = Composite::new(
|
||||
@@ -2312,7 +2312,7 @@ mod tests {
|
||||
bp.param_space().into_iter().map(|p| p.name).collect::<Vec<_>>(),
|
||||
["asym.sma.length"]
|
||||
);
|
||||
assert!(bp.compile_with_params(&[Scalar::I64(2)]).is_ok());
|
||||
assert!(bp.compile_with_params(&[Scalar::i64(2)]).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -240,7 +240,7 @@ mod tests {
|
||||
// cancels in the comparison. Params size buffers, not topology (C11), so
|
||||
// edges/sources are param-independent; a fixed vector just satisfies the
|
||||
// arity gate compile() (no-param) trips on this value-empty harness.
|
||||
let params = [Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)];
|
||||
let params = [Scalar::i64(2), Scalar::i64(4), Scalar::f64(0.5)];
|
||||
|
||||
// hand-wired reference harness -> FlatGraph
|
||||
let (hand_bp, _rx_eq, _rx_ex) = composite_sma_cross_harness();
|
||||
@@ -290,7 +290,7 @@ mod tests {
|
||||
let compiled = g
|
||||
.build()
|
||||
.expect("all port/field names resolve")
|
||||
.compile_with_params(&[Scalar::F64(0.5)]);
|
||||
.compile_with_params(&[Scalar::f64(0.5)]);
|
||||
assert_eq!(
|
||||
compiled.err(),
|
||||
Some(CompileError::UnconnectedPort { node: 1, slot: 0 })
|
||||
|
||||
@@ -25,12 +25,12 @@ fn kind_str(k: ScalarKind) -> &'static str {
|
||||
/// whole values (`1.0`, not `1`) so a bound f64 is never mistaken for an i64 in
|
||||
/// the render. The value side of `kind_str`.
|
||||
fn scalar_str(s: aura_core::Scalar) -> String {
|
||||
use aura_core::Scalar;
|
||||
match s {
|
||||
Scalar::I64(n) => n.to_string(),
|
||||
Scalar::F64(f) => format!("{f:?}"),
|
||||
Scalar::Bool(b) => b.to_string(),
|
||||
Scalar::Ts(t) => t.0.to_string(),
|
||||
use aura_core::ScalarKind;
|
||||
match s.kind() {
|
||||
ScalarKind::I64 => s.as_i64().to_string(),
|
||||
ScalarKind::F64 => format!("{:?}", s.as_f64()),
|
||||
ScalarKind::Bool => s.as_bool().to_string(),
|
||||
ScalarKind::Timestamp => s.as_ts().0.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,13 +426,13 @@ mod tests {
|
||||
#[test]
|
||||
fn prim_record_emits_bound_only_when_bound() {
|
||||
// a bound i64 param → a "bound" field carrying [pos,"name","kind","value"]
|
||||
let bound = prim_record(&Sma::builder().bind("length", Scalar::I64(5)));
|
||||
let bound = prim_record(&Sma::builder().bind("length", Scalar::i64(5)));
|
||||
assert!(bound.contains(r#""bound":[[0,"length","i64","5"]]"#), "{bound}");
|
||||
// the bound slot also leaves the tunable "params" surface (bind's tuning side)
|
||||
assert!(bound.contains(r#""params":[]"#), "{bound}");
|
||||
|
||||
// a bound f64 value renders canonically (shortest round-trip), e.g. 0.5
|
||||
let f = prim_record(&Exposure::builder().bind("scale", Scalar::F64(0.5)));
|
||||
let f = prim_record(&Exposure::builder().bind("scale", Scalar::f64(0.5)));
|
||||
assert!(f.contains(r#""bound":[[0,"scale","f64","0.5"]]"#), "{f}");
|
||||
|
||||
// an unbound builder → no "bound" field at all
|
||||
|
||||
+129
-130
@@ -183,7 +183,7 @@ impl SyntheticSpec {
|
||||
.map(|i| {
|
||||
let shock = (rng.next_f64() - 0.5) * 0.004; // ±0.2% per step
|
||||
price *= 1.0 + shock;
|
||||
(Timestamp(i as i64 * self.step + 1), Scalar::F64(price))
|
||||
(Timestamp(i as i64 * self.step + 1), Scalar::f64(price))
|
||||
})
|
||||
.collect();
|
||||
VecSource::new(stream)
|
||||
@@ -492,7 +492,7 @@ mod tests {
|
||||
|
||||
/// Build an f64 source stream from (timestamp, value) points.
|
||||
fn f64_stream(points: &[(i64, f64)]) -> Vec<(Timestamp, Scalar)> {
|
||||
points.iter().map(|&(t, v)| (Timestamp(t), Scalar::F64(v))).collect()
|
||||
points.iter().map(|&(t, v)| (Timestamp(t), Scalar::f64(v))).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -527,19 +527,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn vec_source_peek_is_non_consuming_and_idempotent() {
|
||||
let mut s = VecSource::new(vec![(Timestamp(1), Scalar::F64(10.0)), (Timestamp(2), Scalar::F64(20.0))]);
|
||||
let mut s = VecSource::new(vec![(Timestamp(1), Scalar::f64(10.0)), (Timestamp(2), Scalar::f64(20.0))]);
|
||||
// peek twice: same head, no advance.
|
||||
assert_eq!(s.peek(), Some(Timestamp(1)));
|
||||
assert_eq!(s.peek(), Some(Timestamp(1)));
|
||||
// next pops it; peek now reflects the new head.
|
||||
assert_eq!(Source::next(&mut s), Some((Timestamp(1), Scalar::F64(10.0))));
|
||||
assert_eq!(Source::next(&mut s), Some((Timestamp(1), Scalar::f64(10.0))));
|
||||
assert_eq!(s.peek(), Some(Timestamp(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_source_exhaustion_yields_none() {
|
||||
let mut s = VecSource::new(vec![(Timestamp(5), Scalar::I64(7))]);
|
||||
assert_eq!(Source::next(&mut s), Some((Timestamp(5), Scalar::I64(7))));
|
||||
let mut s = VecSource::new(vec![(Timestamp(5), Scalar::i64(7))]);
|
||||
assert_eq!(Source::next(&mut s), Some((Timestamp(5), Scalar::i64(7))));
|
||||
assert_eq!(s.peek(), None);
|
||||
assert_eq!(Source::next(&mut s), None);
|
||||
}
|
||||
@@ -639,7 +639,7 @@ mod tests {
|
||||
if a.is_empty() || b.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.out[0] = Scalar::F64(a[0] + b[0]);
|
||||
self.out[0] = Scalar::f64(a[0] + b[0]);
|
||||
Some(&self.out)
|
||||
}
|
||||
}
|
||||
@@ -663,7 +663,7 @@ mod tests {
|
||||
vec![1, 1]
|
||||
}
|
||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Scalar]> {
|
||||
self.out[0] = Scalar::F64(ctx.f64_in(0)[0] + ctx.f64_in(1)[0]);
|
||||
self.out[0] = Scalar::f64(ctx.f64_in(0)[0] + ctx.f64_in(1)[0]);
|
||||
Some(&self.out)
|
||||
}
|
||||
}
|
||||
@@ -698,7 +698,7 @@ mod tests {
|
||||
if a.is_empty() || b.is_empty() || c.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.out[0] = Scalar::F64(a[0] + b[0] + c[0]);
|
||||
self.out[0] = Scalar::f64(a[0] + b[0] + c[0]);
|
||||
Some(&self.out)
|
||||
}
|
||||
}
|
||||
@@ -735,7 +735,7 @@ mod tests {
|
||||
if w.is_empty() {
|
||||
return None; // not yet warmed
|
||||
}
|
||||
self.out[i] = Scalar::F64(w[0]);
|
||||
self.out[i] = Scalar::f64(w[0]);
|
||||
}
|
||||
Some(&self.out) // one 5-field record, all fields co-fresh
|
||||
}
|
||||
@@ -765,8 +765,8 @@ mod tests {
|
||||
vec![1]
|
||||
}
|
||||
fn eval(&mut self, _ctx: Ctx<'_>) -> Option<&[Scalar]> {
|
||||
self.out[0] = Scalar::F64(0.0);
|
||||
self.out[1] = Scalar::I64(0);
|
||||
self.out[0] = Scalar::f64(0.0);
|
||||
self.out[1] = Scalar::i64(0);
|
||||
Some(&self.out)
|
||||
}
|
||||
}
|
||||
@@ -797,8 +797,8 @@ mod tests {
|
||||
return None;
|
||||
}
|
||||
let v = w[0];
|
||||
let _ = self.tx.send((ctx.now(), vec![Scalar::F64(v)])); // sink side effect
|
||||
self.out[0] = Scalar::F64(v);
|
||||
let _ = self.tx.send((ctx.now(), vec![Scalar::f64(v)])); // sink side effect
|
||||
self.out[0] = Scalar::f64(v);
|
||||
Some(&self.out) // producer output: engine forwards it
|
||||
}
|
||||
}
|
||||
@@ -827,9 +827,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
got,
|
||||
vec![
|
||||
(Timestamp(3), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(3.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(4.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(2.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(3.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(4.0)]),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -874,9 +874,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
out,
|
||||
vec![
|
||||
(Timestamp(4), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(6), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(2.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(2.0)]),
|
||||
(Timestamp(6), vec![Scalar::f64(2.0)]),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -895,7 +895,7 @@ mod tests {
|
||||
let build = |tx| {
|
||||
boot(
|
||||
vec![
|
||||
Box::new(AsOfSum { out: [Scalar::F64(0.0)] }),
|
||||
Box::new(AsOfSum { out: [Scalar::f64(0.0)] }),
|
||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||
],
|
||||
vec![
|
||||
@@ -921,10 +921,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
out,
|
||||
vec![
|
||||
(Timestamp(2), vec![Scalar::F64(120.0)]),
|
||||
(Timestamp(3), vec![Scalar::F64(130.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(140.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(240.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(120.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(130.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(140.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(240.0)]),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -941,7 +941,7 @@ mod tests {
|
||||
let build = |tx| {
|
||||
boot(
|
||||
vec![
|
||||
Box::new(BarrierSum { out: [Scalar::F64(0.0)] }),
|
||||
Box::new(BarrierSum { out: [Scalar::f64(0.0)] }),
|
||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||
],
|
||||
vec![
|
||||
@@ -967,8 +967,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
out,
|
||||
vec![
|
||||
(Timestamp(2), vec![Scalar::F64(120.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(240.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(120.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(240.0)]),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -990,7 +990,7 @@ mod tests {
|
||||
vec![
|
||||
Box::new(Sma::new(2)),
|
||||
Box::new(Sma::new(4)),
|
||||
Box::new(BarrierSum { out: [Scalar::F64(0.0)] }),
|
||||
Box::new(BarrierSum { out: [Scalar::f64(0.0)] }),
|
||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||
],
|
||||
vec![
|
||||
@@ -1022,9 +1022,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
out,
|
||||
vec![
|
||||
(Timestamp(4), vec![Scalar::F64(28.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(32.0)]),
|
||||
(Timestamp(6), vec![Scalar::F64(36.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(28.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(32.0)]),
|
||||
(Timestamp(6), vec![Scalar::f64(36.0)]),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1041,7 +1041,7 @@ mod tests {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let mut h = boot(
|
||||
vec![
|
||||
Box::new(MixedSum { out: [Scalar::F64(0.0)] }),
|
||||
Box::new(MixedSum { out: [Scalar::f64(0.0)] }),
|
||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||
],
|
||||
vec![
|
||||
@@ -1067,8 +1067,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
out,
|
||||
vec![
|
||||
(Timestamp(2), vec![Scalar::F64(221.0)]),
|
||||
(Timestamp(3), vec![Scalar::F64(223.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(221.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(223.0)]),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1153,7 +1153,7 @@ mod tests {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let mut h = boot(
|
||||
vec![
|
||||
Box::new(Ohlcv { out: [Scalar::F64(0.0); 5] }),
|
||||
Box::new(Ohlcv { out: [Scalar::f64(0.0); 5] }),
|
||||
Box::new(Recorder::new(
|
||||
&[ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64],
|
||||
Firing::Any,
|
||||
@@ -1180,18 +1180,18 @@ mod tests {
|
||||
out,
|
||||
vec![
|
||||
(Timestamp(1), vec![
|
||||
Scalar::F64(10.0),
|
||||
Scalar::F64(15.0),
|
||||
Scalar::F64(8.0),
|
||||
Scalar::F64(12.0),
|
||||
Scalar::F64(100.0),
|
||||
Scalar::f64(10.0),
|
||||
Scalar::f64(15.0),
|
||||
Scalar::f64(8.0),
|
||||
Scalar::f64(12.0),
|
||||
Scalar::f64(100.0),
|
||||
]),
|
||||
(Timestamp(2), vec![
|
||||
Scalar::F64(20.0),
|
||||
Scalar::F64(25.0),
|
||||
Scalar::F64(19.0),
|
||||
Scalar::F64(22.0),
|
||||
Scalar::F64(200.0),
|
||||
Scalar::f64(20.0),
|
||||
Scalar::f64(25.0),
|
||||
Scalar::f64(19.0),
|
||||
Scalar::f64(22.0),
|
||||
Scalar::f64(200.0),
|
||||
]),
|
||||
]
|
||||
);
|
||||
@@ -1206,7 +1206,7 @@ mod tests {
|
||||
let build = |tx| {
|
||||
boot(
|
||||
vec![
|
||||
Box::new(Ohlcv { out: [Scalar::F64(0.0); 5] }),
|
||||
Box::new(Ohlcv { out: [Scalar::f64(0.0); 5] }),
|
||||
Box::new(Sub::new()),
|
||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||
],
|
||||
@@ -1232,8 +1232,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
out,
|
||||
vec![
|
||||
(Timestamp(1), vec![Scalar::F64(7.0)]),
|
||||
(Timestamp(2), vec![Scalar::F64(6.0)]),
|
||||
(Timestamp(1), vec![Scalar::f64(7.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(6.0)]),
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1252,7 +1252,7 @@ mod tests {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let mut h = boot(
|
||||
vec![
|
||||
Box::new(Ohlcv { out: [Scalar::F64(0.0); 5] }),
|
||||
Box::new(Ohlcv { out: [Scalar::f64(0.0); 5] }),
|
||||
Box::new(Sub::new()),
|
||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||
],
|
||||
@@ -1275,8 +1275,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
out,
|
||||
vec![
|
||||
(Timestamp(1), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(2), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(1), vec![Scalar::f64(2.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(2.0)]),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1304,7 +1304,7 @@ mod tests {
|
||||
// Sma(1)'s f64 input slot is a per-field kind mismatch -> KindMismatch. (The
|
||||
// mismatch is field-specific: from_field 0 would have matched.)
|
||||
let err = boot(
|
||||
vec![Box::new(TwoField { out: [Scalar::F64(0.0), Scalar::I64(0)] }), Box::new(Sma::new(1))],
|
||||
vec![Box::new(TwoField { out: [Scalar::f64(0.0), Scalar::i64(0)] }), Box::new(Sma::new(1))],
|
||||
vec![
|
||||
TwoField::sig(),
|
||||
Sma::builder().schema().clone(),
|
||||
@@ -1355,17 +1355,17 @@ mod tests {
|
||||
assert_eq!(
|
||||
fast,
|
||||
vec![
|
||||
(Timestamp(2), vec![Scalar::F64(11.0)]),
|
||||
(Timestamp(3), vec![Scalar::F64(13.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(15.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(17.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(11.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(13.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(15.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(17.0)]),
|
||||
]
|
||||
);
|
||||
assert_eq!(
|
||||
slow,
|
||||
vec![
|
||||
(Timestamp(4), vec![Scalar::F64(13.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(15.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(13.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(15.0)]),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1377,7 +1377,7 @@ mod tests {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let mut h = boot(
|
||||
vec![
|
||||
Box::new(Ohlcv { out: [Scalar::F64(0.0); 5] }),
|
||||
Box::new(Ohlcv { out: [Scalar::f64(0.0); 5] }),
|
||||
Box::new(Recorder::new(
|
||||
&[ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64],
|
||||
Firing::Any,
|
||||
@@ -1411,11 +1411,11 @@ mod tests {
|
||||
assert_eq!(
|
||||
out,
|
||||
vec![(Timestamp(1), vec![
|
||||
Scalar::F64(10.0),
|
||||
Scalar::F64(15.0),
|
||||
Scalar::F64(8.0),
|
||||
Scalar::F64(12.0),
|
||||
Scalar::F64(100.0),
|
||||
Scalar::f64(10.0),
|
||||
Scalar::f64(15.0),
|
||||
Scalar::f64(8.0),
|
||||
Scalar::f64(12.0),
|
||||
Scalar::f64(100.0),
|
||||
])]
|
||||
);
|
||||
}
|
||||
@@ -1446,19 +1446,19 @@ mod tests {
|
||||
)
|
||||
.expect("valid");
|
||||
h.run(vec![
|
||||
Box::new(VecSource::new(vec![(Timestamp(1), Scalar::I64(7))])),
|
||||
Box::new(VecSource::new(vec![(Timestamp(2), Scalar::F64(1.5))])),
|
||||
Box::new(VecSource::new(vec![(Timestamp(3), Scalar::Bool(true))])),
|
||||
Box::new(VecSource::new(vec![(Timestamp(4), Scalar::Ts(Timestamp(99)))])),
|
||||
Box::new(VecSource::new(vec![(Timestamp(1), Scalar::i64(7))])),
|
||||
Box::new(VecSource::new(vec![(Timestamp(2), Scalar::f64(1.5))])),
|
||||
Box::new(VecSource::new(vec![(Timestamp(3), Scalar::bool(true))])),
|
||||
Box::new(VecSource::new(vec![(Timestamp(4), Scalar::ts(Timestamp(99)))])),
|
||||
]);
|
||||
let out: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
|
||||
assert_eq!(
|
||||
out,
|
||||
vec![(Timestamp(4), vec![
|
||||
Scalar::I64(7),
|
||||
Scalar::F64(1.5),
|
||||
Scalar::Bool(true),
|
||||
Scalar::Ts(Timestamp(99)),
|
||||
Scalar::i64(7),
|
||||
Scalar::f64(1.5),
|
||||
Scalar::bool(true),
|
||||
Scalar::ts(Timestamp(99)),
|
||||
])]
|
||||
);
|
||||
}
|
||||
@@ -1472,7 +1472,7 @@ mod tests {
|
||||
let (tx_down, rx_down) = mpsc::channel();
|
||||
let mut h = boot(
|
||||
vec![
|
||||
Box::new(TapForward { out: [Scalar::F64(0.0)], tx: tx_tap }),
|
||||
Box::new(TapForward { out: [Scalar::f64(0.0)], tx: tx_tap }),
|
||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx_down)),
|
||||
],
|
||||
vec![
|
||||
@@ -1487,9 +1487,9 @@ mod tests {
|
||||
let tapped: Vec<(Timestamp, Vec<Scalar>)> = rx_tap.try_iter().collect();
|
||||
let downstream: Vec<(Timestamp, Vec<Scalar>)> = rx_down.try_iter().collect();
|
||||
let expected = vec![
|
||||
(Timestamp(1), vec![Scalar::F64(10.0)]),
|
||||
(Timestamp(2), vec![Scalar::F64(20.0)]),
|
||||
(Timestamp(3), vec![Scalar::F64(30.0)]),
|
||||
(Timestamp(1), vec![Scalar::f64(10.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(20.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(30.0)]),
|
||||
];
|
||||
assert_eq!(tapped, expected); // it recorded (sink side effect)
|
||||
assert_eq!(downstream, expected); // and forwarded (producer output)
|
||||
@@ -1559,8 +1559,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
out,
|
||||
vec![
|
||||
(Timestamp(2), vec![Scalar::F64(20.0), Scalar::F64(100.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(40.0), Scalar::F64(200.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(20.0), Scalar::f64(100.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(40.0), Scalar::f64(200.0)]),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1595,10 +1595,10 @@ mod tests {
|
||||
assert_eq!(
|
||||
out,
|
||||
vec![
|
||||
(Timestamp(2), vec![Scalar::F64(20.0), Scalar::F64(100.0)]),
|
||||
(Timestamp(3), vec![Scalar::F64(30.0), Scalar::F64(100.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(40.0), Scalar::F64(100.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(40.0), Scalar::F64(200.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(20.0), Scalar::f64(100.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(30.0), Scalar::f64(100.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(40.0), Scalar::f64(100.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(40.0), Scalar::f64(200.0)]),
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1611,7 +1611,7 @@ mod tests {
|
||||
let (tx, _rx) = mpsc::channel();
|
||||
let err = boot(
|
||||
vec![
|
||||
Box::new(TwoField { out: [Scalar::F64(0.0), Scalar::I64(0)] }),
|
||||
Box::new(TwoField { out: [Scalar::f64(0.0), Scalar::i64(0)] }),
|
||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||
],
|
||||
vec![
|
||||
@@ -1679,9 +1679,9 @@ mod tests {
|
||||
let s3: Vec<(Timestamp, Vec<Scalar>)> = rc.try_iter().collect();
|
||||
// SMA(3) warms at cycle 3: mean(2,4,6)=4, mean(4,6,8)=6, mean(6,8,10)=8.
|
||||
let expected = vec![
|
||||
(Timestamp(3), vec![Scalar::F64(4.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(6.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(8.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(4.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(6.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(8.0)]),
|
||||
];
|
||||
assert_eq!(s1, expected);
|
||||
assert_eq!(s2, expected); // every tap sees the identical shared stream
|
||||
@@ -1743,17 +1743,17 @@ mod tests {
|
||||
let sub: Vec<(Timestamp, Vec<Scalar>)> = rs.try_iter().collect();
|
||||
// SMA(2): 11,13,15,17,19 at cycles 2..6 (raw tap).
|
||||
let raw_expected = vec![
|
||||
(Timestamp(2), vec![Scalar::F64(11.0)]),
|
||||
(Timestamp(3), vec![Scalar::F64(13.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(15.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(17.0)]),
|
||||
(Timestamp(6), vec![Scalar::F64(19.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(11.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(13.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(15.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(17.0)]),
|
||||
(Timestamp(6), vec![Scalar::f64(19.0)]),
|
||||
];
|
||||
// Sub warms once SMA(4) is warm (cycle 4): 15-13, 17-15, 19-17 -> 2.
|
||||
let sub_expected = vec![
|
||||
(Timestamp(4), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(6), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(2.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(2.0)]),
|
||||
(Timestamp(6), vec![Scalar::f64(2.0)]),
|
||||
];
|
||||
assert_eq!(raw, raw_expected);
|
||||
assert_eq!(sub, sub_expected);
|
||||
@@ -1778,7 +1778,7 @@ mod tests {
|
||||
vec![
|
||||
Box::new(Sma::new(2)), // 0: shared producer (src A)
|
||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, t_any)), // 1: as-of tap of 0
|
||||
Box::new(BarrierSum { out: [Scalar::F64(0.0)] }), // 2: SMA(2) + src B (barrier)
|
||||
Box::new(BarrierSum { out: [Scalar::f64(0.0)] }), // 2: SMA(2) + src B (barrier)
|
||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, t_bar)), // 3: tap of barrier
|
||||
],
|
||||
vec![
|
||||
@@ -1810,15 +1810,15 @@ mod tests {
|
||||
let bar: Vec<(Timestamp, Vec<Scalar>)> = rbar.try_iter().collect();
|
||||
// As-of tap records every SMA(2) fire: 11@t2, 13@t3, 15@t4.
|
||||
let any_expected = vec![
|
||||
(Timestamp(2), vec![Scalar::F64(11.0)]),
|
||||
(Timestamp(3), vec![Scalar::F64(13.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(15.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(11.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(13.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(15.0)]),
|
||||
];
|
||||
// Barrier fires only where held SMA output and src B share a timestamp:
|
||||
// t=2 (SMA held=11 + B=100 = 111), t=4 (SMA held=15 + B=200 = 215).
|
||||
let bar_expected = vec![
|
||||
(Timestamp(2), vec![Scalar::F64(111.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(215.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(111.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(215.0)]),
|
||||
];
|
||||
assert_eq!(any, any_expected);
|
||||
assert_eq!(bar, bar_expected);
|
||||
@@ -1870,8 +1870,8 @@ mod tests {
|
||||
let out: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
|
||||
// stage1 (c2..c5): 3,5,7,9. stage2 (c3..): 4,6,8. stage3 (c4..): 5,7.
|
||||
let expected = vec![
|
||||
(Timestamp(4), vec![Scalar::F64(5.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(7.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(5.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(7.0)]),
|
||||
];
|
||||
assert_eq!(out, expected);
|
||||
|
||||
@@ -1932,19 +1932,19 @@ mod tests {
|
||||
let w3: Vec<(Timestamp, Vec<Scalar>)> = rb.try_iter().collect();
|
||||
let w4: Vec<(Timestamp, Vec<Scalar>)> = rc.try_iter().collect();
|
||||
let e2 = vec![
|
||||
(Timestamp(2), vec![Scalar::F64(11.0)]),
|
||||
(Timestamp(3), vec![Scalar::F64(13.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(15.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(17.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(11.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(13.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(15.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(17.0)]),
|
||||
];
|
||||
let e3 = vec![
|
||||
(Timestamp(3), vec![Scalar::F64(12.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(14.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(16.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(12.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(14.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(16.0)]),
|
||||
];
|
||||
let e4 = vec![
|
||||
(Timestamp(4), vec![Scalar::F64(13.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(15.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(13.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(15.0)]),
|
||||
];
|
||||
assert_eq!(w2, e2);
|
||||
assert_eq!(w3, e3);
|
||||
@@ -2005,9 +2005,9 @@ mod tests {
|
||||
let prices =
|
||||
f64_stream(&[(1, 10.0), (2, 12.0), (3, 14.0), (4, 16.0), (5, 18.0), (6, 20.0)]);
|
||||
let counts: Vec<(Timestamp, Scalar)> = vec![
|
||||
(Timestamp(1), Scalar::I64(7)),
|
||||
(Timestamp(2), Scalar::I64(8)),
|
||||
(Timestamp(3), Scalar::I64(9)),
|
||||
(Timestamp(1), Scalar::i64(7)),
|
||||
(Timestamp(2), Scalar::i64(8)),
|
||||
(Timestamp(3), Scalar::i64(9)),
|
||||
];
|
||||
|
||||
let (tr, rr) = mpsc::channel();
|
||||
@@ -2019,21 +2019,21 @@ mod tests {
|
||||
let sub: Vec<(Timestamp, Vec<Scalar>)> = rs.try_iter().collect();
|
||||
let i64s: Vec<(Timestamp, Vec<Scalar>)> = ri.try_iter().collect();
|
||||
let raw_expected = vec![
|
||||
(Timestamp(2), vec![Scalar::F64(11.0)]),
|
||||
(Timestamp(3), vec![Scalar::F64(13.0)]),
|
||||
(Timestamp(4), vec![Scalar::F64(15.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(17.0)]),
|
||||
(Timestamp(6), vec![Scalar::F64(19.0)]),
|
||||
(Timestamp(2), vec![Scalar::f64(11.0)]),
|
||||
(Timestamp(3), vec![Scalar::f64(13.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(15.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(17.0)]),
|
||||
(Timestamp(6), vec![Scalar::f64(19.0)]),
|
||||
];
|
||||
let sub_expected = vec![
|
||||
(Timestamp(4), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(5), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(6), vec![Scalar::F64(2.0)]),
|
||||
(Timestamp(4), vec![Scalar::f64(2.0)]),
|
||||
(Timestamp(5), vec![Scalar::f64(2.0)]),
|
||||
(Timestamp(6), vec![Scalar::f64(2.0)]),
|
||||
];
|
||||
let i64_expected = vec![
|
||||
(Timestamp(1), vec![Scalar::I64(7)]),
|
||||
(Timestamp(2), vec![Scalar::I64(8)]),
|
||||
(Timestamp(3), vec![Scalar::I64(9)]),
|
||||
(Timestamp(1), vec![Scalar::i64(7)]),
|
||||
(Timestamp(2), vec![Scalar::i64(8)]),
|
||||
(Timestamp(3), vec![Scalar::i64(9)]),
|
||||
];
|
||||
assert_eq!(raw, raw_expected);
|
||||
assert_eq!(sub, sub_expected);
|
||||
@@ -2105,12 +2105,11 @@ mod tests {
|
||||
// flat until SMA(4) warms (cycle 4) and the held exposure meets the next
|
||||
// price move (cycle 5): the first four equities are exactly 0.
|
||||
for (_, row) in &equity[0..4] {
|
||||
assert_eq!(row, &vec![Scalar::F64(0.0)]);
|
||||
assert_eq!(row, &vec![Scalar::f64(0.0)]);
|
||||
}
|
||||
// cycle 5: prev_exposure 0.00175 * (1.0040 - 1.0020) / 0.0001 = 0.035 pips
|
||||
let Scalar::F64(last) = equity[4].1[0] else {
|
||||
panic!("equity is f64");
|
||||
};
|
||||
debug_assert_eq!(equity[4].1[0].kind(), ScalarKind::F64, "equity is f64");
|
||||
let last = equity[4].1[0].as_f64();
|
||||
assert!((last - 0.035).abs() < 1e-9, "final equity = {last}, want ~0.035");
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ mod reexport_tests {
|
||||
use crate::{Firing, Scalar, ScalarKind, Timestamp};
|
||||
let _k: ScalarKind = ScalarKind::F64;
|
||||
let _f: Firing = Firing::Any;
|
||||
let _s: Scalar = Scalar::F64(0.0);
|
||||
let _s: Scalar = Scalar::f64(0.0);
|
||||
let _t: Timestamp = Timestamp(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ mod tests {
|
||||
|
||||
fn base_point() -> Vec<Scalar> {
|
||||
// matches the composite_sma_cross param_space order: fast, slow, scale
|
||||
vec![Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)]
|
||||
vec![Scalar::i64(2), Scalar::i64(4), Scalar::f64(0.5)]
|
||||
}
|
||||
|
||||
fn draws_with_metrics(values: &[f64]) -> Vec<McDraw> {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//! cycle 0029) — the same encoder the run registry uses, so a record's stdout
|
||||
//! and on-disk shapes coincide.
|
||||
|
||||
use aura_core::{Scalar, Timestamp};
|
||||
use aura_core::{Scalar, ScalarKind, Timestamp};
|
||||
|
||||
/// Summary metrics reduced from a run's recorded streams — the `-> metrics`
|
||||
/// half of C12's atomic sim unit. Pure function of the recorded streams.
|
||||
@@ -134,10 +134,10 @@ pub fn f64_field(rows: &[(Timestamp, Vec<Scalar>)], field: usize) -> Vec<(Timest
|
||||
let Some(&scalar) = row.get(field) else {
|
||||
panic!("f64_field: row has no field {field} (row width {})", row.len());
|
||||
};
|
||||
let Scalar::F64(v) = scalar else {
|
||||
if scalar.kind() != ScalarKind::F64 {
|
||||
panic!("f64_field: field {field} is not an f64 scalar: {scalar:?}");
|
||||
};
|
||||
(*ts, v)
|
||||
}
|
||||
(*ts, scalar.as_f64())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -164,7 +164,7 @@ mod tests {
|
||||
/// harness.rs test helper; the e2e test needs its own copy — the harness
|
||||
/// test module's is private to that module).
|
||||
fn f64_stream(points: &[(i64, f64)]) -> Vec<(Timestamp, Scalar)> {
|
||||
points.iter().map(|&(t, v)| (Timestamp(t), Scalar::F64(v))).collect()
|
||||
points.iter().map(|&(t, v)| (Timestamp(t), Scalar::f64(v))).collect()
|
||||
}
|
||||
|
||||
/// Bootstrap the cycle-0007 signal-quality harness with TWO sinks: one on
|
||||
@@ -316,8 +316,8 @@ mod tests {
|
||||
#[test]
|
||||
fn f64_field_projects_the_named_field() {
|
||||
let rows = vec![
|
||||
(Timestamp(1), vec![Scalar::F64(1.5), Scalar::I64(9)]),
|
||||
(Timestamp(2), vec![Scalar::F64(2.5), Scalar::I64(8)]),
|
||||
(Timestamp(1), vec![Scalar::f64(1.5), Scalar::i64(9)]),
|
||||
(Timestamp(2), vec![Scalar::f64(2.5), Scalar::i64(8)]),
|
||||
];
|
||||
assert_eq!(
|
||||
f64_field(&rows, 0),
|
||||
@@ -328,7 +328,7 @@ mod tests {
|
||||
#[test]
|
||||
#[should_panic(expected = "not an f64 scalar")]
|
||||
fn f64_field_panics_on_kind_mismatch() {
|
||||
let rows = vec![(Timestamp(1), vec![Scalar::I64(7)])];
|
||||
let rows = vec![(Timestamp(1), vec![Scalar::i64(7)])];
|
||||
let _ = f64_field(&rows, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -217,18 +217,18 @@ mod tests {
|
||||
let grid = GridSpace::new(
|
||||
&space,
|
||||
vec![
|
||||
vec![Scalar::I64(2), Scalar::I64(3)],
|
||||
vec![Scalar::I64(4), Scalar::I64(5)],
|
||||
vec![Scalar::i64(2), Scalar::i64(3)],
|
||||
vec![Scalar::i64(4), Scalar::i64(5)],
|
||||
],
|
||||
)
|
||||
.expect("valid grid");
|
||||
assert_eq!(
|
||||
grid.points(),
|
||||
vec![
|
||||
vec![Scalar::I64(2), Scalar::I64(4)],
|
||||
vec![Scalar::I64(2), Scalar::I64(5)],
|
||||
vec![Scalar::I64(3), Scalar::I64(4)],
|
||||
vec![Scalar::I64(3), Scalar::I64(5)],
|
||||
vec![Scalar::i64(2), Scalar::i64(4)],
|
||||
vec![Scalar::i64(2), Scalar::i64(5)],
|
||||
vec![Scalar::i64(3), Scalar::i64(4)],
|
||||
vec![Scalar::i64(3), Scalar::i64(5)],
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -243,9 +243,9 @@ mod tests {
|
||||
let grid = GridSpace::new(
|
||||
&space,
|
||||
vec![
|
||||
vec![Scalar::I64(2), Scalar::I64(3)],
|
||||
vec![Scalar::I64(4), Scalar::I64(5)],
|
||||
vec![Scalar::F64(0.5)],
|
||||
vec![Scalar::i64(2), Scalar::i64(3)],
|
||||
vec![Scalar::i64(4), Scalar::i64(5)],
|
||||
vec![Scalar::f64(0.5)],
|
||||
],
|
||||
)
|
||||
.expect("valid grid");
|
||||
@@ -256,14 +256,14 @@ mod tests {
|
||||
#[test]
|
||||
fn arity_mismatch_is_an_error() {
|
||||
let space = i64_space(2);
|
||||
let err = GridSpace::new(&space, vec![vec![Scalar::I64(2)]]).unwrap_err();
|
||||
let err = GridSpace::new(&space, vec![vec![Scalar::i64(2)]]).unwrap_err();
|
||||
assert_eq!(err, SweepError::Arity { expected: 2, got: 1 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_kind_is_a_kind_mismatch() {
|
||||
let space = i64_space(1);
|
||||
let err = GridSpace::new(&space, vec![vec![Scalar::F64(1.0)]]).unwrap_err();
|
||||
let err = GridSpace::new(&space, vec![vec![Scalar::f64(1.0)]]).unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
SweepError::KindMismatch {
|
||||
@@ -312,9 +312,9 @@ mod tests {
|
||||
GridSpace::new(
|
||||
&space,
|
||||
vec![
|
||||
vec![Scalar::I64(2), Scalar::I64(3)], // fast ∈ {2, 3}
|
||||
vec![Scalar::I64(4), Scalar::I64(5)], // slow ∈ {4, 5}
|
||||
vec![Scalar::F64(0.5)], // scale ∈ {0.5}
|
||||
vec![Scalar::i64(2), Scalar::i64(3)], // fast ∈ {2, 3}
|
||||
vec![Scalar::i64(4), Scalar::i64(5)], // slow ∈ {4, 5}
|
||||
vec![Scalar::f64(0.5)], // scale ∈ {0.5}
|
||||
],
|
||||
)
|
||||
.expect("grid matches the sample param-space")
|
||||
@@ -328,11 +328,11 @@ mod tests {
|
||||
// params carried in enumeration (odometer) order, self-describing
|
||||
assert_eq!(
|
||||
family.points[0].params,
|
||||
vec![Scalar::I64(2), Scalar::I64(4), Scalar::F64(0.5)],
|
||||
vec![Scalar::i64(2), Scalar::i64(4), Scalar::f64(0.5)],
|
||||
);
|
||||
assert_eq!(
|
||||
family.points[3].params,
|
||||
vec![Scalar::I64(3), Scalar::I64(5), Scalar::F64(0.5)],
|
||||
vec![Scalar::i64(3), Scalar::i64(5), Scalar::f64(0.5)],
|
||||
);
|
||||
// each point's metrics equal a direct, independent run of the same point:
|
||||
// the sweep adds enumeration + execution, never a metrics change (C1).
|
||||
@@ -361,7 +361,7 @@ mod tests {
|
||||
.map(|(ps, v)| (ps.name, v))
|
||||
.collect();
|
||||
assert_eq!(family.named_params(0), expected);
|
||||
assert_eq!(family.named_params(0)[0].1, Scalar::I64(2));
|
||||
assert_eq!(family.named_params(0)[0].1, Scalar::i64(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -21,7 +21,7 @@ pub(crate) fn synthetic_prices() -> Vec<(Timestamp, Scalar)> {
|
||||
(7, 0.9990),
|
||||
]
|
||||
.iter()
|
||||
.map(|&(t, p)| (Timestamp(t), Scalar::F64(p)))
|
||||
.map(|&(t, p)| (Timestamp(t), Scalar::f64(p)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -248,9 +248,9 @@ pub fn param_stability(result: &WalkForwardResult) -> Vec<MetricStats> {
|
||||
/// scalar in a param slot is a wiring bug, surfaced like the engine's other
|
||||
/// "checked at wiring" violations.
|
||||
fn scalar_as_f64(s: Scalar) -> f64 {
|
||||
match s {
|
||||
Scalar::I64(n) => n as f64,
|
||||
Scalar::F64(f) => f,
|
||||
match s.kind() {
|
||||
aura_core::ScalarKind::I64 => s.as_i64() as f64,
|
||||
aura_core::ScalarKind::F64 => s.as_f64(),
|
||||
other => unreachable!("non-numeric param scalar: {other:?}"),
|
||||
}
|
||||
}
|
||||
@@ -292,7 +292,7 @@ mod tests {
|
||||
fn run_window_fixture(w: WindowBounds) -> WindowRun {
|
||||
let Timestamp(k) = w.oos.0;
|
||||
WindowRun {
|
||||
chosen_params: vec![Scalar::I64(k % 3), Scalar::F64(k as f64 * 0.5)],
|
||||
chosen_params: vec![Scalar::i64(k % 3), Scalar::f64(k as f64 * 0.5)],
|
||||
oos_equity: vec![(w.oos.0, k as f64 * 0.1), (w.oos.1, k as f64 * 0.1 + 1.0)],
|
||||
oos_report: dummy_report(),
|
||||
}
|
||||
@@ -373,7 +373,7 @@ mod tests {
|
||||
oos: (Timestamp(0), Timestamp(0)),
|
||||
},
|
||||
run: WindowRun {
|
||||
chosen_params: vec![Scalar::I64(a), Scalar::F64(b)],
|
||||
chosen_params: vec![Scalar::i64(a), Scalar::f64(b)],
|
||||
oos_equity: vec![],
|
||||
oos_report: dummy_report(),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user