feat(aura-market, aura-std, aura-strategy): arg-bearing builders go zero-arg
Session, LinComb, and CostSum — the three builders the roster scope doc
excludes — re-shape onto the args seam (refs #271): builder() is now
zero-arg and returns a pending recipe (Session: tz + open; LinComb:
arity; CostSum: n_costs), the full signature forms in make, and
configured(...) is the Rust-path convenience producing the identical
recipe (twin-pinned). Session's period_minutes becomes a real ParamSpec
bound by configured instead of a baked struct field — same behaviour,
now sweepable when left open. Session::new and SessionFrankfurt are
untouched.
All builder(n)-form call sites move mechanically to configured(n):
aura-runner member.rs (wrap_r), aura-composites (vol_stop/cost_graph),
aura-engine blueprint.rs + e2e tests, aura-ingest breakout example,
and the crates' own tests. aura-std drops its unused chrono/chrono-tz
deps (stale since the b39fd63 session move).
The roster is deliberately untouched here: rostering before the op
seam lands would expose unconfigured pending builders to add_node.
This commit is contained in:
@@ -7,13 +7,6 @@ publish.workspace = true
|
||||
|
||||
[dependencies]
|
||||
aura-core = { path = "../aura-core" }
|
||||
# DST-correct wall-clock math for the `Session` node. A vetted
|
||||
# standard crate for timezone/DST — never hand-rolled (C16 per-case policy).
|
||||
# `chrono` is already a transitive workspace dep (0.4 via aura-ingest's
|
||||
# data-server); aligned to that major. `chrono-tz` brings the IANA `Europe/Berlin`
|
||||
# zone + DST transitions.
|
||||
chrono = { version = "0.4", default-features = false, features = ["clock"] }
|
||||
chrono-tz = { version = "0.10", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
aura-strategy = { path = "../aura-strategy" }
|
||||
|
||||
@@ -7,10 +7,15 @@
|
||||
//! indexed `F64` knobs that `Composite::param_space` aggregates (C8/C12/C19).
|
||||
|
||||
use aura_core::{
|
||||
Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder,
|
||||
ScalarKind,
|
||||
ArgKind, ArgSpec, ArgValue, Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec,
|
||||
PrimitiveBuilder, ScalarKind,
|
||||
};
|
||||
|
||||
/// The declared construction args of a `LinComb` recipe: `arity` fixes the
|
||||
/// node's input/param count (topology, C19), taken through the `args`
|
||||
/// channel instead of a Rust-side builder parameter.
|
||||
const LINCOMB_ARGS: &[ArgSpec] = &[ArgSpec { name: "arity", kind: ArgKind::Count }];
|
||||
|
||||
/// Weighted sum of `N` f64 inputs: `Σ weights[i] · input[i]`. The `weights` are
|
||||
/// construction parameters that configure the node and fix its arity
|
||||
/// (`weights.len()` inputs, in slot order). Emits `None` until *all* inputs
|
||||
@@ -40,10 +45,29 @@ impl LinComb {
|
||||
Self { weights, out: [Cell::from_f64(0.0)] }
|
||||
}
|
||||
|
||||
/// The param-generic recipe for a blueprint primitive. The `arity` is topology
|
||||
/// (fixed per blueprint, C19), taken as a builder arg; only the weight *values*
|
||||
/// are injected, slot by slot, through `LinComb::new` (the single sizing gate).
|
||||
pub fn builder(arity: usize) -> PrimitiveBuilder {
|
||||
/// Roster factory: zero-arg, arg-bearing. `arity` is topology (fixed per
|
||||
/// blueprint, C19), now taken through the `args` channel instead of a
|
||||
/// Rust-side parameter.
|
||||
pub fn builder() -> PrimitiveBuilder {
|
||||
PrimitiveBuilder::pending(
|
||||
"LinComb",
|
||||
"linear combination of its inputs with constant weights",
|
||||
LINCOMB_ARGS,
|
||||
Self::make,
|
||||
)
|
||||
}
|
||||
|
||||
/// Turn a validated `arity` into the real, arity-sized signature; only
|
||||
/// the weight *values* are injected, slot by slot, through `LinComb::new`
|
||||
/// (the single sizing gate) once params are bound/built.
|
||||
fn make(values: &[(String, ArgValue)]) -> PrimitiveBuilder {
|
||||
let arity = values
|
||||
.iter()
|
||||
.find_map(|(n, v)| match (n.as_str(), v) {
|
||||
("arity", ArgValue::Count(n)) => Some(*n),
|
||||
_ => None,
|
||||
})
|
||||
.expect("try_args validated `arity` as ArgKind::Count before calling make");
|
||||
let inputs = (0..arity)
|
||||
.map(|i| PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: format!("term[{i}]") })
|
||||
.collect();
|
||||
@@ -63,6 +87,13 @@ impl LinComb {
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Rust-path convenience — same recipe as the data path (twin identity).
|
||||
pub fn configured(arity: usize) -> PrimitiveBuilder {
|
||||
Self::builder()
|
||||
.try_args(&[("arity".to_string(), arity.to_string())])
|
||||
.expect("configured: a positive arity is always the canonical strict-form Count string")
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for LinComb {
|
||||
@@ -149,7 +180,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn input_slots_are_named_term_index() {
|
||||
let lc = LinComb::builder(3);
|
||||
let lc = LinComb::configured(3);
|
||||
let names: Vec<String> = lc.schema().inputs.iter().map(|p| p.name.clone()).collect();
|
||||
assert_eq!(names, ["term[0]", "term[1]", "term[2]"]);
|
||||
}
|
||||
@@ -157,7 +188,7 @@ mod tests {
|
||||
#[test]
|
||||
fn chained_bind_reconstructs_positional_vector() {
|
||||
// bind BOTH weights, in reverse slot order, to DISTINCT values; build empty.
|
||||
let builder = LinComb::builder(2)
|
||||
let builder = LinComb::configured(2)
|
||||
.bind("weights[1]", Scalar::f64(2.0))
|
||||
.bind("weights[0]", Scalar::f64(0.5));
|
||||
assert!(builder.params().is_empty());
|
||||
@@ -173,7 +204,7 @@ mod tests {
|
||||
assert_eq!(lc.eval(Ctx::new(&inputs, Timestamp(0))), Some([Cell::from_f64(11.0)].as_slice()));
|
||||
|
||||
// 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::configured(2).bind("weights[0]", Scalar::f64(0.5));
|
||||
assert_eq!(
|
||||
partial.params().iter().map(|p| p.name.as_str()).collect::<Vec<_>>(),
|
||||
["weights[1]"],
|
||||
@@ -187,4 +218,14 @@ mod tests {
|
||||
inputs2[1].push(Scalar::f64(3.0)).unwrap();
|
||||
assert_eq!(lc2.eval(Ctx::new(&inputs2, Timestamp(0))), Some([Cell::from_f64(11.0)].as_slice()));
|
||||
}
|
||||
|
||||
/// Twin identity (spec §aura-std): `configured(arity)` is exactly
|
||||
/// `builder().try_args([("arity", arity)])`.
|
||||
#[test]
|
||||
fn lincomb_configured_twin_equals_builder_try_args() {
|
||||
let via_configured = LinComb::configured(3);
|
||||
let via_try_args = LinComb::builder().try_args(&[("arity".into(), "3".into())]).expect("valid arity configures");
|
||||
assert_eq!(via_configured.construction_args(), via_try_args.construction_args());
|
||||
assert_eq!(via_configured.schema(), via_try_args.schema());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ mod tests {
|
||||
vec![ParamSpec { name: "scale".into(), kind: ScalarKind::F64 }],
|
||||
);
|
||||
// vector knob expands flat to N indexed F64 entries
|
||||
let lc = LinComb::builder(2).schema().params.clone();
|
||||
let lc = LinComb::configured(2).schema().params.clone();
|
||||
assert_eq!(lc.len(), 2);
|
||||
assert_eq!(lc[0].name, "weights[0]");
|
||||
assert_eq!(lc[1].name, "weights[1]");
|
||||
|
||||
Reference in New Issue
Block a user