Skip to content

Commit 69da10f

Browse files
committed
perturbation-sim: address Codex #509 review (P1 Kirchhoff disconnection, P2 meta-hop depth)
P1 (resilience.rs): kirchhoff_index now returns INFINITY when the graph is disconnected (finite modes < n−1, i.e. ≥2 zero modes). Previously it summed only the live-component spectra and returned a FINITE Kf for a fragmented graph, which would rank an already-disconnected compartment as resilient (low Kf) — backwards. mean_resistance propagates ∞, so a disconnected basin now ranks most-exposed. New test disconnected_graph_has_infinite_kirchhoff. P2 (timing.rs): meta_cascade_phase penetration depth now counts the ARRIVING contribution |signed_amp| (the front), not the cumulative interference field. Once a tier absorbs the front (g→0) no deeper tier is reached, even though the bundled field retains the earlier seed — the old field-based count overstated reach (synthetic 1,0,0,0 reported depth 4). Front reach is gain-driven and phase-independent (|±x|=x); phase governs the field interference, reported in the MetaHop.field column. Test reframed (phase_governs_the_field_not_the_front_depth) + PAPER §4.7 corrected to the true ES number (front penetration 3/4, field peak |Σ|=1.95) EN+DE; meta_hops label fixed. 68 lib tests; clippy -D warnings clean; scorecard/explore numbers unchanged (connected components).
1 parent 3864457 commit 69da10f

4 files changed

Lines changed: 89 additions & 31 deletions

File tree

crates/perturbation-sim/PAPER.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -333,14 +333,18 @@ tier-to-tier λ₂ change, monotonically rising ⇒ all-`+`):
333333
| TWIG | 5.65e-6 | 0.392 | + | 3.0 | +0.344 | +1.822 | 0.44 | 1.68 |
334334
| LEAF | 8.88e-6 | 0.032 | + | 2.0 | +0.130 | +1.951 | 0.36 | 2.04 |
335335

336-
All-aligned phase ⇒ penetration depth 4/4; the coarse→fine inertia ramp puts the
337-
fast seconds in the leaf tiers (dt 0.68 → 0.36 s), and the cumulative ~2 s lands
338-
in the **electromechanical / low-inertia** regime — the same mechanism class as
339-
the 27 s event (the absolute scale depends on `ΔP`, relay band, and the true
340-
`H`-ramp; this run uses illustrative values). The lesson the model encodes: a
341-
deep four-tier cascade needs **phase alignment AND low leaf-inertia** — break
342-
either (a phase flip at one tier, or more synchronous inertia at the leaves) and
343-
the front self-arrests or slows below the protection window.
336+
The **front penetration is 3/4** (arriving `|signed_amp|` 1.0 → 0.48 → 0.34 ≥ 0.25,
337+
then 0.13 at the leaf where the gain `g→0` absorbs it). Front reach is **gain-driven
338+
and phase-independent** (`|±x|=x`); what the all-aligned phase buys is a *growing
339+
interference field* (peak `|Σ|=1.95`) — alternating phases would cancel it. The
340+
coarse→fine inertia ramp puts the fast seconds in the leaf tiers (dt 0.68 → 0.36 s),
341+
and the cumulative ~2 s lands in the **electromechanical / low-inertia** regime — the
342+
same mechanism class as the 27 s event (the absolute scale depends on `ΔP`, relay
343+
band, and the true `H`-ramp; this run uses illustrative values). The lesson the model
344+
encodes: a deep cascade needs **passing gains (weak field × strong infight) AND low
345+
leaf-inertia** — break either (more connectivity at a tier, or more synchronous
346+
inertia at the leaves) and the front self-arrests or slows below the protection
347+
window; phase separately decides whether the bundled field reinforces or cancels.
344348

