#!/usr/bin/env python3
r"""lbl3vp-eps0-evaluate.py -- LBL3VP (row 33 of the paper's table of integrals): the eps^0
Laurent layer c_0 of I_VP at the reference point (s, t, m^2) = (-1, -1/3, 1), computed as ONE
FINITE SUBTRACTED INTEGRAL -- no eps grid, no extrapolation -- with the exact pole layers
c_-2 and c_-1 alongside.  Companion of lbl3vp-evaluate.py (same directory), which serves the
fixed-eps dispersive value I_VP(eps) and the eps^-2 / eps^-1 layers.

THE OBJECT
  I_VP(eps) = (1/pi) Int_1^inf rho_VP(w';eps) K_P(w';eps) dw'
            = c_-2/eps^2 + c_-1/eps + c_0 + O(eps),
  the vacuum-polarisation-dressed light-by-light box (three loops, two closed fermion loops),
  as on the page.  rho_VP is the spectral density of the dressed self-energy (closed two-body
  form on 1 < w' < 9, the Gamma_1(6) sunrise-cut remainder above w' = 9), K_P the
  twice-subtracted one-loop box kernel with a rung of mass^2 w'.

THE ROUTE (subtracted finite integral; the code is the derivation)
  Expand every factor in eps as a truncated Taylor ("jet") series, split the w' integral at
  the exact rational point s0 = 7/5 (L = s0 - 1 = 2/5) and subtract the threshold behaviour
  analytically (a plus-distribution at u = w' - 1 = 0).  Then, with ghat(eps) =
  Gamma(1+eps)Gamma(1-eps)/Gamma(1-2eps) and K_2(eps) the second threshold Taylor moment of
  the kernel,
      c_-2 = -K_2^(0)
      c_-1 = -K_2^(1) + 2 K_2^(0) (gamma_E + ln L) + J_0 + T_-1
      c_0  = -[ghat(eps) K_2(eps) L^(-2eps)]_(eps^2) + ghat_1 J_0 + J_1 + B_0 + T_0
  where J_0, J_1, B_0 are finite integrals over the threshold panel u in (0, L] and T_-1, T_0
  finite integrals over w' in [s0, inf).  Every ingredient is computed at runtime from exact
  input:
    * K_n(eps) jets: exact Feynman-parametric two-folds, Gauss-Legendre with n-doubling until
      the successive-n agreement beats 10^-(dps+12);
    * the kernel layers K^(0)(w'), K^(1)(w'): a three-layer block Taylor-step transport of the
      exact rational one-loop box differential system (8 masters; row33_data.json), seeded at
      w' = 5 by the analytic Laurent jets of the Feynman-parameter masters (also n-doubled until
      bound), each step accepted only when its trailing-window tail bound clears the tolerance;
    * the density layers r_-1(w'), r_0(w'): the closed two-body form in jets on (1, 9), the
      Frobenius correction at the three-body threshold w' = 9 (exponent d-2, analytic
      coefficient) on [9, 10], and a two-layer block transport of the exact rational
      self-energy system (4 masters) above w' = 10;
    * the finite integrals: tanh-sinh, refined until the double-refinement agreement beats
      10^-(dps+12) (each prints its certified bound line); the far tail is mapped and cut at
      W_max = 10^max(35, dps+10) with the remainder bounded from the measured decay power.
  Every truncation is refine-until-bound and fail-closed: a bound not reached raises, and the
  run exits 1 instead of printing an uncertified number.

WHAT THE RUN CHECKS (exit 0 only if every comparison passes; all bars scale with --dps)
  * kernel gates: the eps^-1 layer of the transported box vanishes along the range; K^(0) from
    the transport vs the CLOSED dilogarithmic box (lbl3disp.box1, an independent formula) at
    two spot points; K^(0), K^(1) vs pointwise Feynman-parametric two-fold jets; the threshold
    series vs the transport at the crossover u = 1/64 -- each must reach dps-8 digits;
  * density gates: transported r_-1 vs its closed form 2/u - 1/w' at three points; r_0
    Frobenius series vs transport at w' = 10.5 -- dps-8 digits;
  * every quadrature certificate and series tail bound (printed as [certified] lines) and the
    beyond-W_max remainder, summed into the printed error budget;
  * smoke comparisons: the exact pole layers c_-2, c_-1 vs the recorded 50-digit strings in
    row33_data.json (bars min(44, dps-8) and min(38, dps-8); the strings, a Neville peel of an
    independent AMFlow fixed-eps grid, are honest to about 45.8 and 39.6 digits);
  * ORACLE GATE: c_0 vs the recorded reference value I0 (40 digits): the eps^0 layer as
    recorded on 2026-07-05 by a different route -- pole-subtracted Neville extrapolation of
    the fixed-eps dispersive values (the quantity lbl3vp-evaluate.py serves) over the grid
    eps = 2^-20..2^-25 at dps 150 -- whose two depth-varied runs agreed to 39.16 digits; bar
    min(38, dps-10).  A second comparison of c_0 with the
    record's 50-digit AMFlow-side eps^0 string (itself a Neville extrapolation of the grid,
    honest to about 35 digits) is printed for information and not gated.
  None of the reference values enters the computation: they are independent reference values,
  comparisons only.  (There is no fit in this route, so nothing is or can be "kept out of" one.)

STAGES, CHECKPOINT AND RESUME (the run can be interrupted and continued)
  The run has three stages, printed as they certify: [1] the kernel transport (threshold
  moments, master seeds, the block Taylor-step march and its four gates), [2] the density
  transport (Frobenius jets, the block march, its two gates), [3] the finite integrals and
  the assembly.  Stages [1] and [2] are the cost; [3] takes seconds.
  CHECKPOINT (on by default): when stage [1] and stage [2] complete, their products are
  written to the directory ./row33_ckpt_dps<dps>/ under the current directory (or
  --checkpoint DIR): one JSON file per stage holding every stored Taylor segment as exact
  decimal strings at the working precision (dps+42 digits), the threshold moments, the step
  statistics and the gate results, plus MANIFEST.json with the sha256 of each stage file, the
  run's parameters (dps, guard, N_TAY, SF, W_max, ...) and the producer (the sha256 of the
  engine, its host module and the data file, the launch line, the start time).  Each file is
  re-read and compared with the in-memory values before the MANIFEST names it; the write is
  atomic.  Measured size: 20 MB at --dps 30, 49 MB at --dps 50 (both stages
  together).  --no-checkpoint writes nothing; --mutate implies it.  Why on by default: a run
  of this length that can be interrupted and not continued is not ready to launch.
  RESUME: python3 lbl3vp-eps0-evaluate.py --dps <dps> --resume ./row33_ckpt_dps<dps>
  verifies the MANIFEST BEFORE anything is computed -- every stage file's sha256 against its
  pin (a one-digit change in a stored series is refused by name, exit 3), the engine identity
  (a checkpoint written by other code or data is refused, exit 3), and every parameter against
  the requested --dps (a mismatch is refused by name, exit 3); a directory that does not exist
  or has no MANIFEST is refused, exit 4.  It then rebuilds the finished stages from the stored
  series -- nothing recomputed -- re-runs their gates on the rebuilt objects (the gate lines
  print again) and re-enters at the first missing stage.  Saving continues into the same
  directory.  The values a resumed run prints are the values an uninterrupted run prints: the
  stored series are the run's own numbers, exact.
  --mutate cannot be combined with --resume (exit 2): the mutation is applied when the
  threshold moments are computed, which a resume skips.

COST -- THIS IS A LONG RUN, BY DESIGN (Rule: no quick mode that certifies nothing).
  Measured on a shared 96-core server under a load of 125-134, each stage's wall in
  seconds, the number of accepted Taylor steps of its march, the number of step proposals that were
  halved and the number of Taylor-order raises:
    --dps 30:  [1] kernel 504 s (399 steps, 26 halved, 0 N-raised),
               [2] density 262 s (686 steps, 666 halved, 0 N-raised),
               [3] integrals 18 s; total 785 s = 13 min; ORACLE GATE 32.16 digits (bar 20).
    --dps 50:  [1] kernel 1379 s (1068 steps, 1052 halved, 0 N-raised),
               [2] density 419 s (1048 steps, 1039 halved, 0 N-raised),
               [3] integrals 42 s; total 1841 s = 31 min; ORACLE GATE 39.23 digits (bar 38).
  Before this release the same runs, measured together with the ones above under the same
  load, took 490 s + 502 s + 19 s = 1012 s at --dps 30 and 2556 s + 810 s + 43 s = 3410 s at
  --dps 50, with the same steps, halvings and values: the earlier step controller recomputed
  the local Taylor series after every halving although the series does not depend on the step
  (425 series computations for 399 kernel steps at dps 30, 2120 for 1068 at dps 50); it now
  re-sums the series it has (399 and 1068 computations, one per step).  At the default --dps
  110 the earlier controller (the script as served before this release) ran to completion on
  the same server: [1] kernel 37,281 s (4,090 steps, 8,137 halved, 0 N-raised), [2] density
  13,006 s (4,055 steps), [3] integrals 148 s; total 50,438 s = 14.0 h (completed
  2026-09-04T14:35:12Z); ORACLE GATE 39.23 digits (bar 38) -- the same figure as at --dps 50,
  because the reference value I0 is itself trusted to 39 digits (the run prints 'oracle trust
  ~39 d'): the comparison saturates there, and a higher --dps cannot show more agreement.
  That run is controller-limited -- two recomputed series per accepted kernel step, the waste
  this release removes -- so its cost is the earlier controller's, not the precision's; the
  cured script's wall at --dps 110 is not measured.  The cost is the two block transports at
  dps+42 working digits; the finite integrals take seconds.  --dps 30 is the sensible first
  run.

FILES (beside this script, in vendor_row33/; every one sha256-pinned below and REFUSED on any
mismatch before anything is computed):
  vendor_row33/row33_eps0.py            the engine (jets, block transports, quadratures, assembly,
                                        the stage checkpoint)
  vendor_row33/eval_row33.py            host module the engine looks up by that name: loads the
                                        data file and carries the Taylor-step transport class RatDE
  vendor_row33/row33_data.json          the exact-rational differential systems (self-energy, 4
                                        masters; one-loop box, 8 masters) + the recorded reference strings
  vendor_row33/k33lib.py                Gauss-Legendre nodes and the cancellation-free power difference
  vendor_row33/lbl3disp.py              the closed dilogarithmic one-loop box (independent kernel check)
  vendor_row33/detransport/quad.py      refine-until-bound quadrature helper (+ detransport/__init__.py)
  Each vendored module is the research code with only its comments and message strings edited
  (the engine additionally carries the step-controller re-sum and the checkpoint units, as its
  docstring states); three of them are cut down to the functions this leg reaches.

EXIT CODES: 0 every gate passes; 1 a gate fails or a certified bound is not reached (this is
what --mutate must produce); 2 usage error (--dps below 30; --mutate with --resume; --checkpoint
naming another directory than --resume); 3 a pinned file's sha256 does not match, or a --resume
directory fails its MANIFEST pins, engine identity or parameters (refused before any
computation); 4 a required file, or mpmath / sympy, is missing, or --resume names a directory
that does not exist or has no MANIFEST.

USAGE
  python3 lbl3vp-eps0-evaluate.py --dps 30        # the first run to make: 13 min here (measured)
  python3 lbl3vp-eps0-evaluate.py --dps 50        # 31 min here (measured)
  python3 lbl3vp-eps0-evaluate.py                 # default dps 110 (the research default); see COST
  python3 lbl3vp-eps0-evaluate.py --dps 30 --resume ./row33_ckpt_dps30
        # continue an interrupted --dps 30 run from its saved stages (see STAGES above)
  python3 lbl3vp-eps0-evaluate.py --dps 30 --checkpoint DIR     # save the stages under DIR instead
  python3 lbl3vp-eps0-evaluate.py --dps 30 --no-checkpoint      # save nothing
  python3 lbl3vp-eps0-evaluate.py --dps 30 --mutate
        # control: the eps^2 Taylor coefficient K_2^(2) of the threshold moment is scaled by
        # (1 + 1e-9) after it is computed; only c_0 moves, so the internal gates still pass and
        # the ORACLE GATE must FAIL (exit 1) -- proof that the gate reads the computed value.
  Not offered: a --check two-precision rerun (every truncation is certified in-run by its
  printed bound; a dps+60 rerun of a run this long is not a service -- run two --dps values
  instead), and the research script's retired grid extrapolation (--legacy-grid / --eps0-ks),
  which needs the fixed-eps machinery served by lbl3vp-evaluate.py and is provenance only.

Dependencies: python3 >= 3.8, mpmath (pip install mpmath), sympy (pip install sympy; it parses
the exact-rational differential systems).  Output is line-buffered; the first line prints at
once.
"""
import argparse
import hashlib
import os
import sys

