Current task: —

E-009-suzuki-screw-function.py

Experiments: E-009a · script updated 08-18-2026

E-009-suzuki-screw-function.csv — 449.3 KB, updated 08-18-2026

← prev page 21 of 21 · 500/page · 10001 rows
# tg_tprime_sum_t
10002 10.0000 -6.209008245758e-02 5.630289615203e+02

E-009-suzuki-screw-function.json — 1.4 KB, updated 08-18-2026

JSON sidecar (click to expand)
{
  "experiment": "E-009a",
  "task": "3.4c (D-018 step 1): Suzuki screw function g(t)",
  "description": "mpmath implementation of Suzuki's explicit screw function g(t) (arXiv:2606.09096v1 eq. 1.3); validated against eq. 2.2 near-origin expansion; tabulated on [0,10] step 1e-3 for E-009b Galerkin",
  "script": "research/numerics/scripts/E-009-suzuki-screw-function.py",
  "parameters": {
    "t_max": 10.0,
    "step": 0.001,
    "validation_dps": 30,
    "n_sieve_max": 22028
  },
  "checks": {
    "A1_lerch_convention": true,
    "A2_evenness": true,
    "A3_A_limit": true,
    "A4_r_curvature_converges": true,
    "B_table_finite": true,
    "B_spot_check_vs_mpmath": true
  },
  "key_values": {
    "A_eq2.2": 0.707546365655439,
    "c2_lim_r_over_t2": -0.8750599754042412,
    "digamma_coeff_-half(psi(1/4)-log pi)": 2.6860917096128327,
    "Phi(1,2,1/4)": 17.19732915450711,
    "g(log2)": -0.06355558603756917,
    "g(1)": -0.0440073052368517,
    "g(5)": -0.045371349558830154,
    "g(10)": -0.06209008245757719
  },
  "caveats": [
    "float64 table: Lerch combination has cancellation for small t (~4 digits lost at t=1e-3); spot-checked vs mpmath to 1e-9",
    "prime sum is piecewise linear in t (kinks at log n) — exact via cumsum, no interpolation error",
    "g even: only t >= 0 tabulated"
  ],
  "elapsed_seconds": 3.0,
  "timestamp_utc": "2026-08-18T01:44:26.674491+00:00",
  "row_count": 10001
}

Source code

Python script (click to expand)
"""E-009a — Task 3.4c (Q-3.4c, D-018 plan step 1): Suzuki's screw function g(t).

Suzuki (arXiv:2606.09096v1, eq. 1.3) — explicit screw function of zeta:

  g(t) = -4(e^{t/2} + e^{-t/2} - 2)
         + Sum_{n <= e^{|t|}} (Lambda(n)/sqrt(n)) (|t| - log n)
         - (|t|/2)(psi(1/4) - log pi)
         - (1/4)[ Phi(1,2,1/4) - e^{-|t|/2} Phi(e^{-2|t|}, 2, 1/4) ]

(psi = digamma, Phi = Hurwitz-Lerch transcendent Phi(z,s,a) =
Sum_{k>=0} z^k/(k+a)^s; Lambda = von Mangoldt; prime sum = 0 for
|t| < (1/2) log 2.)

Near-origin expansion (Suzuki eq. 2.2):
  g(t) = (1/2)|t| log|t| + A|t| + (prime sum) + r(t),
  r even C^2, r = O(t^2),
  A = (1/2)(log(2pi) - psi(2)) = (1/2)(log(2pi) + gamma_E - 1)
    = 0.7075463656...

g is a Krein-Langer screw function IFF RH (this is the Cor 1.6 bridge).

Part A (validation, mpmath dps 30):
  A1. Lerch convention: Phi(1,2,1/4) == psi'(1/4) = polygamma(1, 1/4).
  A2. Evenness: g(-t) == g(t) at t = 0.7, 2.3, 5.1 (exercises prime sum).
  A3. A-limit: g(t)/t - (1/2) log t -> A as t -> 0+ (prime sum = 0 there);
      residual must equal r(t)/t = c2*t + o(t) with c2 from A4.
  A4. r-curvature: r(t)/t^2 -> c2 (constant) as t -> 0+, t <= 0.25.

Part B (tabulation, float64 + mpmath hybrid):
  g(t) on t in [0, 10], step 1e-3 (mesh for the E-009b Galerkin stage;
  g is even so t >= 0 suffices). Prime-sum part via exact cumsum arrays
  (P(t) piecewise linear in t with kinks at log n). Lerch combination:
  direct numpy series for t > 0.35 (z = e^{-2t} < 0.5, fast), mpmath
  lerchphi for t <= 0.35 (slow-converging near z = 1).

Outputs: research/numerics/data/E-009-suzuki-screw-function.csv (+ .json)
"""
import json
import sys
import time
from datetime import datetime, timezone
from pathlib import Path