345349
*Honest status: CONJECTURE [H]. The gain law is `meta_cascade`; the phase+inertia
346350
refinement is `meta_cascade_phase`. The structural phase (sign of Δλ₂) and the
@@ -357,11 +361,16 @@ Summe): **Betrag** über die Durchlass-Verstärkung `gᵢ = Infightᵢ·(1−Rau
357361
**Bündelung (laufende Summe) vorzeichenbehafteter Beiträge** — gleichgerichtete
358362
Phasen verstärken (tiefe Kaskade), alternierende löschen aus (Selbst-Arrest in
359363
den oberen Ebenen). **Trägheit** stellt die Uhr `dtᵢ` (Schwinggleichung,
360-
`H`-Rampe grob→fein). Am realen ES-Kern: alle Phasen `+` ⇒ Eindringtiefe 4/4,
361-
die schnellen Sekunden in den Blatt-Ebenen (dt 0,68 → 0,36 s), kumulativ ~2 s im
362-
**elektromechanischen** Regime — dieselbe Mechanismus-Klasse wie die 27 s. Lehre:
363-
eine tiefe Kaskade braucht **Phasen-Ausrichtung UND niedrige Blatt-Trägheit**
364-
bricht eines von beiden, arretiert die Front. Status: Vermutung [H]; Phase (Δλ₂)
364+
`H`-Rampe grob→fein). Am realen ES-Kern: **Front-Eindringtiefe 3/4** (ankommendes
365+
`|signed_amp|` 1,0 → 0,48 → 0,34 ≥ 0,25, dann 0,13 am Blatt, wo `g→0` absorbiert).
366+
Die Front-Reichweite ist **verstärkungs-getrieben und phasen-unabhängig** (`|±x|=x`);
367+
die gleichgerichtete Phase erzeugt ein *wachsendes Interferenzfeld* (Spitze
368+
`|Σ|=1,95`), alternierende Phasen würden es auslöschen. Schnelle Sekunden in den
369+
Blatt-Ebenen (dt 0,68 → 0,36 s), kumulativ ~2 s im **elektromechanischen** Regime —
370+
dieselbe Mechanismus-Klasse wie die 27 s. Lehre: eine tiefe Kaskade braucht
371+
**durchlassende Verstärkungen UND niedrige Blatt-Trägheit** — bricht eines von
372+
beiden, arretiert die Front; die Phase entscheidet separat über Feld-Verstärkung
373+
oder -Auslöschung. Status: Vermutung [H]; Phase (Δλ₂)
365374
und Trägheits-Rampe sind Platzhalter, Kalibrierung gegen beobachtete Kaskade ist
366375
die [H][G]-Probe.
367376

crates/perturbation-sim/examples/meta_hops.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,10 @@ fn main() {
232232
);
233233
}
234234
let total = trace.last().map(|h| h.t).unwrap_or(0.0);
235+
let field_peak = trace.iter().map(|h| h.field.abs()).fold(0.0, f64::max);
235236
println!(
236-
"\n penetration depth (|field| ≥ 0.25): {depth} tiers\n \
237+
"\n front penetration (arriving |signed_amp| ≥ 0.25): {depth} tiers\n \
238+
interference field peak |Σ|: {field_peak:.3} (phase-governed: grows if aligned)\n \
237239
cumulative wall-clock: {total:.1} s → {}",
238240
mechanism_from_timescale(total)
239241
);

crates/perturbation-sim/src/resilience.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ pub fn kirchhoff_index(eigenvalues: &[f64], tol: f64) -> f64 {
4141
if n == 0 {
4242
return 0.0;
4343
}
44+
// A connected graph has EXACTLY one zero mode ⇒ n−1 finite eigenvalues. If
45+
// there are fewer (≥2 zero modes), the graph is disconnected and the
46+
// cross-component effective resistance is infinite — so Kf must be ∞, not the
47+
// finite sum over the live components. Returning the finite sum here would
48+
// rank an already-fragmented compartment as RESILIENT (low Kf), the opposite
49+
// of the truth. (Codex #509 P1.)
50+
let finite = eigenvalues.iter().filter(|&&l| l > tol).count();
51+
if finite < n - 1 {
52+
return f64::INFINITY;
53+
}
4454
let inv_sum: f64 = eigenvalues
4555
.iter()
4656
.filter(|&&l| l > tol)
@@ -116,6 +126,30 @@ mod tests {
116126
}
117127
}
118128

129+
#[test]
130+
fn disconnected_graph_has_infinite_kirchhoff() {
131+
// Two disjoint edges (0–1, 2–3): 2 components ⇒ 2 zero modes ⇒ Kf = ∞,
132+
// NOT the finite sum over each live component. A disconnected compartment
133+
// must rank as the LEAST resilient, not the most. (Codex #509 P1.)
134+
let g = Grid::new(
135+
4,
136+
vec![Edge::new(0, 1, 1.0, 1.0), Edge::new(2, 3, 1.0, 1.0)],
137+
);
138+
let eig = symmetric_eigen(&g.laplacian_of(&vec![true; g.edges.len()]), g.n);
139+
let cert = Resilience::from_eigenvalues(&eig.values, 1e-9);
140+
assert!(cert.kirchhoff.is_infinite(), "disconnected ⇒ Kf = ∞");
141+
assert!(cert.mean_resistance().is_infinite(), "and mean R = ∞");
142+
// λ₂ ≈ 0 for the disconnected graph (the second zero mode).
143+
assert!(cert.lambda2 < 1e-9, "disconnected ⇒ λ₂ ≈ 0");
144+
// The single-edge connected case is finite (n−1 = 1 finite mode): Kf(P₂)=2.
145+
let p2 = Grid::new(2, vec![Edge::new(0, 1, 1.0, 1.0)]);
146+
let e2 = symmetric_eigen(&p2.laplacian_of(&[true]), 2);
147+
assert!(
148+
kirchhoff_index(&e2.values, 1e-9).is_finite(),
149+
"connected ⇒ finite"
150+
);
151+
}
152+
119153
#[test]
120154
fn adding_an_edge_lowers_kirchhoff_and_raises_lambda2() {
121155
// Path 0–1–2–3 vs the same with a chord 0–3 (more connected = more resilient).

crates/perturbation-sim/src/timing.rs

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,15 @@ pub struct MetaHop {
181181
/// tell falls out of low inertia (short `dt`) over a few deep hops.
182182
///
183183
/// Returns `(per-tier MetaHop trace, penetration_depth)` where
184-
/// `penetration_depth` = the number of tiers whose interference field magnitude
185-
/// stays `≥ threshold` (how deep the phase-aware perturbation actually reaches).
186-
/// CONJECTURE [H]: a modeling refinement of `meta_cascade`; needs a probe
187-
/// against an observed multi-tier cascade before promotion to FINDING.
184+
/// `penetration_depth` = the number of tiers the **arriving** contribution
185+
/// `|signed_ampᵢ|` stays `≥ threshold` — the front reach. It is **gain-driven and
186+
/// hence phase-independent** (`|±x| = x`): phase governs the `field` interference
187+
/// (constructive grows, alternating cancels — read it off the `MetaHop.field`
188+
/// column), magnitude/gain governs how deep the front propagates. Counting depth
189+
/// from the cumulative `field` would overstate the reach once a tier absorbs the
190+
/// front (Codex #509 P2). CONJECTURE [H]: a modeling refinement of `meta_cascade`
191+
/// (it adds the clock + the interference field); needs a probe against an observed
192+
/// multi-tier cascade before promotion to FINDING.
188193
#[allow(clippy::too_many_arguments)]
189194
pub fn meta_cascade_phase(
190195
raumgewinn: &[f64],
@@ -233,7 +238,13 @@ pub fn meta_cascade_phase(
233238
dt,
234239
t,
235240
});
236-
if field.abs() >= threshold {
241+
// Penetration depth follows the ARRIVING contribution `|signed_amp|` (the
242+
// front), NOT the cumulative `field`. Once a tier absorbs (g→0) the front
243+
// dies and no deeper tier is reached, even though the bundled `field`
244+
// retains the earlier seed — counting from `field` would overstate the
245+
// reach (Codex #509 P2). The front magnitude is phase-independent (|±x|=x),
246+
// so phase governs the `field` interference, not the depth.
247+
if signed_amp.abs() >= threshold {
237248
depth += 1;
238249
}
239250

@@ -315,40 +326,42 @@ mod tests {
315326
}
316327

317328
#[test]
318-
fn phase_alignment_decides_penetration_depth() {
319-
// Same magnitudes (gains [1,1,1,0]); only the phase pattern differs.
320-
// Aligned phases bundle constructively (field grows 1→2→3→4); alternating
321-
// phases cancel (field 1→2→1→0).
329+
fn phase_governs_the_field_not_the_front_depth() {
330+
// Same magnitudes (gains [1,1,1,0]); only the phase pattern differs. The
331+
// arriving front |signed_amp| is identical (phase = ±1 ⇒ |·| unchanged), so
332+
// penetration depth is EQUAL — phase does NOT change the front reach. What
333+
// phase DOES change is the bundled `field`: aligned phases reinforce
334+
// (1→2→3→4), alternating phases cancel (1→2→1→0). (Post Codex #509 P2.)
322335
let h = [3.0, 3.0, 3.0, 3.0];
323-
let (con, deep) = meta_cascade_phase(
336+
let (con, depth_con) = meta_cascade_phase(
324337
&PASS_RAUM,
325338
&PASS_FIGHT,
326339
&[1, 1, 1, 1],
327340
&h,
328341
0.1,
329342
0.2,
330343
0.2,
331-
1.5,
344+
0.5,
332345
);
333-
let (alt, shallow) = meta_cascade_phase(
346+
let (alt, depth_alt) = meta_cascade_phase(
334347
&PASS_RAUM,
335348
&PASS_FIGHT,
336349
&[1, -1, 1, -1],
337350
&h,
338351
0.1,
339352
0.2,
340353
0.2,
341-
1.5,
354+
0.5,
342355
);
343-
assert!(
344-
deep > shallow,
345-
"constructive phase reaches deeper: {deep} > {shallow}"
356+
assert_eq!(
357+
depth_con, depth_alt,
358+
"front reach is phase-independent: {depth_con} == {depth_alt}"
346359
);
347360
let con_max = con.iter().map(|hp| hp.field.abs()).fold(0.0, f64::max);
348361
let alt_max = alt.iter().map(|hp| hp.field.abs()).fold(0.0, f64::max);
349362
assert!(
350363
alt_max < con_max,
351-
"alternating phase self-arrests: peak field {alt_max} < {con_max}"
364+
"alternating phase self-arrests the FIELD: peak {alt_max} < {con_max}"
352365
);
353366
}
354367

0 commit comments

Comments
 (0)