hand_roll_census — reinvented metrics and the canonical function that already does it

Concept

The standing lookup table for the measurement contract’s first hard rule: never re-derive a metric. Each row is a signal-math idiom that was, at some point, hand-rolled in a one-off script, paired with the canonical function in analysis/data_analyzer.py or analysis/statistics.py that already computes it. Before writing np.mean / np.corrcoef / a norm-of-a-diff / a percentile against a log, look here first. The closing section is the closed ledger: idioms that stay hand-rolled on purpose, so they are never re-filed as backlog.

What this spans

  • analysis/data_analyzer.py ([[data_analyzer]]) — the reduction path and the data idioms: windows, reductions, episode runs, divergence, per-step jumps, spatial projection.
  • analysis/statistics.py ([[statistics]]) — the inference layer: correlation, regression, effect size, dispersion, event studies.
  • Thesis: a second definition of a number is a wrong number. Every idiom below has exactly one home; the census is the map from the thing you were about to write to the thing that already exists.

Constituent pages

  • [[data_analyzer]] — the reduction path + data idioms (the home the measurement contract calls “the palace”).
  • [[statistics]] — the two-signal inference layer, split out of data_analyzer on 2026-06-18.
  • [[signal_measurement]] — the retired facade doorway (see the frame-shift footgun below).

Mechanism (where it lives in code)

Both homes are plain modules; import the function directly.

from analysis.data_analyzer import measure, measure_many, series, as_log_view   # the reduction path
from analysis.statistics import pearson_r, cliffs_delta, episode_contrast       # the inference layer

The census — idiom → canonical function

Keyed by idiom, not by the script that reinvented it: the scripts are purged weekly, the idioms recur forever.

Hand-rolled idiomCanonical functionHome
np.sqrt(np.mean(x**2)) (RMS of a metric)measure(source, key, "rms")data_analyzer
np.percentile(x, 99) on a metricmeasure(source, key, "p99")data_analyzer
np.percentile(x, q) at an arbitrary qpercentile(values, q)data_analyzer
np.nanmax(x) / np.nanmedian(x) on a metricmeasure(source, key, "max" | "median")data_analyzer
(sj[op] < FLOOR).mean() (fraction below a floor)measure(..., "frac_below") / near_singular_fraction(values, threshold)data_analyzer / statistics
steady.max() - steady.min() (back-half peak-to-peak)back_half_pkpk(source, key) or measure(..., "ptp")data_analyzer
norm(np.diff(x, axis=0)) (per-step jump)diff_norm(values, prepend=True)data_analyzer
np.linalg.norm(a - b, axis=1) between two runsdivergence_array(view_a, view_b, key)data_analyzer
d/dt ln D growth rate (chaos vs ill-conditioning)local_growth_rate(t, D, smooth=...)data_analyzer
max-abs-diff of two pinned payloadspayload_diff(ref, cur)data_analyzer
np.diff(cov) >= -ATOL (monotonicity gate)monotonicity_drops(series, atol)data_analyzer
hand-rolled _runs / _merge over a boolean maskcontiguous_runs / merge_runs / drop_short_runs / longest_run_lengthdata_analyzer
threshold crossing / first crossingthreshold_crossings / first_crossingdata_analyzer
floor-diff progress stall detectionquantized_stall_mask(progress)data_analyzer
int(np.nanargmin(s)) / (len(s)-1) (when the extreme occurs)argextreme_fraction(values, which="min")data_analyzer
angle between paired rows of two arraysrows_angle(a, b)data_analyzer
versine → degrees for a printas_pointing_deg(versine) — DISPLAY ONLYdata_analyzer
np.corrcoef(a, b)[0,1]pearson_r(a, b)statistics
scipy.stats.spearmanrspearman_rho(a, b)statistics
OLS slope/intercept fitlinfit(x, y)statistics
cross-correlation peak / lagxcorr_peak_lag(a, b, dt)statistics
rank-based effect size on skewed datacliffs_delta(a, b)statistics
”signal during episode vs healthy” contrastepisode_contrast(signal, mask)statistics
1.96 * std(ddof=1) / sqrt(n) (CI95)ci95(x)statistics
across-seed spread / dispersion bandensemble_dispersion(values) · rel_spread(x)statistics
(B - A) / A percent change between two runsrel_delta(new, base)statistics
angle wrapping / circular mean / circular stdwrap · circ_mean · circ_stdstatistics
seeded event-study control poolmatched_control_pool · event_effect_sizestatistics