import numpy as np
import mpmath as mp

REPO = Path(__file__).resolve().parents[3]
DATA = REPO / "research" / "numerics" / "data"
CSV = DATA / "E-009-suzuki-screw-function.csv"
JSON = DATA / "E-009-suzuki-screw-function.json"

T_MAX = 10.0          # tabulation range [0, T_MAX] (g even)
H = 1e-3              # mesh step
DPS = 30              # validation precision
A_CONST = 0.5 * (np.log(2 * np.pi) + np.euler_gamma - 1.0)  # eq. 2.2 constant


# ---------------------------------------------------------------- von Mangoldt
def mangoldt_sieve(n_max: int) -> np.ndarray:
    """lam_log[n] = log p if n = p^k (prime power), else 0."""
    lam_log = np.zeros(n_max + 1)
    is_comp = np.zeros(n_max + 1, dtype=bool)
    for p in range(2, n_max + 1):
        if not is_comp[p]:
            if p * p <= n_max:
                is_comp[p * p :: p] = True
            pk = p
            while pk <= n_max:
                lam_log[pk] = np.log(p)
                pk *= p
    return lam_log


N_MAX = int(np.exp(T_MAX)) + 2
LAM_LOG = mangoldt_sieve(N_MAX)
NS = np.arange(2, N_MAX + 1)
LOGS = np.log(NS)
LAM = LAM_LOG[2:] / np.sqrt(NS)          # Lambda(n)/sqrt(n)
C1 = np.cumsum(LAM)                       # Sum_{n<=x} Lambda(n)/sqrt(n)
C0 = np.cumsum(LAM * LOGS)                # Sum_{n<=x} Lambda(n) log(n)/sqrt(n)


def prime_sum_vec(t: np.ndarray) -> np.ndarray:
    """P(t) = Sum_{n <= e^t} (Lambda(n)/sqrt(n))(t - log n), vectorized."""
    m = np.searchsorted(LOGS, t, side="right")     # count of n with log n <= t
    out = np.zeros_like(t)
    pos = m > 0
    idx = m[pos] - 1
    out[pos] = C1[idx] * t[pos] - C0[idx]
    return out


def prime_sum_mp(t: mp.mpf) -> mp.mpf:
    s = mp.mpf(0)
    n = 2
    while mp.log(n) <= t:
        s += (LAM_LOG[n] / mp.sqrt(n)) * (t - mp.log(n))
        n += 1
    return s


# ------------------------------------------------------------------- g(t), mp
def g_mp(t) -> mp.mpf:
    t = mp.mpf(t)
    a = abs(t)
    s = -4 * (mp.exp(a / 2) + mp.exp(-a / 2) - 2)
    s += prime_sum_mp(a)
    s -= (a / 2) * (mp.digamma(mp.mpf(1) / 4) - mp.log(mp.pi))
    s -= mp.mpf(1) / 4 * (mp.lerchphi(1, 2, mp.mpf(1) / 4)
                          - mp.exp(-a / 2) * mp.lerchphi(mp.exp(-2 * a), 2,
                                                         mp.mpf(1) / 4))
    return s