EXIT_PASS, EXIT_FAIL, EXIT_USAGE, EXIT_PIN, EXIT_MISSING = 0, 1, 2, 3, 4

HERE = os.path.dirname(os.path.abspath(__file__))
VENDOR = os.path.join(HERE, "vendor_row33")
PINS = {
    "eval_row33.py": "d6f31695bcd7508c7f77be6a630d971f4bc7f9f0d20815ba3c1b44ade4905bf8",
    "row33_eps0.py": "3b2db02a78cd039384ed8e9613fc1c72fd42e56afc37b98a49597cf5149c573f",
    "k33lib.py": "93d31a5132fa71c5a5e7480688e3f7a15532476206b670cbf590e034318fae1d",
    "lbl3disp.py": "e27b4a6754b5774350e43abcb2c5097f70b2b23829a0562a8a006c7ddb485119",
    "detransport/__init__.py": "6989178de899dbf5eea0a72bd950202903cb6250fd4a25e2085b82481deba854",
    "detransport/quad.py": "bfd29dd3605ba00eb2a0801930b84cadc0c42ca43eec2972f9f7fc903db1b83a",
    "row33_data.json": "b51a2efca97287784044cb6992b73eba9b3d6d540ca42ce60a0307491aa1ded6",
}
MIN_DPS = 30


