Skip to content

Commit f29b0bb

Browse files
committed
feat(perturbation-sim): wire ndarray::simd Walsh-Hadamard (Morton-pyramid) + helix-360/turbovec live-encoding doctrine
- ndarray-simd feature (optional, off by default → crate stays zero-dep): the Morton/Walsh pyramid transform (sketch::walsh_pyramid_energy) routes through ndarray::simd::wht_f32 (AVX-512/AMX, x86-64-v4) when enabled; scalar fwht fallback otherwise. ndarray = AdaWorldAPI fork (path dep; git alternative documented). Verified: 48 tests + clippy clean under BOTH default and `--features ndarray-simd` (target-cpu=native); the fork compiles in ~20s. - METHODS §10: documents the wiring + the deeper tile-specific ndarray targets (simd_soa::MultiLaneColumn, hpc::codec::ctu HEVC-quadtree, hilbert, U8x64/ hamming). And the live-4-factor-encoding doctrine: the factors are unit-free spectral magnitudes → carry them on the GENERIC helix Signed360 residue tenant (6B/factor, L1-metric-safe so Spearman/ICC survive the encoding), NOT an electricity-specific tenant; turbovec ANN for episodic factor-vector search; electricity-specific encoding reserved for the raw |V|/MW layer; compute stays on raw f64. Anti-dilution rows + README updated. Real-Iberian validate (261-bus core, release build): Cronbach α=-0.83 (factors are distinct facets, not one scale), spectral cluster convergent (ρ 0.96-1.00), infight orthogonal to it (ρ≈±0.05 — discriminant validity = the Go duality measured), time test-retest ρ≈0.90 (basin ranking stable). Basin model verified.
1 parent e37cd93 commit f29b0bb

6 files changed

Lines changed: 257 additions & 4 deletions

File tree

crates/perturbation-sim/Cargo.lock

Lines changed: 163 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/perturbation-sim/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,22 @@ description = "Spectral + edge-propagation outage simulator: models the perturba
99
# sigker / helix). Verify with:
1010
# cargo test --manifest-path crates/perturbation-sim/Cargo.toml
1111
# cargo run --manifest-path crates/perturbation-sim/Cargo.toml --example simulate
12+
[features]
13+
# Route the Morton/Walsh pyramid transform through ndarray's SIMD
14+
# (AVX-512/AMX, x86-64-v4) Walsh–Hadamard. Default OFF → the crate stays
15+
# zero-dep with the scalar `fwht` fallback. Build accelerated with:
16+
# RUSTFLAGS='-C target-cpu=x86-64-v4' cargo … --features ndarray-simd
17+
# (or target-cpu=native locally). All SIMD comes from `ndarray::simd` per the
18+
# workspace rule — never raw intrinsics here.
19+
ndarray-simd = ["dep:ndarray"]
20+
1221
[dependencies]
22+
# The AdaWorldAPI fork ("The Foundation"), optional + behind `ndarray-simd`.
23+
# Path dep: perturbation-sim lives inside lance-graph, which already path-deps
24+
# this sibling for its core crates, so the fork is expected present. Clean-
25+
# checkout alternative (helix pattern): swap to
26+
# ndarray = { git = "https://github.com/AdaWorldAPI/ndarray.git", branch = "master", optional = true, default-features = false, features = ["std"] }
27+
ndarray = { path = "../../../ndarray", optional = true, default-features = false, features = ["std"] }
1328

1429
[lib]
1530
name = "perturbation_sim"

crates/perturbation-sim/METHODS.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,40 @@ model with the highest criterion validity *is the evidence* for which aging
323323
story (uniform / density-correlated / spend-driven) best explains the blackout —
324324
turning a modeling assumption into a testable claim.
325325