# --------------------------------------------------------------- g(t), float
KK = np.arange(0, 80)
DENOM = (KK + 0.25) ** 2


def lerch_phi2_fast(z: np.ndarray) -> np.ndarray:
    """Phi(z,2,1/4) by direct series; valid for |z| <= 0.5 (t >= 0.35)."""
    powers = np.power.outer(z, KK)          # (n_z, K)
    return (powers / DENOM).sum(axis=1)


def g_float_table(t: np.ndarray) -> np.ndarray:
    g = -4.0 * (np.exp(t / 2) + np.exp(-t / 2) - 2.0)
    g += prime_sum_vec(t)
    dig_coeff = -0.5 * float(mp.digamma(mp.mpf(1) / 4) - mp.log(mp.pi))
    g += dig_coeff * t
    phi1 = float(mp.lerchphi(1, 2, mp.mpf(1) / 4))   # = polygamma(1, 1/4)
    lerch_comb = np.empty_like(t)
    slow = t <= 0.35
    if slow.any():
        mp.dps = 25
        for i in np.flatnonzero(slow):
            ti = mp.mpf(float(t[i]))
            val = (mp.lerchphi(1, 2, mp.mpf(1) / 4)
                   - mp.exp(-ti / 2) * mp.lerchphi(mp.exp(-2 * ti), 2,
                                                   mp.mpf(1) / 4))
            lerch_comb[i] = float(val) / -4.0
        mp.dps = DPS
    if (~slow).any():
        z = np.exp(-2.0 * t[~slow])
        lerch_comb[~slow] = -0.25 * (phi1 - np.exp(-t[~slow] / 2)
                                     * lerch_phi2_fast(z))
    g += lerch_comb
    g[t == 0.0] = 0.0
    return g


