Sweep closure drops named binding back to positional + manual name re-zip #57

Closed
opened 2026-06-11 09:12:26 +02:00 by Brummel · 3 comments
Owner

Surfaced by the milestone fieldtest "The World — parameter-space & sweep"
(docs/specs/fieldtest-milestone-the-world-param-sweep.md, scenario mw_2).

Friction

.axis("sma_cross.fast", [2,3]).axis(...).sweep(run) resolves the named axes
to a positional grid — but the per-point run closure receives a bare
&[Scalar] and must call bootstrap_with_params(point.to_vec()) (positional)
and rebuild the blueprint by hand. To put readable knob names into the per-point
RunManifest.params, the author must re-zip param_space() names onto the point
themselves (the aura sweep CLI does exactly this zip by hand internally).

So the named-binding ergonomics stop at the grid boundary: inside the sweep the
author is back to positional vectors and manual name re-zipping, re-deriving
information param_space() already holds. The mw_2 comparison works, but only
because the consumer re-zips names onto pt.params for display.

Direction (no decision made here)

Consider threading a named view of the point into the closure — e.g. a small
Point with get(name), or having SweepPoint carry the param names — so a
consumer need not re-zip from param_space(). Pairs with the #32/#33 sweep
ergonomics surface. Low-to-medium; strikeable.

Surfaced by the milestone fieldtest "The World — parameter-space & sweep" (docs/specs/fieldtest-milestone-the-world-param-sweep.md, scenario mw_2). ## Friction `.axis("sma_cross.fast", [2,3]).axis(...).sweep(run)` resolves the **named** axes to a positional grid — but the per-point `run` closure receives a bare `&[Scalar]` and must call `bootstrap_with_params(point.to_vec())` (positional) and rebuild the blueprint by hand. To put readable knob names into the per-point `RunManifest.params`, the author must re-zip `param_space()` names onto the point themselves (the `aura sweep` CLI does exactly this zip by hand internally). So the named-binding ergonomics stop at the grid boundary: inside the sweep the author is back to positional vectors and manual name re-zipping, re-deriving information `param_space()` already holds. The mw_2 comparison works, but only because the consumer re-zips names onto `pt.params` for display. ## Direction (no decision made here) Consider threading a named view of the point into the closure — e.g. a small `Point` with `get(name)`, or having `SweepPoint` carry the param names — so a consumer need not re-zip from `param_space()`. Pairs with the #32/#33 sweep ergonomics surface. Low-to-medium; strikeable.
Brummel added the idea label 2026-06-11 09:12:26 +02:00
Author
Owner

Pairs with #52 — the two halves of one sweep-ergonomics surface (both touch sweep.rs and the named↔positional boundary). A named SweepPoint view threaded into the per-point closure (this issue) is the prerequisite for #52's RandomSpace to report readable per-point params. Plan them together, #57 first.

Pairs with #52 — the two halves of one sweep-ergonomics surface (both touch sweep.rs and the named↔positional boundary). A named `SweepPoint` view threaded into the per-point closure (this issue) is the prerequisite for #52's `RandomSpace` to report readable per-point params. Plan them together, #57 first.
Brummel added this to the The World, part II — orchestration families milestone 2026-06-14 23:08:30 +02:00
Author
Owner

Design reconciliation (specify, in-context entry)

