Current task: —

E-019-zeroflow-a12.py

Experiments: E-019 · script updated 08-18-2026

E-019-zeroflow-a12.csv — 55.3 KB, updated 08-18-2026

← prev page 5 of 5 · 500/page · 2129 rows
# seriesajvalue
2002 zero 12 1047 274.3345912452
2003 zero 12 1048 274.6070158394
2004 zero 12 1049 274.8341971134
2005 zero 12 1050 275.1306730413
2006 zero 12 1051 275.4007274651
2007 zero 12 1052 275.5915140553
2008 zero 12 1053 275.9134404340
2009 zero 12 1054 276.1648323523
2010 zero 12 1055 276.4478631382
2011 zero 12 1056 276.6942382494
2012 zero 12 1057 276.9692378804
2013 zero 12 1058 277.2367048400
2014 zero 12 1059 277.4987357490
2015 zero 12 1060 277.8121846723
2016 zero 12 1061 277.9353940583
2017 zero 12 1062 278.2516253871
2018 zero 12 1063 278.5483670212
2019 zero 12 1064 278.8432766306
2020 zero 12 1065 279.0235103133
2021 zero 12 1066 279.2371499945
2022 zero 12 1067 279.5643213687
2023 zero 12 1068 279.8335536549
2024 zero 12 1069 280.0911110422
2025 zero 12 1070 280.3695873820
2026 zero 12 1071 280.6393922679
2027 zero 12 1072 280.9473336636
2028 zero 12 1073 281.0704080022
2029 zero 12 1074 281.3991036551
2030 zero 12 1075 281.6773672661
2031 zero 12 1076 281.9632737434
2032 zero 12 1077 282.1885110142
2033 zero 12 1078 282.4648252136
2034 zero 12 1079 282.7393275389
2035 zero 12 1080 282.9965622127
2036 zero 12 1081 283.2150058991
2037 zero 12 1082 283.5593475495
2038 zero 12 1083 283.6842450142
2039 zero 12 1084 284.0147657795
2040 zero 12 1085 284.2929885160
2041 zero 12 1086 284.5460244825
2042 zero 12 1087 284.8348566894
2043 zero 12 1088 285.0870396560
2044 zero 12 1089 285.3509760773
2045 zero 12 1090 285.6128655371
2046 zero 12 1091 285.8892483123
2047 zero 12 1092 286.2225172987
2048 zero 12 1093 286.3300296352
2049 zero 12 1094 286.6652381840
2050 zero 12 1095 286.9173350151
2051 zero 12 1096 287.2541105554
2052 zero 12 1097 287.3787938941
2053 zero 12 1098 287.7017157261
2054 zero 12 1099 287.9159932551
2055 zero 12 1100 288.2126965767
2056 zero 12 1101 288.4620949337
2057 zero 12 1102 288.7454763587
2058 zero 12 1103 289.0144140751
2059 zero 12 1104 289.2805806941
2060 zero 12 1105 289.5767822509
2061 zero 12 1106 289.8262414173
2062 zero 12 1107 289.9904222433
2063 zero 12 1108 290.3169489319
2064 zero 12 1109 290.5761380014
2065 zero 12 1110 290.8467247020
2066 zero 12 1111 291.1252388588
2067 zero 12 1112 291.4554728529
2068 zero 12 1113 291.5803115479
2069 zero 12 1114 291.8493322156
2070 zero 12 1115 292.1485299849
2071 zero 12 1116 292.4191213654
2072 zero 12 1117 292.6927764235
2073 zero 12 1118 292.9508838594
2074 zero 12 1119 293.2152597087
2075 zero 12 1120 293.5491902312
2076 zero 12 1121 293.6997843817
2077 zero 12 1122 294.0739554707
2078 zero 12 1123 294.1792344017
2079 zero 12 1124 294.5481722982
2080 zero 12 1125 294.6904649142
2081 zero 12 1126 294.9688601428
2082 zero 12 1127 295.2795629084
2083 zero 12 1128 295.5715456281
2084 zero 12 1129 295.8121381696
2085 zero 12 1130 296.0773741128
2086 zero 12 1131 296.3535373106
2087 zero 12 1132 296.5996878600
2088 zero 12 1133 296.8469514430
2089 zero 12 1134 297.1378009018
2090 zero 12 1135 297.4011455508
2091 zero 12 1136 297.6719241727
2092 zero 12 1137 297.9753446034
2093 zero 12 1138 298.2104008967
2094 zero 12 1139 298.3758350627
2095 zero 12 1140 298.7036951461
2096 zero 12 1141 298.9762518390
2097 zero 12 1142 299.2784873767
2098 zero 12 1143 299.4942892818
2099 zero 12 1144 299.8305991231
2100 zero 12 1145 299.9663842803
2101 gamma - 1 14.1347251417
2102 gamma - 2 21.0220396388
2103 gamma - 3 25.0108575801
2104 gamma - 4 30.4248761259
2105 gamma - 5 32.9350615877
2106 gamma - 6 37.5861781588
2107 gamma - 7 40.9187190121
2108 gamma - 8 43.3270732809
2109 gamma - 9 48.0051508812
2110 gamma - 10 49.7738324777
2111 gamma - 11 52.9703214777
2112 gamma - 12 56.4462476971
2113 gamma - 13 59.3470440026
2114 gamma - 14 60.8317785246
2115 gamma - 15 65.1125440481
2116 gamma - 16 67.0798105295
2117 gamma - 17 69.5464017112
2118 gamma - 18 72.0671576745
2119 gamma - 19 75.7046906991
2120 gamma - 20 77.1448400689
2121 gamma - 21 79.3373750202
2122 gamma - 22 82.9103808541
2123 gamma - 23 84.7354929805
2124 gamma - 24 87.4252746131
2125 gamma - 25 88.8091112076
2126 gamma - 26 92.4918992706
2127 gamma - 27 94.6513440405
2128 gamma - 28 95.8706342282
2129 gamma - 29 98.8311942182
2130 gamma - 30 101.3178510057

