Longhand
The content on this page was written by AI under human supervision.
Longhand produces a second numerical value for a Feynman integral by a slow route that shares no machinery with the fast method that produced the first one. It also states how many digits of that value can be trusted, from a measured comparison rather than an assumed error. The name describes the product: the answer worked out longhand, slow, independent and trustworthy. There are two routes. Route A, written by BootLoops, evaluates the Feynman-parameter integral of a finite integral directly in arbitrary precision. Route B drives the public program pySecDec over an integral library you have already compiled, in resumable stages, and reports the digits on which two different integration rules agree.
What it does
A value obtained from differential equations, from AMFlow's auxiliary-mass method or from a fitted formula can be built on only after an independent computation agrees with it. Both routes of Longhand start from the Feynman-parameter formthe representation that trades the product of propagators for one integration variable per propagator, over a simplex, with two polynomials U and F built from the graph and the kinematics of the integral with $N$ propagators and $L$ loops in $d$ dimensions,
$$ I \;=\; \Gamma\!\left(N - \tfrac{L d}{2}\right) \int d^N x \;\delta\!\Big(1-\sum_i x_i\Big)\, \frac{U^{\,N-(L+1)d/2}}{F^{\,N-L d/2}} , $$
and integrate it numerically, with no reduction to master integrals anywhere.
Route A handles finite integrals (the $\varepsilon^0$ term of an integral with no poles) in the deep-Euclidean regions and t negative and internal masses positive, so F is positive on the whole simplex and the integrand is smooth and real; no contour deformation, no thresholds. The input is either a propagator list or a "UF-spec" JSON file. A propagator list is turned into $U$ and $F$ by pySecDec's front end, in pySecDec's normalization, so that values compare digit for digit with pySecDec's own. A UF-spec writes any finite projective integrand as polynomials with exact rational coefficients plus numerator terms, and needs no pySecDec. Kinematics go in as exact numbers and the working precision dps is set per call. Before any numerics, every Feynman parameter that enters $F$ linearly is integrated out in closed form, first by $\int_0^\infty dx/(Ax+B)^2 = 1/(AB)$ and then by partial fractions on the two surviving factors, which leaves a logarithm; both steps are valid because the coefficient polynomials are positive in this region. Each step removes one dimension exactly (the three-loop self-energy benchmark goes from seven dimensions to six this way). A parameter in which $F$ is a positive quadratic, as it is in every parameter of the one-loop massive box, is integrated out by a second closed form, $\int_0^\infty dx/(Px^2+Qx+R)^2$ (reduce_one_quadratic), and that is how the box drops to two dimensions. What remains goes to one of three arbitrary-precision engines. A fixed tensor-product Gauss–Legendre rule converges spectrally at effective dimension up to 3: on the one-loop massive box two reductions that share no algebra beyond $U$ and $F$ agree to about 46 digits, and the package stores 40-digit reference values. Nested tanh-sinh quadrature serves dimension up to 2, to roughly 17 digits. A randomly shifted rank-1 lattice rule (quasi-Monte Carlonumerical integration on a deterministic, evenly spread point set; random shifts of the whole set give independent estimates whose spread is the error bar), parallel over the shifts, serves irreducible dimensions 4 to 12 and returns a value with a statistical error bar, typically a few digits.
Route B stays in double precision and uses pySecDec's own numerics; what Longhand adds is the staging and the checks. The input is the disteval directory of a library compiled with pySecDec (loop_package, then make disteval; the compiled library belongs to the machine that built it and is not part of BootLoops) and one kinematic point. The driver runs pySecDec's disteval evaluator three times. Stage A uses a coarse lattice ($10^4$ points), stage B a fine one ($10^5$ points, relative target $10^{-7}$), and stage C disteval's median-lattice rule at the stage-B targets, so that B and C are two different lattice constructions applied to the same integrand library. Stages B and C run in chunks of sectors, each written to disk when it finishes, so an interrupted stage resumes where it stopped; nothing imposes a time limit. A compare step lines up the three results order by order in $\varepsilon$, and an acceptance check reads them conservatively. It fails if B and C differ by more than three standard deviations at any order; otherwise it reports for each order the smaller of the B–C agreement and what the two error bars allow, and the minimum over orders is the digit count of the whole result. The check also tests itself: a copy of the stage-B result shifted by one part in a thousand must be caught, or the check fails because the compare is blind, and coarse independent evaluations of the same library ("smokes") must agree with stage A. Every result covers all the $\varepsilon$ orders the library carries.
What is guaranteed and what is not. Route A's Gauss–Legendre values are as precise as a two-precision test says: run at dps and at dps+15 and keep the digits on which the two runs agree. Its lattice values are estimates with a statistical bar; quote them only within the bar, and at high dimension raise the point count and compare with another route, because with few points the bar can be too small. Route B's digit count is a measured agreement between two double-precision integrations, about 8 to 9 digits at best, with no proven bound. Neither route handles thresholds or physical-region kinematics: route A has no contour deformation, and route B has so far been established only on libraries compiled without it. Route A does not handle poles in $\varepsilon$. One pitfall deserves its own sentence: a kinematic constant rounded at low precision, such as mpf(-1)/3 at mpmath's default setting, is a slightly different rational number, and every engine then converges to the integral of that number, so pass integers, Fractions or decimal strings.
Examples
Run the self-tests. From tools/longhand/:
python3 selftest.py python3 selftest.py --full python3 selftest.py --route parametric
The default run takes about a minute with pySecDec installed and well under that without it, when the checks that need pySecDec are skipped by name. Route A's checks cover every code path against known answers: the closed-form kernel on both of its branches and near a double root, the reductions against exact values, and the box against its stored 40-digit reference through both reductions. A deliberately rounded kinematic input must lose digits, two deliberately malformed UF-specs must be rejected, and a serial lattice run on the six-dimensional fixture toy_qmc_spec.json must reproduce its exact value $13/1080$ within a few error bars. Route B's checks (pytest, under tests/) replay the worked example's records and exercise the chunk split, the compare rule, the refusals and the two self-checks; they need no compiled library. --full swaps in route A's heavier multi-process checks, and --route runs one route alone. The script exits nonzero on any failure.
A 35-digit value for the one-loop massive box (route A). As the README shows it, with the repository's tools/ directory on sys.path:
from fractions import Fraction
from longhand import box, integrate_spec
box(-1, Fraction(-1, 3), 1, 2, dps=35)
integrate_spec('toy_qmc_spec.json', N=8009, n_shifts=8, dps=25, nproc=8)
The first call evaluates the finite box with three internal lines of mass $m^2=1$ and a fourth of mass $M_5^2=2$ at $s=-1$, $t=-1/3$, by one closed-form integration followed by a two-dimensional Gauss–Legendre rule. It returns an mpmath number beginning 0.1245570572 that agrees with the stored 40-digit reference to the working precision. The kinematics are exact on purpose; -1/3 as a float would cap the agreement near 16 digits. The second call sends the bundled six-dimensional UF-spec fixture through the lattice engine on 8 worker processes and returns a dictionary with value, error (the standard error over the random shifts), digits and the per-shift estimates; the value should sit within a few error bars of $13/1080$.
One pySecDec evaluation in checked stages (route B). With a compiled library under PKG/disteval and a scratch directory WORK, as DISTEVAL.md shows it:
cd tools/longhand/disteval python3 pysecdec_point.py --disteval-dir PKG/disteval --stage A --workers 8 --work WORK python3 pysecdec_point.py --disteval-dir PKG/disteval --stage B --workers 8 --work WORK python3 pysecdec_point.py --disteval-dir PKG/disteval --stage C --workers 8 --work WORK python3 pysecdec_point.py --stage compare --results A=WORK/stage_A/RESULT_A.json B=WORK/stage_B/RESULT_B.json C=WORK/stage_C/RESULT_C.json --out WORK/COMPARE.json python3 pysecdec_point.py --stage check --work WORK --compare WORK/COMPARE.json --leaf-stamp LEAF.stamp --out WORK/GATE.json python3 pysecdec_point.py --stage check --example
Each stage leaves WORK/stage_<S>/RESULT_<S>.json. For B and C every chunk directory under WORK/stage_<S>/chunks/ holds its own result.json, log, run time and DONE marker, and one line per finished chunk goes to WORK/progress.jsonl, which is where a stage's remaining run time should be estimated from. Add --max-chunks 1 to time a single chunk first, --resume to continue an interrupted stage, and --dry to rehearse the whole chain in seconds with the disteval call replaced by a library-presence check and a canned result. The check writes the per-order digit table, the two self-checks and a PASS or FAIL verdict to the --out file and exits 0 or 1; --leaf-stamp hands it an end-of-run key=value readback of the memory-limited process group you ran the stages in, and a nonzero oom_kill there fails the check. The last line runs the check over the records of the package's worked example and must reproduce them. That example is the eight-propagator double pentagon of a five-point family with generic masses (332 sectors) at a Euclidean point: stage B gives $\varepsilon^0 = 0.8246341249 \pm 7.3\times10^{-8}$, and the check reports 9 digits at the leading pole $\varepsilon^{-4}$ and 7 at each order from $\varepsilon^{-3}$ to $\varepsilon^0$. By default --disteval-dir must match the checksums of that example's library in PACKAGE_PINS.json. For your own library, write its checksums once with --emit-pins PINS.json --disteval-dir PKG/disteval --family NAME, then pass --family NAME --pins PINS.json on every line and give the check line --smoke NAME=smoke.json files from coarse runs of your library; or add --unpinned-package, which runs anyway and labels every result unverified.
Routines
Self-tests and benchmarks (run inside tools/longhand/)
selftest.py [--full] [--route parametric|disteval|all]— the self-test suite for both routes;--fullruns route A's multi-process checks in place of the fast ones.selftest_parametric.py— the UF-spec pathway's multi-process check: the box written as a spec, through an 8-process pool, against the reference.bench_box.py [dps]— the one-loop massive box at three values ofM5beside the stored references, with the digits of agreement.bench_se3l.py --N --shifts --p --dps --nproc --out FILE— the finite three-loop, eight-propagator self-energy on the lattice engine, an irreducible six-dimensional integral after one elimination (needs pySecDec and sympy once for the symbolic reduction, cached inse3l_reduced.jsonbeside the script);--outwrites a JSON record of the run.
Route A, values and engines (from longhand import ... with tools/ on sys.path)
box(s, t, m2, M5, dps=40)— the finite one-loop massive box at a deep-Euclidean point; exact kinematics in (int,Fraction, decimal string),ValueErroroutside the supported region.box_crosschart(s, t, m2, M5, dps=40)— the same box through an independent reduction (a different parameter set to 1, a different one integrated out); agreement withboxvalidates reduction and quadrature together.integrate_gauss_product(f, dim, dps=40, nodes=None, limits=None)— fixed tensor-product Gauss–Legendre rule over $[0,\infty)^{dim}$ with per-axis limits that may depend on the outer variables.integrate_tanhsinh(f, dim, dps=40, intervals=None)— nestedmpmath.quadfor low dimension.integrate_qmc(integrand_factory, fac_arg, dim, N=64007, n_shifts=16, korobov_p=3, dps=40, alpha=2, gamma=0.8, nproc=None, seed=1)— randomly shifted rank-1 lattice rule with Korobov periodization in gmpy2 arithmetic, parallel over shifts;Nprime; returns a dict withvalue,error,digits,shifts,N,n_shifts,gvec.cbc_generating_vector(N, dim, alpha=2, gamma=0.8)— component-by-component construction of the lattice generating vector; the vectors the benchmarks and tests need are included incbc_cache.json, and newly built ones go to a user cache ($LONGHAND_CBC_CACHE, default~/.cache/longhand/cbc_cache.json).
Route A, building the integrand (from longhand.feynman import ...; sympy, and pySecDec for build_UF)
build_UF(propagators, loop_momenta, external_momenta, replacement_rules)— $U$, $F$, the Feynman parameters, loop count and propagator count through pySecDec'sLoopIntegralFromPropagators.reduce_linear(F, xs, b=2)— sets the last parameter to 1 and integrates out one parameter linear in $F$ (the first stage only).reduce_linear_full(F, xs, b=2)—reduce_linearfollowed by the partial-fraction stage when a second parameter is linear in both surviving factors; the maximal reduction.reduce_one_quadratic(F, xs, var, b=2)— integrates out one parameter quadratic in $F$ in closed form.assemble_integrand_expr(U, F, xs, L, Nprop, param_values, d=4)— the numeric $\varepsilon^0$ integrand from $U$ and $F$: the full reduction when the exponents of $U$ and $F$ are 0 and 2, otherwise $U^a/F^b$ unreduced with the last parameter set to 1 (finiteness is then the caller's responsibility).build_integrand_expr(propagators, loop_momenta, external_momenta, replacement_rules, param_values)— propagator list to numeric integrand in one call.make_factory(expr, remaining_vars)— wraps a sympy expression as an integrand factory forintegrate_qmc; returns(factory, dim).
Route A, the UF-spec pathway (from longhand import ...)
load_spec(path, family=None)— read a UF-spec JSON file, optionally one named family in it.check_spec(spec)— every polynomial homogeneous, every numerator term of the right projective degree;AssertionErrornaming the offender otherwise.make_eval(spec, chart=None)— compile a spec into a gmpy2 callable; returns(f, dim).spec_factory(cfg)— picklable integrand factory forintegrate_qmc.integrate_spec(json_path, family=None, chart=None, N=8009, n_shifts=8, korobov_p=3, dps=25, nproc=None, seed=1)— spec file straight to a lattice-rule result.box_spec(s=-1, t=Fraction(-1, 3), m2=1, M5=2)— the one-loop box as a spec: a test case with a known answer and, withtoy_qmc_spec.json, a worked example of the schema.
Route B, the command line (tools/longhand/disteval/)
pysecdec_point.py --stage A|B|C --disteval-dir D --work DIR— one evaluation stage; options--workers W,--point 'name=value ...'(every parameter of the library exactly once),--chunk-sectors N,--resume,--max-chunks N,--dry [--dry-result FILE], and--points,--shifts,--epsrel,--epsabs,--epsabs-from,--lattice-candidatesto override a stage's defaults.pysecdec_point.py --stage compare --results NAME=RESULT.json ... --out FILE— the pairwise per-order comparison.pysecdec_point.py --stage check— the acceptance check, over--work DIRwith optional--compare FILE,--smoke NAME=smoke.jsonand--leaf-stamp FILE, or over the worked example's records with--example.pysecdec_point.py --check-package,--emit-pins OUT [--generate-receipt FILE],--family NAME(alias--name),--pins FILE,--unpinned-package— library checksums: verify, write them for another family, select that family and its checksum file, or run an unverified library with every result labeled.pysecdec_point.py --planted-fail-control,--basis W— print the shifted-result self-check; print the memory limit to give a run withWworkers, from the measured per-worker and driver memory of the example run.stage_chunks.py,assemble_compare.py assemble|compare,gate.py,memory_basis.py— the chunk splitter, the per-order assembly and comparison, the acceptance check and the memory formula as standalone scripts (--helpon each); from Python,from longhand.disteval import check, stage_chunks, assemble_compare, memory_basis.- Exit codes: 0 pass; 1 a failed check, an incomplete stage or the example not reproduced; 2 a usage error or a refused input; 3 a checksum mismatch; 4 a required file missing.
Used on this site
- Feynman diagrams — the validation standard described there accepts a closed form only with an independent numerical check; Longhand is the toolkit's route to that second value, by direct parametric integration or by driving pySecDec.
Requirements and source
Python 3 with mpmath, sympy, numpy and gmpy2 (pip install gmpy2; route A imports it at load and stops with a message naming it if it is missing); pytest for route B's tests; GNU time as /usr/bin/time -v for route B's stages, which makes the stages Linux-only as distributed (the compare, the check and the example replay are not). pySecDec is optional and installed by you (pip install pySecDec; the example's records were made with version 1.6.6): route A needs it only for build_UF, route B for the evaluation stages and for --dry. pySecDec is GPL-3.0 and is not bundled; a program that imports it runs under its terms. Worker pools start only under if __name__ == '__main__', so route A also runs where the multiprocessing start method is spawn (the macOS default). Run the tests with python3 selftest.py from tools/longhand/.
The methods are others' and the code of route A and of route B's staging and checks is BootLoops': sector decomposition is due to T. Binoth and G. Heinrich (hep-ph/0004013); pySecDec is by S. Borowka, G. Heinrich, S. Jahn, S. P. Jones, M. Kerner, J. Schlenk and T. Zirke (arXiv:1703.09692), its lattice rules by Borowka et al. (arXiv:1811.11720) and its disteval evaluator by G. Heinrich, S. P. Jones, M. Kerner, V. Magerya, A. Olsson and J. Schlenk (arXiv:2305.19768); rank-1 lattice rules for Feynman integrals follow Z. Li, J. Wang, Q.-S. Yan and X. Zhao (arXiv:1508.02512), the component-by-component construction is D. Nuyens and R. Cools (Math. Comp. 75 (2006) 903), and the median-lattice rule is T. Goda and P. L'Ecuyer (arXiv:2201.09413). The code is tools/longhand/ in BootLoops' bootloops-dev repository (GitHub organization BootLoops-ai), released under the MIT license; the external engine route B drives has its own page, pySecDec and FIESTA. Related pages: AMFlow (the value Longhand most often checks), Nestor (singularity-subtracted dispersion quadrature when a lattice rule cannot reach the digits), SubTropica (exact parametric integration).