326+
## 10. SIMD acceleration + the live-encoding carrier
327+
328+
### `ndarray-simd` feature (the Morton-pyramid transform, accelerated)
329+
The pyramid's Walsh–Hadamard transform routes through **`ndarray::simd::wht_f32`**
330+
(AVX-512/AMX under `target-cpu=x86-64-v4`) — the one workspace-sanctioned SIMD
331+
source (never raw intrinsics here). Default **OFF** → scalar `fwht`, zero-dep;
332+
**ON** via `--features ndarray-simd` (ndarray fork as a git/path dep, `["std"]`).
333+
Both paths pass the same tests. Deeper *tile-specific* ndarray targets, to wire
334+
as the SoA tile layout matures: `simd_soa::MultiLaneColumn` (byte-backed SoA
335+
column → `f32x16`/`f64x8`/`u8x64` lanes — the natural Morton-tile field store),
336+
`hpc::codec::ctu` (HEVC **quadtree** = the Morton tile), `hpc::linalg::hilbert`
337+
(space-filling curve), and `hamming_distance_raw`/`U8x64` (the XOR **sign** side).
338+
339+
### Live 4-factor encoding — generic residue carrier, NOT an electricity tenant
340+
The four factors (`d_lambda2`, `dk_rotation`, `d_conductance`, `infight`,
341+
`raumgewinn`) are **abstract signed spectral magnitudes** — already unit-free —
342+
so they fit the **generic helix `Signed360` residue tenant** (6 B/factor, signed
343+
full-sphere, the `HelixResidue` contract value-tenant), *not* a bespoke
344+
electricity tenant. The load-bearing reason: `Signed360`'s distance is
345+
**L1-metric-safe**, so **Spearman/ICC computed on the residue-encoded factors ≈
346+
on the raw f64** — the statistics battery survives the encoding. Roles:
347+
- **Carrier (live stream/store):** helix `Signed360` residue — 6 B/factor,
348+
metric-safe; a contingency's 5-factor record ≈ 30 B, streamable + comparable
349+
by L1.
350+
- **Search ("which past contingencies resemble this now"):** `turbovec` ANN
351+
(2–4 bit/dim, data-oblivious) over the factor vectors — episodic retrieval.
352+
- **Compute:** the f64 field tier — definitive stats on **raw f64**; residue +
353+
turbovec are storage/stream/search carriers, never the compute.
354+
355+
Reserve **electricity-specific** encoding for the **raw physical layer** (AC
356+
`|V|`, MW — where units actually bite), never the factor layer. (`Signed360` is
357+
256-palette lossy, ±½ bucket — fine for a live screen/stream; compute exact
358+
stats on raw. `turbovec` is coarse — retrieval, not values.)
359+
326360
## Anti-dilution table — the distinctions to never collapse
327361

328362
| Do NOT conflate | Because |
@@ -343,3 +377,5 @@ turning a modeling assumption into a testable claim.
343377
| Uniform-aging null vs density-proxy Gegenhypothese | uniform = relative-invariant null; density-proxy = genuine topology-derived heterogeneity that *should* bend the shape — they are competing hypotheses, validated against the observed footprint |
344378
| DC overload cascade vs voltage collapse | the 28 Apr 2025 Iberian blackout was **voltage/reactive driven** (ENTSO-E expert panel), NOT line-overload — the DC cascade screens *structural* vulnerability, the voltage *trigger* needs the AC fork; do not claim the DC path reproduces that event (see `DATA_SOURCES.md` §5) |
345379
| Electrical mechanism vs human footprint | the cascade/voltage event is the *mechanism*; excess mortality (147 deaths, Eurosurveillance) is the *consequence/severity* — validate them separately |
380+
| Generic Signed360 residue tenant vs electricity-specific tenant | the 4 factors are unit-free spectral magnitudes → the generic L1-metric-safe residue carries them (stats survive); reserve a bespoke electricity tenant for the raw `|V|`/MW layer only |
381+
| Residue/turbovec carrier vs the compute | residue (store/stream) + turbovec (search) are carriers; the definitive 4-factor values + stats are computed on raw f64, never on the lossy code |