Every function above was verified to resolve in its named module on 2026-07-11.

Evidence

  • The narrative these rows were distilled from (which script reinvented what, and the byte-identity proofs when each was retired onto the canonical function): Inspection/validation/INSIGHTS.md § Hand-roll census.
  • The governing contract that sends you here: .claude/skills/sim-runner/references/measurement.md (hard rule 1 — measure through analysis/data_analyzer.py, never re-derive).
  • Parity of the reduction path against the pipeline sweep (1e-12): Inspection/validation/tests/test_signal_measurement.py.

Footguns

The facade is retired — its function names are dead, and the old census still prints them

validation/signal_measurement.py was removed 2026-07-01. The census as written in validation/INSIGHTS.md names the facade’s functions, and several of those names exist nowhere in the tree now. Translate before you reach for one:

dead facade namelive functionhome
divergence_seriesdivergence_arraydata_analyzer
percentile_ofpercentiledata_analyzer
time_to_extremeargextreme_fractiondata_analyzer
step_norm (keyed)diff_norm (raw array)data_analyzer
correlatepearson_r / spearman_rhostatistics
regresslinfitstatistics
lead_lagxcorr_peak_lagstatistics
effect_sizecliffs_deltastatistics

measure / series / back_half_pkpk / episode_contrast kept their names — only their import path moved.

A "no facade equivalent" row is not an invitation to hand-roll a metric

Several census rows correctly read no equivalent — but that verdict applies to plant-level and cross-variant math (below), never to a reduction of a logged signal. If the quantity comes out of a log, it goes through measure / series.

Intentionally NOT promoted — the closed ledger

These stay hand-rolled on purpose: they are not generic signal math, so they live with their own domain code rather than bloating the shared library. Do not re-file them as backlog.

  • Plant / robot-level linear algebra — SVD of J / Γ / J_plus (singular spectrum, manipulability), and the Pinocchio kinetic-energy / momentum-drift conservation checks. These operate on live matrices from utils/robot.py, not on logged scalars. (s_min_G / kappa_J are already logged as catalog metrics for the post-hoc view — use those when you want the logged quantity.)
  • Mesh coverage / set-difference — area-weighted triangle marking couples to mesh geometry + finder state.
  • Schedule / fragility oracles + config-threshold sizing — derate-onset, neighbourhood robustness, worst-case joint-rate propagation: these couple to the controller config, not to a log.
  • Cross-variant scalar comparisons — Richardson d1/d2, the cross-dt common-grid RMS gap, and A/B ratios between two runs’ scalars (pe_p99, smin_min). These compare two runs’ scalars, not a per-step reduction of one run. ensemble_dispersion / rel_delta cover the dispersion and percent-change shapes; the rest is bespoke by design.
  • Raw joint-state percentilesth3 median/p5/p95 and sin(th3) regressors: raw state columns with no catalog spec, so there is no keyed path (by design — the reduction path is keyed on catalog metrics).
  • Deliberate keepers, not duplication — a NaN-propagating percentile lambda (the canonical percentile strips NaN, so the two are not interchangeable); an offline SVD fallback for old logs that lack a live kappa_J; and the test reference values (np.corrcoef, scipy.stats.spearmanr) that exist precisely to validate the promoted pearson_r / spearman_rho against the originals — retiring those would delete the cross-check.

[[data_analyzer]] · [[statistics]] · [[signal_measurement]] · [[metric_catalog]] · [[metrics]] · [[orchestrator]] · terminology