def check_pins():
    """Refuse every vendored file whose sha256 does not match its pin -- before importing any of them."""
    for rel, pin in PINS.items():
        path = os.path.join(VENDOR, rel)
        if not os.path.exists(path):
            print(f"MISSING: vendor_row33/{rel} must sit beside this script (download the vendor_row33 "
                  f"folder from the same page); nothing computed")
            sys.exit(EXIT_MISSING)
        raw = open(path, "rb").read()
        sha = hashlib.sha256(raw).hexdigest()
        if sha != pin:
            print(f"REFUSED: vendor_row33/{rel} sha256 {sha} ({len(raw)} bytes) does not match the pin "
                  f"{pin} -- the file was altered or is not the released version; nothing computed")
            sys.exit(EXIT_PIN)
    print(f"[pins] {len(PINS)} vendored files match their sha256 pins "
          f"(engine {PINS['row33_eps0.py'][:16]}..., data {PINS['row33_data.json'][:16]}...)")


def install_mutation(E, mp):
    """--mutate: subclass the engine's KernelJets so that, once the threshold moment jets are computed,
    the eps^2 coefficient of K_2(eps) is scaled by (1 + 1e-9).  That coefficient enters c_0 alone
    (through -[ghat K_2 L^-2eps]_2), not c_-2, c_-1 or any internal gate, so the run proceeds to the
    ORACLE GATE, which must then FAIL.  The engine's numeric code is untouched; run() picks the class
    up by name from its module namespace."""
    class MutatedKernelJets(E.KernelJets):
        def __init__(self, *a, **k):
            super().__init__(*a, **k)
            c = self.kn[2].c
            self.kn[2] = E.J([c[0], c[1], c[2] * (1 + mp.mpf("1e-9"))])
            print("    !! MUTATE: K_2^(2) (the eps^2 Taylor coefficient of the threshold moment K_2(eps)) "
                  "scaled by (1 + 1e-9) -- only c_0 moves; the ORACLE GATE must FAIL", flush=True)
    E.KernelJets = MutatedKernelJets