# ------------------------------------------------------------------- main
def main() -> int:
    if CSV.exists() and "--force" not in sys.argv:
        print(f"cached: {CSV} exists (use --force to recompute)"); return 0
    t0 = time.time()
    mp.dps = DPS

    # ---- A1: Lerch convention
    phi1_mp = mp.lerchphi(1, 2, mp.mpf(1) / 4)
    trig1_mp = mp.polygamma(1, mp.mpf(1) / 4)
    a1_err = abs(phi1_mp - trig1_mp)
    check_a1 = bool(a1_err < mp.mpf("1e-25"))

    # ---- A2: evenness (t values exercising the prime sum)
    ev_ts = [mp.mpf("0.7"), mp.mpf("2.3"), mp.mpf("5.1")]
    ev_err = max(abs(g_mp(t) - g_mp(-t)) for t in ev_ts)
    check_a2 = bool(ev_err < mp.mpf("1e-20"))

    # ---- A4: r(t)/t^2 -> c2 (t <= 0.25: prime sum = 0)
    A_mp = (mp.log(2 * mp.pi) + mp.euler - 1) / 2
    r_ts = [mp.mpf("0.25"), mp.mpf("0.125"), mp.mpf("0.0625"),
            mp.mpf("0.03125"), mp.mpf("0.015625")]
    r_ratios = []
    for t in r_ts:
        r = g_mp(t) - (t / 2) * mp.log(t) - A_mp * t     # prime sum = 0 here
        r_ratios.append(r / t**2)
    c2 = r_ratios[-1]
    r_gap_last = abs(r_ratios[-1] - r_ratios[-2])
    r_gap_prev = abs(r_ratios[-2] - r_ratios[-3])
    check_a4 = bool(r_gap_last < r_gap_prev)  # gaps shrinking => converging

    # ---- A3: g(t)/t - (1/2) log t -> A; residual ~ c2 * t
    a3_ts = [mp.mpf("1e-2"), mp.mpf("1e-3"), mp.mpf("1e-4")]
    a3_resid = [g_mp(t) / t - mp.log(t) / 2 - A_mp for t in a3_ts]
    # expected residual ~ c2*t; test relative agreement within 25%
    a3_rel = [abs((res - c2 * t) / (c2 * t)) if c2 != 0 else abs(res)
              for res, t in zip(a3_resid, a3_ts)]
    check_a3 = bool(a3_rel[-1] < 0.25 and abs(a3_resid[-1]) < mp.mpf("3e-3"))

    print(f"A1 lerch==polygamma(1,1/4): err {mp.nstr(a1_err, 5)}")
    print(f"A2 evenness max err: {mp.nstr(ev_err, 5)}")
    print(f"A4 r(t)/t^2 -> c2 = {mp.nstr(c2, 12)} "
          f"(gaps {mp.nstr(r_gap_prev, 3)} -> {mp.nstr(r_gap_last, 3)})")
    print(f"A3 residuals g(t)/t - (1/2)log t - A at 1e-2/1e-3/1e-4: "
          f"{[mp.nstr(x, 6) for x in a3_resid]}  (c2*t predicted)")

    # ---- Part B: table on [0, T_MAX]
    t_mesh = np.arange(0.0, T_MAX + 0.5 * H, H)
    g_vals = g_float_table(t_mesh)
    p_vals = prime_sum_vec(t_mesh)
    check_b = bool(np.all(np.isfinite(g_vals)))
    # spot check: float table vs mpmath at a few points
    spot_ts = [0.123, 1.0, 3.7, 7.0, 10.0]
    spot_err = max(abs(g_mp(x) - mp.mpf(float(g_vals[int(round(x / H))])))
                   for x in spot_ts)
    check_spot = bool(spot_err < mp.mpf("1e-9"))
    print(f"Part B: {len(t_mesh)} points, spot-check err vs mpmath: "
          f"{mp.nstr(spot_err, 3)}")

    checks = {
        "A1_lerch_convention": check_a1,
        "A2_evenness": check_a2,
        "A3_A_limit": check_a3,
        "A4_r_curvature_converges": check_a4,
        "B_table_finite": check_b,
        "B_spot_check_vs_mpmath": check_spot,
    }

    DATA.mkdir(parents=True, exist_ok=True)
    with CSV.open("w", encoding="utf-8", newline="") as f:
        f.write("t,g_t,prime_sum_t\n")
        for ti, gi, pi in zip(t_mesh, g_vals, p_vals):
            f.write(f"{ti:.4f},{gi:.12e},{pi:.12e}\n")

    meta = {
        "experiment": "E-009a",
        "task": "3.4c (D-018 step 1): Suzuki screw function g(t)",
        "description": "mpmath implementation of Suzuki's explicit screw "
                       "function g(t) (arXiv:2606.09096v1 eq. 1.3); "
                       "validated against eq. 2.2 near-origin expansion; "
                       "tabulated on [0,10] step 1e-3 for E-009b Galerkin",
        "script": "research/numerics/scripts/E-009-suzuki-screw-function.py",
        "parameters": {"t_max": T_MAX, "step": H, "validation_dps": DPS,
                       "n_sieve_max": N_MAX},
        "checks": checks,
        "key_values": {
            "A_eq2.2": float(A_CONST),
            "c2_lim_r_over_t2": float(c2),
            "digamma_coeff_-half(psi(1/4)-log pi)":
                float(-(mp.digamma(mp.mpf(1) / 4) - mp.log(mp.pi)) / 2),
            "Phi(1,2,1/4)": float(phi1_mp),
            "g(log2)": float(g_mp(mp.log(2))),
            "g(1)": float(g_mp(1)),
            "g(5)": float(g_mp(5)),
            "g(10)": float(g_vals[-1]),
        },
        "caveats": [
            "float64 table: Lerch combination has cancellation for small t "
            "(~4 digits lost at t=1e-3); spot-checked vs mpmath to 1e-9",
            "prime sum is piecewise linear in t (kinks at log n) — exact "
            "via cumsum, no interpolation error",
            "g even: only t >= 0 tabulated",
        ],
        "elapsed_seconds": round(time.time() - t0, 1),
        "timestamp_utc": datetime.now(timezone.utc).isoformat(),
        "row_count": int(len(t_mesh)),
    }
    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())