Spec: 0046-sweep-named-binding. The forks below were listed open on this
issue ("Direction (no decision made here)"); they were resolved in the
in-context design discussion (brainstorm) on 2026-06-15.

  • Fork: named-view attach point / closure-signature change (issue floated
    "a small Point with get(name)" vs "SweepPoint carrying the param names")
    → resolved to a signature-preserving derived view (approach C): the run
    closure stays Fn(&[Scalar]) -> RunReport byte-for-byte (honouring #52's
    #71-firewall constraint), and names are paired with the positional point by a
    derived view, not threaded into the closure (approach A, rejected because it
    would touch the frozen signature and force a mc/walk_forward cascade).
    Provenance: user chose "C" (2026-06-15), after being shown A vs C with
    the trade-offs.

  • Fork: scope of the named view (just remove the in-closure re-zip, vs also
    make the returned family readable by knob name) → resolved to both: (i)
    the in-closure manifest re-zip is removed, and (ii) SweepFamily carries the
    param-space so a returned family is readable by name post-hoc.
    Provenance: user "dann mach beides" (2026-06-15).

  • Fork: named view as a type vs a free function → resolved to a free
    function
    zip_params(space, point) -> Vec<(String, Scalar)> (no new type,
    no lifetime); the borrowed NamedPoint<'a> struct was rejected as ceremony.
    Provenance: user "Das NamedPoint kommt mir auch umständlich vor"
    (2026-06-15), ratified with "ja, mach das fertig".

  • Fork: where the lossy f64 collapse lives → resolved to the manifest
    constructor
    (sim_optimal_manifest takes typed Vec<(String, Scalar)> and
    collapses to f64 internally); the view stays purely typed (no f64 method on
    it).
    Provenance: user rejected an as_f64_pairs view method as "festgenagelt
    auf f64"
    (2026-06-15).

## Design reconciliation (specify, in-context entry) Spec: `0046-sweep-named-binding`. The forks below were listed open on this issue ("Direction (no decision made here)"); they were resolved in the in-context design discussion (brainstorm) on 2026-06-15. - **Fork: named-view attach point / closure-signature change** (issue floated "a small Point with get(name)" vs "SweepPoint carrying the param names") → resolved to a **signature-preserving derived view (approach C)**: the run closure stays `Fn(&[Scalar]) -> RunReport` byte-for-byte (honouring #52's #71-firewall constraint), and names are paired with the positional point by a derived view, not threaded into the closure (approach A, rejected because it would touch the frozen signature and force a mc/walk_forward cascade). Provenance: user chose **"C"** (2026-06-15), after being shown A vs C with the trade-offs. - **Fork: scope of the named view** (just remove the in-closure re-zip, vs also make the returned family readable by knob name) → resolved to **both**: (i) the in-closure manifest re-zip is removed, and (ii) `SweepFamily` carries the param-space so a returned family is readable by name post-hoc. Provenance: user **"dann mach beides"** (2026-06-15). - **Fork: named view as a type vs a free function** → resolved to a **free function** `zip_params(space, point) -> Vec<(String, Scalar)>` (no new type, no lifetime); the borrowed `NamedPoint<'a>` struct was rejected as ceremony. Provenance: user **"Das NamedPoint kommt mir auch umständlich vor"** (2026-06-15), ratified with **"ja, mach das fertig"**. - **Fork: where the lossy f64 collapse lives** → resolved to the **manifest constructor** (`sim_optimal_manifest` takes typed `Vec<(String, Scalar)>` and collapses to f64 internally); the view stays purely typed (no f64 method on it). Provenance: user rejected an `as_f64_pairs` view method as **"festgenagelt auf f64"** (2026-06-15).
Author
Owner

Done — shipped in cycle 0046 and audited drift-clean.

A sweep point's param_space() names are now a derived view, not a hand re-zip:

  • aura_core::zip_params(space, point) -> Vec<(String, Scalar)> is the one shared
    projection. The three open-coded re-zips in the CLI (sweep_family,
    sweep_over, run_oos) collapse to one call each.
  • GridSpace retains the ParamSpec list it already receives in new();
    SweepFamily carries it and exposes named_params(i), so a returned family is
    readable by knob name without the blueprint in hand (consumer ii).
  • sim_optimal_manifest now takes typed Vec<(String, Scalar)> and does the
    lossy f64 collapse internally — the manifest's Vec<(String, f64)> field (the
    typed-param-space precursor) stays deferred.

The run-closure signature Fn(&[Scalar]) -> RunReport is byte-unchanged, so #52's
RandomSpace plugs into the same sweep() execution layer with zero
reconciliation (#71 firewall held). Behaviour-preserving: aura sweep /
walk-forward / run output is byte-identical.

Commits: 119221c (spec), cd9f7e4 (plan), fb8cabf (implement), b188773 (audit —
drift-clean). New tests: zip_params ×2, sweep_family_carries_param_space,
family_named_params_round_trips; full workspace suite + clippy green.

Done — shipped in cycle 0046 and audited drift-clean. A sweep point's `param_space()` names are now a derived view, not a hand re-zip: - `aura_core::zip_params(space, point) -> Vec<(String, Scalar)>` is the one shared projection. The three open-coded re-zips in the CLI (`sweep_family`, `sweep_over`, `run_oos`) collapse to one call each. - `GridSpace` retains the `ParamSpec` list it already receives in `new()`; `SweepFamily` carries it and exposes `named_params(i)`, so a returned family is readable by knob name without the blueprint in hand (consumer ii). - `sim_optimal_manifest` now takes typed `Vec<(String, Scalar)>` and does the lossy f64 collapse internally — the manifest's `Vec<(String, f64)>` field (the typed-param-space precursor) stays deferred. The run-closure signature `Fn(&[Scalar]) -> RunReport` is byte-unchanged, so #52's `RandomSpace` plugs into the same `sweep()` execution layer with zero reconciliation (#71 firewall held). Behaviour-preserving: `aura sweep` / walk-forward / run output is byte-identical. Commits: 119221c (spec), cd9f7e4 (plan), fb8cabf (implement), b188773 (audit — drift-clean). New tests: `zip_params` ×2, `sweep_family_carries_param_space`, `family_named_params_round_trips`; full workspace suite + clippy green.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#57