feat(aura-std): EqConst — i64->bool equality gate
Stateless gate, out = (value == target): the i64->bool comparator the session-breakout strategy needs to turn Session's bars_since_open into isBar3/isBar5Close (two instances bound to 3 and 5). The operator is topology (a concrete node type), only target is a knob (C11/C12). Build-step 1 of milestone 'Strategy node vocabulary I'. closes #84
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
//! `EqConst` — a stateless `i64 -> bool` equality gate: `out = (value == target)`.
|
||||
//!
|
||||
//! The `i64 -> bool` analogue of a relational comparator (`Gt` is the `f64 -> bool`
|
||||
//! cousin). Its purpose in the session-breakout strategy is to turn `Session`'s
|
||||
//! `bars_since_open: i64` into per-bar booleans (`isBar3`, `isBar5Close`) via two
|
||||
//! instances bound to `3` and `5`.
|
||||
//!
|
||||
//! The **operator is topology** — `EqConst` is a concrete node type, not an op
|
||||
//! selected by a swept param. Only `target` is a knob (C11/C12); the `==` is fixed
|
||||
//! by the type. (`!=`, `<`, `>` would each be their own node type, never a param.)
|
||||
//! One `i64` input (`Firing::Any`), one `bool` output, allocation-free on the hot
|
||||
//! path (the single-cell output buffer is sized once at construction, C7).
|
||||
|
||||
use aura_core::{
|
||||
Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder,
|
||||
ScalarKind,
|
||||
};
|
||||
|
||||
/// Stateless `i64 -> bool` equality gate: emits `value == target` each cycle.
|
||||
/// Emits `None` until its input is present (warm-up filter, C8).
|
||||
pub struct EqConst {
|
||||
target: i64,
|
||||
out: [Cell; 1],
|
||||
}
|
||||
|
||||
impl EqConst {
|
||||
/// Build an equality gate that fires `true` exactly when its input equals
|
||||
/// `target`.
|
||||
pub fn new(target: i64) -> Self {
|
||||
Self { target, out: [Cell::from_bool(false)] }
|
||||
}
|
||||
|
||||
/// The param-generic recipe for a blueprint primitive: declares `target` and
|
||||
/// builds through `EqConst::new` (the slice is kind-checked before `build`
|
||||
/// runs, so the typed read is total).
|
||||
pub fn builder() -> PrimitiveBuilder {
|
||||
PrimitiveBuilder::new(
|
||||
"EqConst",
|
||||
NodeSchema {
|
||||
inputs: vec![PortSpec { kind: ScalarKind::I64, firing: Firing::Any, name: "value".into() }],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::Bool }],
|
||||
params: vec![ParamSpec { name: "target".into(), kind: ScalarKind::I64 }],
|
||||
},
|
||||
|p| Box::new(EqConst::new(p[0].i64())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for EqConst {
|
||||
fn lookbacks(&self) -> Vec<usize> {
|
||||
vec![1]
|
||||
}
|
||||
|
||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {
|
||||
let w = ctx.i64_in(0);
|
||||
if w.is_empty() {
|
||||
return None; // not yet warmed up (C8 filter)
|
||||
}
|
||||
self.out[0] = Cell::from_bool(w[0] == self.target);
|
||||
Some(&self.out)
|
||||
}
|
||||
|
||||
fn label(&self) -> String {
|
||||
format!("EqConst({})", self.target)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use aura_core::{AnyColumn, Scalar, Timestamp};
|
||||
|
||||
#[test]
|
||||
fn eqconst_gates_on_equality_with_target() {
|
||||
// The core property: out == (value == target). Bound to target=3, the gate
|
||||
// fires true exactly on the matching input and false on every mismatch —
|
||||
// this is what makes `bars_since_open == 3` the isBar3 boolean.
|
||||
let node_for_depth = EqConst::new(3);
|
||||
|
||||
// size the input column from the node's lookback, as bootstrap will at wiring
|
||||
let mut inputs =
|
||||
vec![AnyColumn::with_capacity(ScalarKind::I64, node_for_depth.lookbacks()[0])];
|
||||
let mut node = node_for_depth;
|
||||
|
||||
let feed = [2_i64, 3, 5];
|
||||
let expect = [Some(false), Some(true), Some(false)];
|
||||
|
||||
for (v, want) in feed.iter().zip(expect) {
|
||||
inputs[0].push(Scalar::i64(*v)).unwrap();
|
||||
let got = node.eval(Ctx::new(&inputs, Timestamp(0)));
|
||||
match want {
|
||||
Some(b) => assert_eq!(got, Some([Cell::from_bool(b)].as_slice())),
|
||||
None => assert_eq!(got, None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eqconst_is_none_until_input_present() {
|
||||
// a warm-up / empty-input cycle filters (C8) — like every other stateless node
|
||||
let mut node = EqConst::new(3);
|
||||
let inputs = vec![AnyColumn::with_capacity(ScalarKind::I64, 1)];
|
||||
assert_eq!(node.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
mod add;
|
||||
mod ema;
|
||||
mod eqconst;
|
||||
mod exposure;
|
||||
mod lincomb;
|
||||
mod recorder;
|
||||
@@ -25,6 +26,7 @@ mod sma;
|
||||
mod sub;
|
||||
pub use add::Add;
|
||||
pub use ema::Ema;
|
||||
pub use eqconst::EqConst;
|
||||
pub use exposure::Exposure;
|
||||
pub use lincomb::LinComb;
|
||||
pub use recorder::Recorder;
|
||||
|
||||
Reference in New Issue
Block a user