feat(aura-core,aura-std,aura-engine): declare node tunable params (#30)
Cycle A of milestone 'The World — parameter-space & sweep'. A node now declares its tunable parameters in its C8 schema, and a blueprint aggregates them into one flat, inspectable param-space — the root that unlocks the C12 orchestration axes (#31 bind, #32 sweep), filling the gap C8/C23 name 'deliberately not in the schema yet'. - aura-core: ParamSpec { name: String, kind: ScalarKind } as a third schema- declaration type; NodeSchema gains a third field 'params'. name is String (not &'static like FieldSpec) because a vector knob carries a runtime index and aggregation prefixes the composite path. - aura-std: Sma declares [length:I64], Exposure [scale:F64], LinComb expands to N flat [weights[i]:F64] (N = its input arity, topology-fixed per C19); Sub/Add/ SimBroker/Recorder declare none (pip_size is metadata, C10/C15; Recorder is wiring). - aura-engine: Blueprint::param_space() walks the graph-as-data depth-first in lower_items order, path-qualifying via the already-public Composite::name() — a read-only projection (C9). compile/inline_composite/lower_items are untouched, so the compilat stays bit-identical (composite_sma_cross_runs_bit_identical_to_hand_wired and the golden render tests stay green). Design: param identity is positional (slot after the deterministic inline order, C23 'by index not name'); the path-qualified name is a non-load-bearing debug symbol — same-type siblings in one composite share a name, uniqueness is at the slot. Flat over a structured arity-bearing ParamSpec because flattening is unavoidable (a sweep enumerates a flat point-space) and structure-in-the-runtime is the nested-composite reading C23 rejects (see spec 0015 for the full rationale). Scope is declaration + aggregation + inspection only; binding (#31), sweep enumeration (#32), search-range, validity-constraint, and default-range/slider are deferred. An E2E test pins the load-bearing C23/#31 invariant: param_space() slot order mirrors compile()'s flat-node param order, kind-by-slot, on the SMA-cross harness. fieldtests/ (excluded crates) left as frozen snapshots. Verified: cargo build/test --workspace green (64 tests), clippy --all-targets -D warnings clean; compile/inline diff empty. closes #30
This commit is contained in:
@@ -39,5 +39,5 @@ pub use any::AnyColumn;
|
||||
pub use column::{Column, Window};
|
||||
pub use ctx::Ctx;
|
||||
pub use error::KindMismatch;
|
||||
pub use node::{FieldSpec, Firing, InputSpec, Node, NodeSchema};
|
||||
pub use node::{FieldSpec, Firing, InputSpec, Node, NodeSchema, ParamSpec};
|
||||
pub use scalar::{Scalar, ScalarKind, Timestamp};
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
//! and its output record (0..K base columns, C7; an empty output declares a
|
||||
//! pure consumer / sink role, C8) via `schema`, and computes one cycle's row
|
||||
//! via `eval`.
|
||||
//! Tunable params (C12/C19) are deliberately not part of the schema yet — see
|
||||
//! spec 0002's "Out of scope".
|
||||
//! Tunable params (C12/C19) are declared via `params` (cycle 0015): each node's
|
||||
//! typed knobs, which `Blueprint::param_space` aggregates into the sweep's flat,
|
||||
//! path-qualified param-space (C8/C23). Identity is positional (slot); the name is
|
||||
//! a non-load-bearing debug symbol.
|
||||
|
||||
use crate::{Ctx, Scalar, ScalarKind};
|
||||
|
||||
@@ -41,6 +43,20 @@ pub struct FieldSpec {
|
||||
pub kind: ScalarKind,
|
||||
}
|
||||
|
||||
/// One declared tunable parameter of a node (C8/C12): its render name and scalar
|
||||
/// kind. The name is a **non-load-bearing** debug symbol (path-qualified at
|
||||
/// aggregation, as `FieldSpec.name` already is); a param's identity is its
|
||||
/// positional slot in the blueprint's aggregated param-space (C23 — by index, not
|
||||
/// by name). Unlike `FieldSpec.name` (`&'static str`), the name is a `String`: a
|
||||
/// vector knob carries a runtime index (`weights[0]`) and aggregation prefixes the
|
||||
/// composite path (`strategy.weights[0]`). Permitted kinds: `I64`/`F64`/`Bool`;
|
||||
/// `Timestamp` is a structural axis (C20), never a numeric knob.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ParamSpec {
|
||||
pub name: String,
|
||||
pub kind: ScalarKind,
|
||||
}
|
||||
|
||||
/// A node's declared interface: its inputs (in order) and its output record — an
|
||||
/// ordered list of named base columns; length 1 is a scalar (the degenerate
|
||||
/// case), and an **empty** `output` (`vec![]`) declares a **pure consumer**
|
||||
@@ -50,6 +66,7 @@ pub struct FieldSpec {
|
||||
pub struct NodeSchema {
|
||||
pub inputs: Vec<InputSpec>,
|
||||
pub output: Vec<FieldSpec>,
|
||||
pub params: Vec<ParamSpec>,
|
||||
}
|
||||
|
||||
/// The universal composable dataflow unit (C8): a **producer, consumer, or both**.
|
||||
@@ -97,7 +114,7 @@ mod tests {
|
||||
struct Bare;
|
||||
impl Node for Bare {
|
||||
fn schema(&self) -> NodeSchema {
|
||||
NodeSchema { inputs: vec![], output: vec![] }
|
||||
NodeSchema { inputs: vec![], output: vec![], params: vec![] }
|
||||
}
|
||||
fn eval(&mut self, _ctx: Ctx<'_>) -> Option<&[Scalar]> {
|
||||
None
|
||||
@@ -105,4 +122,16 @@ mod tests {
|
||||
}
|
||||
assert_eq!(Bare.label(), "node");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schema_carries_declared_params() {
|
||||
let s = NodeSchema {
|
||||
inputs: vec![],
|
||||
output: vec![],
|
||||
params: vec![ParamSpec { name: "length".into(), kind: ScalarKind::I64 }],
|
||||
};
|
||||
assert_eq!(s.params.len(), 1);
|
||||
assert_eq!(s.params[0].name, "length");
|
||||
assert_eq!(s.params[0].kind, ScalarKind::I64);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user