E-019-zeroflow-a12.json — 2.4 KB, updated 08-18-2026

JSON sidecar (click to expand)
{
  "experiment": "E-019",
  "task": "2.8(ii): backend v2 validation + zero-flow at a=12",
  "script": "research/numerics/scripts/E-019-zeroflow-a12.py",
  "parameters": {
    "a_val": 10.0,
    "a_new": 12.0,
    "h": 0.01,
    "lambda": -1.0,
    "z_max": 300.0
  },
  "checks": {
    "VA1_backend_matches": true,
    "VA2_pipeline_a10": true,
    "B3_no_new_complex_zeros": true
  },
  "report": {
    "validation": {
      "VA1_max_dev": 2.2246968001127243e-07,
      "VA2_Q_dev": 4.3871651103644105e-08,
      "VA2_z1_dev": 6.942222297023903e-09,
      "VA2_count_diff": 0
    },
    "B1_zeroflow": {
      "a10": {
        "n": 954,
        "z1": 0.44959701245436284,
        "z1_times_a": 4.4959701245436285,
        "count_vs_lattice": 954.9296585513721,
        "gap_mean": 0.31421194329970964,
        "gap_cv": 0.19770812588191275
      },
      "a12": {
        "n": 1145,
        "z1": 0.3746087478472408,
        "z1_times_a": 4.495304974166889,
        "count_vs_lattice": 1145.9155902616465,
        "gap_mean": 0.26188092266824087,
        "gap_cv": 0.18590651886845952
      }
    },
    "B2_gamma_locking": {
      "a10": {
        "min": 0.00020978025152373903,
        "zero_idx": 245,
        "gamma_idx": 20,
        "zero": 77.14504984912632,
        "gamma": 77.1448400688748,
        "median": 48.899737666799254
      },
      "a12": {
        "min": 5.2439703061679666e-05,
        "zero_idx": 95,
        "gamma_idx": 3,
        "zero": 25.010805140442628,
        "gamma": 25.01085758014569,
        "median": 48.79267672793192
      }
    },
    "B3_strip_winding": {
      "a10": {
        "1.0": {
          "winding": 253.99999999999946,
          "real_inside": 252,
          "excess": 1.99999999999946
        },
        "0.1": {
          "winding": 253.99999999999946,
          "real_inside": 252,
          "excess": 1.99999999999946
        }
      },
      "a12": {
        "1.0": {
          "winding": 305.9999999999994,
          "real_inside": 304,
          "excess": 1.9999999999993747
        },
        "0.1": {
          "winding": 305.9999999999993,
          "real_inside": 304,
          "excess": 1.9999999999993179
        }
      }
    },
    "B4_envelope": {
      "median_M_over_R0_y0.3": 0.006526107920803935
    }
  },
  "elapsed_seconds": 672.2,
  "timestamp_utc": "2026-08-18T19:32:51.149654+00:00"
}

Source code

