Skip to content

Commit 2f96888

Browse files
authored
Merge pull request #551 from AdaWorldAPI/claude/family-basin-weyl-multihop-probe
probe: family-basin Weyl multi-hop is hop-local at the crisp tier (reinstates hop-bounds-reach, tier-scoped)
2 parents da264f3 + 38e5031 commit 2f96888

3 files changed

Lines changed: 286 additions & 0 deletions

File tree

.claude/board/EPIPHANIES.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
## 2026-06-19 — E-FAMILY-BASIN-WEYL-HOP-LOCAL-AT-CRISP-TIER — REINSTATEMENT (tier-scoped) of `E-OUTAGE-CASCADE-IS-NON-LOCAL`: WITH the family-basin partition, a within-basin perturbation is block-localized (95.7 % containment, within/seam ratio 1.54) at the **crisp top split**, so multi-hop Weyl IS hop-local there; finer tiers leak. The earlier non-locality was the *un-partitioned* global solve. (Davis-Kahan is the *mechanism* — see body — NOT the numeric evidence; the gate is the containment ratio.)
2+
3+
**Status:** FINDING (measured, real ES PyPSA data; probe `perturbation-sim/examples/family_basin_weyl_multihop.rs`). **Tier-scoped reinstatement** of the reach claim that `E-OUTAGE-CASCADE-IS-NON-LOCAL` had corrected. Operator hypothesis: "with family basin it works now with weyl multihop." Confirmed — at the crisp tier only.
4+
5+
**The measurement (ES largest component, 261 buses / 348 lines; containment = fraction of L⁺ dipole-response energy kept in the seed's basin):**
6+
7+
| tier | basins | within | seam | ratio | verdict |
8+
|---|---|---|---|---|---|
9+
| **HEEL (top split)** | 2 | **0.957** | 0.621 | **1.54** | **HOP-LOCAL** |
10+
| HIP | 4 | 0.579 | 0.621 | 0.93 | leaks |
11+
| LEAF | 7 | 0.529 | 0.485 | 1.09 | leaks |
12+
13+
At the **crisp top split** a within-basin perturbation stays **95.7 % contained** and beats the seam-straddle (ratio 1.54) — block-localized, Weyl satisfied. This **reinstates "hop bounds reach" on the eigenvalue axis**: the mechanism is Davis-Kahan block-localization under weak inter-block coupling (Cheeger/spectral-clustering). The earlier non-locality (`E-OUTAGE-CASCADE-IS-NON-LOCAL`) was measured on the **un-partitioned global solve** AND seeded the **max-flow line = the seam itself** (a seam trip leaks by construction); both errors are removed here.
14+
15+
**The honest scope — it is the COARSE crisp split, not all tiers.** HIP/LEAF leak (within ≈ seam): the perturbation is NOT contained at finer tiers. This matches the spectral structure exactly — probe C (bisection stability) is solid at the top (ES `(λ₃−λ₂)/λ₂ = 3.23`) and marginal deeper; the operator's "without family nodes" table shows out-of-family ties growing 0→2→9→20 HEEL→HIP→TWIG→LEAF (the deeper blocks are loosely coupled). **The 2-basin "HEEL" split here IS that table's HIP 2-line seam** (lines 46, 150) — `hhtl_keys` puts the first crisp Cheeger cut at the heel nibble; labels offset by one, structure agrees.
16+
17+
**The three-axis close (a "surge" decomposes, and each axis owns one aspect):** WHERE it breaks = CHAODA anomaly (manifold repulsion, `ndarray::hpc::clam`); HOW MUCH = Weyl magnitude (probes A–D, [G] exact in code); HOW it spreads = **family-basin block-localized Weyl multi-hop, hop-local at the crisp tier** (this finding) + the ketchup yield as the seam gate. The family-basin layer (`E-BASIN-IS-A-NODE`) is the keystone that block-diagonalizes the Laplacian so per-basin Weyl chains seam-to-seam.
18+
19+
**Self-correction (process):** the probe's first verdict rubber-stamped a 0.044 within-vs-seam gap + a **vacuous Davis-Kahan bound (2199)** as "SUPPORTED" — the same confirmation-bias trap. Tightened the gate to per-tier within ≥ 0.70 AND within/seam ratio ≥ 1.30 AND Weyl, dropped the DK bound as evidence (it is uninformative when the eigengap is tiny). Under the honest gate only HEEL passes — which is the correct, nuanced result. Cross-refs: `E-OUTAGE-CASCADE-IS-NON-LOCAL` (the claim reinstated), `E-BASIN-IS-A-NODE` (the family-basin keystone), `perturbation-sim::{spectral_perturbation, hhtl_keys}`, probes A–E (the spectral foundation, real PyPSA), the "without family nodes" tier table.
20+
21+
---
22+
123
## 2026-06-19 — E-OUTAGE-CASCADE-IS-NON-LOCAL — CORRECTION to `E-BASIN-IS-A-NODE`: the electricity-outage perturbation does NOT decay with HHTL hop; the "cascade round = hop / hop bounds reach" identity is a property of the COGNITIVE substrate (propagation rides basin-tree EDGES by construction), NOT of the DC-power-flow electrical metaphor — they must not be conflated
224

325
**Status:** FINDING (measured correction; probe `perturbation-sim/examples/outage_over_hhtl_hops.rs`). **Corrects** the second [H] sub-claim posted in `E-BASIN-IS-A-NODE` ("cascade round k = nodes at hop-distance k; Weyl bounds magnitude per round, hop-count bounds the reach"). Operator prompt: "model the electricity outage perturbation again with the HHTL L1-L4." The honest answer: I did, and it **refutes** the literal-electrical reading.

crates/perturbation-sim/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,7 @@ path = "examples/basin_placement_learning.rs"
120120
[[example]]
121121
name = "outage_over_hhtl_hops"
122122
path = "examples/outage_over_hhtl_hops.rs"
123+
124+
[[example]]
125+
name = "family_basin_weyl_multihop"
126+
path = "examples/family_basin_weyl_multihop.rs"
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
//! PROBE — family-basin partitioning recovers hop-locality on the Weyl axis
2+
//! (the reinstatement test for `E-OUTAGE-CASCADE-IS-NON-LOCAL`).
3+
//!
4+
//! Claim (operator): the un-partitioned DC cascade is non-local (measured), but
5+
//! **with family basins** the perturbation math becomes hop-local — a rank-1
6+
//! trip inside a basin localizes to that block (Davis-Kahan), and only crosses
7+
//! to the next basin at the weak seam. So "multi-hop Weyl" = chained per-block
8+
//! first-hop Weyl, gated at the seams.
9+
//!
10+
//! Why the earlier outage probe found non-locality: it seeded the **max-flow
11+
//! line** — which is the *seam* (the bottleneck carrying cross-basin flow). A
12+
//! seam trip leaks globally by construction. This probe separates the two cases.
13+
//!
14+
//! Measurement (real ES grid, the old PyPSA data), swept over **every HHTL tier**
15+
//! (HEEL top split → HIP → LEAF):
16+
//! 1. `hhtl_keys` → the family-basin partition at that tier.
17+
//! 2. CONTAINMENT — inject a balanced dipole, DC-solve θ = L⁺p (the
18+
//! effective-resistance response = the perturbation shape), and measure the
19+
//! fraction of response energy Σθ² that stays in the seed's basin:
20+
//! - WITHIN-basin dipoles → block-localized (high) ⇒ hop-local;
21+
//! - SEAM-straddling dipoles → leaks across (the hop boundary).
22+
//! 3. WEYL — for a within-basin line trip, confirm `weyl_satisfied`.
23+
//!
24+
//! Verdict gate (honest, no rubber-stamp): a tier is HOP-LOCAL only if within-basin
25+
//! containment ≥ 0.70 (block-tight) AND within/seam ratio ≥ 1.30 (a real margin)
26+
//! AND Weyl satisfied. No "2×null" (the null ≈ 0.5 for coarse tiers and breaks the
27+
//! test) and no Davis-Kahan bound as evidence (it is vacuous when the eigengap is
28+
//! tiny). The ratio, not the absolute, is the hop-boundary test.
29+
//!
30+
//! Run: cargo run --manifest-path crates/perturbation-sim/Cargo.toml \
31+
//! --example family_basin_weyl_multihop -- /tmp/pypsa/buses.csv /tmp/pypsa/lines.csv ES
32+
33+
use perturbation_sim::{
34+
from_pypsa_csv, hhtl_keys, spectral_perturbation, symmetric_eigen, Grid, HhtlKey,
35+
};
36+
use std::collections::BTreeMap;
37+
use std::fs;
38+
39+
const MIN_BASIN: usize = 6; // basins smaller than this are too tiny for a meaningful dipole
40+
const REL_TOL: f64 = 1e-9;
41+
42+
type Basin = (u16, u16, u16);
43+
/// A tier partitioner: a node's HHTL key → its basin at one HHTL tier.
44+
type TierPart = fn(HhtlKey) -> Basin;
45+
46+
/// Fraction of response energy `Σθ²` carried by `members`.
47+
fn containment(theta: &[f64], members: &[usize]) -> f64 {
48+
let total: f64 = theta.iter().map(|x| x * x).sum();
49+
if total <= 0.0 {
50+
return 0.0;
51+
}
52+
let inb: f64 = members.iter().map(|&i| theta[i] * theta[i]).sum();
53+
inb / total
54+
}
55+
56+
/// Balanced ±1 dipole between `s` and `t`.
57+
fn dipole(n: usize, s: usize, t: usize) -> Vec<f64> {
58+
let mut p = vec![0.0; n];
59+
p[s] = 1.0;
60+
p[t] = -1.0;
61+
p
62+
}
63+
64+
/// The honest HOP-LOCAL gate (single source, used by `main` and tested):
65+
/// block-tight within-basin containment (`wm ≥ 0.70`) AND a real within/seam
66+
/// margin (`ratio ≥ 1.30`) AND a valid within-basin Weyl check — **and the
67+
/// evidence must actually exist** (`has_within && has_straddle`). Missing
68+
/// evidence ⇒ NOT supported (no ∞-ratio or defaulted-true Weyl false positives).
69+
/// No "2×null" gate (the null ≈ 0.5 for coarse tiers and breaks the test); no
70+
/// Davis-Kahan bound (it is vacuous when the eigengap is tiny).
71+
fn tier_supported(
72+
has_within: bool,
73+
has_straddle: bool,
74+
wm: f64,
75+
ratio: f64,
76+
weyl_ok: bool,
77+
) -> bool {
78+
has_within && has_straddle && wm >= 0.70 && ratio >= 1.30 && weyl_ok
79+
}
80+
81+
fn main() {
82+
let args: Vec<String> = std::env::args().collect();
83+
let (bpath, lpath, country) = (
84+
args.get(1)
85+
.map(String::as_str)
86+
.unwrap_or("/tmp/pypsa/buses.csv"),
87+
args.get(2)
88+
.map(String::as_str)
89+
.unwrap_or("/tmp/pypsa/lines.csv"),
90+
args.get(3).map(String::as_str).unwrap_or("ES"),
91+
);
92+
let buses = fs::read_to_string(bpath).expect("read buses.csv");
93+
let lines = fs::read_to_string(lpath).expect("read lines.csv");
94+
let import = from_pypsa_csv(&buses, &lines, Some(country))
95+
.expect("parse pypsa")
96+
.largest_component();
97+
let grid: &Grid = &import.grid;
98+
let (n, m) = (grid.n, grid.edges.len());
99+
100+
// Family-basin partition (per tier) + eigendecomposition (once).
101+
let keys = hhtl_keys(grid);
102+
let eig = symmetric_eigen(&grid.laplacian_of(&vec![true; m]), n);
103+
let mut deg = vec![0usize; n];
104+
for e in &grid.edges {
105+
deg[e.from] += 1;
106+
deg[e.to] += 1;
107+
}
108+
109+
println!("PROBE — family-basin Weyl multi-hop (reinstatement test, real {country} grid)");
110+
println!(" grid: {n} buses, {m} lines");
111+
println!(
112+
" containment = fraction of L⁺ dipole-response energy in the seed's basin.\n \
113+
hop-local ⇔ WITHIN-basin dipoles contained, SEAM-straddle dipoles leak. Per HHTL tier:\n"
114+
);
115+
116+
// Tier partitioners: HEEL (top crisp split), HIP (+ the 2-line seam), LEAF (finest).
117+
let tiers: [(&str, TierPart); 3] = [
118+
("HEEL", |k| (k.heel, 0, 0)),
119+
("HIP", |k| (k.heel, k.hip, 0)),
120+
("LEAF", |k| (k.heel, k.hip, k.twig)),
121+
];
122+
123+
let mean = |v: &[f64]| {
124+
if v.is_empty() {
125+
0.0
126+
} else {
127+
v.iter().sum::<f64>() / v.len() as f64
128+
}
129+
};
130+
let alive = vec![true; m];
131+
132+
println!(
133+
" {:<5} {:>6} {:>10} {:>10} {:>10} {:>8} verdict",
134+
"tier", "basins", "within", "seam", "ratio", "weyl"
135+
);
136+
137+
let mut any_tier_supported = false;
138+
for (name, part) in tiers {
139+
let mut basins: BTreeMap<Basin, Vec<usize>> = BTreeMap::new();
140+
for (i, k) in keys.iter().enumerate() {
141+
basins.entry(part(*k)).or_default().push(i);
142+
}
143+
144+
// WITHIN-basin containment.
145+
let mut within: Vec<f64> = Vec::new();
146+
for members in basins.values() {
147+
if members.len() < MIN_BASIN {
148+
continue;
149+
}
150+
let mut by_deg = members.clone();
151+
by_deg.sort_by_key(|&i| std::cmp::Reverse(deg[i]));
152+
let theta = eig.pseudo_apply(&dipole(n, by_deg[0], by_deg[1]), REL_TOL);
153+
within.push(containment(&theta, members));
154+
}
155+
// SEAM-straddle containment (dipole across an inter-basin edge).
156+
let mut straddle: Vec<f64> = Vec::new();
157+
for e in &grid.edges {
158+
let (ba, bb) = (part(keys[e.from]), part(keys[e.to]));
159+
if ba == bb {
160+
continue;
161+
}
162+
let (ma, mb) = (&basins[&ba], &basins[&bb]);
163+
if ma.len() < MIN_BASIN || mb.len() < MIN_BASIN {
164+
continue;
165+
}
166+
let theta = eig.pseudo_apply(&dipole(n, e.from, e.to), REL_TOL);
167+
straddle.push(containment(&theta, ma).max(containment(&theta, mb)));
168+
}
169+
// Weyl on a within-basin line trip at this tier. No within-basin line ⇒
170+
// `false` (no valid check ⇒ not supported), NOT a defaulted `true`.
171+
let weyl_ok = grid
172+
.edges
173+
.iter()
174+
.position(|e| part(keys[e.from]) == part(keys[e.to]))
175+
.map(|line| spectral_perturbation(grid, &alive, line).weyl_satisfied)
176+
.unwrap_or(false);
177+
178+
let (has_within, has_straddle) = (!within.is_empty(), !straddle.is_empty());
179+
let (wm, sm) = (mean(&within), mean(&straddle));
180+
// ratio = 0 (not ∞) when there are no seam samples — a missing seam
181+
// comparison must never pass the margin trivially.
182+
let ratio = if has_straddle && sm > 0.0 {
183+
wm / sm
184+
} else {
185+
0.0
186+
};
187+
let supported = tier_supported(has_within, has_straddle, wm, ratio, weyl_ok);
188+
any_tier_supported |= supported;
189+
println!(
190+
" {name:<5} {:>6} {wm:>10.3} {sm:>10.3} {ratio:>10.2} {:>8} {}",
191+
basins.values().filter(|m| m.len() >= MIN_BASIN).count(),
192+
weyl_ok,
193+
if supported { "HOP-LOCAL" } else { "leaks" }
194+
);
195+
}
196+
197+
println!("\n VERDICT:");
198+
if any_tier_supported {
199+
println!(
200+
" [SUPPORTED at the crisp tier(s)] where the bisection is stable, a within-basin \
201+
perturbation stays block-contained (≥0.70) and clearly beats the seam-straddle \
202+
(ratio ≥1.30) with Weyl satisfied → multi-hop Weyl IS hop-local there. REINSTATES \
203+
'hop bounds reach' on the eigenvalue axis — at the tier where the family-basin block \
204+
structure is real. Finer tiers leak (loose coupling), exactly as the out-of-family \
205+
tie growth (0→2→9→20) and the marginal deep-tier DK gaps predict."
206+
);
207+
} else {
208+
println!(
209+
" [NOT SUPPORTED on this grid] no tier shows block-tight containment (≥0.70) with a \
210+
≥1.30 within/seam ratio. Family basins concentrate the perturbation above random, but \
211+
the blocks leak too much to call the seam a hop boundary on real ES — report honestly, \
212+
do not promote. The earlier outage non-locality finding stands unrefined."
213+
);
214+
}
215+
println!(
216+
" NOTE: containment is the L⁺ (effective-resistance) response energy; a high WITHIN vs \
217+
low SEAM ratio is the block-localization signature (Cheeger/spectral-clustering). The \
218+
ratio, not the absolute, is the hop-boundary test."
219+
);
220+
}
221+
222+
#[cfg(test)]
223+
mod tests {
224+
use super::*;
225+
226+
#[test]
227+
fn containment_basic_and_edges() {
228+
// All energy on members → 1.0.
229+
assert!((containment(&[3.0, 4.0, 0.0], &[0, 1]) - 1.0).abs() < 1e-12);
230+
// θ=[3,4] → energies 9,16; members={0} → 9/25.
231+
assert!((containment(&[3.0, 4.0], &[0]) - 9.0 / 25.0).abs() < 1e-12);
232+
// Zero field → 0.0, never NaN.
233+
assert_eq!(containment(&[0.0, 0.0], &[0]), 0.0);
234+
// Empty members → 0.0.
235+
assert_eq!(containment(&[1.0, 2.0], &[]), 0.0);
236+
}
237+
238+
#[test]
239+
fn dipole_is_balanced() {
240+
let p = dipole(5, 1, 3);
241+
assert_eq!(p, vec![0.0, 1.0, 0.0, -1.0, 0.0]);
242+
assert!(p.iter().sum::<f64>().abs() < 1e-12); // Σp = 0
243+
}
244+
245+
#[test]
246+
fn gate_requires_evidence_not_just_thresholds() {
247+
// Strong, complete evidence → supported.
248+
assert!(tier_supported(true, true, 0.96, 1.54, true));
249+
// Missing seam samples (ratio would have been ∞) → NOT supported.
250+
assert!(!tier_supported(true, false, 0.96, 0.0, true));
251+
// Missing within-basin line / Weyl false → NOT supported.
252+
assert!(!tier_supported(true, true, 0.96, 1.54, false));
253+
// No within evidence → NOT supported.
254+
assert!(!tier_supported(false, true, 0.96, 1.54, true));
255+
// Thin margin (the leaky tiers) → NOT supported.
256+
assert!(!tier_supported(true, true, 0.58, 0.93, true));
257+
// Block-tight but margin just under 1.30 → NOT supported.
258+
assert!(!tier_supported(true, true, 0.95, 1.29, true));
259+
}
260+
}

0 commit comments

Comments
 (0)