Files
Aura/crates/aura-std/src/and.rs
T
claude a32dc38d18 feat(std): meaning lines for all 27 shipped node schemas
C29 compile/unit seam, task 3 of the self-description plan: every
aura-std NodeSchema literal threads its one-line doc. Four texts were
corrected against the actual eval/finalize semantics rather than taken
from the plan table verbatim: Delay is a lag-N register (not one-step),
GatedRecorder flushes an ungated final row at finalize, Latch is a
level-sensitive set/reset register (captures no input), SeriesReducer
emits its single summary row at finalize (not per cycle).

Gate: cargo build -p aura-std --lib clean; full std test run follows at
the all-crates gate once strategy/backtest/engine thread their sites
(the earlier per-crate test gate was unsatisfiable in isolation --
std's dev-deps pull crates whose sites belong to later tasks).

refs #316
2026-07-23 15:25:16 +02:00

128 lines
4.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! `And` — a stateless `bool × bool -> bool` conjunction: `out = (a && b)`.
//!
//! The **bool-input twin** of `Gt` (which *emits* a bool from two `f64` legs);
//! `And` *consumes* two bools and ANDs them. Its purpose in the session-breakout
//! strategy is `entry = breakout && isBar3`: a fresh-15m-close breakout that
//! lands exactly on the third bar of the session.
//!
//! The **seed of the logic family** — `Or` and `Not` would each be their own
//! node type, never a swept op param (the operator is topology, like the
//! relational comparators). Two `bool` inputs (`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, PortSpec, PrimitiveBuilder, ScalarKind};
/// Stateless `bool × bool -> bool` conjunction: emits `a && b` each cycle.
/// Emits `None` until **both** inputs have a value (warm-up gate, C8).
pub struct And {
out: [Cell; 1],
}
impl And {
/// Build an `And` node.
pub fn new() -> Self {
Self { out: [Cell::from_bool(false)] }
}
/// The param-generic recipe for a blueprint primitive: paramless, builds
/// through `And::new`.
pub fn builder() -> PrimitiveBuilder {
PrimitiveBuilder::new(
"And",
NodeSchema {
inputs: vec![
PortSpec { kind: ScalarKind::Bool, firing: Firing::Any, name: "a".into() },
PortSpec { kind: ScalarKind::Bool, firing: Firing::Any, name: "b".into() },
],
output: vec![FieldSpec { name: "value".into(), kind: ScalarKind::Bool }],
params: vec![],
doc: "logical AND of two boolean series",
},
|_| Box::new(And::new()),
)
}
}
impl Default for And {
fn default() -> Self {
Self::new()
}
}
impl Node for And {
fn lookbacks(&self) -> Vec<usize> {
vec![1, 1]
}
fn eval(&mut self, ctx: Ctx<'_>) -> Option<&[Cell]> {
let a = ctx.bool_in(0);
let b = ctx.bool_in(1);
if a.is_empty() || b.is_empty() {
return None; // not yet warmed up — both legs required (C8 filter)
}
self.out[0] = Cell::from_bool(a[0] && b[0]);
Some(&self.out)
}
fn label(&self) -> String {
"And".to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use aura_core::{AnyColumn, Scalar, Timestamp};
#[test]
fn and_is_conjunction_once_both_inputs_present() {
// The core property: out == (a && b). The full truth table is pinned —
// true exactly when BOTH legs are true, false on every other row. This is
// what makes `entry = breakout && isBar3` fire only on a breakout that
// lands on the third bar.
let mut node = And::new();
let mut inputs = vec![
AnyColumn::with_capacity(ScalarKind::Bool, 1),
AnyColumn::with_capacity(ScalarKind::Bool, 1),
];
let a_feed = [false, true, false, true];
let b_feed = [false, false, true, true];
// F,F T,F F,T T,T
let expect = [false, false, false, true];
for ((av, bv), want) in a_feed.iter().zip(b_feed.iter()).zip(expect) {
inputs[0].push(Scalar::bool(*av)).unwrap();
inputs[1].push(Scalar::bool(*bv)).unwrap();
let got = node.eval(Ctx::new(&inputs, Timestamp(0)));
assert_eq!(got, Some([Cell::from_bool(want)].as_slice()));
}
}
#[test]
fn and_is_none_until_both_inputs_present() {
// Both-inputs warm-up gate (C8), like `Gt`: only one leg present -> None.
let mut node = And::new();
let mut inputs = vec![
AnyColumn::with_capacity(ScalarKind::Bool, 1),
AnyColumn::with_capacity(ScalarKind::Bool, 1),
];
// only input 0 present -> None
inputs[0].push(Scalar::bool(true)).unwrap();
assert_eq!(node.eval(Ctx::new(&inputs, Timestamp(0))), None);
// both present -> the conjunction (true && true == true)
inputs[1].push(Scalar::bool(true)).unwrap();
assert_eq!(node.eval(Ctx::new(&inputs, Timestamp(0))), Some([Cell::from_bool(true)].as_slice()));
}
#[test]
fn input_slots_are_named_a_b() {
let a = And::builder();
let names: Vec<&str> = a.schema().inputs.iter().map(|p| p.name.as_str()).collect();
assert_eq!(names, ["a", "b"]);
}
}