Python script (click to expand)
"""E-019 — task 2.8(ii): backend v2 validation (A) + zero-flow at a=12 (B).

Sub-stage A (validation gate, all must pass before a=12 is trusted):
  V-A1. prime_backend_v2 vs E-016 in-memory cumsums: max |dev| on 1e4
        random t in (0,20] < 1e-6 abs (float64 summation-reordering
        noise floor ~2e-7 measured; both backends carry the same
        rounding level, so agreement at this level = exact).
  V-A2. full pipeline at a=10 with the v2 backend: stiffness matrix Q
        vs E16.build(10) entrywise max dev < 1e-8; located zeros at
        a=10 match the E-016 family (z1 dev < 1e-6; count equal).

Sub-stage B (measurements at a=12, theta=pi):
  B1. zero-flow: located real zeros (0,300]: count vs aT/pi, z1*a law,
      gap statistics vs a=10 (inflow continues? any bounce?).
  B2. gamma-locking metric: min |z_j - gamma_k| over first 30 Riemann
      zeros (mpmath zetazero, safe per STATE) vs a=10 value.
  B3. strip winding counts: W-zeros in |Re z| <= 40, |Im z| <= y0 for
      y0 in {1, 0.1} (argument principle; detects any non-real
      deviation beyond the known origin pair).
  B4. envelope spot check: median |M|/|R_0| at y0 = 0.3 (E-018 family).

Outputs: research/numerics/data/E-019-zeroflow-a12.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-019-zeroflow-a12.csv"
JSON = DATA / "E-019-zeroflow-a12.json"

_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)                     # runs the e^20 sieve

sys.path.insert(0, str(Path(__file__).parent))
from prime_backend_v2 import PrimeBackendV2       # noqa: E402

H = E16.H_MAIN
LAM = E16.LAM_SHIFT
A_VAL = 10.0
A_NEW = 12.0
Z_MAX = 300.0
N_GRID = 40000
mp.mp.dps = 40

XN, XW = E16.XN, E16.XW


def collect_t_points(a, h):
    """Replicates E-016 S_full/quad segment structure (no g evaluation)
    to enumerate every quadrature abscissa t in (0, 2a)."""
    n_elem = int(round(2 * a / h))
    pts = []

    def add_quad(lo, hi, peak):
        n_lo = int(np.ceil(np.exp(lo)))
        n_hi = int(np.floor(np.exp(hi)))
        if n_hi - n_lo > 64:
            for aa, bb in ((lo, peak), (peak, hi)):
                if bb - aa > 0:
                    mid, half = 0.5 * (aa + bb), 0.5 * (bb - aa)
                    pts.extend(mid + half * XN)
            return
        segs = sorted({lo, peak, hi} |
                      {float(np.log(n))
                       for n in range(max(n_lo, 2), n_hi + 1)
                       if lo < np.log(n) < hi})
        for p0, p1 in zip(segs, segs[1:]):
            if p1 - p0 > 0:
                mid, half = 0.5 * (p0 + p1), 0.5 * (p1 - p0)
                pts.extend(mid + half * XN)

    for k in range(n_elem):
        if k == 0:
            add_quad(0.0, h, 0.0)
        else:
            add_quad((k - 1) * h, (k + 1) * h, k * h)
    return np.array(pts)


def locate_zeros(cp, x, h, z_max=Z_MAX, n_grid=N_GRID):
    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_rect(cp, cm, x, h, half_re, half_im, n_per_edge=4000):
    """Zeros of W(theta=pi) inside |Re z|<=half_re, |Im z|<=half_im via
    the argument principle (direct evaluation, e^{+-a*y0} range OK at
    y0 <= 1, a <= 12)."""
    edges = []
    xr = np.linspace(-half_re, half_re, n_per_edge)
    yr = np.linspace(-half_im, half_im, max(8, n_per_edge // 10))
    edges.append(xr - 1j * half_im)          # bottom, left->right
    edges.append(half_re + 1j * yr)          # right, bottom->top
    edges.append(xr[::-1] + 1j * half_im)    # top, right->left
    edges.append(-half_re + 1j * yr[::-1])   # left, top->bottom
    total = 0.0
    for z in edges:
        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
        total += np.diff(np.unwrap(np.angle(W))).sum()
    return total / (2 * np.pi)


def solve_ab(a, h):
    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)
    return x, hh, cp, cm * np.sqrt(np_ / nm), Q


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()
    report = {}

    # ---------------- backend v2: collect queries & build -------------
    print("collecting quadrature t-points (a=10 & a=12) ...", flush=True)
    rng = np.random.default_rng(19)
    t_rand = np.sort(rng.uniform(0.01, 20.0, 10000))
    tq = np.concatenate([collect_t_points(A_VAL, H),
                         collect_t_points(A_NEW, H), t_rand])
    print(f"  {tq.size} points; building backend to e^24 ...", flush=True)
    CACHE = DATA / "E-019-backend-cache.npz"
    be = PrimeBackendV2(tq)
    if CACHE.exists() and "--force" not in sys.argv:
        be.load_cache(CACHE)
        print("  backend loaded from cache", flush=True)
    else:
        be.build(verbose=True)
        be.save_cache(CACHE)

    # ---------------- A: validation -----------------------------------
    print("V-A1: backend vs E-016 in-memory cumsums ...", flush=True)
    p_ref = E16.prime_sum_vec(t_rand)
    p_new = be.prime_sum(t_rand)
    va1 = float(np.max(np.abs(p_ref - p_new)))
    print(f"  V-A1 max |dev|: {va1:.2e}", flush=True)

    print("V-A2: full pipeline at a=10 (v2 backend) ...", flush=True)
    orig_psv = E16.prime_sum_vec
    # reference build with the ORIGINAL backend first
    x10r, hh10r, cp10r, cm10r, Q_ref = solve_ab(A_VAL, H)
    zeros10r = locate_zeros(cp10r, x10r, hh10r)
    # rebuild with v2 backend (cache must be cleared — keyed on (a,h))
    E16._BUILD.clear()
    E16.prime_sum_vec = be.prime_sum
    x10, hh10, cp10, cm10, Q_new = solve_ab(A_VAL, H)
    E16.prime_sum_vec = orig_psv
    va2_q = float(np.max(np.abs(Q_new - Q_ref)))
    zeros10 = locate_zeros(cp10, x10, hh10)
    z1_10 = float(zeros10[0])
    va2_z1 = abs(z1_10 - float(zeros10r[0]))
    va2_cnt = abs(len(zeros10) - len(zeros10r))
    print(f"  V-A2 Q dev {va2_q:.2e}; z1 dev {va2_z1:.2e}; "
          f"count diff {va2_cnt}", flush=True)
    report["validation"] = {"VA1_max_dev": va1, "VA2_Q_dev": va2_q,
                            "VA2_z1_dev": va2_z1, "VA2_count_diff": va2_cnt}

    # ---------------- B: a=12 measurements ----------------------------
    print("B: a=12 build + zeros + winding ...", flush=True)
    E16.prime_sum_vec = be.prime_sum
    x12, hh12, cp12, cm12, _ = solve_ab(A_NEW, H)
    E16.prime_sum_vec = orig_psv
    zeros12 = locate_zeros(cp12, x12, hh12)

    # B1 zero-flow
    gaps10 = np.diff(zeros10)
    gaps12 = np.diff(zeros12)
    b1 = {
        "a10": {"n": int(len(zeros10)), "z1": z1_10,
                "z1_times_a": z1_10 * A_VAL,
                "count_vs_lattice": A_VAL * Z_MAX / np.pi,
                "gap_mean": float(gaps10.mean()),
                "gap_cv": float(gaps10.std() / gaps10.mean())},
        "a12": {"n": int(len(zeros12)), "z1": float(zeros12[0]),
                "z1_times_a": float(zeros12[0]) * A_NEW,
                "count_vs_lattice": A_NEW * Z_MAX / np.pi,
                "gap_mean": float(gaps12.mean()),
                "gap_cv": float(gaps12.std() / gaps12.mean())},
    }
    print(f"  B1 a=12: n={b1['a12']['n']} (lattice "
          f"{b1['a12']['count_vs_lattice']:.0f}) z1*a="
          f"{b1['a12']['z1_times_a']:.4f} gap_cv={b1['a12']['gap_cv']:.4f}",
          flush=True)

    # B2 gamma-locking
    gammas = np.array([float(mp.zetazero(k).imag) for k in range(1, 31)])
    def lock_metric(zeros):
        d = np.abs(zeros[:, None] - gammas[None, :])
        j, k = np.unravel_index(np.argmin(d), d.shape)
        dmin = np.min(d, axis=1)
        return (float(d[j, k]), int(j + 1), int(k + 1),
                float(zeros[j]), float(gammas[k]),
                float(np.median(dmin)))
    lm10 = lock_metric(zeros10)
    lm12 = lock_metric(zeros12)
    b2 = {"a10": {"min": lm10[0], "zero_idx": lm10[1], "gamma_idx": lm10[2],
                  "zero": lm10[3], "gamma": lm10[4], "median": lm10[5]},
          "a12": {"min": lm12[0], "zero_idx": lm12[1], "gamma_idx": lm12[2],
                  "zero": lm12[3], "gamma": lm12[4], "median": lm12[5]}}
    print(f"  B2 lock: a=10 min {lm10[0]:.5f} (z_{lm10[1]}={lm10[3]:.4f} "
          f"vs g_{lm10[2]}={lm10[4]:.4f}); a=12 min {lm12[0]:.5f} "
          f"(z_{lm12[1]}={lm12[3]:.4f} vs g_{lm12[2]}={lm12[4]:.4f})",
          flush=True)

    # B3 strip winding (a=12 and a=10 for comparison)
    b3 = {}
    for tag, cp, cm, x, hh in (("a10", cp10, cm10, x10, hh10),
                               ("a12", cp12, cm12, x12, hh12)):
        real_in = {}
        for y0 in (1.0, 0.1):
            w = winding_rect(cp, cm, x, hh, 40.0, y0)
            zz = zeros10 if tag == "a10" else zeros12
            n_real = int(2 * np.sum(zz <= 40.0))
            real_in[str(y0)] = {"winding": w, "real_inside": n_real,
                                "excess": w - n_real}
        b3[tag] = real_in
    print(f"  B3: {b3}", flush=True)

    # B4 envelope spot check at y0 = 0.3 (E-018 family), a=12
    def R0_exact(a, th, z):
        z = np.asarray(z, dtype=complex)
        t1 = -2j * (np.sinh(a * (1 + 1j * z))
                    + np.exp(1j * th) * np.sinh(a * (1j * z - 1)))
        t2 = (2j * np.sin(a * z) / z) * ((z - 1j) - np.exp(1j * th)
                                         * (z + 1j))
        return t1 + t2
    xg = np.linspace(-20, 20, 41) + 0.3j
    xm = x12.min()
    u = x12 - xm
    E = np.exp(1j * np.outer(xg, u))
    hat = E16.hat_FT(xg, hh12)
    W = np.exp(1j * xm * xg) * ((xg - 1j) * (hat * (E @ cp12))
                                - (xg + 1j) * (hat * (E @ cm12)))
    Fv = np.array([])
    def F_complex(z):
        s = mp.mpc(0.5 + z.imag, -z.real)
        val = 0.5 * s * (s - 1) * mp.pi ** (-s / 2) * mp.gamma(s / 2) \
            * mp.zeta(s)
        dval = mp.diff(lambda t: 0.5 * t * (t - 1) * mp.pi ** (-t / 2)
                       * mp.gamma(t / 2) * mp.zeta(t), s)
        return complex(z**2 * val / dval)
    Fv = np.array([F_complex(z) for z in xg])
    ratio = np.abs(W / Fv) / np.abs(R0_exact(A_NEW, np.pi, xg))
    b4 = {"median_M_over_R0_y0.3": float(np.median(ratio))}
    print(f"  B4 median |M/R0| at y0=0.3: {b4['median_M_over_R0_y0.3']:.3e}",
          flush=True)

    report.update({"B1_zeroflow": b1, "B2_gamma_locking": b2,
                   "B3_strip_winding": b3, "B4_envelope": b4})

    checks = {
        "VA1_backend_matches": va1 < 1e-6,
        "VA2_pipeline_a10": va2_q < 1e-6 and va2_z1 < 1e-6
        and va2_cnt == 0,
        "B3_no_new_complex_zeros": all(
            abs(b3[t][y]["excess"]) < 2.5 for t in b3 for y in b3[t]),
    }
    with CSV.open("w", encoding="utf-8", newline="") as f:
        f.write("series,a,j,value\n")
        for j, z in enumerate(zeros10):
            f.write(f"zero,10,{j+1},{z:.10f}\n")
        for j, z in enumerate(zeros12):
            f.write(f"zero,12,{j+1},{z:.10f}\n")
        for k, g in enumerate(gammas):
            f.write(f"gamma,-,{k+1},{g:.10f}\n")
    meta = {
        "experiment": "E-019",
        "task": "2.8(ii): backend v2 validation + zero-flow at a=12",
        "script": "research/numerics/scripts/E-019-zeroflow-a12.py",
        "parameters": {"a_val": A_VAL, "a_new": A_NEW, "h": H,
                       "lambda": LAM, "z_max": Z_MAX},
        "checks": checks,
        "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")
    ok = all(checks.values())
    print(f"wrote {CSV.name}, {JSON.name} — "
          f"{'ALL CHECKS PASS' if ok else 'CHECK FAILURE: ' + str(checks)}")
    return 0 if ok else 1


if __name__ == "__main__":
    raise SystemExit(main())