feat(std): fill the by-chance vocabulary gaps — Const, Div, Abs, Max, Min
Five new rostered node types (count-pin 23 -> 28, both the in-crate shape test and the cross-boundary CLI vocabulary e2e): Const is unary with an f64 'value' param — the clock input drives it, its value is ignored, since a zero-input node never evaluates in the total-push engine — mirroring EqConst's constant-as-param pattern; Div is binary IEEE-754 (x/0 -> signed inf, 0/0 -> NaN, unit-tested, no error channel); Abs unary mirroring Sqrt; Max/Min binary pairwise, distinct from the windowed RollingMax/RollingMin. Acceptance proof: the committed executable spec composes an RSI-class gain/loss-split-and-ratio signal purely from blueprint data through std_vocabulary and runs it to hand-computed RS values — the r_meanrev constant-folding workaround is no longer forced. Verified: headline test green, aura-std 163/0, full workspace suite green (independent mini-verify), clippy -D warnings clean. closes #236
This commit is contained in:
@@ -121,8 +121,8 @@ fn graph_introspect_vocabulary_lists_exactly_the_closed_roster_count() {
|
||||
let lines: Vec<&str> = stdout.lines().filter(|l| !l.is_empty()).collect();
|
||||
assert_eq!(
|
||||
lines.len(),
|
||||
23,
|
||||
"the std-only (no project) vocabulary has exactly the roster's 23 entries: {stdout}"
|
||||
28,
|
||||
"the std-only (no project) vocabulary has exactly the roster's 28 entries: {stdout}"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
//! `Abs` — one-input f64 absolute value. Turns a signed delta (e.g. the
|
||||
//! negative-side split of a price change) into a positive magnitude, e.g.
|
||||
//! RSI-class signals' `loss = Abs(Min(delta, 0))`. Emits `None` until its
|
||||
//! input has a value.
|
||||
|
||||
use aura_core::{Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, ScalarKind};
|
||||
|
||||
/// One-input f64 absolute value, `value.abs()`. Emits `None` until its input
|
||||
/// has a value (warm-up filter, C8).
|
||||
pub struct Abs {
|
||||
out: [Cell; 1],
|
||||
}
|
||||
|
||||
impl Abs {
|
||||
pub fn new() -> Self {
|
||||
Self { out: [Cell::from_f64(0.0)] }
|
||||
}
|
||||
|
||||
pub fn builder() -> PrimitiveBuilder {
|
||||
PrimitiveBuilder::new(
|
||||
"Abs",
|
||||
NodeSchema {
|
||||
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "value".into() }],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
},
|
||||
|_| Box::new(Abs::new()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Abs {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for Abs {
|
||||
fn lookbacks(&self) -> Vec<usize> {
|
||||
vec![1]
|
||||
}
|
||||
|
||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {
|
||||
let w = ctx.f64_in(0);
|
||||
if w.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.out[0] = Cell::from_f64(w[0].abs());
|
||||
Some(&self.out)
|
||||
}
|
||||
|
||||
fn label(&self) -> String {
|
||||
"Abs".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use aura_core::{AnyColumn, Scalar, Timestamp};
|
||||
|
||||
#[test]
|
||||
fn abs_of_negative_and_positive_is_the_magnitude() {
|
||||
let mut a = Abs::new();
|
||||
let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 1)];
|
||||
inputs[0].push(Scalar::f64(-3.0)).unwrap();
|
||||
assert_eq!(a.eval(Ctx::new(&inputs, Timestamp(0))), Some([Cell::from_f64(3.0)].as_slice()));
|
||||
inputs[0].push(Scalar::f64(3.0)).unwrap();
|
||||
assert_eq!(a.eval(Ctx::new(&inputs, Timestamp(0))), Some([Cell::from_f64(3.0)].as_slice()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn abs_is_none_until_input_present() {
|
||||
let mut a = Abs::new();
|
||||
let inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 1)];
|
||||
assert_eq!(a.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
//! `Const` — a constant-as-param source: emits a fixed `f64` value every cycle
|
||||
//! its clock input fires, ignoring the clock's actual value. Mirrors `EqConst`'s
|
||||
//! constant-as-param pattern (the constant is a bound param, not topology), but
|
||||
//! as a *source*: a zero-input node never evaluates in the total-push engine
|
||||
//! (C8), so `Const` still needs one input purely to be driven by the sim clock
|
||||
//! — its value is discarded.
|
||||
|
||||
use aura_core::{
|
||||
Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder,
|
||||
ScalarKind,
|
||||
};
|
||||
|
||||
/// Emits the bound `value` every cycle its clock input fires; the clock's own
|
||||
/// value is ignored. Emits `None` until the clock input has fired at least once
|
||||
/// (warm-up filter, C8) — same shape as every other stateless node here.
|
||||
pub struct Const {
|
||||
value: f64,
|
||||
out: [Cell; 1],
|
||||
}
|
||||
|
||||
impl Const {
|
||||
/// Build a constant source bound to `value`.
|
||||
pub fn new(value: f64) -> Self {
|
||||
Self { value, out: [Cell::from_f64(value)] }
|
||||
}
|
||||
|
||||
/// The param-generic recipe for a blueprint primitive: declares `value` and
|
||||
/// one clock input, builds through `Const::new`.
|
||||
pub fn builder() -> PrimitiveBuilder {
|
||||
PrimitiveBuilder::new(
|
||||
"Const",
|
||||
NodeSchema {
|
||||
inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "clock".into() }],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![ParamSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
},
|
||||
|p| Box::new(Const::new(p[0].f64())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for Const {
|
||||
fn lookbacks(&self) -> Vec<usize> {
|
||||
vec![1]
|
||||
}
|
||||
|
||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {
|
||||
let w = ctx.f64_in(0);
|
||||
if w.is_empty() {
|
||||
return None; // not yet warmed up (C8 filter)
|
||||
}
|
||||
self.out[0] = Cell::from_f64(self.value);
|
||||
Some(&self.out)
|
||||
}
|
||||
|
||||
fn label(&self) -> String {
|
||||
format!("Const({})", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use aura_core::{AnyColumn, Scalar, Timestamp};
|
||||
|
||||
#[test]
|
||||
fn const_emits_bound_value_regardless_of_clock_value() {
|
||||
// The core property: out == value, always, once the clock has fired at
|
||||
// least once — the clock's own reading is never observed in the output.
|
||||
let mut node = Const::new(7.0);
|
||||
let mut inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 1)];
|
||||
|
||||
inputs[0].push(Scalar::f64(-100.0)).unwrap();
|
||||
assert_eq!(node.eval(Ctx::new(&inputs, Timestamp(0))), Some([Cell::from_f64(7.0)].as_slice()));
|
||||
|
||||
inputs[0].push(Scalar::f64(42.0)).unwrap();
|
||||
assert_eq!(node.eval(Ctx::new(&inputs, Timestamp(0))), Some([Cell::from_f64(7.0)].as_slice()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_is_none_until_clock_present() {
|
||||
let mut node = Const::new(7.0);
|
||||
let inputs = vec![AnyColumn::with_capacity(ScalarKind::F64, 1)];
|
||||
assert_eq!(node.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
//! `Div` — two-input f64 quotient (input 0 divided by input 1), the ratio
|
||||
//! combinator RSI-class signals need (`avg_gain / avg_loss`). IEEE-754
|
||||
//! division, no error channel: `x / 0.0 -> inf` (signed by `x`'s sign),
|
||||
//! `0.0 / 0.0 -> NaN` — Rust's native `f64` division already follows this,
|
||||
//! so no special-casing is needed.
|
||||
|
||||
use aura_core::{Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, ScalarKind};
|
||||
|
||||
/// Two-input f64 quotient: input 0 divided by input 1 (IEEE-754). Emits `None`
|
||||
/// until both inputs have a value.
|
||||
pub struct Div {
|
||||
out: [Cell; 1],
|
||||
}
|
||||
|
||||
impl Div {
|
||||
/// Build a `Div` node.
|
||||
pub fn new() -> Self {
|
||||
Self { out: [Cell::from_f64(0.0)] }
|
||||
}
|
||||
|
||||
/// The param-generic recipe for a blueprint primitive: paramless, builds
|
||||
/// through `Div::new`.
|
||||
pub fn builder() -> PrimitiveBuilder {
|
||||
PrimitiveBuilder::new(
|
||||
"Div",
|
||||
NodeSchema {
|
||||
inputs: vec![
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "lhs".into() },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "rhs".into() },
|
||||
],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
},
|
||||
|_| Box::new(Div::new()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Div {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for Div {
|
||||
fn lookbacks(&self) -> Vec<usize> {
|
||||
vec![1, 1]
|
||||
}
|
||||
|
||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {
|
||||
let a = ctx.f64_in(0);
|
||||
let b = ctx.f64_in(1);
|
||||
if a.is_empty() || b.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.out[0] = Cell::from_f64(a[0] / b[0]);
|
||||
Some(&self.out)
|
||||
}
|
||||
|
||||
fn label(&self) -> String {
|
||||
"Div".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use aura_core::{AnyColumn, Scalar, Timestamp};
|
||||
|
||||
#[test]
|
||||
fn div_is_quotient_once_both_inputs_present() {
|
||||
let mut div = Div::new();
|
||||
let mut inputs = vec![
|
||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||
];
|
||||
|
||||
// only input 0 present -> None
|
||||
inputs[0].push(Scalar::f64(10.0)).unwrap();
|
||||
assert_eq!(div.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
||||
|
||||
// both present -> a / b
|
||||
inputs[1].push(Scalar::f64(4.0)).unwrap();
|
||||
assert_eq!(div.eval(Ctx::new(&inputs, Timestamp(0))), Some([Cell::from_f64(2.5)].as_slice()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_by_zero_is_signed_infinity_zero_over_zero_is_nan() {
|
||||
// The IEEE-754 property that makes Div safe with no error channel: a
|
||||
// nonzero numerator over zero yields a signed infinity, and 0/0 yields
|
||||
// NaN — both representable as plain f64, never a panic or an Err.
|
||||
let mut div = Div::new();
|
||||
let mut inputs = vec![
|
||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||
];
|
||||
|
||||
inputs[0].push(Scalar::f64(5.0)).unwrap();
|
||||
inputs[1].push(Scalar::f64(0.0)).unwrap();
|
||||
let got = div.eval(Ctx::new(&inputs, Timestamp(0))).unwrap();
|
||||
assert_eq!(got[0].f64(), f64::INFINITY);
|
||||
|
||||
inputs[0].push(Scalar::f64(0.0)).unwrap();
|
||||
inputs[1].push(Scalar::f64(0.0)).unwrap();
|
||||
let got = div.eval(Ctx::new(&inputs, Timestamp(0))).unwrap();
|
||||
assert!(got[0].f64().is_nan());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_slots_are_named_lhs_rhs() {
|
||||
let names: Vec<String> = Div::builder().schema().inputs.iter().map(|p| p.name.clone()).collect();
|
||||
assert_eq!(names, ["lhs", "rhs"]);
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,17 @@
|
||||
//! The first block lands with the walking skeleton: [`Sma`], the simple moving
|
||||
//! average — a worked producer node proving the `aura-core` `Node` contract.
|
||||
|
||||
mod abs;
|
||||
mod add;
|
||||
mod and;
|
||||
mod bias;
|
||||
mod carry_cost;
|
||||
mod const_node;
|
||||
mod constant_cost;
|
||||
mod cost;
|
||||
mod cost_sum;
|
||||
mod delay;
|
||||
mod div;
|
||||
mod ema;
|
||||
mod eqconst;
|
||||
mod gated_recorder;
|
||||
@@ -32,6 +35,8 @@ mod gt;
|
||||
mod latch;
|
||||
mod lincomb;
|
||||
mod longonly;
|
||||
mod max;
|
||||
mod min;
|
||||
mod mul;
|
||||
mod position_management;
|
||||
mod recorder;
|
||||
@@ -49,10 +54,12 @@ mod stop_rule;
|
||||
mod sub;
|
||||
mod vocabulary;
|
||||
mod vol_slippage_cost;
|
||||
pub use abs::Abs;
|
||||
pub use add::Add;
|
||||
pub use and::And;
|
||||
pub use bias::Bias;
|
||||
pub use carry_cost::CarryCost;
|
||||
pub use const_node::Const;
|
||||
pub use constant_cost::ConstantCost;
|
||||
pub use cost::{
|
||||
cost_node_builder, ChargeMode, CostNode, CostRunner, COST_FIELD_NAMES, COST_WIDTH,
|
||||
@@ -60,6 +67,7 @@ pub use cost::{
|
||||
};
|
||||
pub use cost_sum::CostSum;
|
||||
pub use delay::Delay;
|
||||
pub use div::Div;
|
||||
pub use ema::Ema;
|
||||
pub use eqconst::EqConst;
|
||||
pub use gated_recorder::GatedRecorder;
|
||||
@@ -67,6 +75,8 @@ pub use gt::Gt;
|
||||
pub use latch::Latch;
|
||||
pub use lincomb::LinComb;
|
||||
pub use longonly::LongOnly;
|
||||
pub use max::Max;
|
||||
pub use min::Min;
|
||||
pub use mul::Mul;
|
||||
pub use position_management::{
|
||||
ExitReason, FIELD_NAMES as PM_FIELD_NAMES, PositionManagement, RECORD_KINDS as PM_RECORD_KINDS,
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
//! `Max` — two-input f64 pairwise maximum (input 0 vs input 1), e.g. the
|
||||
//! positive-part split of a price change: `gain = Max(delta, 0)`. Distinct
|
||||
//! from `RollingMax`, which reduces a *window* of one input; `Max` combines
|
||||
//! two co-present inputs, mirroring `Add`/`Sub`.
|
||||
|
||||
use aura_core::{Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, ScalarKind};
|
||||
|
||||
/// Two-input f64 pairwise maximum: `input 0 .max(input 1)`. Emits `None` until
|
||||
/// both inputs have a value.
|
||||
pub struct Max {
|
||||
out: [Cell; 1],
|
||||
}
|
||||
|
||||
impl Max {
|
||||
pub fn new() -> Self {
|
||||
Self { out: [Cell::from_f64(0.0)] }
|
||||
}
|
||||
|
||||
pub fn builder() -> PrimitiveBuilder {
|
||||
PrimitiveBuilder::new(
|
||||
"Max",
|
||||
NodeSchema {
|
||||
inputs: vec![
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "lhs".into() },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "rhs".into() },
|
||||
],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
},
|
||||
|_| Box::new(Max::new()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Max {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for Max {
|
||||
fn lookbacks(&self) -> Vec<usize> {
|
||||
vec![1, 1]
|
||||
}
|
||||
|
||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {
|
||||
let a = ctx.f64_in(0);
|
||||
let b = ctx.f64_in(1);
|
||||
if a.is_empty() || b.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.out[0] = Cell::from_f64(a[0].max(b[0]));
|
||||
Some(&self.out)
|
||||
}
|
||||
|
||||
fn label(&self) -> String {
|
||||
"Max".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use aura_core::{AnyColumn, Scalar, Timestamp};
|
||||
|
||||
#[test]
|
||||
fn max_is_the_pairwise_larger_once_both_inputs_present() {
|
||||
let mut m = Max::new();
|
||||
let mut inputs = vec![
|
||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||
];
|
||||
|
||||
// only input 0 present -> None
|
||||
inputs[0].push(Scalar::f64(2.0)).unwrap();
|
||||
assert_eq!(m.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
||||
|
||||
// both present -> max(a, b), either order
|
||||
inputs[1].push(Scalar::f64(-1.0)).unwrap();
|
||||
assert_eq!(m.eval(Ctx::new(&inputs, Timestamp(0))), Some([Cell::from_f64(2.0)].as_slice()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_slots_are_named_lhs_rhs() {
|
||||
let names: Vec<String> = Max::builder().schema().inputs.iter().map(|p| p.name.clone()).collect();
|
||||
assert_eq!(names, ["lhs", "rhs"]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
//! `Min` — two-input f64 pairwise minimum (input 0 vs input 1), the companion
|
||||
//! to `Max`, e.g. the negative-part split of a price change:
|
||||
//! `loss = Abs(Min(delta, 0))`. Distinct from `RollingMin`, which reduces a
|
||||
//! *window* of one input; `Min` combines two co-present inputs, mirroring
|
||||
//! `Add`/`Sub`.
|
||||
|
||||
use aura_core::{Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, ScalarKind};
|
||||
|
||||
/// Two-input f64 pairwise minimum: `input 0 .min(input 1)`. Emits `None` until
|
||||
/// both inputs have a value.
|
||||
pub struct Min {
|
||||
out: [Cell; 1],
|
||||
}
|
||||
|
||||
impl Min {
|
||||
pub fn new() -> Self {
|
||||
Self { out: [Cell::from_f64(0.0)] }
|
||||
}
|
||||
|
||||
pub fn builder() -> PrimitiveBuilder {
|
||||
PrimitiveBuilder::new(
|
||||
"Min",
|
||||
NodeSchema {
|
||||
inputs: vec![
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "lhs".into() },
|
||||
PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "rhs".into() },
|
||||
],
|
||||
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::F64 }],
|
||||
params: vec![],
|
||||
},
|
||||
|_| Box::new(Min::new()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Min {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for Min {
|
||||
fn lookbacks(&self) -> Vec<usize> {
|
||||
vec![1, 1]
|
||||
}
|
||||
|
||||
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {
|
||||
let a = ctx.f64_in(0);
|
||||
let b = ctx.f64_in(1);
|
||||
if a.is_empty() || b.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.out[0] = Cell::from_f64(a[0].min(b[0]));
|
||||
Some(&self.out)
|
||||
}
|
||||
|
||||
fn label(&self) -> String {
|
||||
"Min".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use aura_core::{AnyColumn, Scalar, Timestamp};
|
||||
|
||||
#[test]
|
||||
fn min_is_the_pairwise_smaller_once_both_inputs_present() {
|
||||
let mut m = Min::new();
|
||||
let mut inputs = vec![
|
||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||
AnyColumn::with_capacity(ScalarKind::F64, 1),
|
||||
];
|
||||
|
||||
// only input 0 present -> None
|
||||
inputs[0].push(Scalar::f64(2.0)).unwrap();
|
||||
assert_eq!(m.eval(Ctx::new(&inputs, Timestamp(0))), None);
|
||||
|
||||
// both present -> min(a, b), either order
|
||||
inputs[1].push(Scalar::f64(-1.0)).unwrap();
|
||||
assert_eq!(m.eval(Ctx::new(&inputs, Timestamp(0))), Some([Cell::from_f64(-1.0)].as_slice()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn input_slots_are_named_lhs_rhs() {
|
||||
let names: Vec<String> = Min::builder().schema().inputs.iter().map(|p| p.name.clone()).collect();
|
||||
assert_eq!(names, ["lhs", "rhs"]);
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,9 @@
|
||||
//! later, additive extension (#156/C20).
|
||||
|
||||
use crate::{
|
||||
Add, And, Bias, CarryCost, ConstantCost, Delay, Ema, EqConst, FixedStop, Gt, Latch, LongOnly,
|
||||
Mul, PositionManagement, Resample, RollingMax, RollingMin, Scale, Sizer, Sma, Sqrt, Sub,
|
||||
VolSlippageCost,
|
||||
Abs, Add, And, Bias, CarryCost, Const, ConstantCost, Delay, Div, Ema, EqConst, FixedStop, Gt,
|
||||
Latch, LongOnly, Max, Min, Mul, PositionManagement, Resample, RollingMax, RollingMin, Scale,
|
||||
Sizer, Sma, Sqrt, Sub, VolSlippageCost,
|
||||
};
|
||||
use aura_core::PrimitiveBuilder;
|
||||
|
||||
@@ -58,18 +58,23 @@ macro_rules! std_vocabulary_roster {
|
||||
}
|
||||
|
||||
std_vocabulary_roster! {
|
||||
"Abs" => Abs,
|
||||
"Add" => Add,
|
||||
"And" => And,
|
||||
"Bias" => Bias,
|
||||
"CarryCost" => CarryCost,
|
||||
"Const" => Const,
|
||||
"ConstantCost" => ConstantCost,
|
||||
"Delay" => Delay,
|
||||
"Div" => Div,
|
||||
"EMA" => Ema,
|
||||
"EqConst" => EqConst,
|
||||
"FixedStop" => FixedStop,
|
||||
"Gt" => Gt,
|
||||
"Latch" => Latch,
|
||||
"LongOnly" => LongOnly,
|
||||
"Max" => Max,
|
||||
"Min" => Min,
|
||||
"Mul" => Mul,
|
||||
"PositionManagement" => PositionManagement,
|
||||
"Resample" => Resample,
|
||||
@@ -124,7 +129,7 @@ mod tests {
|
||||
assert!(!std_vocabulary_types().contains(&"LinComb")); // construction-arg node
|
||||
assert!(!std_vocabulary_types().contains(&"Recorder")); // sink
|
||||
assert!(!std_vocabulary_types().contains(&"nope"));
|
||||
// count guard: pins the roster at exactly 23 entries
|
||||
assert_eq!(std_vocabulary_types().len(), 23);
|
||||
// count guard: pins the roster at exactly 28 entries
|
||||
assert_eq!(std_vocabulary_types().len(), 28);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user