def main():
    try:
        sys.stdout.reconfigure(line_buffering=True)
    except Exception:
        pass
    ap = argparse.ArgumentParser(
        description="Row 33 (LBL3VP): eps^0 Laurent layer c_0 at (s,t,m^2) = (-1,-1/3,1) as one finite "
                    "subtracted integral, with the exact pole layers; every truncation refine-until-bound. "
                    "A LONG run: see the module docstring for the measured cost and the checkpoint/resume.")
    ap.add_argument("--dps", type=int, default=110,
                    help="target decimal digits (default 110, the research default; minimum 30). "
                         "Internal bars: kernel/density gates dps-8, ORACLE GATE min(38, dps-10).")
    ap.add_argument("--mutate", action="store_true",
                    help="control: scale K_2^(2) by (1 + 1e-9) after it is computed; the ORACLE GATE must "
                         "FAIL and the exit code must be nonzero (implies --no-checkpoint)")
    ap.add_argument("--checkpoint", metavar="DIR", default=None,
                    help="save stage [1] and stage [2] under DIR as they complete "
                         "(default ./row33_ckpt_dps<dps> under the current directory)")
    ap.add_argument("--no-checkpoint", action="store_true", help="save nothing")
    ap.add_argument("--resume", metavar="DIR", default=None,
                    help="verify DIR's MANIFEST pins and parameters before computing, rebuild its saved "
                         "stages and re-enter at the first missing one; saving continues into DIR")
    args = ap.parse_args()
    if args.dps < MIN_DPS:
        ap.error(f"--dps must be >= {MIN_DPS} (the reference comparisons need at least that)")  # exit 2
    if args.mutate and args.resume:
        ap.error("--mutate cannot be combined with --resume: the mutation is applied when the threshold "
                 "moments are computed, which a resume skips")  # exit 2
    if args.resume and args.checkpoint and os.path.abspath(args.checkpoint) != os.path.abspath(args.resume):
        ap.error("--checkpoint with --resume: a resumed run saves into the resume directory")  # exit 2
    ckpt = None if (args.no_checkpoint or args.mutate) else (args.checkpoint or args.resume
                                                              or f"./row33_ckpt_dps{args.dps}")

    print(f"lbl3vp-eps0-evaluate.py: row 33 (LBL3VP) eps^0 Laurent layer by the subtracted-integral route, "
          f"dps={args.dps}{' [MUTATE control]' if args.mutate else ''}"
          f"{' [RESUME ' + args.resume + ']' if args.resume else ''} -- a long run (measured: 13 minutes "
          f"at dps 30 and 31 minutes at dps 50 on a loaded 96-core server; the module docstring "
          f"has the per-stage figures and the dps-110 one); stages print as they certify"
          + (f"; stages [1] and [2] are saved under {ckpt} (resume with --resume {ckpt})" if ckpt else
             "; no checkpoint is written"))

    try:
        import mpmath as mp
    except ImportError:
        print("MISSING: python3 module mpmath (pip install mpmath); nothing computed")
        return EXIT_MISSING
    try:
        import sympy  # noqa: F401  (the engine parses the exact-rational systems with it)
    except ImportError:
        print("MISSING: python3 module sympy (pip install sympy); nothing computed")
        return EXIT_MISSING

    check_pins()                       # exits 3 / 4 before any vendored code is imported
    sys.dont_write_bytecode = True     # leave no __pycache__ beside the downloaded files
    sys.path.insert(0, VENDOR)
    import eval_row33 as VP  # noqa: E402  (the engine looks its host up under this name)
    import row33_eps0 as E   # noqa: E402
    for key in ("sigvp_de", "box1eq_de"):
        if key not in VP.DATA:
            print(f"REFUSED: row33_data.json carries no '{key}' system")
            return EXIT_PIN
    if args.mutate:
        install_mutation(E, mp)

    try:
        out = E.run(args.dps, sabotage=None, verbose=True, checkpoint=ckpt, resume=args.resume)
    except E.CheckpointRefused as e:     # --resume directory absent (4) or failing its pins / parameters (3)
        print(f"\n{e}")
        return e.exit_code
    except SystemExit as e:              # the engine's own gate refusals (ORACLE GATE, smoke comparisons)
        msg = e.code if isinstance(e.code, str) else f"exit code {e.code}"
        print(f"\n[gate] FAIL (exit {EXIT_FAIL}): {msg}")
        return EXIT_FAIL
    except RuntimeError as e:            # fail-closed bounds (quadrature caps, transport steps, kernel/density gates)
        print(f"\n[gate] FAIL (exit {EXIT_FAIL}): {e}")
        return EXIT_FAIL

    # second comparison of c_0, for information only: the record's 50-digit AMFlow-side eps^0 string
    # (a Neville extrapolation of the fixed-eps grid; honest to about 35 digits against I0).
    s50 = VP.ORC["laurent"].get("eps0")
    if s50:
        with mp.workdps(max(80, args.dps + 20)):
            d50 = float(-mp.log10(abs(out["c0"] - mp.mpf(s50)) / abs(mp.mpf(s50))))
        print(f"  c_0 vs the recorded 50-digit AMFlow-side eps^0 string: {d50:.2f} d "
              f"(comparison only, not gated; that string is a grid extrapolation honest to about 35 digits)")
    print(f"\n[gate] PASS: every certified bound reached, kernel and density gates passed, pole layers agree "
          f"with the recorded strings, ORACLE GATE {out['oracle_d']:.2f} d vs bar {out['oracle_bar']} "
          f"(computed at dps {args.dps}, {out['wall_s']:.0f} s)")
    return EXIT_PASS


if __name__ == "__main__":
    sys.exit(main())
