PMflow
The content on this page was written by AI under human supervision.
PMflow supplies the boundary values that AMFlow needs but cannot compute on its own for a class of gravitational scattering integrals. It takes an AMFlow input file for such an integral family, runs a series of short AMFlow probe jobs, and solves a linear self-consistency problem at high precision. The result is a table of boundary values that a normal AMFlow run then uses to finish the evaluation. A companion script extracts exact rational ratios between the resulting integrals in the limit where the dimensional regulator $\varepsilon$ goes to zero.
What it does
Calculations of two black holes scattering, expanded order by order in Newton's constant (the post-Minkowskian expansion), produce families of Feynman integrals. Every integral in a family reduces to a finite set of master integralsa basis for the family: once these are known numerically, every other integral in the family follows by linear algebra. AMFlow evaluates the masters by giving some propagators an artificial mass $\eta$ and following a differential equation from very large $\eta$, where the integrals are simple, down to $\eta=0$. The values at large $\eta$ come from simpler "boundary families", found by applying the same idea recursively.
In the families PMflow is built for, the two heavy bodies appear as linearized propagators $1/(2k\cdot u_i)$ placed on shell (cut). A cut line cannot carry the artificial mass, and sending $\eta$ to infinity on the other lines reproduces the original family exactly, so the recursion descends through identical copies and never reaches a simple one. The detect subcommand runs AMFlow briefly with its recursion trace on and reports FIXED_POINT when the engine stops with its GRAVITYFLOW_FIXEDPOINT message (a boundary family that reproduces its parent's propagators) or when successive levels of the trace are identical, and DESCENDING when they get simpler (then ordinary AMFlow should suffice and PMflow is not needed).
PMflow turns the fixed point into the boundary condition. The flow is linear in its boundary data, so running it once per unit boundary vector gives a response matrix $A(\varepsilon)$ at each $\varepsilon$ on a grid, and a self-consistent boundary vector must satisfy $(A(\varepsilon)-I)\,M=0$. One flow leaves many directions undetermined, so PMflow combines several flows that put $\eta$ on different subsets of propagators, stacks their constraints into one overdetermined system, and fixes the remaining directions with closed-form values at chosen corner sectors of the basis ("anchors", in the solver's wording), by default the cut tadpole $J=-(i/8)\,\pi^{-3/2}\,\Gamma(\varepsilon-\tfrac12)^3$. solve handles this system with mpmath at 120 digits by default, then checks every redundant equation, reports the residuals, and exits nonzero unless all are below $10^{-20}$ relative. They are passed as --anchors "SECTOR:FORM,...", where bit $i$ of the sector number marks propagator slot $i$ and J63 is the built-in form; the default pair fits only the 15-propagator reference family, and on any other family solve refuses with a message until a matching --anchors list is supplied. Its output is a JSON table of boundary values per $\varepsilon$, which inject writes into the parent job's explicit_boundary option before running AMFlow to the physical kinematics.
The companion gf_eps0_ratios.py reads the per-$\varepsilon$ master values such a run dumps to its log. All masters of one sector share a large factor that jumps erratically between $\varepsilon$ nodes, so fitting raw values fails; ratios of masters within a sector cancel it exactly. The script extrapolates each ratio to $\varepsilon=0$ and trusts only the digits on which the full grid and an inner subset agree. It then tries to identify the limit as a rational number with PSLQan integer-relation algorithm: given a few high-precision numbers it finds small integers combining them to zero, or reports that none exist below a size bound (integers up to $10^{19}$). Every identification is re-checked and flagged WEAK if fewer than 90 digits agree.
All results are high-precision numerics with internal consistency checks, without a proven error bound. Each probe is a separate AMFlow process, so a family with many boundary keys pays the engine's startup cost once per key.
Examples
Run the self-test suite. It needs only Python and mpmath:
python3 selftest.py
The suite checks that pmflow.py --help runs and lists all six subcommands, and that pmflow.py solve --help shows --anchors and --n-eps. It then builds a synthetic log in which three masters share an erratic common factor and confirms that gf_eps0_ratios.py recovers the ratios built into that log, $-2/5$ and $22/7$, exactly, and that gf_eps0_ratios.py --selftest stops with an error when its reference log is absent. Next it runs gf_solve.py --anchors "1:J63" on a synthetic two-master fixed-point system whose answer is known, $J$ and $2J$, and checks that the solver reproduces it with every redundant equation consistent. It also checks that malformed or ill-fitting --anchors values, including the built-in default applied to this foreign family, are refused with a message rather than a traceback. Last it checks that the pattern detect uses for the engine's GRAVITYFLOW_FIXEDPOINT line parses a sample line. Two further checks compare that pattern with the engine source and compile the patched engine file with g++ -fsyntax-only; they run only when a checkout of the amflow-cpp-dev engine source (named by AMFLOW_CPP_SRC, default a sibling amflow-cpp-dev folder beside the repository), g++ and the FLINT headers are present, and print [SKIP] otherwise. Each check prints [PASS] or [FAIL]; a good run ends with OVERALL: PASS.
Extract rational $\varepsilon^0$ ratios from a dumped $\varepsilon$-grid log. The call below is the one the self-test makes:
python3 gf_eps0_ratios.py --log synthetic_eps_grid.log --rows 0-2 --n-eps 8 --dps 120 --out table.json
--rows 0-2 selects masters 0 through 2 with master 0 as the denominator, --n-eps is the number of $\varepsilon$ nodes in the log, and --dps the working precision. For each ratio the script prints the identified fraction and how many digits it matches, or NOT identified. table.json holds one entry per ratio with numerator p, denominator q and verified_digits (or the decimal value when no rational was found); the exit status is nonzero if any identification is weak.
Run the full boundary pipeline. Upper-case names stand for your files:
python tools/pmflow/pmflow.py detect --json FAMILY.json --etac CSV python tools/pmflow/pmflow.py discover --json CHAIN.json --keys-out keys.json --etac CSV python tools/pmflow/pmflow.py respond --json CHAIN.json --keys keys.json --dir PROBEDIR --par N python tools/pmflow/pmflow.py map --keys keys.json --families ending_families.json --fpA fp_A.json --out circular_map.json python tools/pmflow/pmflow.py solve --fpA fp_A.json --probes PROBEDIR --cmap circular_map.json --anchors SPEC --out fp_boundary.json python tools/pmflow/pmflow.py inject --parent PARENT.json --table fp_boundary.json --depth 1 --out OUT.json
--etac has one comma-separated entry per propagator saying which lines carry the artificial mass; pass --etac '' to let the engine choose (needed when a graviton line is cut). Do not omit it: the built-in default fits only a 15-propagator family and the engine aborts on any other. fp_A.json is the response matrix written by an AMFlow run with AMFLOW_FIXEDPOINT_PROBE=1. SPEC is the --anchors list described above (the default, "63:J63,111:J63", fits only the 15-propagator reference family; on that family the flag may be left out). respond fills PROBEDIR with one log per key, solve prints the residual summary and writes fp_boundary.json, and inject leaves the AMFlow result for the parent family in OUT.json.
Routines
pmflow.py detect— runs AMFlow with the recursion trace on and reportsFIXED_POINTorDESCENDING; the engine'sGRAVITYFLOW_FIXEDPOINTstop message decides the verdict when present, otherwise successive trace levels are compared (--work-dir DIRsets where the probe runs).pmflow.py discover— iterates zero-valued boundary probes, collects the keys the engine reports missing, and writeskeys.json(--max-iter,--force-depth,--cut-probeand--timeoutadjust the loop).pmflow.py respond— generates one unit-injection input per key and runs them--par Nat a time, dumping per-$\varepsilon$ master values to the probe logs.pmflow.py map— matches each key's propagator content to the basis family (parent propagators from--parent-json CHAIN.jsonor fromending_families.json) and writescircular_map.json; unmatched keys are printed and left out unless--foreign-as-j63assigns them the tadpole value.pmflow.py solve— assembles and solves the stacked system at--dpsdigits with the--anchorscorner values (--n-eps Nis the grid length of the probe logs,--extra-zero KEY ...adds keys written as exact zeros), checks every redundant equation, and writes theexplicit_boundarytable.pmflow.py inject— builds the parent input withexplicit_boundaryfilled in and runs AMFlow withAMFLOW_FORCE_ENDING_DEPTH.- Options on every subcommand:
--amflow-cli PATH,--jemalloc-wrap PATH,--cache PATH(defaults fromAMFLOW_CLI,AMFLOW_JEMALLOC_WRAP,AMFLOW_IBP_CACHE;FERMATPATHlocates Fermat). gf_solve.py— the solver behindsolve, also callable directly (--fpA --probes --cmap --out [--anchors SPEC] [--extra-zero KEY ...] [--n-eps N] [--dps N]) or imported asgf_solve.solve(...);gf_solve.J63(eps)is the closed-form cut tadpole andgf_solve.ANCHOR_FORMSthe table of closed forms--anchorsaccepts (J63only, at present).gf_eps0_ratios.py— ratio extrapolation to $\varepsilon=0$ and rational identification (--log --rows --n-eps --dps --out, plus--selftestagainst a reference log named by the environment variableGF_SELFTEST_LOG).gravityflow.py— the tool's former name; forwards every call topmflow.py.selftest.py— the self-test suite.
Used on this site
- Black-hole scattering — supplied the boundary values for a fourth-order cut family with 43 master integrals, and
gf_eps0_ratios.pyidentified the seven leading-order ratios of its top-sector masters, at each of two kinematic points, as rational numbers (Appendix B.3 of that paper; the fractions are listed in Section S6 of its supplementary material).
Requirements and source
Python 3 with mpmath (sympy, if installed, lets discover recognize a cut graviton line before probing). pmflow.py imports the memfence module from the neighboring tools/amflow-kit/ directory (amflow_kit.memfence, see AMFlow), so keep the repository's tools/ tree together. Through it every engine process runs under a memory budget (AMFLOW_KIRA_MEM_CAP_GB, 60 GiB unless you export another value), has its environment read back through /proc (Linux), and leaves a <log>.memfence.json record beside its log. The engine steps (detect, discover, respond, inject) call the amflow_cli program built from BootLoops' patched amflow-cpp fork, the amflow-cpp-dev repository (GitHub organization BootLoops-ai; MIT license; upstream amflow-cpp, method of Liu and Ma; see AMFlow). That build adds the switches PMflow drives (AMFLOW_FIXEDPOINT_PROBE, AMFLOW_ETAC_OVERRIDE, AMFLOW_FORCE_ENDING_DEPTH, and the GRAVITYFLOW_FIXEDPOINT guard) and needs Fermat and an IBP reduction cache. Set AMFLOW_CLI, FERMATPATH and AMFLOW_IBP_CACHE, or pass the per-call options. solve, map and gf_eps0_ratios.py need no engine. Self-tests: python3 selftest.py in the package directory. The code is in tools/pmflow/ in the bootloops-dev repository under the same organization, released under the MIT license; DESIGN.md there describes the algorithm and its validation.