crates/perturbation-sim/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ method:
1414
| **Gaussian-splat magnitude side** (PROTOTYPE) | anisotropic `Σ` fit to the electrical neighbourhood + EWA pyramid coarsen (Morton-seam anti-alias) + `morton2`. The magnitude algebra complementing the Walsh sign side. | `splat.rs` |
1515
| **Data-shaped scoping** | run on today's data; `assess_capability` gates which outputs are valid; missing variables modeled as uniform constants (provably free for relative results); `AgeModel` = Uniform null vs DensityProxy Gegenhypothese (topology-only) vs ModernizationSpend (official planning data). | `model.rs` |
1616

17+
> **SIMD:** the Morton/Walsh pyramid transform optionally routes through
18+
> `ndarray::simd::wht_f32` (AVX-512/AMX) via `--features ndarray-simd`
19+
> (`RUSTFLAGS='-C target-cpu=x86-64-v4'`); default is the zero-dep scalar path.
20+
> All SIMD comes from `ndarray::simd` (workspace rule). See `METHODS.md §10`.
21+
1722
> **Methods & math grounding:** see [`METHODS.md`](METHODS.md) — the one-operator
1823
> grounding that connects all four, the anti-dilution distinctions (combinatorial
1924
> `λ₂` vs normalized `μ₂`; geography vs electrical distance; infight vs

crates/perturbation-sim/src/acflow.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,26 @@ mod tests {
416416
// (With charging, zero load still lifts V slightly — the Ferranti effect.)
417417
let sys = AcSystem::new(
418418
vec![
419-
AcBus { kind: BusKind::Slack, p: 0.0, q: 0.0, v_set: 1.0 },
420-
AcBus { kind: BusKind::Pq, p: 0.0, q: 0.0, v_set: 1.0 },
419+
AcBus {
420+
kind: BusKind::Slack,
421+
p: 0.0,
422+
q: 0.0,
423+
v_set: 1.0,
424+
},
425+
AcBus {
426+
kind: BusKind::Pq,
427+
p: 0.0,
428+
q: 0.0,
429+
v_set: 1.0,
430+
},
421431
],
422-
vec![AcLine { from: 0, to: 1, r: 0.02, x: 0.1, b_shunt: 0.0 }],
432+
vec![AcLine {
433+
from: 0,
434+
to: 1,
435+
r: 0.02,
436+
x: 0.1,
437+
b_shunt: 0.0,
438+
}],
423439
);
424440
let r = sys.solve(50, 1e-10);
425441
assert!(r.converged);

crates/perturbation-sim/src/sketch.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,25 @@ pub fn resistance_sketch(
9494

9595
// ── Walsh / Morton pyramid screen ────────────────────────────────────────────
9696

97+
/// Walsh–Hadamard dispatch: the SIMD `ndarray::simd::wht_f32` when the
98+
/// `ndarray-simd` feature is on (the Morton pyramid transform, accelerated),
99+
/// else the scalar [`fwht`]. The pyramid energy is a screen, so the f32 SIMD
100+
/// path's precision is ample.
101+
#[cfg(feature = "ndarray-simd")]
102+
fn wht_dispatch(a: &mut [f64]) {
103+
let mut f: Vec<f32> = a.iter().map(|&x| x as f32).collect();
104+
ndarray::simd::wht_f32(&mut f);
105+
for (d, s) in a.iter_mut().zip(f) {
106+
*d = s as f64;
107+
}
108+
}
109+
#[cfg(not(feature = "ndarray-simd"))]
110+
fn wht_dispatch(a: &mut [f64]) {
111+
fwht(a);
112+
}
113+
97114
/// In-place fast Walsh–Hadamard transform (length must be a power of two).
115+
/// Scalar reference; the accelerated path is [`wht_dispatch`].
98116
pub fn fwht(a: &mut [f64]) {
99117
let n = a.len();
100118
let mut h = 1;
@@ -132,7 +150,7 @@ pub fn walsh_pyramid_energy(field: &[f64]) -> WalshEnergy {
132150
}
133151
let mut a = vec![0.0; n];
134152
a[..field.len()].copy_from_slice(field);
135-
fwht(&mut a);
153+
wht_dispatch(&mut a);
136154

137155
let levels = (n as f64).log2() as usize + 1;
138156
let mut per = vec![0.0; levels];

0 commit comments

Comments
 (0)