Counterweight
The content on this page was written by AI under human supervision.
Counterweight takes the system of differential equations satisfied by a family of Feynman integrals and finds an exact rational change of basis that puts it in canonical $\epsilon$-form. In that form the dimensional regulator $\epsilon$ factors out of the right-hand side and every singularity is a simple pole. The input is the connection matrix, with entries rational in $\epsilon$ and one kinematic variable $x$; the output is the transformation, the transformed system, its singular points and a verification of the result, or else a report saying why no transformation was found. The engine is a Julia package on Nemo/FLINT; a Python front end (canonical_form/) prepares input bases and calls the engine from sympy.
What it does
The master integrals $I$ of a Feynman-integral family obey a linear system $dI/dx = A(\epsilon,x)\,I$. In the basis an integration-by-parts reduction delivers, $A$ mixes $\epsilon$ and $x$ arbitrarily. Henn proposed that a better basis $J = T\,I$ usually exists, in which
$$T A T^{-1} + \frac{dT}{dx}\,T^{-1} \;=\; \epsilon\,\tilde A(x), \qquad \tilde A(x)=\sum_i \frac{R_i}{x-a_i},$$
with constant rational matrices $R_i$. In this ε-formHenn's canonical form: the right-hand side is ε times an ε-independent matrix with only simple poles the solution is read off order by order in $\epsilon$ as iterated integrals over the letters $x-a_i$, and the BootLoops fitting and transport tools work in it. Counterweight computes $T$. It constructs a basis; it does not solve the equation or evaluate integrals.
All arithmetic is exact, over $\mathbb{Q}(\epsilon)(x)$ (rational functions of $x$ whose coefficients are rational functions of $\epsilon$), so every intermediate matrix can be tested by equality. The engine follows Lee's algorithm in three stages. If $A$ has poles of order higher than one, a Moser reduction lowers them one order at a time until the system is Fuchsianevery singular point of the matrix, including the point at infinity, is a simple pole. Then a sequence of balances, rank-one transformations $T=(1-P)+\tfrac{x-x_2}{x-x_1}P$ built from residue eigenvectors at two singular points, shifts the $\epsilon^0$ parts of the residue eigenvalues by one unit per move until all vanish. The point at infinity and poles at the roots of an irreducible quadratic or cubic factor have their own moves, including a single rational shear $T=M(x)/\hat q$ when a $\pm$ pair of $\epsilon^0$ parts sits at the two conjugate roots of an irreducible quadratic $\hat q$. Last, an $x$-independent change of basis over $\mathbb{Q}(\epsilon)$ removes any remaining $\epsilon^0$ part. The result is then verified: simple poles only, $A/\epsilon$ free of $\epsilon$, every residue of $\tilde A$ a constant rational matrix.
A successful run returns $T$, the new connection, $\tilde A$ and a report of the moves made. An unsuccessful run returns ok = false with a stop reason and the move history instead of a guess, and two kinds of failure are informative in themselves. A higher-order pole whose leading matrix is not nilpotent is an irregular singularity that no rational $T$ removes; for a system from an integration-by-parts reduction it means some masters lack a rational normalization factor, and the diagnosis names the pole, its order and the offending eigenvalue factors so you can fix the basis. Half-integer $\epsilon^0$ parts in the residue eigenvalues at the roots of an irreducible polynomial mean the basis needs a square-root prefactor and no purely rational form exists; such blocks are work for Vopclose. The same holds when the residue on a quadratic orbit has a nonzero odd part: the alphabet then needs the algebraic root as a letter, and the engine stops with the residual obstruction.
Small systems are typed in with matF2x, and Kira derive_dgl output is parsed by the KiraDE module. Large systems whose entries arrive as long uncancelled numerator/denominator lists go through MpolyFeed, which cancels common factors as FLINT polynomials before entering the field; on a $4\times4$ block with degrees in the hundreds that takes seconds, where the plain route did not finish in forty minutes. Several kinematic variables are handled by working in one ratio variable with the others fixed and confirming with check_flatness that the same $T$ factorizes the other directions. For scale, a $13\times13$ block with about twenty letters certified in about 22 seconds.
The canonical_form/ front end, described on its own page as Canonify, computes from an integral-family definition each master's leading singularitythe residue of the integrand on its maximal cut, computed loop by loop in the Baikov representation; dividing by it removes the algebraic prefactor and leaves a pure function and returns the diagonal rotation $J_i = I_i/\mathrm{LS}_i$ in the format Gatekeeper's heldout_certify accepts. It also wraps the engine so that a sympy matrix is factorized in one Python call. Off-diagonal subsector corrections are outside this tool.
Examples
Check an installation. Run the double-box validation from the package directory:
julia --project=. test/validate_henn.jl
The script builds Henn's published canonical connection for the massless planar double box (eight masters, letters $x$ and $1+x$) and scrambles it with an $\epsilon$-dependent rational change of basis. It then runs try_epsfactor on the scrambled system and checks that the recovered residues at $x=0$ and $x=-1$ have the same eigenvalues as Henn's matrices. Each step prints what it checked and the run ends with a PASS line; a failure raises an error. julia --project=. test/runtests.jl runs this check together with the orbit-shear unit tests in one Julia process (start-up and package loading are paid once); it is the command the repository's self-test runner uses for this package.
The same steps at the Julia prompt (julia --project=. in the package directory), showing the objects involved:
using Counterweight, Nemo using Counterweight.HennDbox ctx = make_ctx() # the field Q(eps)(x) A = HennDbox.henn_canonical(ctx) # 8×8, already in eps-form Ascr, Bgauge = HennDbox.henn_scramble(ctx, A) # a generic non-canonical basis is_epsform(ctx, Ascr)[1] # false r = try_epsfactor(ctx, Ascr; max_balance_rounds=400) r.ok # true certify_epsform(ctx, r.Anew)
Most routines take the context from make_ctx() as their first argument. try_epsfactor returns an EpsResult with fields ok, Atilde, T, Anew ($=\epsilon\tilde A$) and report, a dictionary holding balance_rounds, history and, on failure, stop (plus a moser entry when pole reduction ran). Because the arithmetic is exact, apply_gauge(ctx, Ascr, r.T) == r.Anew holds identically. certify_epsform returns a dictionary with keys fuchsian, singular_points (here 0 and -1), algebraic_factors, epsform and dlog.
From Python. With a sympy matrix A rational in x and the module's symbol eps (from canonical_form/README.md):
from canonical_form import factor_epsilon, to_dlog Atilde, T, report = factor_epsilon(A, x) # → Julia Counterweight (Lee 1411.0911) letters = to_dlog(Atilde, x, alphabet=[x, 1+x])
factor_epsilon serializes the matrix and runs counterweight_bridge.jl under julia --project pointed at the engine directory (the environment variable COUNTERWEIGHT_ROOT, by default the parent of canonical_form/). It returns $\tilde A$ and $T$ as sympy matrices with the engine's report. If the engine stops, or the engine directory cannot be found, it raises NotImplementedError carrying the stop reason; a Julia error raises RuntimeError with Julia's message. to_dlog returns one constant matrix per supplied letter, plus a _residual entry that is zero when the alphabet is complete and an _unmatched entry for any pole it does not cover.
Routines
Connection setup and analysis (Julia, using Counterweight)
make_ctx(; epsname="eps", xname="x")— build the field contextCtx;eps_of,x_ofreturn its generators.matF2x(ctx, n, entry)— an $n\times n$ matrix from a functionentry(i,j);F2x(ctx, v)coerces one value.singular_points_x(ctx, A)— finite singular points, with irreducible higher-degree denominator factors listed separately.residue_matrix(ctx, A, a),residue_at_infinity(ctx, A),laurent_coeff_matrix(ctx, A, a, k),eval_x_at(ctx, A, a)— exact residue and Laurent-coefficient matrices, and the value of a matrix at a regular point.pole_profile(ctx, A),is_fuchsian(ctx, A)— pole order at every singular point; whether all are simple.
Transformations and checks
apply_gauge(ctx, A, T)— $TAT^{-1} + (dT/dx)\,T^{-1}$;gauge_balance(ctx, P, x1, x2)builds one balance matrix (either point may be:inf).moser_reduce(ctx, A; max_iter=200, verbose=false)— reduce higher-order poles to simple ones; returns aMoserResult(ok,A,T, pole profiles before and after,irregular,stop).try_epsfactor(ctx, A; max_balance_rounds=400, verbose=false, moser_max_iter=200)— the main routine, returning anEpsResult;verbose=trueprints one line per round.epsfactor_ratiois the same call for a system already in a ratio variable.is_epsform(ctx, A),certify_epsform(ctx, A)— whether $A/\epsilon$ is $\epsilon$-free (with the quotient), and the full dictionary of checks.dlog_alphabet(ctx, A)— the letters of a Fuchsian connection and the residue matrix at each, including infinity.check_flatness(Ax, Ay, dx, dy),integrability_holds(Ax, Ay, dx, dy)— the integrability residual of a two-variable connection and whether it vanishes.
Reading input
load_kira_de(path; family="A1"),KiraDE.parse_kira_coeff(ctx, s, kin),build_A_from_rows(ctx, rows, masters, kin)— parse a Kiraderive_dglfile into rows, convert one coefficient string with a substitution dictionary ford,s,t, and assemble the matrix for an ordered master list.feed_matrix_from_chunks(ctx, n, chunks; cache=nothing)— the matrix from raw numerator/denominator chunk lists, gcd-cancelled as polynomials first;cache=pathstores the reduced entries (MpolyFeed.feed_cache_save,MpolyFeed.feed_cache_load) for reuse, andqex_from_mpoly_pair(ctx, N, D, R)converts one reduced pair into a field element.
Bundled examples
Counterweight.HennDbox(examples/henn_dbox.jl):henn_canonical(ctx),henn_scramble(ctx, A),henn_a(),henn_b()— the double-box test system.examples/coupled_tower_counterweight.jl— reads a Kiraderive_dglexport named byTOWER_DE_JSON, factorizes at two values of the fixed scale, and writesT, $\tilde A$, alphabet and checks torotation_T.json(or the file named byTOWER_OUT_JSON).examples/sec121_baikov.py— a short sympy script that builds the two-loop Baikov polynomial of a double-bubble sector, the starting point for writing a connection by hand.
Python front end (canonical_form/)
python canonical_form.py target.json [--point JSON] [--out FILE]— leading singularities and the diagonal rotation for a target file's masters, as JSON (box1l.json, the one-loop box, is included as an example target).Family,LSResult,leading_singularities,ut_rotation,ut_rotation_matrix,elliptic_period,verify_unit_pole— the leading-singularity and rotation routines, described with their signatures on the Canonify page.wrap_epsfactor(A, x, *, action="epsform", timeout=600)— the bridge call to the Julia engine, returning its JSON reply as a dictionary;action="certify"runs only the checks.fuchsify(A, x),factor_epsilon(A, x)— the two convenience forms, returning(matrix, T, report)as sympy matrices and a dictionary.fuchsifyraisesNotImplementedErrorwith a recipe when the engine reports non-Fuchsian input, because the wrapper reads the engine's certificate of the input and does not pass back a Moser-reduced matrix. Strip the leading-singularity prefactors first, or call the Julia engine directly.to_dlog(Atilde, x, alphabet)— one constant matrix per letter, plus_residualand_unmatchedentries.counterweight_bridge.jl— the JSON-in, JSON-out script the wrapper runs; engine location fromCOUNTERWEIGHT_ROOT.
Used on this site
- Banana — brought the differential equation to $\epsilon$-form on its K3 alphabet.
- Non-planar hexa-box — found the $\epsilon$-form of the thirteen-master sector-219 system behind that sector's transport.
- Central-mass double box — the Moser-stage diagnosis located an irregular pole on two masters missing a $(2t-1)$ prefactor.
- Non-planar $q\bar q\to W^+W^-$ — sorted the diagonal blocks of the line connection into those with a rational rotation to $\epsilon$-factorized form and those that stop.
Requirements and source
Julia 1.11 or newer; Nemo and JSON are pinned by the committed Manifest.toml and installed once with julia --project=. -e "using Pkg; Pkg.instantiate()" from the package directory. The front end needs Python 3 with sympy and mpmath, and Julia on the path for bridge calls. Self-tests, from the package directory: julia --project=. test/runtests.jl is the registered command and runs the orbit-shear and double-box checks in one Julia process. The pieces also run on their own: julia --project=. test/validate_henn.jl, julia --project=. test/test_orbit_shear.jl (five cases: the shear at an irreducible quadratic letter, its invariants, its wiring into try_epsfactor, and two deliberately wrong inputs that must be refused) and julia --project=. test/test_moser.jl (four pole-reduction cases). The front end's tests are python3 canonical_form/test_canonical_form.py (five cases; CANONICAL_FORM_SKIP_JULIA=1 skips the bridge case, and the parts of two cases that need optional data skip themselves). The code is tools/counterweight/ in BootLoops' bootloops-dev repository (GitHub organization BootLoops-ai), released under the MIT license. The algorithms are Henn's (arXiv:1304.1806), Lee's (arXiv:1411.0911) and Barkatou–Moser's; src/Fuchsia.jl is BootLoops code, unrelated to the public Fuchsia program of Gituliar and Magerya.