Coalescer
The content on this page was written by AI under human supervision.
Coalescer computes connection coefficients of a linear differential equation with polynomial coefficients. You give it the operator and the exact Taylor series of one solution around a point where that solution is holomorphic, as one small JSON file, plus a working precision. It continues the solution to a singular point where some local exponents are fractions and returns, for each fractional exponent $\alpha$, the coefficient $c_\alpha$ of the branch $t^{\alpha}(1+\dots)$ there. Each coefficient comes to many digits with self-consistency figures, ready for an integer-relation search that turns the digits into a closed form. For the worked example that comes with the package, the banana Feynman integral at its threshold, the masses alone are enough: the operator and the series are built for you.
What it does
A Fuchsian operatora linear differential operator with polynomial coefficients whose singular points are all regular: near each of them every solution behaves like a power of the local coordinate times a polynomial in its logarithm $L=\sum_{j} P_j(z)\,\theta_z^{\,j}$, with $\theta_z=z\,d/dz$, has a set of local exponents at each singular point. Where they are all integers the local solutions form a tower of integer powers and logarithms. Where some are fractions the local basis also contains branches $\Phi_\alpha(t)=t^{\alpha}\,(1+O(t))$ that return multiplied by $e^{2\pi i\alpha}\neq 1$ when $t$ is carried once around the point. A solution $f(z)=\sum_n a_n z^n$ holomorphic at $z=0$, continued to such a point, splits into the integer-and-logarithm tower plus $\sum_\alpha c_\alpha\Phi_\alpha(t)$; the numbers $c_\alpha$ are what Coalescer computes, one for every fractional exponent the operator has there. Such coefficients are wanted at the threshold or pseudo-threshold of a Feynman-integral family whose Picard–Fuchs operator (the minimal differential operator in one kinematic variable that annihilates the integral) is known, and at conifold-type points of Calabi–Yau operators. They also fix the constant in the large-$n$ asymptotics of a P-recursive sequence whose generating function has an algebraic branch point.
Solving for $f$ against the full local basis loses most of the precision, because the fractional piece can be far smaller than the logarithmic tower (about ten thousand times smaller in the package's K3 example). Coalescer avoids the subtraction. It integrates the equation once around the singular point to obtain the monodromy matrixthe matrix by which a basis of solutions transforms when the variable is carried once around a singular point and back $M_0$. The eigenvalue $1$ of $M_0$ belongs to the logarithmic ("unipotent") tower; each eigenvalue $\lambda=e^{2\pi i\alpha}\neq1$ belongs to one fractional branch. The projector onto one $\lambda$ is a polynomial in $M_0$,
$$P_\lambda=\frac{(M_0-\mathbb{1})^{n}\,\prod_{\mu\neq 1,\lambda}(M_0-\mu\mathbb{1})}{(\lambda-1)^{n}\,\prod_{\mu\neq 1,\lambda}(\lambda-\mu)}\,,$$
with $n$ the size of the unipotent block and $\mu$ running over the other fractional eigenvalues. The factor $(M_0-\mathbb{1})^{n}$ removes the logarithmic tower exactly, the remaining factors remove the other branches, and $P_\lambda f=c_\alpha\Phi_\alpha$ is read off component by component from the vector $(f,\theta f,\dots,\theta^{r-1}f)$, with $r$ the order. Order, exponents, multiplicities and eigenvalues are read from the operator at run time, so nothing in the code is tied to one example. The series $\Phi_\alpha$ are generated exactly over the rationals, and $f$ is carried from two seed points, where its Taylor series still converges, to a small circle around the singular point by adaptive Taylor-series integration.
In the general mode, coalescer.py --op FILE.json, the file holds the exact coefficients of each $P_j(z)$ and of the solution's series (a few hundred terms). Optional entries name the exponent to print as the headline, a label, the series' radius of convergence and run settings. The coordinates are yours to arrange: the series must converge around $z=0$ and the branch point must sit at $z=\infty$, where $t=1/z$ is the local coordinate. Apply a Möbius transformation to your operator first if they lie elsewhere; the fractional branches must be free of logarithms. The file examples/gauss_2f1.json, Gauss's hypergeometric function ${}_2F_1(\tfrac14,\tfrac34;1;z)$, is a runnable example and the template for your own. In the banana mode, coalescer.py --masses 1,1,1,9, the input is the worked example that comes with the package: the Feynman integral in which every propagator joins the same two vertices. The squared masses are tuned so that the threshold sits at $t=p^2=0$ ($\sqrt9=\sqrt1+\sqrt1+\sqrt1$) and the exponents there are half- or quarter-integers. Coalescer then builds the Picard–Fuchs operator exactly over the rationals from the known all-loop series of the holomorphic period (Bönisch, Fischbach, Klemm, Nega and Safari, arXiv:2008.10574), takes that series as the seed, and calls the same kernel.
The output is a high-precision numerical value for each $c_\alpha$, computed in Python with mpmath ("Route A") and accompanied by three self-consistency figures described in the examples; closed forms come afterwards, from an integer-relation search. For banana mass tuples the package also carries an independent implementation, "Route B", in Julia with ball arithmeticinterval arithmetic in which every number carries a rigorous error radius, here the Arb library on the transport engine of Eichler. It differs from Route A in coordinate ($z=1/p^2$ rather than $s=-p^2$), contour direction and seed, and returns a value with a rigorous error radius. Agreement between the routes is the acceptance test, after which an integer-relation (PSLQ) search runs on the agreed digits. An operator supplied through --op runs Route A only.
Coalescer says nothing about the coefficients of the logarithmic tower, which the projector deletes by design, and it is not meant for expansions around a point of maximal unipotent monodromy such as the banana's large-momentum point (use Eichler). Two cautions apply. First, the seed points must lie inside the disk where the seed series converges; outside it the truncated series sums to a wrong value with no visible symptom. coalescer.py therefore computes a floor for the seed points from the operator before integrating anything, prints it, and refuses a seed point at or below it with exit status 3. In banana mode the floor is the threshold scale, one over the seed series' radius of convergence; for an operator given through --op it is by default the more conservative $1/\min|z|$ over all roots of the leading polynomial, and --anchor-law seed switches it to one over the "seed_radius" declared in the file. Seed coefficients must be exact; a float with a fractional part is refused with a message. Second, at a point whose monodromy has order four (quarter-integer exponents, eigenvalues $\pm i$) the sign of $c_\alpha$ depends on branch and orientation conventions, which differ between the two routes; compare $|c_\alpha|$ across routes and quote signs in the convention stated at the top of pf_engine.py.
Examples
Self-test. Check the installation:
python3 tools/coalescer/coalescer.py --test
The test takes about 35 seconds and needs no Julia. It runs three extractions at reduced settings (30-digit arithmetic): the three-loop banana $(1,1,1,9)$, the K3 case, whose coefficient is $c_{3/2}=-\sqrt3/(36\pi)$, in banana mode; the same operator and seed written to a JSON file and run through --op in a second process; and examples/gauss_2f1.json through --op, checked against Gauss's connection formula $c_a=e^{i\pi a}\,\Gamma(b-a)/(\Gamma(b)\,\Gamma(1-a))$ for $a=\tfrac14$ and $\tfrac34$. It prints the structure it finds before each number:
threshold exponents: {3/2: 1, 1: 3} fractional=[3/2] nilp_rank=3
semisimple eigenvalues: {3/2: -1}
...
[selftest] c_3/2 = (-0.01531469153949422359753685 - 4.112978363705437420542903e-28j)
[selftest] ref = -0.01531469153949422359753685
...
[selftest] gauss c_{1/4}: ref = (0.8346268416740731862814297 + 0.8346268416740731862814297j) match = 25.41 d over s_dec in ['0.15', '0.3']
The banana operator has order four with one exponent $3/2$ (eigenvalue $-1$) above a rank-three integer block, so here $P_{-1}=-\tfrac18(M_0-\mathbb{1})^3$; the Gauss operator has order two with exponents $\tfrac14,\tfrac34$ (eigenvalues $\pm i$) and no logarithmic block at all. Each part must match its closed form to at least 20 digits (about 25 at these settings); the script prints PASS and exits with status 0, or stops with status 1. The $10^{-28}$ imaginary part is round-off and marks the noise floor.
Your own operator. The Gauss file shows the layout. Its operator is $\theta^2-z(\theta+\tfrac14)(\theta+\tfrac34)$, so $P_0=-\tfrac{3}{16}z$, $P_1=-z$, $P_2=1-z$, each listed lowest power of $z$ first; the seed is $a_n=(\tfrac14)_n(\tfrac34)_n/n!^2$ with radius of convergence 1. Coefficients are integers, "p/q" strings or [p, q] pairs. Abridged (the file carries 161 seed coefficients):
{
"label": "Gauss 2F1(1/4,3/4;1;z)",
"order": 2,
"theta_coeffs": [["0", "-3/16"], ["0", "-1"], ["1", "-1"]],
"seed_radius": 1,
"alpha": "1/4",
"sdec": ["0.15", "0.3"],
"seed": ["1", "3/16", "105/1024", "1155/16384", "225225/4194304", ...]
}
python3 tools/coalescer/coalescer.py --op tools/coalescer/examples/gauss_2f1.json --dps 30 --out gauss_out.json
The first line printed gives the seed-point floor (here 1: the only root of the leading polynomial $1-z$, which coincides with one over the declared "seed_radius"), the two seed points in force (2.2 and 4.2 times the floor unless the file or --sb, --sb2 set them) and the verdict; with --check-only the run stops there, in seconds. Then come the exponents and eigenvalues found, the number of digits to which the truncated seed series has converged at the seed point, and one block per matching radius in sdec. Each block gives every $c_\alpha$ with two consistency figures in digits: agreement among the components of $P_\lambda f/\Phi_\alpha$, and agreement between the two seed points. A last line gives the agreement between radii. Here $c_{1/4}=0.83462684167407318628\ldots\,(1+i)$, that is $|c_{1/4}|=\sqrt\pi/\Gamma(\tfrac34)^2$ with argument $\pi/4$, good to about 25 digits at --dps 30. A trustworthy run shows all three figures near the working precision; a drop means the digits requested need more seed terms, a larger --nthr or more precision. A banana operator makes the same round trip: python3 tools/coalescer/dump_pf_data.py --msq 1,1,1,9 --out DIR/k3.jl also writes DIR/k3.json in this layout, and coalescer.py --op DIR/k3.json runs it.
The banana worked example by both routes. Take the four-loop banana $(1,1,1,1,16)$, whose period geometry is a Calabi–Yau threefold and whose threshold exponents are $\tfrac54$ and $\tfrac74$. Run Route A at 200 digits, then Route B on the data file that comes with the package (dump_pf_data.py --msq ... --out NAME.jl writes one for a new tuple):
python3 tools/coalescer/coalescer.py --masses 1,1,1,1,16 --dps 200 --route A --out ROUTEA_CY3.json PFDATA=pf_cy3_data.jl julia tools/coalescer/routeB_monoproj.jl
Route A places its seed points at 2.2 and 4.2 times the threshold scale $(\sum_i\sqrt{m_i^2})^2=64$, runs the extraction at two circle radii (0.15 and 0.25) and writes the record. The Julia script prints the seed point it chose from the operator's singularities, the accuracy in bits after transport, and each component of $c_\alpha$ as a ball (midpoint and radius), then writes ROUTEB.json beside itself; PREC sets its precision in bits (about 0.3 digits per bit) and SDEC the circle radius. The comparison script then reads both outputs, shown here with the names of the recorded files that come with the package in tests/fixtures/ (run it there, or prefix the paths):
python3 tools/coalescer/gate_compare.py --family CY3 --routeA ROUTEA_CY3.json \
--routeB ROUTEB_CY3_sdec025.json,ROUTEB_CY3_sdec0175.json \
--k3-routeA ROUTEA_K3.json --k3-routeB ROUTEB_K3_ctrl.json --out compare_cy3.json
The comparison reports four things: the digits every Route A sample shares with every Route B sample (the smallest must reach --bar, default 30); the digits each shares with the recorded closed forms $c_{5/4}=-1/(\sqrt{2\pi}\,\Gamma(\tfrac14)^2)$ and $c_{7/4}=-5\,\Gamma(\tfrac14)^2/(384\sqrt2\,\pi^{5/2})$; the independence from the circle radius within each route; and the K3 outputs as a control. Its JSON summary carries a targets block (the best Route B midpoint trimmed to its certified digits) for the integer-relation script. Exit status 0 means every comparison cleared the bar; 1 means one did not or a route is missing. In the reference summary beside the code the routes agree to roughly 70 digits and Route B matches the closed forms to about 260.
Routines
Command-line scripts
coalescer.py— main entry point. General mode--op FILE.json(Route A only); banana mode--masses a,b,...with--route A|B. Common options:--alpha p/q(the coefficient to print; every fractional exponent is extracted either way),--dps(default 80),--out FILE,--test;--sb S,--sb2 S2,--lift Lset the seed points and the imaginary offset of the path (--sbalone setssb2 = 2*sb);--sdec a,bthe matching radii (default0.15,0.25);--nthr Nterms of the $\Phi_\alpha$ series;--nser Nseed terms;--anchor-law seed|rootsthe floor the seed points are checked against (radius of convergence, or all roots of the leading polynomial; defaultseedfor--masses,rootsfor--op);--check-onlyprints the check and exits. JSON keys read:theta_form_Pj(aliasestheta_coeffs,Pj),bfkns_a(aliasseed),order,alpha,label,seed_radius,sb,sb2,lift,sdec,nthr,nser; command-line flags override the file. With--route Bthe check runs first and an integer--sbreaches the Julia script asSB. Exit status: 0 success, 1 self-test below its bar or an error, 2 usage (including an unreadable--opfile), 3 seed point refused.routeA_monoproj.py— run as a script, Route A for the two reference bananas:--target K3|CY3|both,--dps,--out(defaultROUTEA.json),--probe(small fast settings).routeB_monoproj.jl— Route B in Julia/Arb on the Eichler.jl integrator, set by environment variables:PFDATA(data file beside the script, defaultpf_cy3_data.jl),PREC(bits, default 512),SDEC(circle radius as a rational, default1/4),SB(seed point; chosen from the operator's singularities if unset),NSER,NTHR,NLOOP,OUT(defaultROUTEB.json),EICHLER_PROJECT.dump_pf_data.py— builds the banana operator for--msqand writes<out>.jl(the operator in $\theta$ and $d/dz$ forms, the exact $\Phi_\alpha$ series to--nthrterms, the integer period series to--nserterms) plus the matching.jsonin the--oplayout; a bare--outname is written beside the script, a path with a directory is used as given. Importable asdump_pf_data.dump(msq, nthr, nser, out).gate_compare.py— two-route comparison:--family K3|CY3|CY4(or--masses,--alphas),--routeA,--routeB(comma-separated JSON paths),--k3-routeA,--k3-routeB(the $(1,1,1,9)$ control),--closed auto|none|FILE.json(auto: the family's recorded closed form, $c_{3/2}=-\sqrt5/(40\pi^2)$ for $(1,1,1,1,1,25)$),--bar D(default 30 digits),--dps,--out; exit 3 if a named input file is missing or the request is refused (no route file named, an exponent absent from every input, a closed form outside the basis).pslq_calpha.py— integer-relation search for the coefficients through the Lockpick engine in the siblingtools/lockpick/package:--family(or--masses,--alphas); the target from--c ALPHA=STRING,--from-routeB FILEor--from-gate FILE(the comparison script's summary);--ring natural|extended|FILE.json(the basis of constants: logarithms of small primes, of $\pi$ and of $\Gamma(a)$, or radicals and $\Gamma(a)^2$ in the additive form),--form log,additive,--dps-pair LO,HI,--k3-routeBor--k3-c(the K3 control, which must be found in the same basis),--closed,--out;--planted-wrong NAME=EXPRand--add-member NAME=EXPRfeed it deliberately wrong inputs that it must reject. Each relation found is re-verified on digits not used in the fit and compared with the one the recorded closed form implies; a search that finds nothing reports the height it excludes. Exit status: 0 closed, 1 nothing found or a mismatch, 2 controls failed, 3 refused, 4 too few digits.calpha_rings.py— the family table behind the two scripts above: mass tuples, exponents, bases of constants and recorded closed forms for $(1,1,1,9)$, $(1,1,1,1,16)$ and $(1,1,1,1,1,25)$, evaluated at run time; run as a script it checks itself. Importable:family_spec(name),spec_from_masses(msq, alphas),ring_names(spec, form, ring),closed_form(spec, alpha)(aClosedFormwith.value()and.log_vector(names)).
Python functions
routeA_monoproj.run_operator(tag, Pj, order, seed_a, dps, sb, sb2, lift, sdec_list, Nthr, frac, nloop, Nser=None, label=None)— the general kernel: operator as a dictionary of SymPy polynomials inz, exact seed coefficients, two seed points and the circle radii; returns a dictionary with the exponents and eigenvalues found, the per-radius values (per_sdec, real and imaginary parts as strings) with their consistency figures, the agreement between radii, andc_alpha_best.routeA_monoproj.load_operator_json(path)— reads a--opfile intoPj,order,degz,seed(exact fractions),alpha,label,seed_radiusand the optional run settings; raises an error naming what is missing.routeA_monoproj.op_from_coeffs(table),make_series_state(seed_a, order, Nser=None)— coefficient lists to(Pj, order)with a common integer normalization, and a function giving the vector $(f,\theta_z f,\dots)$ at a seed point from the series.routeA_monoproj.run(tag, msq, dps, sb, sb2, lift, sdec_list, Nser, Nthr, frac, nloop)— the banana wrapper: builds the operator and the period series for the mass tuple, then callsrun_operator.pf_engine.find_minimal_op(msq, ord_max=12, degz_max=20, nprime=8)— lowest-order operator annihilating the banana period series, exact over the rationals (nullspaces modulo primes, then rational reconstruction);build_exact_op,verify_annihilation(a_int, Pj, Ncheck)andwms_series_int(msq, N)expose the reconstruction, the check on further coefficients and the integer series itself.pf_engine.indicial_threshold(Pj),indicial_mum(Pj)— indicial polynomial and local exponents with multiplicities at $z=\infty$ and at $z=0$.pf_engine.theta_t_terms(Pj, order),frob_power_branch(terms, rho, N),phi_theta_state(a_coeffs, rho, s_dec, order, branch='lower', N=None)— the operator in $\theta_t=t\,d/dt$, the exact rational series of $t^{\rho}\sum_n a_n t^n$ (with an obstruction index if the recursion hits a resonance), and the vector $(\Phi_\rho,\theta\Phi_\rho,\dots)$ at radiuss_dec.pf_engine.build_ode_s(Pj, order),transport_adaptive(num, den, Y0, waypoints, ordr, sings, frac=0.3, ncols=1)— the first-order system $dY/ds=A(s)Y$ with its singular points, and the integrator that carries a state vector, or withncolsa full matrix (how $M_0$ is computed), between waypoints by Taylor steps sized to a fraction of the distance to the nearest singularity.
Used on this site
- Threshold banana, K3 rung — extracted $c_{3/2}=-\sqrt3/(36\pi)$ for the $(1,1,1,9)$ banana from the transported period.
- Threshold banana, CY₃ rung — extracted $c_{5/4}$ and $c_{7/4}$ for the $(1,1,1,1,16)$ banana by both routes, ahead of the $\Gamma(\tfrac14)$-ring identification.
Requirements and source
Python 3 with mpmath, SymPy and NumPy for coalescer.py, Route A and the data, comparison and search scripts; pslq_calpha.py also needs the sibling tools/lockpick/ package, and the test suite needs pytest. Route B needs Julia with Arblib and the Eichler.jl project, which is included in BootLoops' bootloops-dev repository (GitHub organization BootLoops-ai) under upgrades/Eichler.jl/; instantiate it once, or point EICHLER_PROJECT at your copy. Self-test: python3 tools/coalescer/coalescer.py --test from the repository root (Python only, about 35 seconds, exit status 0 on success; its scratch files go to a temporary directory that is removed afterwards). Test suite: python3 -m pytest tools/coalescer/tests -rs, about a minute. It covers the seed-point floors and exit codes for the three reference tuples and the --op mode's check, refusal and usage errors on a freshly written K3 file. It also reruns the comparison and integer-relation scripts on the recorded CY3 and CY4 outputs in tests/fixtures/; a repeat of --test compared with its recorded output is skipped unless COALESCER_SLOW=1. Code: tools/coalescer/ in the bootloops-dev repository, released under the MIT license. Related pages: Eichler, Annihilator, Lockpick, PSLQ, Arb.