E-016b-v3-rootcause.py
Experiments: E-016b · script updated 08-18-2026
E-016b-v3-rootcause.csv
— 331.5 KB, updated 08-18-2026
← prev
page 12 of 12
· 500/page · 5600 rows
| # | series | variant | a | y | resid | resid_shifted |
|---|---|---|---|---|---|---|
| 5502 | rho_analytic | - | 10.0 | 30.100000 | 3.1818491059e+00 | |
| 5503 | rho_analytic | - | 10.0 | 30.200000 | 3.1818495059e+00 | |
| 5504 | rho_analytic | - | 10.0 | 30.300000 | 3.1818499041e+00 | |
| 5505 | rho_analytic | - | 10.0 | 30.400000 | 3.1818503004e+00 | |
| 5506 | rho_analytic | - | 10.0 | 30.500000 | 3.1818506949e+00 | |
| 5507 | rho_analytic | - | 10.0 | 30.600000 | 3.1818510875e+00 | |
| 5508 | rho_analytic | - | 10.0 | 30.700000 | 3.1818514782e+00 | |
| 5509 | rho_analytic | - | 10.0 | 30.800000 | 3.1818518672e+00 | |
| 5510 | rho_analytic | - | 10.0 | 30.900000 | 3.1818522543e+00 | |
| 5511 | rho_analytic | - | 10.0 | 31.000000 | 3.1818526395e+00 | |
| 5512 | rho_analytic | - | 10.0 | 31.100000 | 3.1818530230e+00 | |
| 5513 | rho_analytic | - | 10.0 | 31.200000 | 3.1818534046e+00 | |
| 5514 | rho_analytic | - | 10.0 | 31.300000 | 3.1818537845e+00 | |
| 5515 | rho_analytic | - | 10.0 | 31.400000 | 3.1818541625e+00 | |
| 5516 | rho_analytic | - | 10.0 | 31.500000 | 3.1818545388e+00 | |
| 5517 | rho_analytic | - | 10.0 | 31.600000 | 3.1818549133e+00 | |
| 5518 | rho_analytic | - | 10.0 | 31.700000 | 3.1818552860e+00 | |
| 5519 | rho_analytic | - | 10.0 | 31.800000 | 3.1818556569e+00 | |
| 5520 | rho_analytic | - | 10.0 | 31.900000 | 3.1818560261e+00 | |
| 5521 | rho_analytic | - | 10.0 | 32.000000 | 3.1818563936e+00 | |
| 5522 | rho_analytic | - | 10.0 | 32.100000 | 3.1818567593e+00 | |
| 5523 | rho_analytic | - | 10.0 | 32.200000 | 3.1818571232e+00 | |
| 5524 | rho_analytic | - | 10.0 | 32.300000 | 3.1818574855e+00 | |
| 5525 | rho_analytic | - | 10.0 | 32.400000 | 3.1818578460e+00 | |
| 5526 | rho_analytic | - | 10.0 | 32.500000 | 3.1818582048e+00 | |
| 5527 | rho_analytic | - | 10.0 | 32.600000 | 3.1818585619e+00 | |
| 5528 | rho_analytic | - | 10.0 | 32.700000 | 3.1818589173e+00 | |
| 5529 | rho_analytic | - | 10.0 | 32.800000 | 3.1818592711e+00 | |
| 5530 | rho_analytic | - | 10.0 | 32.900000 | 3.1818596231e+00 | |
| 5531 | rho_analytic | - | 10.0 | 33.000000 | 3.1818599735e+00 | |
| 5532 | rho_analytic | - | 10.0 | 33.100000 | 3.1818603222e+00 | |
| 5533 | rho_analytic | - | 10.0 | 33.200000 | 3.1818606692e+00 | |
| 5534 | rho_analytic | - | 10.0 | 33.300000 | 3.1818610146e+00 | |
| 5535 | rho_analytic | - | 10.0 | 33.400000 | 3.1818613584e+00 | |
| 5536 | rho_analytic | - | 10.0 | 33.500000 | 3.1818617005e+00 | |
| 5537 | rho_analytic | - | 10.0 | 33.600000 | 3.1818620410e+00 | |
| 5538 | rho_analytic | - | 10.0 | 33.700000 | 3.1818623798e+00 | |
| 5539 | rho_analytic | - | 10.0 | 33.800000 | 3.1818627171e+00 | |
| 5540 | rho_analytic | - | 10.0 | 33.900000 | 3.1818630528e+00 | |
| 5541 | rho_analytic | - | 10.0 | 34.000000 | 3.1818633868e+00 | |
| 5542 | rho_analytic | - | 10.0 | 34.100000 | 3.1818637193e+00 | |
| 5543 | rho_analytic | - | 10.0 | 34.200000 | 3.1818640502e+00 | |
| 5544 | rho_analytic | - | 10.0 | 34.300000 | 3.1818643795e+00 | |
| 5545 | rho_analytic | - | 10.0 | 34.400000 | 3.1818647073e+00 | |
| 5546 | rho_analytic | - | 10.0 | 34.500000 | 3.1818650335e+00 | |
| 5547 | rho_analytic | - | 10.0 | 34.600000 | 3.1818653581e+00 | |
| 5548 | rho_analytic | - | 10.0 | 34.700000 | 3.1818656813e+00 | |
| 5549 | rho_analytic | - | 10.0 | 34.800000 | 3.1818660028e+00 | |
| 5550 | rho_analytic | - | 10.0 | 34.900000 | 3.1818663229e+00 | |
| 5551 | rho_analytic | - | 10.0 | 35.000000 | 3.1818666414e+00 | |
| 5552 | rho_analytic | - | 10.0 | 35.100000 | 3.1818669585e+00 | |
| 5553 | rho_analytic | - | 10.0 | 35.200000 | 3.1818672740e+00 | |
| 5554 | rho_analytic | - | 10.0 | 35.300000 | 3.1818675880e+00 | |
| 5555 | rho_analytic | - | 10.0 | 35.400000 | 3.1818679006e+00 | |
| 5556 | rho_analytic | - | 10.0 | 35.500000 | 3.1818682116e+00 | |
| 5557 | rho_analytic | - | 10.0 | 35.600000 | 3.1818685212e+00 | |
| 5558 | rho_analytic | - | 10.0 | 35.700000 | 3.1818688294e+00 | |
| 5559 | rho_analytic | - | 10.0 | 35.800000 | 3.1818691361e+00 | |
| 5560 | rho_analytic | - | 10.0 | 35.900000 | 3.1818694413e+00 | |
| 5561 | rho_analytic | - | 10.0 | 36.000000 | 3.1818697451e+00 | |
| 5562 | rho_analytic | - | 10.0 | 36.100000 | 3.1818700475e+00 | |
| 5563 | rho_analytic | - | 10.0 | 36.200000 | 3.1818703484e+00 | |
| 5564 | rho_analytic | - | 10.0 | 36.300000 | 3.1818706479e+00 | |
| 5565 | rho_analytic | - | 10.0 | 36.400000 | 3.1818709460e+00 | |
| 5566 | rho_analytic | - | 10.0 | 36.500000 | 3.1818712427e+00 | |
| 5567 | rho_analytic | - | 10.0 | 36.600000 | 3.1818715380e+00 | |
| 5568 | rho_analytic | - | 10.0 | 36.700000 | 3.1818718320e+00 | |
| 5569 | rho_analytic | - | 10.0 | 36.800000 | 3.1818721245e+00 | |
| 5570 | rho_analytic | - | 10.0 | 36.900000 | 3.1818724157e+00 | |
| 5571 | rho_analytic | - | 10.0 | 37.000000 | 3.1818727055e+00 | |
| 5572 | rho_analytic | - | 10.0 | 37.100000 | 3.1818729939e+00 | |
| 5573 | rho_analytic | - | 10.0 | 37.200000 | 3.1818732810e+00 | |
| 5574 | rho_analytic | - | 10.0 | 37.300000 | 3.1818735668e+00 | |
| 5575 | rho_analytic | - | 10.0 | 37.400000 | 3.1818738512e+00 | |
| 5576 | rho_analytic | - | 10.0 | 37.500000 | 3.1818741343e+00 | |
| 5577 | rho_analytic | - | 10.0 | 37.600000 | 3.1818744161e+00 | |
| 5578 | rho_analytic | - | 10.0 | 37.700000 | 3.1818746966e+00 | |
| 5579 | rho_analytic | - | 10.0 | 37.800000 | 3.1818749757e+00 | |
| 5580 | rho_analytic | - | 10.0 | 37.900000 | 3.1818752536e+00 | |
| 5581 | rho_analytic | - | 10.0 | 38.000000 | 3.1818755301e+00 | |
| 5582 | rho_analytic | - | 10.0 | 38.100000 | 3.1818758054e+00 | |
| 5583 | rho_analytic | - | 10.0 | 38.200000 | 3.1818760794e+00 | |
| 5584 | rho_analytic | - | 10.0 | 38.300000 | 3.1818763522e+00 | |
| 5585 | rho_analytic | - | 10.0 | 38.400000 | 3.1818766236e+00 | |
| 5586 | rho_analytic | - | 10.0 | 38.500000 | 3.1818768939e+00 | |
| 5587 | rho_analytic | - | 10.0 | 38.600000 | 3.1818771628e+00 | |
| 5588 | rho_analytic | - | 10.0 | 38.700000 | 3.1818774306e+00 | |
| 5589 | rho_analytic | - | 10.0 | 38.800000 | 3.1818776971e+00 | |
| 5590 | rho_analytic | - | 10.0 | 38.900000 | 3.1818779623e+00 | |
| 5591 | rho_analytic | - | 10.0 | 39.000000 | 3.1818782264e+00 | |
| 5592 | rho_analytic | - | 10.0 | 39.100000 | 3.1818784892e+00 | |
| 5593 | rho_analytic | - | 10.0 | 39.200000 | 3.1818787509e+00 | |
| 5594 | rho_analytic | - | 10.0 | 39.300000 | 3.1818790113e+00 | |
| 5595 | rho_analytic | - | 10.0 | 39.400000 | 3.1818792705e+00 | |
| 5596 | rho_analytic | - | 10.0 | 39.500000 | 3.1818795286e+00 | |
| 5597 | rho_analytic | - | 10.0 | 39.600000 | 3.1818797855e+00 | |
| 5598 | rho_analytic | - | 10.0 | 39.700000 | 3.1818800412e+00 | |
| 5599 | rho_analytic | - | 10.0 | 39.800000 | 3.1818802957e+00 | |
| 5600 | rho_analytic | - | 10.0 | 39.900000 | 3.1818805491e+00 | |
| 5601 | rho_analytic | - | 10.0 | 40.000000 | 3.1818808014e+00 |
E-016b-v3-rootcause.json
— 6.9 KB, updated 08-18-2026
JSON sidecar (click to expand)
{
"experiment": "E-016b",
"task": "2.6: root-cause E-016 V3 Poisson-reconstruction failure",
"script": "research/numerics/scripts/E-016b-v3-rootcause.py",
"parameters": {
"a_sweep": [
4.0,
10.0
],
"h": 0.01,
"lambda": -1.0,
"y_grid": [
0.1,
40.0,
400
],
"z_max": 300.0
},
"report": {
"4.0": {
"T1_kappa_re": -0.9914379801328772,
"T1_kappa_im": -1.4222222222222223,
"T2_census": {
"n_real": 381,
"z1": 1.1260734006983593,
"z_last": 299.8646747694667,
"lattice_count_window": 381.9718634205488
},
"T2_winding": {
"0.3927": {
"winding": 1.9971283856655768,
"real_zeros_inside": 0,
"excess": 1.9971283856655768
},
"1.1781": {
"winding": 3.957366770074305,
"real_zeros_inside": 2,
"excess": 1.9573667700743052
},
"0.0000": {
"winding": 1.9960937479061762,
"real_zeros_inside": 0,
"excess": 1.9960937479061762
},
"0.0001": {
"winding": 1.9960937500549862,
"real_zeros_inside": 0,
"excess": 1.9960937500549862
},
"0.0100": {
"winding": 1.996094371929568,
"real_zeros_inside": 0,
"excess": 1.996094371929568
},
"0.1000": {
"winding": 1.9961562249900062,
"real_zeros_inside": 0,
"excess": 1.9961562249900062
},
"2.0000": {
"winding": 5.942871677547558,
"real_zeros_inside": 4,
"excess": 1.9428716775475579
},
"5.0000": {
"winding": 12.077973882052314,
"real_zeros_inside": 10,
"excess": 2.077973882052314
}
},
"T2_imag_pair": {
"eps": 5.829287623947368e-09,
"W_at_ieps": 2.825742938081395e-49,
"scan_min": 5.749824935036285e-06,
"scan_at_y": 0.0001
},
"T3_A_over_y_fit": 0.6343007263567857,
"T3_two_over_pi": 0.6366197723675814,
"T3_mean_shift": -1.2815872994129114,
"T4_variants": {
"base": {
"resid_y0.5": 1.2739134084231456,
"resid_y5": 0.07332699024962142,
"resid_y20": -0.9925381870041659,
"shape_rms_rel": 1.0497908515365875
},
"trunc60": {
"resid_y0.5": 1.2766229564752873,
"resid_y5": -0.14349117160523672,
"resid_y20": -4.94373487936198,
"shape_rms_rel": 4.338262071341213
},
"trunc100": {
"resid_y0.5": 1.27526491761311,
"resid_y5": -0.034968760716001634,
"resid_y20": -3.0133337980338,
"shape_rms_rel": 2.809762823982386
},
"notail": {
"resid_y0.5": 1.2745883033553684,
"resid_y5": 0.14081030856832366,
"resid_y20": 0.08570192959282363,
"shape_rms_rel": 0.2895380312354026
},
"base+pair": {
"resid_y0.5": 0.0006738636879826121,
"resid_y5": -0.05399696422389488,
"resid_y20": -1.0243691756225448,
"shape_rms_rel": 0.9421400227183758
},
"mp60": {
"resid_y0.5": 1.276622956475289,
"resid_y5": -0.14349117160523361,
"resid_y20": -4.943734879361979,
"shape_rms_rel": 4.338262071341214
}
},
"T4_mp60": {
"n_relocated": 75,
"max_abs_dz": 1.2434497875801753e-12
},
"T5_sinc_est_max": 0.0002055923399439411
},
"10.0": {
"T1_kappa_re": -3.6447054985051435,
"T1_kappa_im": -4.397114393679149,
"T2_census": {
"n_real": 954,
"z1": 0.44959700551214055,
"z_last": 299.89357740917427,
"lattice_count_window": 954.9296585513721
},
"T2_winding": {
"0.1571": {
"winding": 1.9971325652242644,
"real_zeros_inside": 0,
"excess": 1.9971325652242644
},
"0.4712": {
"winding": 3.9589810973742314,
"real_zeros_inside": 2,
"excess": 1.9589810973742314
},
"0.0000": {
"winding": 1.996093695657393,
"real_zeros_inside": 0,
"excess": 1.996093695657393
},
"0.0001": {
"winding": 1.9960937503824123,
"real_zeros_inside": 0,
"excess": 1.9960937503824123
},
"0.0100": {
"winding": 1.9960976525143628,
"real_zeros_inside": 0,
"excess": 1.9960976525143628
},
"0.1000": {
"winding": 1.9964955397146036,
"real_zeros_inside": 0,
"excess": 1.9964955397146036
},
"2.0000": {
"winding": 12.085419712205663,
"real_zeros_inside": 10,
"excess": 2.0854197122056632
},
"5.0000": {
"winding": 31.975335381644364,
"real_zeros_inside": 30,
"excess": 1.9753353816443635
}
},
"T2_imag_pair": {
"eps": 2.0797966440236883e-09,
"W_at_ieps": 1.6504059507421108e-26,
"scan_min": 0.01459451355707131,
"scan_at_y": 0.0001
},
"T3_A_over_y_fit": 0.6308206254641555,
"T3_two_over_pi": 0.6366197723675814,
"T3_mean_shift": -3.3631535473854246,
"T4_variants": {
"base": {
"resid_y0.5": 1.2749261380781172,
"resid_y5": -0.007753707743303639,
"resid_y20": -2.530820269504034,
"shape_rms_rel": 1.0143675095987894
},
"trunc60": {
"resid_y0.5": 1.2816709492693397,
"resid_y5": -0.5465597395886301,
"resid_y20": -12.354286478739445,
"shape_rms_rel": 4.43884573453234
},
"trunc100": {
"resid_y0.5": 1.278305028486991,
"resid_y5": -0.27789168396584607,
"resid_y20": -7.57271682340175,
"shape_rms_rel": 2.860573136578675
},
"notail": {
"resid_y0.5": 1.2766145378113016,
"resid_y5": 0.16107079639175792,
"resid_y20": 0.16663165033476446,
"shape_rms_rel": 0.11754357982145858
},
"base+pair": {
"resid_y0.5": 0.0016865933429546232,
"resid_y5": -0.13507766221681994,
"resid_y20": -2.5626512581224135,
"shape_rms_rel": 0.9793208614044097
},
"mp60": {
"resid_y0.5": 1.2816709492693397,
"resid_y5": -0.5465597395886301,
"resid_y20": -12.354286478739445,
"shape_rms_rel": 4.438845734532339
}
},
"T4_mp60": {
"n_relocated": 190,
"max_abs_dz": 1.0587086762825493e-12
},
"T5_sinc_est_max": 0.0002055923399439411
}
},
"elapsed_seconds": 702.8,
"timestamp_utc": "2026-08-18T17:19:13.123417+00:00"
}
Source code
Python script (click to expand)
"""E-016b — root-cause the E-016 V3 (zero-Poisson reconstruction) failure.
E-016 V3: rho_W^analytic (Galerkin z-derivative) vs rho_W^recon (located
real zeros + Euler-Maclaurin lattice tail) mismatch rel ~0.3 at a=4.
Residual (D-024 sec.4): +1.274 (y=0.5), +0.637 (1), +0.313 (2), +0.073
(5), -0.179 (10), -0.993 (20). Note 2/(pi*y) = 1.2732, 0.6366, 0.3183 —
the low-y residual matches a MISSING zero pair at |z|~0 to 4 digits.
Tests (D-024 sec.4 candidates + new hypothesis):
T1. kappa = W'/W(0): exact gauge arithmetic gives kappa = 0 by symmetry
(x symmetric, c real). Numeric confirmation. => candidate (a) dead.
T2. Zero census + ARGUMENT PRINCIPLE on circles |z|=r: the real-gauge
brentq scan can only find REAL zeros. If Galerkin W has a complex
pair near 0 (candidate: purely imaginary +/-i*eps, self-conjugate
under the mirror symmetries), the winding number exceeds the real-
zero count. If found: locate eps by scanning |W(iy)|.
T3. Residual shape on a fine y-grid: test A/y law; report A vs 2/pi.
T4. Recon variants: (i) base (zeros<=300 + EM tail); (ii) trunc60;
(iii) trunc100; (iv) notail; (v) mp60 (zeros<=60 relocated at
dps 50 + EM tail from 60 — float64 contamination test, D-024 (c));
(vi) base+pair (recon + exact contribution of the T2 imaginary
pair, (2/pi) y/(y^2-eps^2) — decisive if T2 fires).
T5. Sinc-artifact zeros of hat h at z=2*pi*k/h (=628k, beyond window):
analytic size of their rho contribution (expected ~1e-4, dismiss).
Outputs: research/numerics/data/E-016b-v3-rootcause.csv (+ .json)
"""
import importlib.util
import json
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
import mpmath as mp
import numpy as np
import scipy.optimize
REPO = Path(__file__).resolve().parents[3]
DATA = REPO / "research" / "numerics" / "data"
CSV = DATA / "E-016b-v3-rootcause.csv"
JSON = DATA / "E-016b-v3-rootcause.json"
# ---- import E-016 machinery (runs the sieve once, ~15 s) ---------------
_spec = importlib.util.spec_from_file_location(
"E16", Path(__file__).with_name("E-016-herglotz-fine-structure.py"))
E16 = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(E16)
A_SWEEP = [4.0, 10.0]
H = E16.H_MAIN
LAM = E16.LAM_SHIFT
Y_GRID = np.linspace(0.1, 40.0, 400)
Z_MAX = 300.0
N_GRID = 40000
WIND_RADII_FRAC = [0.5, 1.5] # in units of pi/a, plus absolute below
WIND_RADII_ABS = [1e-6, 1e-4, 1e-2, 0.1, 2.0, 5.0]
def locate_real_zeros(cp, x, h, z_max=Z_MAX, n_grid=N_GRID):
"""Real-axis sign-change + brentq on g(z) = z Im Vp - Re Vp (E-016)."""
zg = np.linspace(1e-3, z_max, n_grid)
Vpr, _ = E16.V_and_dV(zg, cp, x, h)
g = zg * Vpr.imag - Vpr.real
def g_direct(zz):
V = E16.hat_FT(np.array([zz]), h)[0] * complex(
np.exp(1j * zz * x) @ cp)
return zz * V.imag - V.real
idx = np.flatnonzero(np.sign(g[:-1]) * np.sign(g[1:]) < 0)
zeros = []
for i in idx:
try:
zeros.append(scipy.optimize.brentq(g_direct, zg[i], zg[i + 1],
xtol=1e-13, rtol=1e-13))
except ValueError:
pass
return np.array(zeros)
def winding(cp, cm, x, h, r, n_pts=512):
"""Argument-principle zero count of W(theta=pi) on |z|=r."""
phi = np.linspace(0, 2 * np.pi, n_pts, endpoint=False)
z = r * np.exp(1j * phi)
Vp, dVp = E16.V_and_dV(z, cp, x, h)
Vm, dVm = E16.V_and_dV(z, cm, x, h)
W, _ = E16.W_and_dW(z, Vp, dVp, Vm, dVm, np.pi)
dphi = np.diff(np.unwrap(np.angle(W)))
return float(dphi.sum() / (2 * np.pi))
def imag_pair_scan(cp, cm, x, h, y_max=3.0):
"""Scan |W(iy)| for a minimum -> candidate purely-imaginary pair."""
ys = np.linspace(1e-4, y_max, 30000)
z = 1j * ys
Vp, _ = E16.V_and_dV(z, cp, x, h)
Vm, _ = E16.V_and_dV(z, cm, x, h)
W = (z - 1j) * Vp - (z + 1j) * Vm # theta = pi
aw = np.abs(W)
i = int(np.argmin(aw))
# polish with mpmath findroot on W(iy)=0 (real unknown y)
mp.mp.dps = 50
cpm = [mp.mpf(float(v)) for v in cp]
cmm = [mp.mpf(float(v)) for v in cm]
xm = [mp.mpf(float(v)) for v in x]
def Wimag(yy):
yy = mp.mpf(yy)
Sp = sum(c * mp.e ** (1j * yy * xx) for c, xx in zip(cpm, xm))
Sm = sum(c * mp.e ** (1j * yy * xx) for c, xx in zip(cmm, xm))
hh = mp.mpf(h)
hat = hh * (mp.sin(yy * hh / 2) / (yy * hh / 2)) ** 2
Vpv, Vmv = hat * Sp, hat * Sm
W = 1j * (yy - 1) * Vpv - 1j * (yy + 1) * Vmv # W(i*yy), th=pi
return mp.im(W) # mirror symmetry: W(iy) = i * (real)
try:
root = mp.findroot(Wimag, mp.mpf(float(ys[i])),
solver="secant", tol=mp.mpf("1e-40"))
eps = float(root)
resid = float(abs(Wimag(root)))
return eps, resid, float(aw[i]), float(ys[i])
except (ValueError, ZeroDivisionError):
return None, None, float(aw[i]), float(ys[i])
def em_tail(y, z_start, d_lat):
return (y / d_lat) * (np.pi / 2.0 - np.arctan(z_start / y))
def recon_rho(y, zeros_pos, d_lat, z_tail_start=None, extra_pairs=()):
"""Poisson density from positive zeros (doubled) + EM tail + explicit
extra pairs (purely imaginary +/-i*eps: 2y/(y^2 - eps^2))."""
y = np.asarray(y, dtype=float)
zeros_pos = np.asarray(zeros_pos, dtype=float)
if zeros_pos.size:
s = 2.0 * np.sum(y[:, None] / (y[:, None] ** 2
+ zeros_pos[None, :] ** 2), axis=1)
else:
s = np.zeros_like(y)
if z_tail_start is not None:
s = s + 2.0 * em_tail(y, z_tail_start, d_lat)
for eps in extra_pairs:
s = s + 2.0 * y / (y**2 - eps**2)
return s / np.pi
def mp_relocate(cp, x, h, zeros_pos, z_hi=60.0):
"""Re-locate zeros <= z_hi with mpmath dps 50 (float64 contamination
test). Bisection on the real gauge, 80 iterations."""
mp.mp.dps = 50
cpm = [mp.mpf(float(v)) for v in cp]
xm = [mp.mpf(float(v)) for v in x]
hh = mp.mpf(h)
def g_mp(zz):
zz = mp.mpf(zz)
S = sum(c * mp.e ** (1j * zz * xx) for c, xx in zip(cpm, xm))
hat = hh * (mp.sin(zz * hh / 2) / (zz * hh / 2)) ** 2
V = hat * S
return zz * mp.im(V) - mp.re(V)
out = []
for z0 in zeros_pos:
if z0 > z_hi:
continue
lo, hi = mp.mpf(float(z0)) - mp.mpf("0.05"), \
mp.mpf(float(z0)) + mp.mpf("0.05")
flo, fhi = g_mp(lo), g_mp(hi)
if flo * fhi > 0:
out.append((float(z0), None))
continue
for _ in range(80):
mid = (lo + hi) / 2
fm = g_mp(mid)
if flo * fm <= 0:
hi, fhi = mid, fm
else:
lo, flo = mid, fm
out.append((float(z0), float((lo + hi) / 2)))
return out
def main() -> int:
if CSV.exists() and "--force" not in sys.argv:
print(f"cached: {CSV} (use --force to recompute)")
return 0
t0 = time.time()
rows = []
report = {}
for a in A_SWEEP:
print(f"a={a}: build + solve ...", flush=True)
x, hh, Q, M, b = E16.build(a, H)
n = len(x)
cp, T = E16.solve_v(Q, M, b[+1.0], LAM, n)
cm, _ = E16.solve_v(Q, M, b[-1.0], LAM, n)
np_ = float(cp @ T @ cp)
nm = float(cm @ T @ cm)
cm = cm * np.sqrt(np_ / nm)
d_lat = np.pi / a
# analytic rho_W at theta=pi on the fine grid
ziy = 1j * Y_GRID
Vp, dVp = E16.V_and_dV(ziy, cp, x, hh)
Vm, dVm = E16.V_and_dV(ziy, cm, x, hh)
W, dW = E16.W_and_dW(ziy, Vp, dVp, Vm, dVm, np.pi)
rho_an = -np.imag(dW / W) / np.pi
# T1: kappa = W'/W(0)
z0 = np.array([0.0 + 0.0j])
Vp0, dVp0 = E16.V_and_dV(z0, cp, x, hh)
Vm0, dVm0 = E16.V_and_dV(z0, cm, x, hh)
W0, dW0 = E16.W_and_dW(z0, Vp0, dVp0, Vm0, dVm0, np.pi)
kappa = complex(dW0[0] / W0[0])
print(f" T1 kappa = {kappa:.3e}", flush=True)
# T2: census + argument principle
zeros = locate_real_zeros(cp, x, hh)
census = {"n_real": int(len(zeros)), "z1": float(zeros[0]),
"z_last": float(zeros[-1]),
"lattice_count_window": a * Z_MAX / np.pi}
radii = [f * d_lat for f in WIND_RADII_FRAC] + WIND_RADII_ABS
wind = {}
for r in radii:
w = winding(cp, cm, x, hh, r)
n_real_in = int(2 * np.sum(zeros < r))
wind[f"{r:.4f}"] = {"winding": w, "real_zeros_inside": n_real_in,
"excess": w - n_real_in}
print(f" T2 winding: {wind}", flush=True)
eps, eps_resid, scan_min, scan_at = None, None, None, None
if any(abs(v["excess"]) > 0.5 for v in wind.values()):
eps, eps_resid, scan_min, scan_at = imag_pair_scan(cp, cm, x, hh)
print(f" T2 imaginary pair: eps={eps} |W(i*eps)|={eps_resid:.2e} "
f"(scan min {scan_min:.3e} at y={scan_at:.4f})", flush=True)
# T3: residual shape (base variant), A/y fit at small y
z_tail = zeros[-1] + d_lat / 2.0
rec_base = recon_rho(Y_GRID, zeros, d_lat, z_tail)
resid = rho_an - rec_base
sel_small = (Y_GRID >= 0.3) & (Y_GRID <= 2.0)
A_fit = float(np.mean(resid[sel_small] * Y_GRID[sel_small]))
# mean-shift removed version for shape comparison
shift = float(np.mean(resid))
# T4: variants
z60 = zeros[zeros <= 60.0]
z100 = zeros[zeros <= 100.0]
variants = {
"base": rec_base,
"trunc60": recon_rho(Y_GRID, z60, d_lat, z60[-1] + d_lat / 2),
"trunc100": recon_rho(Y_GRID, z100, d_lat, z100[-1] + d_lat / 2),
"notail": recon_rho(Y_GRID, zeros, d_lat, None),
}
if eps is not None and eps_resid is not None and eps_resid < 1e-20:
variants["base+pair"] = recon_rho(
Y_GRID, zeros, d_lat, z_tail, extra_pairs=(eps,))
print(" T4 mp60 relocation (dps 50) ...", flush=True)
reloc = mp_relocate(cp, x, hh, zeros, 60.0)
n_ok = sum(1 for _, r in reloc if r is not None)
max_dz = max((abs(z0 - r) for z0, r in reloc if r is not None),
default=0.0)
z60mp = np.array([r for _, r in reloc if r is not None])
variants["mp60"] = recon_rho(Y_GRID, z60mp, d_lat,
z60mp[-1] + d_lat / 2)
var_summary = {}
for name, rec in variants.items():
res = rho_an - rec
res_shifted = res - np.mean(res)
var_summary[name] = {
"resid_y0.5": float(res[np.argmin(abs(Y_GRID - 0.5))]),
"resid_y5": float(res[np.argmin(abs(Y_GRID - 5.0))]),
"resid_y20": float(res[np.argmin(abs(Y_GRID - 20.0))]),
"shape_rms_rel": float(np.sqrt(np.mean(res_shifted**2))
/ np.sqrt(np.mean(rho_an**2))),
}
for y, rv, rsv in zip(Y_GRID, res, res_shifted):
rows.append(f"resid,{name},{a},{y:.6f},{rv:.10e},{rsv:.10e}")
for y, rv in zip(Y_GRID, rho_an):
rows.append(f"rho_analytic,-,{a},{y:.6f},{rv:.10e},")
# T5: sinc-artifact zero contribution estimate (analytic formula)
k = np.arange(1, 20)
zk = 2 * np.pi * k / hh
sinc_est = float(np.max(
(4.0 / np.pi) * np.max(Y_GRID) * np.sum(1.0 / zk**2)))
report[str(a)] = {
"T1_kappa_re": kappa.real, "T1_kappa_im": kappa.imag,
"T2_census": census, "T2_winding": wind,
"T2_imag_pair": {"eps": eps, "W_at_ieps": eps_resid,
"scan_min": scan_min, "scan_at_y": scan_at},
"T3_A_over_y_fit": A_fit, "T3_two_over_pi": 2.0 / np.pi,
"T3_mean_shift": shift,
"T4_variants": var_summary,
"T4_mp60": {"n_relocated": n_ok, "max_abs_dz": max_dz},
"T5_sinc_est_max": sinc_est,
}
print(f" T3 A/y fit: A={A_fit:.4f} vs 2/pi={2/np.pi:.4f} "
f"shift={shift:.4f}", flush=True)
print(f" T4 variants: {json.dumps(var_summary, indent=1)}",
flush=True)
with CSV.open("w", encoding="utf-8", newline="") as f:
f.write("series,variant,a,y,resid,resid_shifted\n")
f.write("\n".join(rows) + "\n")
meta = {
"experiment": "E-016b",
"task": "2.6: root-cause E-016 V3 Poisson-reconstruction failure",
"script": "research/numerics/scripts/E-016b-v3-rootcause.py",
"parameters": {"a_sweep": A_SWEEP, "h": H, "lambda": LAM,
"y_grid": [0.1, 40.0, 400], "z_max": Z_MAX},
"report": report,
"elapsed_seconds": round(time.time() - t0, 1),
"timestamp_utc": datetime.now(timezone.utc).isoformat(),
}
JSON.write_text(json.dumps(meta, indent=2), encoding="utf-8")
print(f"wrote {CSV.name}, {JSON.name}")
return 0
if __name__ == "__main__":
raise SystemExit(main())