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:
+24
-24
@@ -41,7 +41,7 @@ fn synthetic_prices() -> Vec<(Timestamp, Scalar)> {
|
|||||||
(7, 0.9990),
|
(7, 0.9990),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&(t, p)| (Timestamp(t), Scalar::F64(p)))
|
.map(|&(t, p)| (Timestamp(t), Scalar::f64(p)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ fn showcase_prices() -> Vec<(Timestamp, Scalar)> {
|
|||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, &p)| (Timestamp(i as i64 + 1), Scalar::F64(p)))
|
.map(|(i, &p)| (Timestamp(i as i64 + 1), Scalar::f64(p)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,9 +163,9 @@ fn run_sample() -> RunReport {
|
|||||||
RunReport {
|
RunReport {
|
||||||
manifest: sim_optimal_manifest(
|
manifest: sim_optimal_manifest(
|
||||||
vec![
|
vec![
|
||||||
("sma_fast".to_string(), Scalar::F64(2.0)),
|
("sma_fast".to_string(), Scalar::f64(2.0)),
|
||||||
("sma_slow".to_string(), Scalar::F64(4.0)),
|
("sma_slow".to_string(), Scalar::f64(4.0)),
|
||||||
("exposure_scale".to_string(), Scalar::F64(0.5)),
|
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||||
],
|
],
|
||||||
window,
|
window,
|
||||||
0,
|
0,
|
||||||
@@ -226,9 +226,9 @@ fn run_sample_real(symbol: &str, from_ms: Option<i64>, to_ms: Option<i64>) -> Ru
|
|||||||
RunReport {
|
RunReport {
|
||||||
manifest: sim_optimal_manifest(
|
manifest: sim_optimal_manifest(
|
||||||
vec![
|
vec![
|
||||||
("sma_fast".to_string(), Scalar::F64(2.0)),
|
("sma_fast".to_string(), Scalar::f64(2.0)),
|
||||||
("sma_slow".to_string(), Scalar::F64(4.0)),
|
("sma_slow".to_string(), Scalar::f64(4.0)),
|
||||||
("exposure_scale".to_string(), Scalar::F64(0.5)),
|
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||||
],
|
],
|
||||||
window,
|
window,
|
||||||
0,
|
0,
|
||||||
@@ -295,7 +295,7 @@ fn signals(name: &str) -> Composite {
|
|||||||
let blend = g.add(
|
let blend = g.add(
|
||||||
LinComb::builder(3)
|
LinComb::builder(3)
|
||||||
.named("blend")
|
.named("blend")
|
||||||
.bind("weights[2]", Scalar::F64(0.5)),
|
.bind("weights[2]", Scalar::f64(0.5)),
|
||||||
);
|
);
|
||||||
let price = g.input_role("price");
|
let price = g.input_role("price");
|
||||||
g.feed(price, [trend.input("price"), momentum.input("price")]);
|
g.feed(price, [trend.input("price"), momentum.input("price")]);
|
||||||
@@ -351,9 +351,9 @@ fn sample_blueprint() -> Composite {
|
|||||||
/// Coerce a sweep point's `Scalar` value to the manifest's `f64` param type.
|
/// Coerce a sweep point's `Scalar` value to the manifest's `f64` param type.
|
||||||
/// A tuning grid carries only numeric params (`I64` lengths, `F64` scales).
|
/// A tuning grid carries only numeric params (`I64` lengths, `F64` scales).
|
||||||
fn scalar_as_param_f64(s: &Scalar) -> f64 {
|
fn scalar_as_param_f64(s: &Scalar) -> f64 {
|
||||||
match s {
|
match s.kind() {
|
||||||
Scalar::I64(n) => *n as f64,
|
ScalarKind::I64 => s.as_i64() as f64,
|
||||||
Scalar::F64(f) => *f,
|
ScalarKind::F64 => s.as_f64(),
|
||||||
other => unreachable!("non-numeric sweep param: {other:?}"),
|
other => unreachable!("non-numeric sweep param: {other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -600,9 +600,9 @@ fn mc_family() -> McFamily {
|
|||||||
RunReport {
|
RunReport {
|
||||||
manifest: sim_optimal_manifest(
|
manifest: sim_optimal_manifest(
|
||||||
vec![
|
vec![
|
||||||
("sma_fast".to_string(), Scalar::F64(2.0)),
|
("sma_fast".to_string(), Scalar::f64(2.0)),
|
||||||
("sma_slow".to_string(), Scalar::F64(4.0)),
|
("sma_slow".to_string(), Scalar::f64(4.0)),
|
||||||
("exposure_scale".to_string(), Scalar::F64(0.5)),
|
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||||
],
|
],
|
||||||
window,
|
window,
|
||||||
seed,
|
seed,
|
||||||
@@ -813,7 +813,7 @@ fn macd_blueprint() -> Composite {
|
|||||||
/// windows so the 7-tick synthetic stream still produces a non-trivial trace
|
/// windows so the 7-tick synthetic stream still produces a non-trivial trace
|
||||||
/// (conventional MACD is 12/26/9, meaningless on 7 points).
|
/// (conventional MACD is 12/26/9, meaningless on 7 points).
|
||||||
fn macd_point() -> Vec<Scalar> {
|
fn macd_point() -> Vec<Scalar> {
|
||||||
vec![Scalar::I64(2), Scalar::I64(4), Scalar::I64(3), Scalar::F64(0.5)]
|
vec![Scalar::i64(2), Scalar::i64(4), Scalar::i64(3), Scalar::f64(0.5)]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A longer synthetic stream than the SMA sample's 7 ticks: MACD's EMAs each warm
|
/// A longer synthetic stream than the SMA sample's 7 ticks: MACD's EMAs each warm
|
||||||
@@ -826,7 +826,7 @@ fn macd_prices() -> Vec<(Timestamp, Scalar)> {
|
|||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, &p)| (Timestamp(i as i64 + 1), Scalar::F64(p)))
|
.map(|(i, &p)| (Timestamp(i as i64 + 1), Scalar::f64(p)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -856,10 +856,10 @@ fn run_macd() -> RunReport {
|
|||||||
RunReport {
|
RunReport {
|
||||||
manifest: sim_optimal_manifest(
|
manifest: sim_optimal_manifest(
|
||||||
vec![
|
vec![
|
||||||
("ema_fast".to_string(), Scalar::F64(2.0)),
|
("ema_fast".to_string(), Scalar::f64(2.0)),
|
||||||
("ema_slow".to_string(), Scalar::F64(4.0)),
|
("ema_slow".to_string(), Scalar::f64(4.0)),
|
||||||
("ema_signal".to_string(), Scalar::F64(3.0)),
|
("ema_signal".to_string(), Scalar::f64(3.0)),
|
||||||
("exposure_scale".to_string(), Scalar::F64(0.5)),
|
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||||
],
|
],
|
||||||
window,
|
window,
|
||||||
0,
|
0,
|
||||||
@@ -957,9 +957,9 @@ mod tests {
|
|||||||
let report = RunReport {
|
let report = RunReport {
|
||||||
manifest: sim_optimal_manifest(
|
manifest: sim_optimal_manifest(
|
||||||
vec![
|
vec![
|
||||||
("sma_fast".to_string(), Scalar::F64(2.0)),
|
("sma_fast".to_string(), Scalar::f64(2.0)),
|
||||||
("sma_slow".to_string(), Scalar::F64(4.0)),
|
("sma_slow".to_string(), Scalar::f64(4.0)),
|
||||||
("exposure_scale".to_string(), Scalar::F64(0.5)),
|
("exposure_scale".to_string(), Scalar::f64(0.5)),
|
||||||
],
|
],
|
||||||
window,
|
window,
|
||||||
seed,
|
seed,
|
||||||
|
|||||||
+22
-22
@@ -60,34 +60,34 @@ impl AnyColumn {
|
|||||||
/// kind; the column is left untouched (C7 guard). The hot path bypasses this
|
/// kind; the column is left untouched (C7 guard). The hot path bypasses this
|
||||||
/// by taking the concrete `Column<T>` once via `as_*_mut`.
|
/// by taking the concrete `Column<T>` once via `as_*_mut`.
|
||||||
pub fn push(&mut self, v: Scalar) -> Result<(), KindMismatch> {
|
pub fn push(&mut self, v: Scalar) -> Result<(), KindMismatch> {
|
||||||
match (self, v) {
|
match (self, v.kind()) {
|
||||||
(AnyColumn::I64(c), Scalar::I64(x)) => {
|
(AnyColumn::I64(c), ScalarKind::I64) => {
|
||||||
c.push(x);
|
c.push(v.as_i64());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
(AnyColumn::F64(c), Scalar::F64(x)) => {
|
(AnyColumn::F64(c), ScalarKind::F64) => {
|
||||||
c.push(x);
|
c.push(v.as_f64());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
(AnyColumn::Bool(c), Scalar::Bool(x)) => {
|
(AnyColumn::Bool(c), ScalarKind::Bool) => {
|
||||||
c.push(x);
|
c.push(v.as_bool());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
(AnyColumn::Ts(c), Scalar::Ts(x)) => {
|
(AnyColumn::Ts(c), ScalarKind::Timestamp) => {
|
||||||
c.push(x);
|
c.push(v.as_ts());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
(this, v) => Err(KindMismatch { expected: this.kind(), got: v.kind() }),
|
(this, got) => Err(KindMismatch { expected: this.kind(), got }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read one type-erased value (0 = newest). `None` if cold / out of range.
|
/// Read one type-erased value (0 = newest). `None` if cold / out of range.
|
||||||
pub fn get(&self, k: usize) -> Option<Scalar> {
|
pub fn get(&self, k: usize) -> Option<Scalar> {
|
||||||
match self {
|
match self {
|
||||||
AnyColumn::I64(c) => c.get(k).map(Scalar::I64),
|
AnyColumn::I64(c) => c.get(k).map(Scalar::i64),
|
||||||
AnyColumn::F64(c) => c.get(k).map(Scalar::F64),
|
AnyColumn::F64(c) => c.get(k).map(Scalar::f64),
|
||||||
AnyColumn::Bool(c) => c.get(k).map(Scalar::Bool),
|
AnyColumn::Bool(c) => c.get(k).map(Scalar::bool),
|
||||||
AnyColumn::Ts(c) => c.get(k).map(Scalar::Ts),
|
AnyColumn::Ts(c) => c.get(k).map(Scalar::ts),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,20 +160,20 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn same_kind_push_round_trips() {
|
fn same_kind_push_round_trips() {
|
||||||
let mut e = AnyColumn::with_capacity(ScalarKind::F64, 4);
|
let mut e = AnyColumn::with_capacity(ScalarKind::F64, 4);
|
||||||
e.push(Scalar::F64(1.5)).unwrap();
|
e.push(Scalar::f64(1.5)).unwrap();
|
||||||
e.push(Scalar::F64(2.5)).unwrap();
|
e.push(Scalar::f64(2.5)).unwrap();
|
||||||
assert_eq!(e.kind(), ScalarKind::F64);
|
assert_eq!(e.kind(), ScalarKind::F64);
|
||||||
assert_eq!(e.len(), 2);
|
assert_eq!(e.len(), 2);
|
||||||
assert_eq!(e.run_count(), 2);
|
assert_eq!(e.run_count(), 2);
|
||||||
assert_eq!(e.get(0), Some(Scalar::F64(2.5))); // newest
|
assert_eq!(e.get(0), Some(Scalar::f64(2.5))); // newest
|
||||||
assert_eq!(e.get(1), Some(Scalar::F64(1.5)));
|
assert_eq!(e.get(1), Some(Scalar::f64(1.5)));
|
||||||
assert_eq!(e.get(2), None);
|
assert_eq!(e.get(2), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wrong_kind_push_is_rejected() {
|
fn wrong_kind_push_is_rejected() {
|
||||||
let mut edge = AnyColumn::with_capacity(ScalarKind::I64, 8);
|
let mut edge = AnyColumn::with_capacity(ScalarKind::I64, 8);
|
||||||
let err = edge.push(Scalar::F64(1.5)).unwrap_err(); // f64 into an i64 edge
|
let err = edge.push(Scalar::f64(1.5)).unwrap_err(); // f64 into an i64 edge
|
||||||
assert_eq!(err, KindMismatch { expected: ScalarKind::I64, got: ScalarKind::F64 });
|
assert_eq!(err, KindMismatch { expected: ScalarKind::I64, got: ScalarKind::F64 });
|
||||||
assert_eq!(edge.len(), 0); // nothing was stored
|
assert_eq!(edge.len(), 0); // nothing was stored
|
||||||
assert_eq!(edge.run_count(), 0); // and the counter did not move
|
assert_eq!(edge.run_count(), 0); // and the counter did not move
|
||||||
@@ -188,7 +188,7 @@ mod tests {
|
|||||||
assert!(e.as_ts_mut().is_none());
|
assert!(e.as_ts_mut().is_none());
|
||||||
// hot-path push through the concrete column, monomorphic:
|
// hot-path push through the concrete column, monomorphic:
|
||||||
e.as_f64_mut().unwrap().push(3.0);
|
e.as_f64_mut().unwrap().push(3.0);
|
||||||
assert_eq!(e.get(0), Some(Scalar::F64(3.0)));
|
assert_eq!(e.get(0), Some(Scalar::f64(3.0)));
|
||||||
assert_eq!(e.run_count(), 1);
|
assert_eq!(e.run_count(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn timestamp_edge_round_trips() {
|
fn timestamp_edge_round_trips() {
|
||||||
let mut e = AnyColumn::with_capacity(ScalarKind::Timestamp, 2);
|
let mut e = AnyColumn::with_capacity(ScalarKind::Timestamp, 2);
|
||||||
e.push(Scalar::Ts(Timestamp(1_700_000_000_000_000_000))).unwrap();
|
e.push(Scalar::ts(Timestamp(1_700_000_000_000_000_000))).unwrap();
|
||||||
assert_eq!(e.get(0), Some(Scalar::Ts(Timestamp(1_700_000_000_000_000_000))));
|
assert_eq!(e.get(0), Some(Scalar::ts(Timestamp(1_700_000_000_000_000_000))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
//! `Cell` — a type-erased 64-bit word holding one scalar value with its kind
|
||||||
|
//! stripped away (C7). The type lives in the schema / column / port, never in
|
||||||
|
//! the value, so a cell is read back only by naming its type via a typed
|
||||||
|
//! accessor. `Scalar` is built on top of this; `Cell` itself knows nothing of
|
||||||
|
//! `Scalar` or `ScalarKind`.
|
||||||
|
|
||||||
|
use crate::scalar::Timestamp;
|
||||||
|
|
||||||
|
/// A type-erased 64-bit cell: the raw storage of one scalar value with its kind
|
||||||
|
/// stripped away. The bits are meaningless on their own — the type lives in the
|
||||||
|
/// schema/column/port, not in the value (C7), so the word is recovered **only**
|
||||||
|
/// by naming the type at the call site via a typed accessor ([`Cell::i64`],
|
||||||
|
/// [`Cell::f64`], [`Cell::bool`], [`Cell::ts`]). Each reinterprets the word with
|
||||||
|
/// no tag to check and therefore no branch — the caller's choice of accessor
|
||||||
|
/// *is* the type context the hot path already holds. The inner word is private;
|
||||||
|
/// there is no kind-free way to read it.
|
||||||
|
///
|
||||||
|
/// All four base types fit one 64-bit word: `i64`/`Timestamp`/`bool` reuse the
|
||||||
|
/// same integer word, `f64` via its IEEE-754 bit pattern. `Eq`/`Hash` are
|
||||||
|
/// bit-exact (so `+0.0`/`-0.0` differ and a `NaN` bit pattern equals itself) —
|
||||||
|
/// the semantics of a raw word, not of a number.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub struct Cell(u64);
|
||||||
|
|
||||||
|
impl Cell {
|
||||||
|
/// Store an `i64` (identity bit-cast).
|
||||||
|
pub fn from_i64(v: i64) -> Self {
|
||||||
|
Cell(v as u64)
|
||||||
|
}
|
||||||
|
/// Store an `f64` as its IEEE-754 bit pattern.
|
||||||
|
pub fn from_f64(v: f64) -> Self {
|
||||||
|
Cell(v.to_bits())
|
||||||
|
}
|
||||||
|
/// Store a `bool` as `0`/`1`.
|
||||||
|
pub fn from_bool(v: bool) -> Self {
|
||||||
|
Cell(v as u64)
|
||||||
|
}
|
||||||
|
/// Store a [`Timestamp`] (its `i64` epoch-ns, bit-cast).
|
||||||
|
pub fn from_ts(v: Timestamp) -> Self {
|
||||||
|
Cell(v.0 as u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the word as `i64` (identity bit-cast). The caller asserts the type
|
||||||
|
/// by choosing this accessor; there is no tag to check, hence no branch.
|
||||||
|
pub fn i64(self) -> i64 {
|
||||||
|
self.0 as i64
|
||||||
|
}
|
||||||
|
/// Read the word as `f64` from its IEEE-754 bit pattern. Branch-free.
|
||||||
|
pub fn f64(self) -> f64 {
|
||||||
|
f64::from_bits(self.0)
|
||||||
|
}
|
||||||
|
/// Read the word as `bool` (any non-zero word is `true`). Branch-free.
|
||||||
|
pub fn bool(self) -> bool {
|
||||||
|
self.0 != 0
|
||||||
|
}
|
||||||
|
/// Read the word as a [`Timestamp`] (identity bit-cast). Branch-free.
|
||||||
|
pub fn ts(self) -> Timestamp {
|
||||||
|
Timestamp(self.0 as i64)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,7 +76,7 @@ mod tests {
|
|||||||
fn ctx_hands_financial_indexed_windows() {
|
fn ctx_hands_financial_indexed_windows() {
|
||||||
let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 4)];
|
let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 4)];
|
||||||
for v in [10.0_f64, 20.0, 30.0] {
|
for v in [10.0_f64, 20.0, 30.0] {
|
||||||
inputs[0].push(Scalar::F64(v)).unwrap();
|
inputs[0].push(Scalar::f64(v)).unwrap();
|
||||||
}
|
}
|
||||||
let ctx = Ctx::new(&inputs, Timestamp(0));
|
let ctx = Ctx::new(&inputs, Timestamp(0));
|
||||||
let w = ctx.f64_in(0);
|
let w = ctx.f64_in(0);
|
||||||
@@ -91,8 +91,8 @@ mod tests {
|
|||||||
AnyColumn::with_capacity(ScalarKind::F64, 2),
|
AnyColumn::with_capacity(ScalarKind::F64, 2),
|
||||||
AnyColumn::with_capacity(ScalarKind::I64, 2),
|
AnyColumn::with_capacity(ScalarKind::I64, 2),
|
||||||
];
|
];
|
||||||
inputs[0].push(Scalar::F64(1.5)).unwrap();
|
inputs[0].push(Scalar::f64(1.5)).unwrap();
|
||||||
inputs[1].push(Scalar::I64(42)).unwrap();
|
inputs[1].push(Scalar::i64(42)).unwrap();
|
||||||
let ctx = Ctx::new(&inputs, Timestamp(0));
|
let ctx = Ctx::new(&inputs, Timestamp(0));
|
||||||
assert_eq!(ctx.f64_in(0)[0], 1.5);
|
assert_eq!(ctx.f64_in(0)[0], 1.5);
|
||||||
assert_eq!(ctx.i64_in(1)[0], 42);
|
assert_eq!(ctx.i64_in(1)[0], 42);
|
||||||
@@ -102,7 +102,7 @@ mod tests {
|
|||||||
#[should_panic(expected = "engine bug")]
|
#[should_panic(expected = "engine bug")]
|
||||||
fn ctx_panics_on_kind_mismatch() {
|
fn ctx_panics_on_kind_mismatch() {
|
||||||
let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::I64, 2)];
|
let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::I64, 2)];
|
||||||
inputs[0].push(Scalar::I64(7)).unwrap();
|
inputs[0].push(Scalar::i64(7)).unwrap();
|
||||||
let ctx = Ctx::new(&inputs, Timestamp(0));
|
let ctx = Ctx::new(&inputs, Timestamp(0));
|
||||||
let _ = ctx.f64_in(0); // wrong kind → panic
|
let _ = ctx.f64_in(0); // wrong kind → panic
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
//! (C18/C22).
|
//! (C18/C22).
|
||||||
|
|
||||||
mod any;
|
mod any;
|
||||||
|
mod cell;
|
||||||
mod column;
|
mod column;
|
||||||
mod ctx;
|
mod ctx;
|
||||||
mod error;
|
mod error;
|
||||||
@@ -36,6 +37,7 @@ mod node;
|
|||||||
mod scalar;
|
mod scalar;
|
||||||
|
|
||||||
pub use any::AnyColumn;
|
pub use any::AnyColumn;
|
||||||
|
pub use cell::Cell;
|
||||||
pub use column::{Column, Window};
|
pub use column::{Column, Window};
|
||||||
pub use ctx::Ctx;
|
pub use ctx::Ctx;
|
||||||
pub use error::KindMismatch;
|
pub use error::KindMismatch;
|
||||||
|
|||||||
@@ -435,45 +435,45 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn bind_removes_slot_and_reconstructs_positionally() {
|
fn bind_removes_slot_and_reconstructs_positionally() {
|
||||||
// chained bind of b then a (reverse slot order); c stays open
|
// chained bind of b then a (reverse slot order); c stays open
|
||||||
let bound = probe3().bind("b", Scalar::I64(8)).bind("a", Scalar::I64(7));
|
let bound = probe3().bind("b", Scalar::i64(8)).bind("a", Scalar::i64(7));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bound.params().iter().map(|p| p.name.as_str()).collect::<Vec<_>>(),
|
bound.params().iter().map(|p| p.name.as_str()).collect::<Vec<_>>(),
|
||||||
["c"], // only c remains open
|
["c"], // only c remains open
|
||||||
);
|
);
|
||||||
let built = bound.build(&[Scalar::F64(9.0)]); // inject c
|
let built = bound.build(&[Scalar::f64(9.0)]); // inject c
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
built.label(),
|
built.label(),
|
||||||
format!("{:?}", vec![Scalar::I64(7), Scalar::I64(8), Scalar::F64(9.0)]),
|
format!("{:?}", vec![Scalar::i64(7), Scalar::i64(8), Scalar::f64(9.0)]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// partial: bind only b; a and c stay open and keep their positions
|
// partial: bind only b; a and c stay open and keep their positions
|
||||||
let built2 = probe3()
|
let built2 = probe3()
|
||||||
.bind("b", Scalar::I64(8))
|
.bind("b", Scalar::i64(8))
|
||||||
.build(&[Scalar::I64(7), Scalar::F64(9.0)]); // a, c injected in order
|
.build(&[Scalar::i64(7), Scalar::f64(9.0)]); // a, c injected in order
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
built2.label(),
|
built2.label(),
|
||||||
format!("{:?}", vec![Scalar::I64(7), Scalar::I64(8), Scalar::F64(9.0)]),
|
format!("{:?}", vec![Scalar::i64(7), Scalar::i64(8), Scalar::f64(9.0)]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bind_records_bound_param_at_original_position() {
|
fn bind_records_bound_param_at_original_position() {
|
||||||
// middle-of-three: binding `b` records its ORIGINAL slot position (1).
|
// middle-of-three: binding `b` records its ORIGINAL slot position (1).
|
||||||
let mid = probe3().bind("b", Scalar::I64(8));
|
let mid = probe3().bind("b", Scalar::i64(8));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mid.bound_params(),
|
mid.bound_params(),
|
||||||
[BoundParam { pos: 1, name: "b".into(), kind: ScalarKind::I64, value: Scalar::I64(8) }]
|
[BoundParam { pos: 1, name: "b".into(), kind: ScalarKind::I64, value: Scalar::i64(8) }]
|
||||||
.as_slice(),
|
.as_slice(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// chained reverse-order bind (b then a): each entry carries its ORIGINAL
|
// chained reverse-order bind (b then a): each entry carries its ORIGINAL
|
||||||
// position regardless of the shrinking array — positions {1, 0} in call order.
|
// position regardless of the shrinking array — positions {1, 0} in call order.
|
||||||
let pair = probe3().bind("b", Scalar::I64(8)).bind("a", Scalar::I64(7));
|
let pair = probe3().bind("b", Scalar::i64(8)).bind("a", Scalar::i64(7));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
pair.bound_params(),
|
pair.bound_params(),
|
||||||
[
|
[
|
||||||
BoundParam { pos: 1, name: "b".into(), kind: ScalarKind::I64, value: Scalar::I64(8) },
|
BoundParam { pos: 1, name: "b".into(), kind: ScalarKind::I64, value: Scalar::i64(8) },
|
||||||
BoundParam { pos: 0, name: "a".into(), kind: ScalarKind::I64, value: Scalar::I64(7) },
|
BoundParam { pos: 0, name: "a".into(), kind: ScalarKind::I64, value: Scalar::i64(7) },
|
||||||
]
|
]
|
||||||
.as_slice(),
|
.as_slice(),
|
||||||
);
|
);
|
||||||
@@ -494,7 +494,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
|_| Box::new(Bare),
|
|_| Box::new(Bare),
|
||||||
);
|
);
|
||||||
let _ = b.bind("width", Scalar::I64(2)); // "width" is not a declared param
|
let _ = b.bind("width", Scalar::i64(2)); // "width" is not a declared param
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -512,7 +512,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
|_| Box::new(Bare),
|
|_| Box::new(Bare),
|
||||||
);
|
);
|
||||||
let _ = b.bind("dup", Scalar::I64(1)); // two slots named "dup"
|
let _ = b.bind("dup", Scalar::i64(1)); // two slots named "dup"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -527,7 +527,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
|_| Box::new(Bare),
|
|_| Box::new(Bare),
|
||||||
);
|
);
|
||||||
let _ = b.bind("length", Scalar::F64(2.0)); // F64 value for an I64 slot
|
let _ = b.bind("length", Scalar::f64(2.0)); // F64 value for an I64 slot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,12 +542,12 @@ mod zip_params_tests {
|
|||||||
ParamSpec { name: "fast".into(), kind: ScalarKind::I64 },
|
ParamSpec { name: "fast".into(), kind: ScalarKind::I64 },
|
||||||
ParamSpec { name: "scale".into(), kind: ScalarKind::F64 },
|
ParamSpec { name: "scale".into(), kind: ScalarKind::F64 },
|
||||||
];
|
];
|
||||||
let point = vec![Scalar::I64(2), Scalar::F64(0.5)];
|
let point = vec![Scalar::i64(2), Scalar::f64(0.5)];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
zip_params(&space, &point),
|
zip_params(&space, &point),
|
||||||
vec![
|
vec![
|
||||||
("fast".to_string(), Scalar::I64(2)),
|
("fast".to_string(), Scalar::i64(2)),
|
||||||
("scale".to_string(), Scalar::F64(0.5)),
|
("scale".to_string(), Scalar::f64(0.5)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -558,7 +558,7 @@ mod zip_params_tests {
|
|||||||
ParamSpec { name: "a".into(), kind: ScalarKind::I64 },
|
ParamSpec { name: "a".into(), kind: ScalarKind::I64 },
|
||||||
ParamSpec { name: "b".into(), kind: ScalarKind::I64 },
|
ParamSpec { name: "b".into(), kind: ScalarKind::I64 },
|
||||||
];
|
];
|
||||||
let point = vec![Scalar::I64(7), Scalar::I64(9)];
|
let point = vec![Scalar::i64(7), Scalar::i64(9)];
|
||||||
let hand: Vec<(String, Scalar)> =
|
let hand: Vec<(String, Scalar)> =
|
||||||
space.iter().zip(&point).map(|(ps, v)| (ps.name.clone(), *v)).collect();
|
space.iter().zip(&point).map(|(ps, v)| (ps.name.clone(), *v)).collect();
|
||||||
assert_eq!(zip_params(&space, &point), hand);
|
assert_eq!(zip_params(&space, &point), hand);
|
||||||
|
|||||||
+127
-44
@@ -1,6 +1,10 @@
|
|||||||
//! The closed four-type scalar set streamed on aura's hot path (C7):
|
//! The closed four-type scalar set streamed on aura's hot path (C7): `i64`,
|
||||||
//! `i64`, `f64`, `bool`, and `Timestamp` (epoch-ns UTC). The carrier `Scalar`
|
//! `f64`, `bool`, and `Timestamp` (epoch-ns UTC). The raw value is a type-erased
|
||||||
//! is a `Copy` POD enum — no type-erased payloads, no heap, no reference counting.
|
//! [`Cell`] — one 64-bit word, no tag; a [`Scalar`] pairs a `Cell` with its
|
||||||
|
//! [`ScalarKind`] for the dynamic boundaries. Both are `Copy` PODs — no heap, no
|
||||||
|
//! reference counting.
|
||||||
|
|
||||||
|
use crate::cell::Cell;
|
||||||
|
|
||||||
/// Canonical engine time: epoch-nanoseconds UTC. Newtype over `i64` (C7).
|
/// Canonical engine time: epoch-nanoseconds UTC. Newtype over `i64` (C7).
|
||||||
/// `Default` (epoch 0) lets `Column<Timestamp>` pre-size its ring.
|
/// `Default` (epoch 0) lets `Column<Timestamp>` pre-size its ring.
|
||||||
@@ -16,53 +20,110 @@ pub enum ScalarKind {
|
|||||||
Timestamp,
|
Timestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The four scalar base types, type-erased into one `Copy` carrier (C7).
|
/// One scalar value: a type-erased [`Cell`] plus its [`ScalarKind`] tag — the
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
/// self-describing form for the dynamic boundaries (builder binding,
|
||||||
pub enum Scalar {
|
/// serialization, rendering). The hot path uses the bare [`Cell`]; a `Scalar`
|
||||||
I64(i64),
|
/// is the fatter form that remembers its own type.
|
||||||
F64(f64),
|
///
|
||||||
Bool(bool),
|
/// Reading is the caller's responsibility. Each `as_*` accessor `debug_assert`s
|
||||||
Ts(Timestamp),
|
/// that the stored kind matches, then hands back the **native** value — in
|
||||||
|
/// release the assert is gone, so an accessor *is* the bare cell read
|
||||||
|
/// (branch-free). Calling the wrong `as_*` for the stored kind is a caller bug
|
||||||
|
/// (debug-asserted), not a recoverable error.
|
||||||
|
///
|
||||||
|
/// `PartialEq` is **value** equality (hand-written, not derived — the derived
|
||||||
|
/// one would inherit the `Cell`'s bitwise compare). Two scalars are equal iff
|
||||||
|
/// their kinds match and their native payloads compare equal, so `f64` keeps
|
||||||
|
/// IEEE-754 semantics (`NaN != NaN`, `+0.0 == -0.0`) exactly as the former enum
|
||||||
|
/// did, and a kind mismatch is never equal even when the words coincide
|
||||||
|
/// (`i64(0) != f64(0.0)`).
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Scalar {
|
||||||
|
kind: ScalarKind,
|
||||||
|
cell: Cell,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scalar {
|
impl Scalar {
|
||||||
/// The kind tag of this scalar.
|
/// Construct an `i64` scalar.
|
||||||
|
pub fn i64(v: i64) -> Self {
|
||||||
|
Scalar { kind: ScalarKind::I64, cell: Cell::from_i64(v) }
|
||||||
|
}
|
||||||
|
/// Construct an `f64` scalar.
|
||||||
|
pub fn f64(v: f64) -> Self {
|
||||||
|
Scalar { kind: ScalarKind::F64, cell: Cell::from_f64(v) }
|
||||||
|
}
|
||||||
|
/// Construct a `bool` scalar.
|
||||||
|
pub fn bool(v: bool) -> Self {
|
||||||
|
Scalar { kind: ScalarKind::Bool, cell: Cell::from_bool(v) }
|
||||||
|
}
|
||||||
|
/// Construct a [`Timestamp`] scalar.
|
||||||
|
pub fn ts(v: Timestamp) -> Self {
|
||||||
|
Scalar { kind: ScalarKind::Timestamp, cell: Cell::from_ts(v) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind tag of this scalar (a field read, no branch).
|
||||||
pub fn kind(self) -> ScalarKind {
|
pub fn kind(self) -> ScalarKind {
|
||||||
match self {
|
self.kind
|
||||||
Scalar::I64(_) => ScalarKind::I64,
|
}
|
||||||
Scalar::F64(_) => ScalarKind::F64,
|
|
||||||
Scalar::Bool(_) => ScalarKind::Bool,
|
/// Read as `i64`. The caller asserts the kind by choosing this accessor; a
|
||||||
Scalar::Ts(_) => ScalarKind::Timestamp,
|
/// mismatch is a caller bug (debug-asserted, free in release).
|
||||||
|
pub fn as_i64(self) -> i64 {
|
||||||
|
debug_assert_eq!(self.kind, ScalarKind::I64);
|
||||||
|
self.cell.i64()
|
||||||
|
}
|
||||||
|
/// Read as `f64`. Caller asserts the kind (debug-asserted, free in release).
|
||||||
|
pub fn as_f64(self) -> f64 {
|
||||||
|
debug_assert_eq!(self.kind, ScalarKind::F64);
|
||||||
|
self.cell.f64()
|
||||||
|
}
|
||||||
|
/// Read as `bool`. Caller asserts the kind (debug-asserted, free in release).
|
||||||
|
pub fn as_bool(self) -> bool {
|
||||||
|
debug_assert_eq!(self.kind, ScalarKind::Bool);
|
||||||
|
self.cell.bool()
|
||||||
|
}
|
||||||
|
/// Read as [`Timestamp`]. Caller asserts the kind (debug-asserted, free in release).
|
||||||
|
pub fn as_ts(self) -> Timestamp {
|
||||||
|
debug_assert_eq!(self.kind, ScalarKind::Timestamp);
|
||||||
|
self.cell.ts()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value equality, not the `Cell`'s bitwise one: kinds must match, then the
|
||||||
|
/// native payloads compare — so `f64` keeps IEEE-754 semantics (`NaN != NaN`,
|
||||||
|
/// `+0.0 == -0.0`), matching the pre-`Cell` enum's behaviour.
|
||||||
|
impl PartialEq for Scalar {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
if self.kind != other.kind {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match self.kind {
|
||||||
|
ScalarKind::I64 => self.as_i64() == other.as_i64(),
|
||||||
|
ScalarKind::F64 => self.as_f64() == other.as_f64(),
|
||||||
|
ScalarKind::Bool => self.as_bool() == other.as_bool(),
|
||||||
|
ScalarKind::Timestamp => self.as_ts() == other.as_ts(),
|
||||||
}
|
}
|
||||||
}
|
|
||||||
/// The `i64` payload, or `None` if this scalar is not an `I64`.
|
|
||||||
pub fn as_i64(self) -> Option<i64> {
|
|
||||||
if let Scalar::I64(v) = self { Some(v) } else { None }
|
|
||||||
}
|
|
||||||
/// The `f64` payload, or `None` if this scalar is not an `F64`.
|
|
||||||
pub fn as_f64(self) -> Option<f64> {
|
|
||||||
if let Scalar::F64(v) = self { Some(v) } else { None }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<i64> for Scalar {
|
impl From<i64> for Scalar {
|
||||||
fn from(v: i64) -> Self {
|
fn from(v: i64) -> Self {
|
||||||
Scalar::I64(v)
|
Scalar::i64(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<f64> for Scalar {
|
impl From<f64> for Scalar {
|
||||||
fn from(v: f64) -> Self {
|
fn from(v: f64) -> Self {
|
||||||
Scalar::F64(v)
|
Scalar::f64(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<bool> for Scalar {
|
impl From<bool> for Scalar {
|
||||||
fn from(v: bool) -> Self {
|
fn from(v: bool) -> Self {
|
||||||
Scalar::Bool(v)
|
Scalar::bool(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<Timestamp> for Scalar {
|
impl From<Timestamp> for Scalar {
|
||||||
fn from(v: Timestamp) -> Self {
|
fn from(v: Timestamp) -> Self {
|
||||||
Scalar::Ts(v)
|
Scalar::ts(v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,18 +133,18 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn kind_matches_variant() {
|
fn kind_matches_variant() {
|
||||||
assert_eq!(Scalar::I64(1).kind(), ScalarKind::I64);
|
assert_eq!(Scalar::i64(1).kind(), ScalarKind::I64);
|
||||||
assert_eq!(Scalar::F64(1.0).kind(), ScalarKind::F64);
|
assert_eq!(Scalar::f64(1.0).kind(), ScalarKind::F64);
|
||||||
assert_eq!(Scalar::Bool(true).kind(), ScalarKind::Bool);
|
assert_eq!(Scalar::bool(true).kind(), ScalarKind::Bool);
|
||||||
assert_eq!(Scalar::Ts(Timestamp(1)).kind(), ScalarKind::Timestamp);
|
assert_eq!(Scalar::ts(Timestamp(1)).kind(), ScalarKind::Timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_widenings() {
|
fn from_widenings() {
|
||||||
assert_eq!(Scalar::from(7i64), Scalar::I64(7));
|
assert_eq!(Scalar::from(7i64), Scalar::i64(7));
|
||||||
assert_eq!(Scalar::from(2.5f64), Scalar::F64(2.5));
|
assert_eq!(Scalar::from(2.5f64), Scalar::f64(2.5));
|
||||||
assert_eq!(Scalar::from(true), Scalar::Bool(true));
|
assert_eq!(Scalar::from(true), Scalar::bool(true));
|
||||||
assert_eq!(Scalar::from(Timestamp(9)), Scalar::Ts(Timestamp(9)));
|
assert_eq!(Scalar::from(Timestamp(9)), Scalar::ts(Timestamp(9)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -97,8 +158,8 @@ mod tests {
|
|||||||
fn lower(v: impl Into<Scalar>) -> Scalar {
|
fn lower(v: impl Into<Scalar>) -> Scalar {
|
||||||
v.into()
|
v.into()
|
||||||
}
|
}
|
||||||
assert_eq!(lower(2), Scalar::I64(2));
|
assert_eq!(lower(2), Scalar::i64(2));
|
||||||
assert_eq!(lower(0.5), Scalar::F64(0.5));
|
assert_eq!(lower(0.5), Scalar::f64(0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -108,16 +169,38 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scalar_value_accessors_are_kind_exact() {
|
fn scalar_value_accessors_return_native_payload() {
|
||||||
assert_eq!(Scalar::I64(3).as_i64(), Some(3));
|
assert_eq!(Scalar::i64(3).as_i64(), 3);
|
||||||
assert_eq!(Scalar::I64(3).as_f64(), None);
|
assert_eq!(Scalar::f64(0.5).as_f64(), 0.5);
|
||||||
assert_eq!(Scalar::F64(0.5).as_f64(), Some(0.5));
|
assert!(Scalar::bool(true).as_bool());
|
||||||
assert_eq!(Scalar::F64(0.5).as_i64(), None);
|
assert_eq!(Scalar::ts(Timestamp(7)).as_ts(), Timestamp(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scalar_eq_is_value_not_bitwise() {
|
||||||
|
// f64 follows IEEE-754 exactly as the former enum did: a NaN is never
|
||||||
|
// equal to itself, and +0.0 equals -0.0 — value equality, NOT the
|
||||||
|
// bitwise equality the underlying `Cell` carries (those two cases are
|
||||||
|
// precisely where bit- and value-equality diverge).
|
||||||
|
assert_ne!(Scalar::f64(f64::NAN), Scalar::f64(f64::NAN));
|
||||||
|
assert_eq!(Scalar::f64(0.0), Scalar::f64(-0.0));
|
||||||
|
assert_eq!(Scalar::f64(2.5), Scalar::f64(2.5));
|
||||||
|
// i64 / bool / timestamp compare by native value.
|
||||||
|
assert_eq!(Scalar::i64(7), Scalar::i64(7));
|
||||||
|
assert_ne!(Scalar::i64(7), Scalar::i64(8));
|
||||||
|
assert_eq!(Scalar::bool(true), Scalar::bool(true));
|
||||||
|
assert_ne!(Scalar::bool(true), Scalar::bool(false));
|
||||||
|
assert_eq!(Scalar::ts(Timestamp(9)), Scalar::ts(Timestamp(9)));
|
||||||
|
// A kind mismatch is never equal — even when the words coincide:
|
||||||
|
// i64(0) and f64(0.0) share the bit pattern 0; bool(true) and i64(1)
|
||||||
|
// share 1. Value equality still separates them by kind.
|
||||||
|
assert_ne!(Scalar::i64(0), Scalar::f64(0.0));
|
||||||
|
assert_ne!(Scalar::i64(1), Scalar::bool(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn scalar_is_copy() {
|
fn scalar_is_copy() {
|
||||||
let s = Scalar::F64(1.0);
|
let s = Scalar::f64(1.0);
|
||||||
let a = s;
|
let a = s;
|
||||||
let b = s; // Copy: `s` still usable after move-by-value
|
let b = s; // Copy: `s` still usable after move-by-value
|
||||||
assert_eq!(a, b);
|
assert_eq!(a, b);
|
||||||
|
|||||||
@@ -827,16 +827,16 @@ mod tests {
|
|||||||
ParamSpec { name: "scale".into(), kind: ScalarKind::F64 },
|
ParamSpec { name: "scale".into(), kind: ScalarKind::F64 },
|
||||||
];
|
];
|
||||||
let axes = vec![
|
let axes = vec![
|
||||||
("scale".to_string(), vec![Scalar::F64(0.5)]),
|
("scale".to_string(), vec![Scalar::f64(0.5)]),
|
||||||
("sma_cross.fast".to_string(), vec![Scalar::I64(2), Scalar::I64(3)]),
|
("sma_cross.fast".to_string(), vec![Scalar::i64(2), Scalar::i64(3)]),
|
||||||
("sma_cross.slow".to_string(), vec![Scalar::I64(4), Scalar::I64(5)]),
|
("sma_cross.slow".to_string(), vec![Scalar::i64(4), Scalar::i64(5)]),
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve_axes(&space, &axes),
|
resolve_axes(&space, &axes),
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
vec![Scalar::I64(2), Scalar::I64(3)],
|
vec![Scalar::i64(2), Scalar::i64(3)],
|
||||||
vec![Scalar::I64(4), Scalar::I64(5)],
|
vec![Scalar::i64(4), Scalar::i64(5)],
|
||||||
vec![Scalar::F64(0.5)],
|
vec![Scalar::f64(0.5)],
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -857,7 +857,7 @@ mod tests {
|
|||||||
ParamSpec { name: "b".into(), kind: ScalarKind::I64 },
|
ParamSpec { name: "b".into(), kind: ScalarKind::I64 },
|
||||||
];
|
];
|
||||||
assert_eq!(
|
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())),
|
Err(BindError::MissingKnob("b".to_string())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -867,7 +867,7 @@ mod tests {
|
|||||||
// a MIXED-kind axis: the second element mismatches; per-element check must
|
// 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).
|
// 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 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!(
|
assert_eq!(
|
||||||
resolve_axes(&space, &axes),
|
resolve_axes(&space, &axes),
|
||||||
Err(BindError::KindMismatch {
|
Err(BindError::KindMismatch {
|
||||||
@@ -907,16 +907,16 @@ mod tests {
|
|||||||
let named = resolve_axes(
|
let named = resolve_axes(
|
||||||
&space,
|
&space,
|
||||||
&[
|
&[
|
||||||
("sma_cross.fast.length".to_string(), vec![Scalar::I64(2), Scalar::I64(3)]),
|
("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)]),
|
("sma_cross.slow.length".to_string(), vec![Scalar::i64(4), Scalar::i64(5)]),
|
||||||
("exposure.scale".to_string(), vec![Scalar::F64(0.5)]),
|
("exposure.scale".to_string(), vec![Scalar::f64(0.5)]),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.expect("named axes resolve");
|
.expect("named axes resolve");
|
||||||
let positional = vec![
|
let positional = vec![
|
||||||
vec![Scalar::I64(2), Scalar::I64(3)],
|
vec![Scalar::i64(2), Scalar::i64(3)],
|
||||||
vec![Scalar::I64(4), Scalar::I64(5)],
|
vec![Scalar::i64(4), Scalar::i64(5)],
|
||||||
vec![Scalar::F64(0.5)],
|
vec![Scalar::f64(0.5)],
|
||||||
];
|
];
|
||||||
let named_pts = GridSpace::new(&space, named).expect("named grid").points();
|
let named_pts = GridSpace::new(&space, named).expect("named grid").points();
|
||||||
let pos_pts = GridSpace::new(&space, positional).expect("positional 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 },
|
ParamSpec { name: "scale".into(), kind: ScalarKind::F64 },
|
||||||
];
|
];
|
||||||
let bound = vec![
|
let bound = vec![
|
||||||
("scale".to_string(), Scalar::F64(0.5)),
|
("scale".to_string(), Scalar::f64(0.5)),
|
||||||
("sma_cross.fast".to_string(), Scalar::I64(2)),
|
("sma_cross.fast".to_string(), Scalar::i64(2)),
|
||||||
("sma_cross.slow".to_string(), Scalar::I64(4)),
|
("sma_cross.slow".to_string(), Scalar::i64(4)),
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve(&space, &bound),
|
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() {
|
fn resolve_unknown_knob() {
|
||||||
let space = vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }];
|
let space = vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }];
|
||||||
assert_eq!(
|
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())),
|
Err(BindError::UnknownKnob("nope".to_string())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -958,7 +958,7 @@ mod tests {
|
|||||||
ParamSpec { name: "b".into(), kind: ScalarKind::I64 },
|
ParamSpec { name: "b".into(), kind: ScalarKind::I64 },
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve(&space, &[("a".to_string(), Scalar::I64(1))]),
|
resolve(&space, &[("a".to_string(), Scalar::i64(1))]),
|
||||||
Err(BindError::MissingKnob("b".to_string())),
|
Err(BindError::MissingKnob("b".to_string())),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -967,7 +967,7 @@ mod tests {
|
|||||||
fn resolve_kind_mismatch() {
|
fn resolve_kind_mismatch() {
|
||||||
let space = vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }];
|
let space = vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve(&space, &[("scale".to_string(), Scalar::I64(2))]),
|
resolve(&space, &[("scale".to_string(), Scalar::i64(2))]),
|
||||||
Err(BindError::KindMismatch {
|
Err(BindError::KindMismatch {
|
||||||
knob: "scale".to_string(),
|
knob: "scale".to_string(),
|
||||||
expected: ScalarKind::F64,
|
expected: ScalarKind::F64,
|
||||||
@@ -982,7 +982,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
resolve(
|
resolve(
|
||||||
&space,
|
&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())),
|
Err(BindError::DuplicateBinding("a".to_string())),
|
||||||
);
|
);
|
||||||
@@ -993,8 +993,8 @@ mod tests {
|
|||||||
// Phase-1 (unknown) wins over Phase-2 (kind mismatch)
|
// Phase-1 (unknown) wins over Phase-2 (kind mismatch)
|
||||||
let space = vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }];
|
let space = vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }];
|
||||||
let bound = vec![
|
let bound = vec![
|
||||||
("typo".to_string(), Scalar::I64(9)),
|
("typo".to_string(), Scalar::i64(9)),
|
||||||
("scale".to_string(), Scalar::I64(2)),
|
("scale".to_string(), Scalar::i64(2)),
|
||||||
];
|
];
|
||||||
assert_eq!(resolve(&space, &bound), Err(BindError::UnknownKnob("typo".to_string())));
|
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)
|
// intra-binding: check a (unknown) precedes check d (duplicate)
|
||||||
let space = vec![ParamSpec { name: "a".into(), kind: ScalarKind::I64 }];
|
let space = vec![ParamSpec { name: "a".into(), kind: ScalarKind::I64 }];
|
||||||
let bound = vec![
|
let bound = vec![
|
||||||
("typo".to_string(), Scalar::I64(1)),
|
("typo".to_string(), Scalar::i64(1)),
|
||||||
("typo".to_string(), Scalar::I64(2)),
|
("typo".to_string(), Scalar::i64(2)),
|
||||||
];
|
];
|
||||||
assert_eq!(resolve(&space, &bound), Err(BindError::UnknownKnob("typo".to_string())));
|
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 (bp2, pos_eq, pos_ex) = composite_sma_cross_harness();
|
||||||
let mut positional = bp2
|
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");
|
.expect("positional bootstrap");
|
||||||
positional.run(vec![Box::new(VecSource::new(synthetic_prices()))]);
|
positional.run(vec![Box::new(VecSource::new(synthetic_prices()))]);
|
||||||
let pos_eq_v = pos_eq.try_iter().collect::<Vec<_>>();
|
let pos_eq_v = pos_eq.try_iter().collect::<Vec<_>>();
|
||||||
@@ -1066,7 +1066,7 @@ mod tests {
|
|||||||
if a.is_empty() || b.is_empty() {
|
if a.is_empty() || b.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
self.out[0] = Scalar::F64(a[0] + b[0]);
|
self.out[0] = Scalar::f64(a[0] + b[0]);
|
||||||
Some(&self.out)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1084,7 +1084,7 @@ mod tests {
|
|||||||
if w.is_empty() {
|
if w.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
self.out[0] = Scalar::F64(w[0]);
|
self.out[0] = Scalar::f64(w[0]);
|
||||||
Some(&self.out)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1116,7 +1116,7 @@ mod tests {
|
|||||||
PrimitiveBuilder::new(
|
PrimitiveBuilder::new(
|
||||||
"Pass1",
|
"Pass1",
|
||||||
NodeSchema { inputs: vec![f64_any()], output: out_v(), params: vec![] },
|
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()
|
.into()
|
||||||
}
|
}
|
||||||
@@ -1124,7 +1124,7 @@ mod tests {
|
|||||||
PrimitiveBuilder::new(
|
PrimitiveBuilder::new(
|
||||||
"Join2",
|
"Join2",
|
||||||
NodeSchema { inputs: vec![f64_any(), f64_any()], output: out_v(), params: vec![] },
|
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()
|
.into()
|
||||||
}
|
}
|
||||||
@@ -1236,7 +1236,7 @@ mod tests {
|
|||||||
// distinct node names -> fan-in distinguishable -> compiles (the two SMA
|
// distinct node names -> fan-in distinguishable -> compiles (the two SMA
|
||||||
// length params are supplied so the only thing under test is the fan-in)
|
// length params are supplied so the only thing under test is the fan-in)
|
||||||
let bp = sma_cross_under_root(true);
|
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]
|
#[test]
|
||||||
@@ -1745,7 +1745,7 @@ mod tests {
|
|||||||
// (b) the same graph authored as a composite blueprint, compiled
|
// (b) the same graph authored as a composite blueprint, compiled
|
||||||
let (bp, comp_eq, comp_ex) = composite_sma_cross_harness();
|
let (bp, comp_eq, comp_ex) = composite_sma_cross_harness();
|
||||||
let mut composed = bp
|
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");
|
.expect("composite blueprint compiles");
|
||||||
composed.run(vec![Box::new(VecSource::new(prices))]);
|
composed.run(vec![Box::new(VecSource::new(prices))]);
|
||||||
|
|
||||||
@@ -1808,7 +1808,7 @@ mod tests {
|
|||||||
vec![], // output
|
vec![], // output
|
||||||
);
|
);
|
||||||
let mut h = bp
|
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");
|
.expect("multi-output composite bootstraps");
|
||||||
h.run(vec![Box::new(VecSource::new(prices))]);
|
h.run(vec![Box::new(VecSource::new(prices))]);
|
||||||
|
|
||||||
@@ -1825,13 +1825,13 @@ mod tests {
|
|||||||
fn injecting_a_different_vector_changes_the_run() {
|
fn injecting_a_different_vector_changes_the_run() {
|
||||||
let prices = synthetic_prices();
|
let prices = synthetic_prices();
|
||||||
let (bp, eq, _ex) = composite_sma_cross_harness();
|
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");
|
.expect("compiles");
|
||||||
a.run(vec![Box::new(VecSource::new(prices.clone()))]);
|
a.run(vec![Box::new(VecSource::new(prices.clone()))]);
|
||||||
let a_eq = eq.try_iter().collect::<Vec<_>>();
|
let a_eq = eq.try_iter().collect::<Vec<_>>();
|
||||||
|
|
||||||
let (bp2, eq2, _ex2) = composite_sma_cross_harness();
|
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");
|
.expect("compiles");
|
||||||
b.run(vec![Box::new(VecSource::new(prices))]);
|
b.run(vec![Box::new(VecSource::new(prices))]);
|
||||||
let b_eq = eq2.try_iter().collect::<Vec<_>>();
|
let b_eq = eq2.try_iter().collect::<Vec<_>>();
|
||||||
@@ -1844,7 +1844,7 @@ mod tests {
|
|||||||
fn wrong_kind_is_a_param_kind_mismatch() {
|
fn wrong_kind_is_a_param_kind_mismatch() {
|
||||||
let (bp, _eq, _ex) = composite_sma_cross_harness();
|
let (bp, _eq, _ex) = composite_sma_cross_harness();
|
||||||
// slot 0 is I64 (an SMA length); inject F64 there
|
// 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();
|
.unwrap_err();
|
||||||
assert!(matches!(err, CompileError::ParamKindMismatch { slot: 0, .. }));
|
assert!(matches!(err, CompileError::ParamKindMismatch { slot: 0, .. }));
|
||||||
}
|
}
|
||||||
@@ -1853,13 +1853,13 @@ mod tests {
|
|||||||
fn wrong_arity_is_a_param_arity_error() {
|
fn wrong_arity_is_a_param_arity_error() {
|
||||||
let (short, _e1, _x1) = composite_sma_cross_harness();
|
let (short, _e1, _x1) = composite_sma_cross_harness();
|
||||||
assert!(matches!(
|
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 }
|
CompileError::ParamArity { expected: 3, got: 1 }
|
||||||
));
|
));
|
||||||
let (long, _e2, _x2) = composite_sma_cross_harness();
|
let (long, _e2, _x2) = composite_sma_cross_harness();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
long.bootstrap_with_params(
|
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(),
|
).unwrap_err(),
|
||||||
CompileError::ParamArity { expected: 3, got: 4 }
|
CompileError::ParamArity { expected: 3, got: 4 }
|
||||||
));
|
));
|
||||||
@@ -1869,11 +1869,11 @@ mod tests {
|
|||||||
fn same_vector_bootstraps_identically() {
|
fn same_vector_bootstraps_identically() {
|
||||||
let prices = synthetic_prices();
|
let prices = synthetic_prices();
|
||||||
let (bp, eq, _ex) = composite_sma_cross_harness();
|
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");
|
.expect("compiles");
|
||||||
a.run(vec![Box::new(VecSource::new(prices.clone()))]);
|
a.run(vec![Box::new(VecSource::new(prices.clone()))]);
|
||||||
let (bp2, eq2, _ex2) = composite_sma_cross_harness();
|
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");
|
.expect("compiles");
|
||||||
b.run(vec![Box::new(VecSource::new(prices))]);
|
b.run(vec![Box::new(VecSource::new(prices))]);
|
||||||
assert_eq!(eq.try_iter().collect::<Vec<_>>(), eq2.try_iter().collect::<Vec<_>>());
|
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
|
// the same blueprint, actually compiled to its flat node array; each flat
|
||||||
// node's own declared params, concatenated in flat-node order
|
// 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> =
|
let from_flat: Vec<ParamSpec> =
|
||||||
flat.signatures.iter().flat_map(|s| s.params.clone()).collect();
|
flat.signatures.iter().flat_map(|s| s.params.clone()).collect();
|
||||||
|
|
||||||
@@ -1981,7 +1981,7 @@ mod tests {
|
|||||||
let strat = Composite::new(
|
let strat = Composite::new(
|
||||||
"sma2_entry",
|
"sma2_entry",
|
||||||
vec![
|
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(),
|
Exposure::builder().named("exp").into(),
|
||||||
],
|
],
|
||||||
vec![], // edges — irrelevant to param_space()
|
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
|
// the same blueprint, compiled to its flat node array; each flat node's
|
||||||
// own declared params, concatenated in flat-node order
|
// 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> =
|
let from_flat: Vec<ParamSpec> =
|
||||||
flat.signatures.iter().flat_map(|s| s.params.clone()).collect();
|
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![Role { name: "price".into(), targets: vec![Target { node: 0, slot: 0 }], source: Some(ScalarKind::F64) }],
|
||||||
vec![],
|
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
|
// kind fault caught pre-build (no panic) — same variant bootstrap would give
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
err,
|
err,
|
||||||
@@ -2209,7 +2209,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
// the Ok arm holds a FlatGraph (not Debug), so assert via the Err arm.
|
// the Ok arm holds a FlatGraph (not Debug), so assert via the Err arm.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
root.compile_with_params(&[Scalar::I64(3)]).err(),
|
root.compile_with_params(&[Scalar::i64(3)]).err(),
|
||||||
Some(CompileError::UnboundRootRole { role: 0 })
|
Some(CompileError::UnboundRootRole { role: 0 })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2262,7 +2262,7 @@ mod tests {
|
|||||||
// two params declared (one per SMA); supply both so the only failure under
|
// two params declared (one per SMA); supply both so the only failure under
|
||||||
// test is the duplicate path (green today, DuplicateParamPath after).
|
// test is the duplicate path (green today, DuplicateParamPath after).
|
||||||
assert_eq!(
|
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()))
|
Some(CompileError::DuplicateParamPath("dup.sma.length".to_string()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2280,7 +2280,7 @@ mod tests {
|
|||||||
let paramless_sma = PrimitiveBuilder::new(
|
let paramless_sma = PrimitiveBuilder::new(
|
||||||
"Pass1",
|
"Pass1",
|
||||||
NodeSchema { inputs: vec![f64_any()], output: out_v(), params: vec![] },
|
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");
|
.named("sma");
|
||||||
let asym = Composite::new(
|
let asym = Composite::new(
|
||||||
@@ -2312,7 +2312,7 @@ mod tests {
|
|||||||
bp.param_space().into_iter().map(|p| p.name).collect::<Vec<_>>(),
|
bp.param_space().into_iter().map(|p| p.name).collect::<Vec<_>>(),
|
||||||
["asym.sma.length"]
|
["asym.sma.length"]
|
||||||
);
|
);
|
||||||
assert!(bp.compile_with_params(&[Scalar::I64(2)]).is_ok());
|
assert!(bp.compile_with_params(&[Scalar::i64(2)]).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ mod tests {
|
|||||||
// cancels in the comparison. Params size buffers, not topology (C11), so
|
// cancels in the comparison. Params size buffers, not topology (C11), so
|
||||||
// edges/sources are param-independent; a fixed vector just satisfies the
|
// edges/sources are param-independent; a fixed vector just satisfies the
|
||||||
// arity gate compile() (no-param) trips on this value-empty harness.
|
// 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
|
// hand-wired reference harness -> FlatGraph
|
||||||
let (hand_bp, _rx_eq, _rx_ex) = composite_sma_cross_harness();
|
let (hand_bp, _rx_eq, _rx_ex) = composite_sma_cross_harness();
|
||||||
@@ -290,7 +290,7 @@ mod tests {
|
|||||||
let compiled = g
|
let compiled = g
|
||||||
.build()
|
.build()
|
||||||
.expect("all port/field names resolve")
|
.expect("all port/field names resolve")
|
||||||
.compile_with_params(&[Scalar::F64(0.5)]);
|
.compile_with_params(&[Scalar::f64(0.5)]);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
compiled.err(),
|
compiled.err(),
|
||||||
Some(CompileError::UnconnectedPort { node: 1, slot: 0 })
|
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
|
/// 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`.
|
/// the render. The value side of `kind_str`.
|
||||||
fn scalar_str(s: aura_core::Scalar) -> String {
|
fn scalar_str(s: aura_core::Scalar) -> String {
|
||||||
use aura_core::Scalar;
|
use aura_core::ScalarKind;
|
||||||
match s {
|
match s.kind() {
|
||||||
Scalar::I64(n) => n.to_string(),
|
ScalarKind::I64 => s.as_i64().to_string(),
|
||||||
Scalar::F64(f) => format!("{f:?}"),
|
ScalarKind::F64 => format!("{:?}", s.as_f64()),
|
||||||
Scalar::Bool(b) => b.to_string(),
|
ScalarKind::Bool => s.as_bool().to_string(),
|
||||||
Scalar::Ts(t) => t.0.to_string(),
|
ScalarKind::Timestamp => s.as_ts().0.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,13 +426,13 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn prim_record_emits_bound_only_when_bound() {
|
fn prim_record_emits_bound_only_when_bound() {
|
||||||
// a bound i64 param → a "bound" field carrying [pos,"name","kind","value"]
|
// 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}");
|
assert!(bound.contains(r#""bound":[[0,"length","i64","5"]]"#), "{bound}");
|
||||||
// the bound slot also leaves the tunable "params" surface (bind's tuning side)
|
// the bound slot also leaves the tunable "params" surface (bind's tuning side)
|
||||||
assert!(bound.contains(r#""params":[]"#), "{bound}");
|
assert!(bound.contains(r#""params":[]"#), "{bound}");
|
||||||
|
|
||||||
// a bound f64 value renders canonically (shortest round-trip), e.g. 0.5
|
// 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}");
|
assert!(f.contains(r#""bound":[[0,"scale","f64","0.5"]]"#), "{f}");
|
||||||
|
|
||||||
// an unbound builder → no "bound" field at all
|
// an unbound builder → no "bound" field at all
|
||||||
|
|||||||
+129
-130
@@ -183,7 +183,7 @@ impl SyntheticSpec {
|
|||||||
.map(|i| {
|
.map(|i| {
|
||||||
let shock = (rng.next_f64() - 0.5) * 0.004; // ±0.2% per step
|
let shock = (rng.next_f64() - 0.5) * 0.004; // ±0.2% per step
|
||||||
price *= 1.0 + shock;
|
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();
|
.collect();
|
||||||
VecSource::new(stream)
|
VecSource::new(stream)
|
||||||
@@ -492,7 +492,7 @@ mod tests {
|
|||||||
|
|
||||||
/// Build an f64 source stream from (timestamp, value) points.
|
/// Build an f64 source stream from (timestamp, value) points.
|
||||||
fn f64_stream(points: &[(i64, f64)]) -> Vec<(Timestamp, Scalar)> {
|
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]
|
#[test]
|
||||||
@@ -527,19 +527,19 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vec_source_peek_is_non_consuming_and_idempotent() {
|
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.
|
// peek twice: same head, no advance.
|
||||||
assert_eq!(s.peek(), Some(Timestamp(1)));
|
assert_eq!(s.peek(), Some(Timestamp(1)));
|
||||||
assert_eq!(s.peek(), Some(Timestamp(1)));
|
assert_eq!(s.peek(), Some(Timestamp(1)));
|
||||||
// next pops it; peek now reflects the new head.
|
// 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)));
|
assert_eq!(s.peek(), Some(Timestamp(2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vec_source_exhaustion_yields_none() {
|
fn vec_source_exhaustion_yields_none() {
|
||||||
let mut s = VecSource::new(vec![(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!(Source::next(&mut s), Some((Timestamp(5), Scalar::i64(7))));
|
||||||
assert_eq!(s.peek(), None);
|
assert_eq!(s.peek(), None);
|
||||||
assert_eq!(Source::next(&mut s), None);
|
assert_eq!(Source::next(&mut s), None);
|
||||||
}
|
}
|
||||||
@@ -639,7 +639,7 @@ mod tests {
|
|||||||
if a.is_empty() || b.is_empty() {
|
if a.is_empty() || b.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
self.out[0] = Scalar::F64(a[0] + b[0]);
|
self.out[0] = Scalar::f64(a[0] + b[0]);
|
||||||
Some(&self.out)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -663,7 +663,7 @@ mod tests {
|
|||||||
vec![1, 1]
|
vec![1, 1]
|
||||||
}
|
}
|
||||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Scalar]> {
|
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)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -698,7 +698,7 @@ mod tests {
|
|||||||
if a.is_empty() || b.is_empty() || c.is_empty() {
|
if a.is_empty() || b.is_empty() || c.is_empty() {
|
||||||
return None;
|
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)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -735,7 +735,7 @@ mod tests {
|
|||||||
if w.is_empty() {
|
if w.is_empty() {
|
||||||
return None; // not yet warmed
|
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
|
Some(&self.out) // one 5-field record, all fields co-fresh
|
||||||
}
|
}
|
||||||
@@ -765,8 +765,8 @@ mod tests {
|
|||||||
vec![1]
|
vec![1]
|
||||||
}
|
}
|
||||||
fn eval(&mut self, _ctx: Ctx<'_>) -> Option<&[Scalar]> {
|
fn eval(&mut self, _ctx: Ctx<'_>) -> Option<&[Scalar]> {
|
||||||
self.out[0] = Scalar::F64(0.0);
|
self.out[0] = Scalar::f64(0.0);
|
||||||
self.out[1] = Scalar::I64(0);
|
self.out[1] = Scalar::i64(0);
|
||||||
Some(&self.out)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -797,8 +797,8 @@ mod tests {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let v = w[0];
|
let v = w[0];
|
||||||
let _ = self.tx.send((ctx.now(), vec![Scalar::F64(v)])); // sink side effect
|
let _ = self.tx.send((ctx.now(), vec![Scalar::f64(v)])); // sink side effect
|
||||||
self.out[0] = Scalar::F64(v);
|
self.out[0] = Scalar::f64(v);
|
||||||
Some(&self.out) // producer output: engine forwards it
|
Some(&self.out) // producer output: engine forwards it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -827,9 +827,9 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
got,
|
got,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(3), vec![Scalar::F64(2.0)]),
|
(Timestamp(3), vec![Scalar::f64(2.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(3.0)]),
|
(Timestamp(4), vec![Scalar::f64(3.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(4.0)]),
|
(Timestamp(5), vec![Scalar::f64(4.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -874,9 +874,9 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(4), vec![Scalar::F64(2.0)]),
|
(Timestamp(4), vec![Scalar::f64(2.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(2.0)]),
|
(Timestamp(5), vec![Scalar::f64(2.0)]),
|
||||||
(Timestamp(6), vec![Scalar::F64(2.0)]),
|
(Timestamp(6), vec![Scalar::f64(2.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -895,7 +895,7 @@ mod tests {
|
|||||||
let build = |tx| {
|
let build = |tx| {
|
||||||
boot(
|
boot(
|
||||||
vec![
|
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)),
|
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
@@ -921,10 +921,10 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(120.0)]),
|
(Timestamp(2), vec![Scalar::f64(120.0)]),
|
||||||
(Timestamp(3), vec![Scalar::F64(130.0)]),
|
(Timestamp(3), vec![Scalar::f64(130.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(140.0)]),
|
(Timestamp(4), vec![Scalar::f64(140.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(240.0)]),
|
(Timestamp(4), vec![Scalar::f64(240.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -941,7 +941,7 @@ mod tests {
|
|||||||
let build = |tx| {
|
let build = |tx| {
|
||||||
boot(
|
boot(
|
||||||
vec![
|
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)),
|
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
@@ -967,8 +967,8 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(120.0)]),
|
(Timestamp(2), vec![Scalar::f64(120.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(240.0)]),
|
(Timestamp(4), vec![Scalar::f64(240.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -990,7 +990,7 @@ mod tests {
|
|||||||
vec![
|
vec![
|
||||||
Box::new(Sma::new(2)),
|
Box::new(Sma::new(2)),
|
||||||
Box::new(Sma::new(4)),
|
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)),
|
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
@@ -1022,9 +1022,9 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(4), vec![Scalar::F64(28.0)]),
|
(Timestamp(4), vec![Scalar::f64(28.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(32.0)]),
|
(Timestamp(5), vec![Scalar::f64(32.0)]),
|
||||||
(Timestamp(6), vec![Scalar::F64(36.0)]),
|
(Timestamp(6), vec![Scalar::f64(36.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1041,7 +1041,7 @@ mod tests {
|
|||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let mut h = boot(
|
let mut h = boot(
|
||||||
vec![
|
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)),
|
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
@@ -1067,8 +1067,8 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(221.0)]),
|
(Timestamp(2), vec![Scalar::f64(221.0)]),
|
||||||
(Timestamp(3), vec![Scalar::F64(223.0)]),
|
(Timestamp(3), vec![Scalar::f64(223.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1153,7 +1153,7 @@ mod tests {
|
|||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let mut h = boot(
|
let mut h = boot(
|
||||||
vec![
|
vec![
|
||||||
Box::new(Ohlcv { out: [Scalar::F64(0.0); 5] }),
|
Box::new(Ohlcv { out: [Scalar::f64(0.0); 5] }),
|
||||||
Box::new(Recorder::new(
|
Box::new(Recorder::new(
|
||||||
&[ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64],
|
&[ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64],
|
||||||
Firing::Any,
|
Firing::Any,
|
||||||
@@ -1180,18 +1180,18 @@ mod tests {
|
|||||||
out,
|
out,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(1), vec![
|
(Timestamp(1), vec![
|
||||||
Scalar::F64(10.0),
|
Scalar::f64(10.0),
|
||||||
Scalar::F64(15.0),
|
Scalar::f64(15.0),
|
||||||
Scalar::F64(8.0),
|
Scalar::f64(8.0),
|
||||||
Scalar::F64(12.0),
|
Scalar::f64(12.0),
|
||||||
Scalar::F64(100.0),
|
Scalar::f64(100.0),
|
||||||
]),
|
]),
|
||||||
(Timestamp(2), vec![
|
(Timestamp(2), vec![
|
||||||
Scalar::F64(20.0),
|
Scalar::f64(20.0),
|
||||||
Scalar::F64(25.0),
|
Scalar::f64(25.0),
|
||||||
Scalar::F64(19.0),
|
Scalar::f64(19.0),
|
||||||
Scalar::F64(22.0),
|
Scalar::f64(22.0),
|
||||||
Scalar::F64(200.0),
|
Scalar::f64(200.0),
|
||||||
]),
|
]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -1206,7 +1206,7 @@ mod tests {
|
|||||||
let build = |tx| {
|
let build = |tx| {
|
||||||
boot(
|
boot(
|
||||||
vec![
|
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(Sub::new()),
|
||||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||||
],
|
],
|
||||||
@@ -1232,8 +1232,8 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(1), vec![Scalar::F64(7.0)]),
|
(Timestamp(1), vec![Scalar::f64(7.0)]),
|
||||||
(Timestamp(2), vec![Scalar::F64(6.0)]),
|
(Timestamp(2), vec![Scalar::f64(6.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1252,7 +1252,7 @@ mod tests {
|
|||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let mut h = boot(
|
let mut h = boot(
|
||||||
vec![
|
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(Sub::new()),
|
||||||
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||||
],
|
],
|
||||||
@@ -1275,8 +1275,8 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(1), vec![Scalar::F64(2.0)]),
|
(Timestamp(1), vec![Scalar::f64(2.0)]),
|
||||||
(Timestamp(2), 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
|
// Sma(1)'s f64 input slot is a per-field kind mismatch -> KindMismatch. (The
|
||||||
// mismatch is field-specific: from_field 0 would have matched.)
|
// mismatch is field-specific: from_field 0 would have matched.)
|
||||||
let err = boot(
|
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![
|
vec![
|
||||||
TwoField::sig(),
|
TwoField::sig(),
|
||||||
Sma::builder().schema().clone(),
|
Sma::builder().schema().clone(),
|
||||||
@@ -1355,17 +1355,17 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast,
|
fast,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(11.0)]),
|
(Timestamp(2), vec![Scalar::f64(11.0)]),
|
||||||
(Timestamp(3), vec![Scalar::F64(13.0)]),
|
(Timestamp(3), vec![Scalar::f64(13.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(15.0)]),
|
(Timestamp(4), vec![Scalar::f64(15.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(17.0)]),
|
(Timestamp(5), vec![Scalar::f64(17.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
slow,
|
slow,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(4), vec![Scalar::F64(13.0)]),
|
(Timestamp(4), vec![Scalar::f64(13.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(15.0)]),
|
(Timestamp(5), vec![Scalar::f64(15.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1377,7 +1377,7 @@ mod tests {
|
|||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let mut h = boot(
|
let mut h = boot(
|
||||||
vec![
|
vec![
|
||||||
Box::new(Ohlcv { out: [Scalar::F64(0.0); 5] }),
|
Box::new(Ohlcv { out: [Scalar::f64(0.0); 5] }),
|
||||||
Box::new(Recorder::new(
|
Box::new(Recorder::new(
|
||||||
&[ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64],
|
&[ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64, ScalarKind::F64],
|
||||||
Firing::Any,
|
Firing::Any,
|
||||||
@@ -1411,11 +1411,11 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![(Timestamp(1), vec![
|
vec![(Timestamp(1), vec![
|
||||||
Scalar::F64(10.0),
|
Scalar::f64(10.0),
|
||||||
Scalar::F64(15.0),
|
Scalar::f64(15.0),
|
||||||
Scalar::F64(8.0),
|
Scalar::f64(8.0),
|
||||||
Scalar::F64(12.0),
|
Scalar::f64(12.0),
|
||||||
Scalar::F64(100.0),
|
Scalar::f64(100.0),
|
||||||
])]
|
])]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1446,19 +1446,19 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.expect("valid");
|
.expect("valid");
|
||||||
h.run(vec![
|
h.run(vec![
|
||||||
Box::new(VecSource::new(vec![(Timestamp(1), Scalar::I64(7))])),
|
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(2), Scalar::f64(1.5))])),
|
||||||
Box::new(VecSource::new(vec![(Timestamp(3), Scalar::Bool(true))])),
|
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(4), Scalar::ts(Timestamp(99)))])),
|
||||||
]);
|
]);
|
||||||
let out: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
|
let out: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![(Timestamp(4), vec![
|
vec![(Timestamp(4), vec![
|
||||||
Scalar::I64(7),
|
Scalar::i64(7),
|
||||||
Scalar::F64(1.5),
|
Scalar::f64(1.5),
|
||||||
Scalar::Bool(true),
|
Scalar::bool(true),
|
||||||
Scalar::Ts(Timestamp(99)),
|
Scalar::ts(Timestamp(99)),
|
||||||
])]
|
])]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1472,7 +1472,7 @@ mod tests {
|
|||||||
let (tx_down, rx_down) = mpsc::channel();
|
let (tx_down, rx_down) = mpsc::channel();
|
||||||
let mut h = boot(
|
let mut h = boot(
|
||||||
vec![
|
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)),
|
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx_down)),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
@@ -1487,9 +1487,9 @@ mod tests {
|
|||||||
let tapped: Vec<(Timestamp, Vec<Scalar>)> = rx_tap.try_iter().collect();
|
let tapped: Vec<(Timestamp, Vec<Scalar>)> = rx_tap.try_iter().collect();
|
||||||
let downstream: Vec<(Timestamp, Vec<Scalar>)> = rx_down.try_iter().collect();
|
let downstream: Vec<(Timestamp, Vec<Scalar>)> = rx_down.try_iter().collect();
|
||||||
let expected = vec![
|
let expected = vec![
|
||||||
(Timestamp(1), vec![Scalar::F64(10.0)]),
|
(Timestamp(1), vec![Scalar::f64(10.0)]),
|
||||||
(Timestamp(2), vec![Scalar::F64(20.0)]),
|
(Timestamp(2), vec![Scalar::f64(20.0)]),
|
||||||
(Timestamp(3), vec![Scalar::F64(30.0)]),
|
(Timestamp(3), vec![Scalar::f64(30.0)]),
|
||||||
];
|
];
|
||||||
assert_eq!(tapped, expected); // it recorded (sink side effect)
|
assert_eq!(tapped, expected); // it recorded (sink side effect)
|
||||||
assert_eq!(downstream, expected); // and forwarded (producer output)
|
assert_eq!(downstream, expected); // and forwarded (producer output)
|
||||||
@@ -1559,8 +1559,8 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(20.0), Scalar::F64(100.0)]),
|
(Timestamp(2), vec![Scalar::f64(20.0), Scalar::f64(100.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(40.0), Scalar::F64(200.0)]),
|
(Timestamp(4), vec![Scalar::f64(40.0), Scalar::f64(200.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1595,10 +1595,10 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
out,
|
out,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(20.0), Scalar::F64(100.0)]),
|
(Timestamp(2), vec![Scalar::f64(20.0), Scalar::f64(100.0)]),
|
||||||
(Timestamp(3), vec![Scalar::F64(30.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(100.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(40.0), Scalar::F64(200.0)]),
|
(Timestamp(4), vec![Scalar::f64(40.0), Scalar::f64(200.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1611,7 +1611,7 @@ mod tests {
|
|||||||
let (tx, _rx) = mpsc::channel();
|
let (tx, _rx) = mpsc::channel();
|
||||||
let err = boot(
|
let err = boot(
|
||||||
vec![
|
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)),
|
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, tx)),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
@@ -1679,9 +1679,9 @@ mod tests {
|
|||||||
let s3: Vec<(Timestamp, Vec<Scalar>)> = rc.try_iter().collect();
|
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.
|
// SMA(3) warms at cycle 3: mean(2,4,6)=4, mean(4,6,8)=6, mean(6,8,10)=8.
|
||||||
let expected = vec![
|
let expected = vec![
|
||||||
(Timestamp(3), vec![Scalar::F64(4.0)]),
|
(Timestamp(3), vec![Scalar::f64(4.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(6.0)]),
|
(Timestamp(4), vec![Scalar::f64(6.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(8.0)]),
|
(Timestamp(5), vec![Scalar::f64(8.0)]),
|
||||||
];
|
];
|
||||||
assert_eq!(s1, expected);
|
assert_eq!(s1, expected);
|
||||||
assert_eq!(s2, expected); // every tap sees the identical shared stream
|
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();
|
let sub: Vec<(Timestamp, Vec<Scalar>)> = rs.try_iter().collect();
|
||||||
// SMA(2): 11,13,15,17,19 at cycles 2..6 (raw tap).
|
// SMA(2): 11,13,15,17,19 at cycles 2..6 (raw tap).
|
||||||
let raw_expected = vec![
|
let raw_expected = vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(11.0)]),
|
(Timestamp(2), vec![Scalar::f64(11.0)]),
|
||||||
(Timestamp(3), vec![Scalar::F64(13.0)]),
|
(Timestamp(3), vec![Scalar::f64(13.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(15.0)]),
|
(Timestamp(4), vec![Scalar::f64(15.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(17.0)]),
|
(Timestamp(5), vec![Scalar::f64(17.0)]),
|
||||||
(Timestamp(6), vec![Scalar::F64(19.0)]),
|
(Timestamp(6), vec![Scalar::f64(19.0)]),
|
||||||
];
|
];
|
||||||
// Sub warms once SMA(4) is warm (cycle 4): 15-13, 17-15, 19-17 -> 2.
|
// Sub warms once SMA(4) is warm (cycle 4): 15-13, 17-15, 19-17 -> 2.
|
||||||
let sub_expected = vec![
|
let sub_expected = vec![
|
||||||
(Timestamp(4), vec![Scalar::F64(2.0)]),
|
(Timestamp(4), vec![Scalar::f64(2.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(2.0)]),
|
(Timestamp(5), vec![Scalar::f64(2.0)]),
|
||||||
(Timestamp(6), vec![Scalar::F64(2.0)]),
|
(Timestamp(6), vec![Scalar::f64(2.0)]),
|
||||||
];
|
];
|
||||||
assert_eq!(raw, raw_expected);
|
assert_eq!(raw, raw_expected);
|
||||||
assert_eq!(sub, sub_expected);
|
assert_eq!(sub, sub_expected);
|
||||||
@@ -1778,7 +1778,7 @@ mod tests {
|
|||||||
vec![
|
vec![
|
||||||
Box::new(Sma::new(2)), // 0: shared producer (src A)
|
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(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
|
Box::new(Recorder::new(&[ScalarKind::F64], Firing::Any, t_bar)), // 3: tap of barrier
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
@@ -1810,15 +1810,15 @@ mod tests {
|
|||||||
let bar: Vec<(Timestamp, Vec<Scalar>)> = rbar.try_iter().collect();
|
let bar: Vec<(Timestamp, Vec<Scalar>)> = rbar.try_iter().collect();
|
||||||
// As-of tap records every SMA(2) fire: 11@t2, 13@t3, 15@t4.
|
// As-of tap records every SMA(2) fire: 11@t2, 13@t3, 15@t4.
|
||||||
let any_expected = vec![
|
let any_expected = vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(11.0)]),
|
(Timestamp(2), vec![Scalar::f64(11.0)]),
|
||||||
(Timestamp(3), vec![Scalar::F64(13.0)]),
|
(Timestamp(3), vec![Scalar::f64(13.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(15.0)]),
|
(Timestamp(4), vec![Scalar::f64(15.0)]),
|
||||||
];
|
];
|
||||||
// Barrier fires only where held SMA output and src B share a timestamp:
|
// 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).
|
// t=2 (SMA held=11 + B=100 = 111), t=4 (SMA held=15 + B=200 = 215).
|
||||||
let bar_expected = vec![
|
let bar_expected = vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(111.0)]),
|
(Timestamp(2), vec![Scalar::f64(111.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(215.0)]),
|
(Timestamp(4), vec![Scalar::f64(215.0)]),
|
||||||
];
|
];
|
||||||
assert_eq!(any, any_expected);
|
assert_eq!(any, any_expected);
|
||||||
assert_eq!(bar, bar_expected);
|
assert_eq!(bar, bar_expected);
|
||||||
@@ -1870,8 +1870,8 @@ mod tests {
|
|||||||
let out: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
|
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.
|
// stage1 (c2..c5): 3,5,7,9. stage2 (c3..): 4,6,8. stage3 (c4..): 5,7.
|
||||||
let expected = vec![
|
let expected = vec![
|
||||||
(Timestamp(4), vec![Scalar::F64(5.0)]),
|
(Timestamp(4), vec![Scalar::f64(5.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(7.0)]),
|
(Timestamp(5), vec![Scalar::f64(7.0)]),
|
||||||
];
|
];
|
||||||
assert_eq!(out, expected);
|
assert_eq!(out, expected);
|
||||||
|
|
||||||
@@ -1932,19 +1932,19 @@ mod tests {
|
|||||||
let w3: Vec<(Timestamp, Vec<Scalar>)> = rb.try_iter().collect();
|
let w3: Vec<(Timestamp, Vec<Scalar>)> = rb.try_iter().collect();
|
||||||
let w4: Vec<(Timestamp, Vec<Scalar>)> = rc.try_iter().collect();
|
let w4: Vec<(Timestamp, Vec<Scalar>)> = rc.try_iter().collect();
|
||||||
let e2 = vec![
|
let e2 = vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(11.0)]),
|
(Timestamp(2), vec![Scalar::f64(11.0)]),
|
||||||
(Timestamp(3), vec![Scalar::F64(13.0)]),
|
(Timestamp(3), vec![Scalar::f64(13.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(15.0)]),
|
(Timestamp(4), vec![Scalar::f64(15.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(17.0)]),
|
(Timestamp(5), vec![Scalar::f64(17.0)]),
|
||||||
];
|
];
|
||||||
let e3 = vec![
|
let e3 = vec![
|
||||||
(Timestamp(3), vec![Scalar::F64(12.0)]),
|
(Timestamp(3), vec![Scalar::f64(12.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(14.0)]),
|
(Timestamp(4), vec![Scalar::f64(14.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(16.0)]),
|
(Timestamp(5), vec![Scalar::f64(16.0)]),
|
||||||
];
|
];
|
||||||
let e4 = vec![
|
let e4 = vec![
|
||||||
(Timestamp(4), vec![Scalar::F64(13.0)]),
|
(Timestamp(4), vec![Scalar::f64(13.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(15.0)]),
|
(Timestamp(5), vec![Scalar::f64(15.0)]),
|
||||||
];
|
];
|
||||||
assert_eq!(w2, e2);
|
assert_eq!(w2, e2);
|
||||||
assert_eq!(w3, e3);
|
assert_eq!(w3, e3);
|
||||||
@@ -2005,9 +2005,9 @@ mod tests {
|
|||||||
let prices =
|
let prices =
|
||||||
f64_stream(&[(1, 10.0), (2, 12.0), (3, 14.0), (4, 16.0), (5, 18.0), (6, 20.0)]);
|
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![
|
let counts: Vec<(Timestamp, Scalar)> = vec![
|
||||||
(Timestamp(1), Scalar::I64(7)),
|
(Timestamp(1), Scalar::i64(7)),
|
||||||
(Timestamp(2), Scalar::I64(8)),
|
(Timestamp(2), Scalar::i64(8)),
|
||||||
(Timestamp(3), Scalar::I64(9)),
|
(Timestamp(3), Scalar::i64(9)),
|
||||||
];
|
];
|
||||||
|
|
||||||
let (tr, rr) = mpsc::channel();
|
let (tr, rr) = mpsc::channel();
|
||||||
@@ -2019,21 +2019,21 @@ mod tests {
|
|||||||
let sub: Vec<(Timestamp, Vec<Scalar>)> = rs.try_iter().collect();
|
let sub: Vec<(Timestamp, Vec<Scalar>)> = rs.try_iter().collect();
|
||||||
let i64s: Vec<(Timestamp, Vec<Scalar>)> = ri.try_iter().collect();
|
let i64s: Vec<(Timestamp, Vec<Scalar>)> = ri.try_iter().collect();
|
||||||
let raw_expected = vec![
|
let raw_expected = vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(11.0)]),
|
(Timestamp(2), vec![Scalar::f64(11.0)]),
|
||||||
(Timestamp(3), vec![Scalar::F64(13.0)]),
|
(Timestamp(3), vec![Scalar::f64(13.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(15.0)]),
|
(Timestamp(4), vec![Scalar::f64(15.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(17.0)]),
|
(Timestamp(5), vec![Scalar::f64(17.0)]),
|
||||||
(Timestamp(6), vec![Scalar::F64(19.0)]),
|
(Timestamp(6), vec![Scalar::f64(19.0)]),
|
||||||
];
|
];
|
||||||
let sub_expected = vec![
|
let sub_expected = vec![
|
||||||
(Timestamp(4), vec![Scalar::F64(2.0)]),
|
(Timestamp(4), vec![Scalar::f64(2.0)]),
|
||||||
(Timestamp(5), vec![Scalar::F64(2.0)]),
|
(Timestamp(5), vec![Scalar::f64(2.0)]),
|
||||||
(Timestamp(6), vec![Scalar::F64(2.0)]),
|
(Timestamp(6), vec![Scalar::f64(2.0)]),
|
||||||
];
|
];
|
||||||
let i64_expected = vec![
|
let i64_expected = vec![
|
||||||
(Timestamp(1), vec![Scalar::I64(7)]),
|
(Timestamp(1), vec![Scalar::i64(7)]),
|
||||||
(Timestamp(2), vec![Scalar::I64(8)]),
|
(Timestamp(2), vec![Scalar::i64(8)]),
|
||||||
(Timestamp(3), vec![Scalar::I64(9)]),
|
(Timestamp(3), vec![Scalar::i64(9)]),
|
||||||
];
|
];
|
||||||
assert_eq!(raw, raw_expected);
|
assert_eq!(raw, raw_expected);
|
||||||
assert_eq!(sub, sub_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
|
// 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.
|
// price move (cycle 5): the first four equities are exactly 0.
|
||||||
for (_, row) in &equity[0..4] {
|
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
|
// cycle 5: prev_exposure 0.00175 * (1.0040 - 1.0020) / 0.0001 = 0.035 pips
|
||||||
let Scalar::F64(last) = equity[4].1[0] else {
|
debug_assert_eq!(equity[4].1[0].kind(), ScalarKind::F64, "equity is f64");
|
||||||
panic!("equity is f64");
|
let last = equity[4].1[0].as_f64();
|
||||||
};
|
|
||||||
assert!((last - 0.035).abs() < 1e-9, "final equity = {last}, want ~0.035");
|
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};
|
use crate::{Firing, Scalar, ScalarKind, Timestamp};
|
||||||
let _k: ScalarKind = ScalarKind::F64;
|
let _k: ScalarKind = ScalarKind::F64;
|
||||||
let _f: Firing = Firing::Any;
|
let _f: Firing = Firing::Any;
|
||||||
let _s: Scalar = Scalar::F64(0.0);
|
let _s: Scalar = Scalar::f64(0.0);
|
||||||
let _t: Timestamp = Timestamp(0);
|
let _t: Timestamp = Timestamp(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ mod tests {
|
|||||||
|
|
||||||
fn base_point() -> Vec<Scalar> {
|
fn base_point() -> Vec<Scalar> {
|
||||||
// matches the composite_sma_cross param_space order: fast, slow, scale
|
// 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> {
|
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
|
//! cycle 0029) — the same encoder the run registry uses, so a record's stdout
|
||||||
//! and on-disk shapes coincide.
|
//! 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`
|
/// Summary metrics reduced from a run's recorded streams — the `-> metrics`
|
||||||
/// half of C12's atomic sim unit. Pure function of the recorded streams.
|
/// 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 {
|
let Some(&scalar) = row.get(field) else {
|
||||||
panic!("f64_field: row has no field {field} (row width {})", row.len());
|
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:?}");
|
panic!("f64_field: field {field} is not an f64 scalar: {scalar:?}");
|
||||||
};
|
}
|
||||||
(*ts, v)
|
(*ts, scalar.as_f64())
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@ mod tests {
|
|||||||
/// harness.rs test helper; the e2e test needs its own copy — the harness
|
/// harness.rs test helper; the e2e test needs its own copy — the harness
|
||||||
/// test module's is private to that module).
|
/// test module's is private to that module).
|
||||||
fn f64_stream(points: &[(i64, f64)]) -> Vec<(Timestamp, Scalar)> {
|
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
|
/// Bootstrap the cycle-0007 signal-quality harness with TWO sinks: one on
|
||||||
@@ -316,8 +316,8 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn f64_field_projects_the_named_field() {
|
fn f64_field_projects_the_named_field() {
|
||||||
let rows = vec![
|
let rows = vec![
|
||||||
(Timestamp(1), vec![Scalar::F64(1.5), Scalar::I64(9)]),
|
(Timestamp(1), vec![Scalar::f64(1.5), Scalar::i64(9)]),
|
||||||
(Timestamp(2), vec![Scalar::F64(2.5), Scalar::I64(8)]),
|
(Timestamp(2), vec![Scalar::f64(2.5), Scalar::i64(8)]),
|
||||||
];
|
];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
f64_field(&rows, 0),
|
f64_field(&rows, 0),
|
||||||
@@ -328,7 +328,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "not an f64 scalar")]
|
#[should_panic(expected = "not an f64 scalar")]
|
||||||
fn f64_field_panics_on_kind_mismatch() {
|
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);
|
let _ = f64_field(&rows, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,18 +217,18 @@ mod tests {
|
|||||||
let grid = GridSpace::new(
|
let grid = GridSpace::new(
|
||||||
&space,
|
&space,
|
||||||
vec![
|
vec![
|
||||||
vec![Scalar::I64(2), Scalar::I64(3)],
|
vec![Scalar::i64(2), Scalar::i64(3)],
|
||||||
vec![Scalar::I64(4), Scalar::I64(5)],
|
vec![Scalar::i64(4), Scalar::i64(5)],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.expect("valid grid");
|
.expect("valid grid");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
grid.points(),
|
grid.points(),
|
||||||
vec![
|
vec![
|
||||||
vec![Scalar::I64(2), Scalar::I64(4)],
|
vec![Scalar::i64(2), Scalar::i64(4)],
|
||||||
vec![Scalar::I64(2), Scalar::I64(5)],
|
vec![Scalar::i64(2), Scalar::i64(5)],
|
||||||
vec![Scalar::I64(3), Scalar::I64(4)],
|
vec![Scalar::i64(3), Scalar::i64(4)],
|
||||||
vec![Scalar::I64(3), Scalar::I64(5)],
|
vec![Scalar::i64(3), Scalar::i64(5)],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -243,9 +243,9 @@ mod tests {
|
|||||||
let grid = GridSpace::new(
|
let grid = GridSpace::new(
|
||||||
&space,
|
&space,
|
||||||
vec![
|
vec![
|
||||||
vec![Scalar::I64(2), Scalar::I64(3)],
|
vec![Scalar::i64(2), Scalar::i64(3)],
|
||||||
vec![Scalar::I64(4), Scalar::I64(5)],
|
vec![Scalar::i64(4), Scalar::i64(5)],
|
||||||
vec![Scalar::F64(0.5)],
|
vec![Scalar::f64(0.5)],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.expect("valid grid");
|
.expect("valid grid");
|
||||||
@@ -256,14 +256,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn arity_mismatch_is_an_error() {
|
fn arity_mismatch_is_an_error() {
|
||||||
let space = i64_space(2);
|
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 });
|
assert_eq!(err, SweepError::Arity { expected: 2, got: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn wrong_kind_is_a_kind_mismatch() {
|
fn wrong_kind_is_a_kind_mismatch() {
|
||||||
let space = i64_space(1);
|
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!(
|
assert_eq!(
|
||||||
err,
|
err,
|
||||||
SweepError::KindMismatch {
|
SweepError::KindMismatch {
|
||||||
@@ -312,9 +312,9 @@ mod tests {
|
|||||||
GridSpace::new(
|
GridSpace::new(
|
||||||
&space,
|
&space,
|
||||||
vec![
|
vec![
|
||||||
vec![Scalar::I64(2), Scalar::I64(3)], // fast ∈ {2, 3}
|
vec![Scalar::i64(2), Scalar::i64(3)], // fast ∈ {2, 3}
|
||||||
vec![Scalar::I64(4), Scalar::I64(5)], // slow ∈ {4, 5}
|
vec![Scalar::i64(4), Scalar::i64(5)], // slow ∈ {4, 5}
|
||||||
vec![Scalar::F64(0.5)], // scale ∈ {0.5}
|
vec![Scalar::f64(0.5)], // scale ∈ {0.5}
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.expect("grid matches the sample param-space")
|
.expect("grid matches the sample param-space")
|
||||||
@@ -328,11 +328,11 @@ mod tests {
|
|||||||
// params carried in enumeration (odometer) order, self-describing
|
// params carried in enumeration (odometer) order, self-describing
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
family.points[0].params,
|
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!(
|
assert_eq!(
|
||||||
family.points[3].params,
|
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:
|
// each point's metrics equal a direct, independent run of the same point:
|
||||||
// the sweep adds enumeration + execution, never a metrics change (C1).
|
// the sweep adds enumeration + execution, never a metrics change (C1).
|
||||||
@@ -361,7 +361,7 @@ mod tests {
|
|||||||
.map(|(ps, v)| (ps.name, v))
|
.map(|(ps, v)| (ps.name, v))
|
||||||
.collect();
|
.collect();
|
||||||
assert_eq!(family.named_params(0), expected);
|
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]
|
#[test]
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub(crate) fn synthetic_prices() -> Vec<(Timestamp, Scalar)> {
|
|||||||
(7, 0.9990),
|
(7, 0.9990),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&(t, p)| (Timestamp(t), Scalar::F64(p)))
|
.map(|&(t, p)| (Timestamp(t), Scalar::f64(p)))
|
||||||
.collect()
|
.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
|
/// scalar in a param slot is a wiring bug, surfaced like the engine's other
|
||||||
/// "checked at wiring" violations.
|
/// "checked at wiring" violations.
|
||||||
fn scalar_as_f64(s: Scalar) -> f64 {
|
fn scalar_as_f64(s: Scalar) -> f64 {
|
||||||
match s {
|
match s.kind() {
|
||||||
Scalar::I64(n) => n as f64,
|
aura_core::ScalarKind::I64 => s.as_i64() as f64,
|
||||||
Scalar::F64(f) => f,
|
aura_core::ScalarKind::F64 => s.as_f64(),
|
||||||
other => unreachable!("non-numeric param scalar: {other:?}"),
|
other => unreachable!("non-numeric param scalar: {other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,7 +292,7 @@ mod tests {
|
|||||||
fn run_window_fixture(w: WindowBounds) -> WindowRun {
|
fn run_window_fixture(w: WindowBounds) -> WindowRun {
|
||||||
let Timestamp(k) = w.oos.0;
|
let Timestamp(k) = w.oos.0;
|
||||||
WindowRun {
|
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_equity: vec![(w.oos.0, k as f64 * 0.1), (w.oos.1, k as f64 * 0.1 + 1.0)],
|
||||||
oos_report: dummy_report(),
|
oos_report: dummy_report(),
|
||||||
}
|
}
|
||||||
@@ -373,7 +373,7 @@ mod tests {
|
|||||||
oos: (Timestamp(0), Timestamp(0)),
|
oos: (Timestamp(0), Timestamp(0)),
|
||||||
},
|
},
|
||||||
run: WindowRun {
|
run: WindowRun {
|
||||||
chosen_params: vec![Scalar::I64(a), Scalar::F64(b)],
|
chosen_params: vec![Scalar::i64(a), Scalar::f64(b)],
|
||||||
oos_equity: vec![],
|
oos_equity: vec![],
|
||||||
oos_report: dummy_report(),
|
oos_report: dummy_report(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl M1Columns {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The close column as an engine source stream: each normalized timestamp
|
/// The close column as an engine source stream: each normalized timestamp
|
||||||
/// zipped with its close as a [`Scalar::F64`]. Ascending in timestamp iff
|
/// zipped with its close as a [`Scalar::f64`]. Ascending in timestamp iff
|
||||||
/// the bars were (the C3 ingestion precondition `Harness::run` relies on).
|
/// the bars were (the C3 ingestion precondition `Harness::run` relies on).
|
||||||
/// This is the price input the SMA-cross sample strategy consumes; a project
|
/// This is the price input the SMA-cross sample strategy consumes; a project
|
||||||
/// that wants another field reads the public columns and zips its own.
|
/// that wants another field reads the public columns and zips its own.
|
||||||
@@ -71,7 +71,7 @@ impl M1Columns {
|
|||||||
self.ts
|
self.ts
|
||||||
.iter()
|
.iter()
|
||||||
.zip(&self.close)
|
.zip(&self.close)
|
||||||
.map(|(&t, &v)| (t, Scalar::F64(v)))
|
.map(|(&t, &v)| (t, Scalar::f64(v)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -147,12 +147,12 @@ pub enum M1Field {
|
|||||||
/// in isolation. Time is normalized ms→epoch-ns at this one seam (C3).
|
/// in isolation. Time is normalized ms→epoch-ns at this one seam (C3).
|
||||||
fn decode(field: M1Field, bar: &M1Parsed) -> (Timestamp, Scalar) {
|
fn decode(field: M1Field, bar: &M1Parsed) -> (Timestamp, Scalar) {
|
||||||
let value = match field {
|
let value = match field {
|
||||||
M1Field::Open => Scalar::F64(bar.open),
|
M1Field::Open => Scalar::f64(bar.open),
|
||||||
M1Field::High => Scalar::F64(bar.high),
|
M1Field::High => Scalar::f64(bar.high),
|
||||||
M1Field::Low => Scalar::F64(bar.low),
|
M1Field::Low => Scalar::f64(bar.low),
|
||||||
M1Field::Close => Scalar::F64(bar.close),
|
M1Field::Close => Scalar::f64(bar.close),
|
||||||
M1Field::Spread => Scalar::F64(bar.spread),
|
M1Field::Spread => Scalar::f64(bar.spread),
|
||||||
M1Field::Volume => Scalar::I64(bar.volume),
|
M1Field::Volume => Scalar::i64(bar.volume),
|
||||||
};
|
};
|
||||||
(unix_ms_to_epoch_ns(bar.time_ms), value)
|
(unix_ms_to_epoch_ns(bar.time_ms), value)
|
||||||
}
|
}
|
||||||
@@ -313,9 +313,9 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
stream,
|
stream,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(1_000_000), Scalar::F64(10.0)),
|
(Timestamp(1_000_000), Scalar::f64(10.0)),
|
||||||
(Timestamp(2_000_000), Scalar::F64(20.0)),
|
(Timestamp(2_000_000), Scalar::f64(20.0)),
|
||||||
(Timestamp(3_000_000), Scalar::F64(30.0)),
|
(Timestamp(3_000_000), Scalar::f64(30.0)),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
// ascending in timestamp (the merge precondition).
|
// ascending in timestamp (the merge precondition).
|
||||||
@@ -331,12 +331,12 @@ mod tests {
|
|||||||
fn decode_projects_each_field_to_its_scalar_kind() {
|
fn decode_projects_each_field_to_its_scalar_kind() {
|
||||||
// full_bar: open 1.0, high 2.0, low 0.5, close 1.5, spread 0.1, volume 100.
|
// full_bar: open 1.0, high 2.0, low 0.5, close 1.5, spread 0.1, volume 100.
|
||||||
let bar = full_bar(1_000);
|
let bar = full_bar(1_000);
|
||||||
assert_eq!(decode(M1Field::Open, &bar), (Timestamp(1_000_000_000), Scalar::F64(1.0)));
|
assert_eq!(decode(M1Field::Open, &bar), (Timestamp(1_000_000_000), Scalar::f64(1.0)));
|
||||||
assert_eq!(decode(M1Field::High, &bar), (Timestamp(1_000_000_000), Scalar::F64(2.0)));
|
assert_eq!(decode(M1Field::High, &bar), (Timestamp(1_000_000_000), Scalar::f64(2.0)));
|
||||||
assert_eq!(decode(M1Field::Low, &bar), (Timestamp(1_000_000_000), Scalar::F64(0.5)));
|
assert_eq!(decode(M1Field::Low, &bar), (Timestamp(1_000_000_000), Scalar::f64(0.5)));
|
||||||
assert_eq!(decode(M1Field::Close, &bar), (Timestamp(1_000_000_000), Scalar::F64(1.5)));
|
assert_eq!(decode(M1Field::Close, &bar), (Timestamp(1_000_000_000), Scalar::f64(1.5)));
|
||||||
assert_eq!(decode(M1Field::Spread, &bar), (Timestamp(1_000_000_000), Scalar::F64(0.1)));
|
assert_eq!(decode(M1Field::Spread, &bar), (Timestamp(1_000_000_000), Scalar::f64(0.1)));
|
||||||
// volume is the one i64 column.
|
// volume is the one i64 column.
|
||||||
assert_eq!(decode(M1Field::Volume, &bar), (Timestamp(1_000_000_000), Scalar::I64(100)));
|
assert_eq!(decode(M1Field::Volume, &bar), (Timestamp(1_000_000_000), Scalar::i64(100)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,12 +178,12 @@ fn two_field_sources_share_one_window() {
|
|||||||
|
|
||||||
let mut n_close = 0usize;
|
let mut n_close = 0usize;
|
||||||
while let Some((_, v)) = Source::next(&mut close) {
|
while let Some((_, v)) = Source::next(&mut close) {
|
||||||
assert!(matches!(v, Scalar::F64(_)), "close is an f64 column");
|
assert_eq!(v.kind(), ScalarKind::F64, "close is an f64 column");
|
||||||
n_close += 1;
|
n_close += 1;
|
||||||
}
|
}
|
||||||
let mut n_volume = 0usize;
|
let mut n_volume = 0usize;
|
||||||
while let Some((_, v)) = Source::next(&mut volume) {
|
while let Some((_, v)) = Source::next(&mut volume) {
|
||||||
assert!(matches!(v, Scalar::I64(_)), "volume is an i64 column");
|
assert_eq!(v.kind(), ScalarKind::I64, "volume is an i64 column");
|
||||||
n_volume += 1;
|
n_volume += 1;
|
||||||
}
|
}
|
||||||
assert_eq!(n_close, n_volume, "both fields share the same ts axis ⇒ same count");
|
assert_eq!(n_close, n_volume, "both fields share the same ts axis ⇒ same count");
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ mod tests {
|
|||||||
// total_pips (3.0); the EARLIER of the two (index 1, params p=10) must
|
// total_pips (3.0); the EARLIER of the two (index 1, params p=10) must
|
||||||
// win, not the later (index 3, params p=30) — that pins the tie rule.
|
// win, not the later (index 3, params p=30) — that pins the tie rule.
|
||||||
let point = |p: f64, pips: f64| SweepPoint {
|
let point = |p: f64, pips: f64| SweepPoint {
|
||||||
params: vec![Scalar::F64(p)],
|
params: vec![Scalar::f64(p)],
|
||||||
report: report_with(pips, 0.5, 0),
|
report: report_with(pips, 0.5, 0),
|
||||||
};
|
};
|
||||||
let family = SweepFamily {
|
let family = SweepFamily {
|
||||||
@@ -291,7 +291,7 @@ mod tests {
|
|||||||
assert_eq!(winner.report.metrics.total_pips, 3.0);
|
assert_eq!(winner.report.metrics.total_pips, 3.0);
|
||||||
// ...and ties resolve to the earliest enumeration-order point: its params
|
// ...and ties resolve to the earliest enumeration-order point: its params
|
||||||
// identify point 1, not point 3.
|
// identify point 1, not point 3.
|
||||||
assert_eq!(winner.params, vec![Scalar::F64(10.0)]);
|
assert_eq!(winner.params, vec![Scalar::f64(10.0)]);
|
||||||
// the whole winning point is returned, params AND report together.
|
// the whole winning point is returned, params AND report together.
|
||||||
assert_eq!(winner, family.points[1]);
|
assert_eq!(winner, family.points[1]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub struct Add {
|
|||||||
impl Add {
|
impl Add {
|
||||||
/// Build an `Add` node.
|
/// Build an `Add` node.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { out: [Scalar::F64(0.0)] }
|
Self { out: [Scalar::f64(0.0)] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The param-generic recipe for a blueprint primitive: paramless, builds through
|
/// The param-generic recipe for a blueprint primitive: paramless, builds through
|
||||||
@@ -61,7 +61,7 @@ impl Node for Add {
|
|||||||
if a.is_empty() || b.is_empty() {
|
if a.is_empty() || b.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
self.out[0] = Scalar::F64(a[0] + b[0]);
|
self.out[0] = Scalar::f64(a[0] + b[0]);
|
||||||
Some(&self.out)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,12 +84,12 @@ mod tests {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// only input 0 present -> None
|
// only input 0 present -> None
|
||||||
inputs[0].push(Scalar::F64(10.0)).unwrap();
|
inputs[0].push(Scalar::f64(10.0)).unwrap();
|
||||||
assert_eq!(add.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
assert_eq!(add.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
||||||
|
|
||||||
// both present -> a + b
|
// both present -> a + b
|
||||||
inputs[1].push(Scalar::F64(4.0)).unwrap();
|
inputs[1].push(Scalar::f64(4.0)).unwrap();
|
||||||
assert_eq!(add.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(14.0)].as_slice()));
|
assert_eq!(add.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::f64(14.0)].as_slice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ impl Ema {
|
|||||||
warmup_sum: 0.0,
|
warmup_sum: 0.0,
|
||||||
count: 0,
|
count: 0,
|
||||||
ema: 0.0,
|
ema: 0.0,
|
||||||
out: [Scalar::F64(0.0)],
|
out: [Scalar::f64(0.0)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ impl Ema {
|
|||||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||||
params: vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
params: vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
||||||
},
|
},
|
||||||
|p| Box::new(Ema::new(p[0].as_i64().expect("length slot is I64") as usize)),
|
|p| Box::new(Ema::new(p[0].as_i64() as usize)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ impl Node for Ema {
|
|||||||
// O(1) recurrence: one sub, one mul, one add.
|
// O(1) recurrence: one sub, one mul, one add.
|
||||||
self.ema += self.alpha * (x - self.ema);
|
self.ema += self.alpha * (x - self.ema);
|
||||||
}
|
}
|
||||||
self.out[0] = Scalar::F64(self.ema);
|
self.out[0] = Scalar::f64(self.ema);
|
||||||
Some(&self.out)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,11 +124,11 @@ mod tests {
|
|||||||
let expect = [None, None, Some(4.0), Some(7.0)];
|
let expect = [None, None, Some(4.0), Some(7.0)];
|
||||||
|
|
||||||
for (v, want) in feed.iter().zip(expect) {
|
for (v, want) in feed.iter().zip(expect) {
|
||||||
inputs[0].push(Scalar::F64(*v)).unwrap();
|
inputs[0].push(Scalar::f64(*v)).unwrap();
|
||||||
let got = ema.eval(Ctx::new(&inputs, Timestamp(0)));
|
let got = ema.eval(Ctx::new(&inputs, Timestamp(0)));
|
||||||
match want {
|
match want {
|
||||||
None => assert_eq!(got, None),
|
None => assert_eq!(got, None),
|
||||||
Some(m) => assert_eq!(got, Some([Scalar::F64(m)].as_slice())),
|
Some(m) => assert_eq!(got, Some([Scalar::f64(m)].as_slice())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,11 +140,11 @@ mod tests {
|
|||||||
let mut ema = Ema::new(1);
|
let mut ema = Ema::new(1);
|
||||||
let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 1)];
|
let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 1)];
|
||||||
|
|
||||||
inputs[0].push(Scalar::F64(7.0)).unwrap();
|
inputs[0].push(Scalar::f64(7.0)).unwrap();
|
||||||
assert_eq!(ema.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(7.0)].as_slice()));
|
assert_eq!(ema.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::f64(7.0)].as_slice()));
|
||||||
|
|
||||||
inputs[0].push(Scalar::F64(9.0)).unwrap();
|
inputs[0].push(Scalar::f64(9.0)).unwrap();
|
||||||
assert_eq!(ema.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(9.0)].as_slice()));
|
assert_eq!(ema.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::f64(9.0)].as_slice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ impl Exposure {
|
|||||||
/// Build an exposure node with saturation magnitude `scale` (must be > 0).
|
/// Build an exposure node with saturation magnitude `scale` (must be > 0).
|
||||||
pub fn new(scale: f64) -> Self {
|
pub fn new(scale: f64) -> Self {
|
||||||
assert!(scale > 0.0, "Exposure scale must be > 0");
|
assert!(scale > 0.0, "Exposure scale must be > 0");
|
||||||
Self { scale, out: [Scalar::F64(0.0)] }
|
Self { scale, out: [Scalar::f64(0.0)] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The param-generic recipe for a blueprint primitive: declares `scale` and builds
|
/// The param-generic recipe for a blueprint primitive: declares `scale` and builds
|
||||||
@@ -33,7 +33,7 @@ impl Exposure {
|
|||||||
output: vec![FieldSpec { name: "exposure".into(), kind: ScalarKind::F64 }],
|
output: vec![FieldSpec { name: "exposure".into(), kind: ScalarKind::F64 }],
|
||||||
params: vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }],
|
params: vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }],
|
||||||
},
|
},
|
||||||
|p| Box::new(Exposure::new(p[0].as_f64().expect("scale slot is F64"))),
|
|p| Box::new(Exposure::new(p[0].as_f64())),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ impl Node for Exposure {
|
|||||||
if w.is_empty() {
|
if w.is_empty() {
|
||||||
return None; // not yet warmed up (C8 filter)
|
return None; // not yet warmed up (C8 filter)
|
||||||
}
|
}
|
||||||
self.out[0] = Scalar::F64((w[0] / self.scale).clamp(-1.0, 1.0));
|
self.out[0] = Scalar::f64((w[0] / self.scale).clamp(-1.0, 1.0));
|
||||||
Some(&self.out)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,10 +75,10 @@ mod tests {
|
|||||||
(-1.0, -1.0), // saturates low
|
(-1.0, -1.0), // saturates low
|
||||||
];
|
];
|
||||||
for (sig, want) in cases {
|
for (sig, want) in cases {
|
||||||
inputs[0].push(Scalar::F64(sig)).unwrap();
|
inputs[0].push(Scalar::f64(sig)).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
e.eval(Ctx::new(&inputs, Timestamp(0))),
|
e.eval(Ctx::new(&inputs, Timestamp(0))),
|
||||||
Some([Scalar::F64(want)].as_slice())
|
Some([Scalar::f64(want)].as_slice())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ impl LinComb {
|
|||||||
/// Panics if `weights` is empty.
|
/// Panics if `weights` is empty.
|
||||||
pub fn new(weights: Vec<f64>) -> Self {
|
pub fn new(weights: Vec<f64>) -> Self {
|
||||||
assert!(!weights.is_empty(), "LinComb needs at least one weight");
|
assert!(!weights.is_empty(), "LinComb needs at least one weight");
|
||||||
Self { weights, out: [Scalar::F64(0.0)] }
|
Self { weights, out: [Scalar::f64(0.0)] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The param-generic recipe for a blueprint primitive. The `arity` is topology
|
/// The param-generic recipe for a blueprint primitive. The `arity` is topology
|
||||||
@@ -54,7 +54,7 @@ impl LinComb {
|
|||||||
"LinComb",
|
"LinComb",
|
||||||
NodeSchema { inputs, output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }], params },
|
NodeSchema { inputs, output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }], params },
|
||||||
|p| Box::new(LinComb::new(
|
|p| Box::new(LinComb::new(
|
||||||
p.iter().map(|s| s.as_f64().expect("weight slot is F64")).collect(),
|
p.iter().map(|s| s.as_f64()).collect(),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ impl Node for LinComb {
|
|||||||
}
|
}
|
||||||
acc += w * w_in[0];
|
acc += w * w_in[0];
|
||||||
}
|
}
|
||||||
self.out[0] = Scalar::F64(acc);
|
self.out[0] = Scalar::f64(acc);
|
||||||
Some(&self.out)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,12 +97,12 @@ mod tests {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// only input 0 present -> None
|
// only input 0 present -> None
|
||||||
inputs[0].push(Scalar::F64(10.0)).unwrap();
|
inputs[0].push(Scalar::f64(10.0)).unwrap();
|
||||||
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
||||||
|
|
||||||
// both present -> 0.5*10 + 2.0*3 = 11.0
|
// both present -> 0.5*10 + 2.0*3 = 11.0
|
||||||
inputs[1].push(Scalar::F64(3.0)).unwrap();
|
inputs[1].push(Scalar::f64(3.0)).unwrap();
|
||||||
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(11.0)].as_slice()));
|
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::f64(11.0)].as_slice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -112,10 +112,10 @@ mod tests {
|
|||||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||||
];
|
];
|
||||||
inputs[0].push(Scalar::F64(7.0)).unwrap();
|
inputs[0].push(Scalar::f64(7.0)).unwrap();
|
||||||
inputs[1].push(Scalar::F64(5.0)).unwrap();
|
inputs[1].push(Scalar::f64(5.0)).unwrap();
|
||||||
// unit weights reproduce Add: 7 + 5
|
// unit weights reproduce Add: 7 + 5
|
||||||
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(12.0)].as_slice()));
|
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::f64(12.0)].as_slice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -126,14 +126,14 @@ mod tests {
|
|||||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||||
];
|
];
|
||||||
inputs[0].push(Scalar::F64(1.0)).unwrap();
|
inputs[0].push(Scalar::f64(1.0)).unwrap();
|
||||||
inputs[1].push(Scalar::F64(2.0)).unwrap();
|
inputs[1].push(Scalar::f64(2.0)).unwrap();
|
||||||
// third leg still cold -> None (withheld until every leg is present)
|
// third leg still cold -> None (withheld until every leg is present)
|
||||||
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
||||||
|
|
||||||
inputs[2].push(Scalar::F64(3.0)).unwrap();
|
inputs[2].push(Scalar::f64(3.0)).unwrap();
|
||||||
// all warm -> 1 + 2 + 3
|
// all warm -> 1 + 2 + 3
|
||||||
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(6.0)].as_slice()));
|
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::f64(6.0)].as_slice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -153,33 +153,33 @@ mod tests {
|
|||||||
fn chained_bind_reconstructs_positional_vector() {
|
fn chained_bind_reconstructs_positional_vector() {
|
||||||
// bind BOTH weights, in reverse slot order, to DISTINCT values; build empty.
|
// bind BOTH weights, in reverse slot order, to DISTINCT values; build empty.
|
||||||
let builder = LinComb::builder(2)
|
let builder = LinComb::builder(2)
|
||||||
.bind("weights[1]", Scalar::F64(2.0))
|
.bind("weights[1]", Scalar::f64(2.0))
|
||||||
.bind("weights[0]", Scalar::F64(0.5));
|
.bind("weights[0]", Scalar::f64(0.5));
|
||||||
assert!(builder.params().is_empty());
|
assert!(builder.params().is_empty());
|
||||||
let mut lc = builder.build(&[]);
|
let mut lc = builder.build(&[]);
|
||||||
let mut inputs = vec![
|
let mut inputs = vec![
|
||||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||||
];
|
];
|
||||||
inputs[0].push(Scalar::F64(10.0)).unwrap();
|
inputs[0].push(Scalar::f64(10.0)).unwrap();
|
||||||
inputs[1].push(Scalar::F64(3.0)).unwrap();
|
inputs[1].push(Scalar::f64(3.0)).unwrap();
|
||||||
// 0.5*10 + 2.0*3 = 11.0 — holds ONLY if each weight landed in its right slot
|
// 0.5*10 + 2.0*3 = 11.0 — holds ONLY if each weight landed in its right slot
|
||||||
// (a swap would give 2.0*10 + 0.5*3 = 21.5)
|
// (a swap would give 2.0*10 + 0.5*3 = 21.5)
|
||||||
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(11.0)].as_slice()));
|
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::f64(11.0)].as_slice()));
|
||||||
|
|
||||||
// partial: bind weights[0], leave weights[1] open → inject it at build
|
// partial: bind weights[0], leave weights[1] open → inject it at build
|
||||||
let partial = LinComb::builder(2).bind("weights[0]", Scalar::F64(0.5));
|
let partial = LinComb::builder(2).bind("weights[0]", Scalar::f64(0.5));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
partial.params().iter().map(|p| p.name.as_str()).collect::<Vec<_>>(),
|
partial.params().iter().map(|p| p.name.as_str()).collect::<Vec<_>>(),
|
||||||
["weights[1]"],
|
["weights[1]"],
|
||||||
);
|
);
|
||||||
let mut lc2 = partial.build(&[Scalar::F64(2.0)]); // weights[1] = 2.0 injected
|
let mut lc2 = partial.build(&[Scalar::f64(2.0)]); // weights[1] = 2.0 injected
|
||||||
let mut inputs2 = vec![
|
let mut inputs2 = vec![
|
||||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||||
];
|
];
|
||||||
inputs2[0].push(Scalar::F64(10.0)).unwrap();
|
inputs2[0].push(Scalar::f64(10.0)).unwrap();
|
||||||
inputs2[1].push(Scalar::F64(3.0)).unwrap();
|
inputs2[1].push(Scalar::f64(3.0)).unwrap();
|
||||||
assert_eq!(lc2.eval(Ctx::new(&inputs2, Timestamp(0))), Some([Scalar::F64(11.0)].as_slice()));
|
assert_eq!(lc2.eval(Ctx::new(&inputs2, Timestamp(0))), Some([Scalar::f64(11.0)].as_slice()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,10 +62,10 @@ impl Node for Recorder {
|
|||||||
for (i, &kind) in self.kinds.iter().enumerate() {
|
for (i, &kind) in self.kinds.iter().enumerate() {
|
||||||
// newest of each column by kind; `?` returns None (warm-up) if cold.
|
// newest of each column by kind; `?` returns None (warm-up) if cold.
|
||||||
let scalar = match kind {
|
let scalar = match kind {
|
||||||
ScalarKind::F64 => Scalar::F64(ctx.f64_in(i).get(0)?),
|
ScalarKind::F64 => Scalar::f64(ctx.f64_in(i).get(0)?),
|
||||||
ScalarKind::I64 => Scalar::I64(ctx.i64_in(i).get(0)?),
|
ScalarKind::I64 => Scalar::i64(ctx.i64_in(i).get(0)?),
|
||||||
ScalarKind::Bool => Scalar::Bool(ctx.bool_in(i).get(0)?),
|
ScalarKind::Bool => Scalar::bool(ctx.bool_in(i).get(0)?),
|
||||||
ScalarKind::Timestamp => Scalar::Ts(ctx.ts_in(i).get(0)?),
|
ScalarKind::Timestamp => Scalar::ts(ctx.ts_in(i).get(0)?),
|
||||||
};
|
};
|
||||||
row.push(scalar);
|
row.push(scalar);
|
||||||
}
|
}
|
||||||
@@ -107,16 +107,16 @@ mod tests {
|
|||||||
|
|
||||||
// warm: returns None (pure consumer) but records (now, [F64(newest)]).
|
// warm: returns None (pure consumer) but records (now, [F64(newest)]).
|
||||||
for (t, v) in [(2_i64, 10.0_f64), (3, 20.0), (4, 30.0)] {
|
for (t, v) in [(2_i64, 10.0_f64), (3, 20.0), (4, 30.0)] {
|
||||||
inputs[0].push(Scalar::F64(v)).unwrap();
|
inputs[0].push(Scalar::f64(v)).unwrap();
|
||||||
assert_eq!(rec.eval(Ctx::new(&inputs, Timestamp(t))), None);
|
assert_eq!(rec.eval(Ctx::new(&inputs, Timestamp(t))), None);
|
||||||
}
|
}
|
||||||
let rows: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
|
let rows: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rows,
|
rows,
|
||||||
vec![
|
vec![
|
||||||
(Timestamp(2), vec![Scalar::F64(10.0)]),
|
(Timestamp(2), vec![Scalar::f64(10.0)]),
|
||||||
(Timestamp(3), vec![Scalar::F64(20.0)]),
|
(Timestamp(3), vec![Scalar::f64(20.0)]),
|
||||||
(Timestamp(4), vec![Scalar::F64(30.0)]),
|
(Timestamp(4), vec![Scalar::f64(30.0)]),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -131,15 +131,15 @@ mod tests {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// only column 0 present -> None, nothing recorded.
|
// only column 0 present -> None, nothing recorded.
|
||||||
inputs[0].push(Scalar::F64(1.0)).unwrap();
|
inputs[0].push(Scalar::f64(1.0)).unwrap();
|
||||||
assert_eq!(rec.eval(Ctx::new(&inputs, Timestamp(1))), None);
|
assert_eq!(rec.eval(Ctx::new(&inputs, Timestamp(1))), None);
|
||||||
assert!(rx.try_recv().is_err());
|
assert!(rx.try_recv().is_err());
|
||||||
|
|
||||||
// both present -> records the full row (still returns None).
|
// both present -> records the full row (still returns None).
|
||||||
inputs[1].push(Scalar::F64(2.0)).unwrap();
|
inputs[1].push(Scalar::f64(2.0)).unwrap();
|
||||||
assert_eq!(rec.eval(Ctx::new(&inputs, Timestamp(2))), None);
|
assert_eq!(rec.eval(Ctx::new(&inputs, Timestamp(2))), None);
|
||||||
let rows: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
|
let rows: Vec<(Timestamp, Vec<Scalar>)> = rx.try_iter().collect();
|
||||||
assert_eq!(rows, vec![(Timestamp(2), vec![Scalar::F64(1.0), Scalar::F64(2.0)])]);
|
assert_eq!(rows, vec![(Timestamp(2), vec![Scalar::f64(1.0), Scalar::f64(2.0)])]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ impl SimBroker {
|
|||||||
prev_price: None,
|
prev_price: None,
|
||||||
prev_exposure: 0.0,
|
prev_exposure: 0.0,
|
||||||
cum: 0.0,
|
cum: 0.0,
|
||||||
out: [Scalar::F64(0.0)],
|
out: [Scalar::f64(0.0)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ impl Node for SimBroker {
|
|||||||
}
|
}
|
||||||
self.prev_price = Some(price);
|
self.prev_price = Some(price);
|
||||||
self.prev_exposure = expo; // update AFTER taking PnL — no look-ahead (C2)
|
self.prev_exposure = expo; // update AFTER taking PnL — no look-ahead (C2)
|
||||||
self.out[0] = Scalar::F64(self.cum);
|
self.out[0] = Scalar::f64(self.cum);
|
||||||
Some(&self.out)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,11 +117,11 @@ mod tests {
|
|||||||
// stays empty) and a price into slot 1, then eval and return the equity.
|
// stays empty) and a price into slot 1, then eval and return the equity.
|
||||||
fn step(b: &mut SimBroker, inputs: &mut [AnyColumn], expo: Option<f64>, price: f64) -> f64 {
|
fn step(b: &mut SimBroker, inputs: &mut [AnyColumn], expo: Option<f64>, price: f64) -> f64 {
|
||||||
if let Some(e) = expo {
|
if let Some(e) = expo {
|
||||||
inputs[0].push(Scalar::F64(e)).unwrap();
|
inputs[0].push(Scalar::f64(e)).unwrap();
|
||||||
}
|
}
|
||||||
inputs[1].push(Scalar::F64(price)).unwrap();
|
inputs[1].push(Scalar::f64(price)).unwrap();
|
||||||
match b.eval(Ctx::new(inputs, Timestamp(0))) {
|
match b.eval(Ctx::new(inputs, Timestamp(0))) {
|
||||||
Some([Scalar::F64(v)]) => *v,
|
Some([s]) => s.as_f64(),
|
||||||
other => panic!("expected Some([F64]), got {other:?}"),
|
other => panic!("expected Some([F64]), got {other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -18,7 +18,7 @@ impl Sma {
|
|||||||
/// Build an SMA of window `length` (must be >= 1).
|
/// Build an SMA of window `length` (must be >= 1).
|
||||||
pub fn new(length: usize) -> Self {
|
pub fn new(length: usize) -> Self {
|
||||||
assert!(length >= 1, "SMA length must be >= 1");
|
assert!(length >= 1, "SMA length must be >= 1");
|
||||||
Self { length, out: [Scalar::F64(0.0)] }
|
Self { length, out: [Scalar::f64(0.0)] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The param-generic recipe for a blueprint primitive: declares `length` and builds
|
/// The param-generic recipe for a blueprint primitive: declares `length` and builds
|
||||||
@@ -32,7 +32,7 @@ impl Sma {
|
|||||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||||
params: vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
params: vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
||||||
},
|
},
|
||||||
|p| Box::new(Sma::new(p[0].as_i64().expect("length slot is I64") as usize)),
|
|p| Box::new(Sma::new(p[0].as_i64() as usize)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ impl Node for Sma {
|
|||||||
for k in 0..self.length {
|
for k in 0..self.length {
|
||||||
sum += w[k]; // index 0 = newest (financial indexing)
|
sum += w[k]; // index 0 = newest (financial indexing)
|
||||||
}
|
}
|
||||||
self.out[0] = Scalar::F64(sum / self.length as f64);
|
self.out[0] = Scalar::f64(sum / self.length as f64);
|
||||||
Some(&self.out)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,11 +79,11 @@ mod tests {
|
|||||||
let expect = [None, None, Some(2.0), Some(3.0), Some(4.0)];
|
let expect = [None, None, Some(2.0), Some(3.0), Some(4.0)];
|
||||||
|
|
||||||
for (v, want) in feed.iter().zip(expect) {
|
for (v, want) in feed.iter().zip(expect) {
|
||||||
inputs[0].push(Scalar::F64(*v)).unwrap();
|
inputs[0].push(Scalar::f64(*v)).unwrap();
|
||||||
let got = sma.eval(Ctx::new(&inputs, Timestamp(0)));
|
let got = sma.eval(Ctx::new(&inputs, Timestamp(0)));
|
||||||
match want {
|
match want {
|
||||||
None => assert_eq!(got, None),
|
None => assert_eq!(got, None),
|
||||||
Some(m) => assert_eq!(got, Some([Scalar::F64(m)].as_slice())),
|
Some(m) => assert_eq!(got, Some([Scalar::f64(m)].as_slice())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,11 +93,11 @@ mod tests {
|
|||||||
let mut sma = Sma::new(1);
|
let mut sma = Sma::new(1);
|
||||||
let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 1)];
|
let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 1)];
|
||||||
|
|
||||||
inputs[0].push(Scalar::F64(7.0)).unwrap();
|
inputs[0].push(Scalar::f64(7.0)).unwrap();
|
||||||
assert_eq!(sma.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(7.0)].as_slice()));
|
assert_eq!(sma.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::f64(7.0)].as_slice()));
|
||||||
|
|
||||||
inputs[0].push(Scalar::F64(9.0)).unwrap();
|
inputs[0].push(Scalar::f64(9.0)).unwrap();
|
||||||
assert_eq!(sma.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(9.0)].as_slice()));
|
assert_eq!(sma.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::f64(9.0)].as_slice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -160,7 +160,7 @@ mod tests {
|
|||||||
fn bind_removes_slot_from_param_space() {
|
fn bind_removes_slot_from_param_space() {
|
||||||
// a bound param-bearing node reports an empty param surface — parity with the
|
// a bound param-bearing node reports an empty param surface — parity with the
|
||||||
// SimBroker precedent (nodes_declare_expected_params, this file)
|
// SimBroker precedent (nodes_declare_expected_params, this file)
|
||||||
let sma2 = Sma::builder().named("bias").bind("length", Scalar::I64(2));
|
let sma2 = Sma::builder().named("bias").bind("length", Scalar::i64(2));
|
||||||
assert!(sma2.schema().params.is_empty());
|
assert!(sma2.schema().params.is_empty());
|
||||||
// contrast: the length-generic SMA keeps `length` open
|
// contrast: the length-generic SMA keeps `length` open
|
||||||
assert_eq!(Sma::builder().named("bias").params().len(), 1);
|
assert_eq!(Sma::builder().named("bias").params().len(), 1);
|
||||||
@@ -169,7 +169,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn bound_node_builds_with_injected_value() {
|
fn bound_node_builds_with_injected_value() {
|
||||||
// built with an empty open slice, the bound builder yields SMA(2)
|
// built with an empty open slice, the bound builder yields SMA(2)
|
||||||
let node = Sma::builder().bind("length", Scalar::I64(2)).build(&[]);
|
let node = Sma::builder().bind("length", Scalar::i64(2)).build(&[]);
|
||||||
assert_eq!(node.label(), "SMA(2)");
|
assert_eq!(node.label(), "SMA(2)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ pub struct Sub {
|
|||||||
impl Sub {
|
impl Sub {
|
||||||
/// Build a `Sub` node.
|
/// Build a `Sub` node.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { out: [Scalar::F64(0.0)] }
|
Self { out: [Scalar::f64(0.0)] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The param-generic recipe for a blueprint primitive: paramless, builds through
|
/// The param-generic recipe for a blueprint primitive: paramless, builds through
|
||||||
@@ -52,7 +52,7 @@ impl Node for Sub {
|
|||||||
if a.is_empty() || b.is_empty() {
|
if a.is_empty() || b.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
self.out[0] = Scalar::F64(a[0] - b[0]);
|
self.out[0] = Scalar::f64(a[0] - b[0]);
|
||||||
Some(&self.out)
|
Some(&self.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,12 +75,12 @@ mod tests {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// only input 0 present -> None
|
// only input 0 present -> None
|
||||||
inputs[0].push(Scalar::F64(10.0)).unwrap();
|
inputs[0].push(Scalar::f64(10.0)).unwrap();
|
||||||
assert_eq!(sub.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
assert_eq!(sub.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
||||||
|
|
||||||
// both present -> a - b
|
// both present -> a - b
|
||||||
inputs[1].push(Scalar::F64(4.0)).unwrap();
|
inputs[1].push(Scalar::f64(4.0)).unwrap();
|
||||||
assert_eq!(sub.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::F64(6.0)].as_slice()));
|
assert_eq!(sub.eval(Ctx::new(&inputs, Timestamp(0))), Some([Scalar::f64(6.0)].as_slice()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user