Canonify
The content on this page was written by AI under human supervision.
Canonify (the canonical_form Python module) prepares the basis of master integrals in which BootLoops fits Feynman integrals and writes their differential equations. Given the definition of an integral family and a list of master integrals, it computes each master's leading singularity on the maximal cut. It returns the rescaling that divides that singularity out, in the format the Gatekeeper fitting routines read. It also lets Python code call the Julia ε-factorization engine of Counterweight, the package that contains it, and splits the resulting matrix into constant matrices times logarithmic derivatives.
What it does
Reduction programs such as Kira return a set of master integrals, and the matrix $A(x,\epsilon)$ of their differential equation, in whatever basis the reduction happened to choose. Recognizing exact constants in high-precision numerical values, and solving the differential equation order by order in the dimensional regulator $\epsilon$, both work far better in a uniform-transcendentality basisa choice of masters in which every order in ε is a pure function of fixed transcendental weight with rational coefficients. The usual first move toward that basis is to divide each master $I_i$ by its leading singularitythe residue of the integrand when every propagator of the master is put on shell (the maximal cut); an algebraic function of the kinematics, or a period of a curve for elliptic sectors $\mathrm{LS}_i$:
$$J_i = \frac{1}{\mathrm{LS}_i(x)}\, I_i .$$
Canonify computes $\mathrm{LS}_i$ from a family block, the same JSON object the amflow-cpp evaluator reads (loop momenta, external legs, momentum conservation, kinematic replacement rules, propagators), and a list of masters written as {"tag": ..., "indices": [...]}. Family builds the Baikov representation, in which the propagators are the integration variables and the integrand is a power of a Gram-determinant polynomial. leading_singularities sets each master's propagators to zero and takes iterated residues in the variables that remain, labeling the result rational, sqrt, elliptic or unknown. For an elliptic sector the quartic $y^2=P(z)$ is read off the cut and the rotation entry is $1/\varpi_0$, the inverse of its holomorphic period, which elliptic_period evaluates with a complete elliptic integral. ut_rotation returns {ut_tag: {raw_tag: coefficient}}: exact strings such as '21/4' or mpmath numbers at a kinematic point, expression strings in the invariants without one.
The rotation is diagonal; subtracting subsector integrals to complete the basis off the diagonal is outside this module. The automatic Baikov construction is exact for the top sector of a family. For subsectors the top-family Gram determinant can pick up spurious factors, and when the Baikov exponent vanishes at $\epsilon=0$ (the equal-mass sunrise, for example) the representation is degenerate. In both cases pass the cut polynomial yourself through a per-master maxcut entry. verify_unit_pole checks the result on numbers: after the rotation, the $\epsilon^{-L}$ coefficient of each master ($L$ the loop number) must be the same constant at every kinematic point.
The other half of the module reaches the full ε-form $\partial_x J=\epsilon\,\tilde A(x)\,J$. factor_epsilon(A, x) sends a sympy matrix, rational in x and eps, through counterweight_bridge.jl to Counterweight's Julia engine and returns $\tilde A$, $T$ and a report, with $TAT^{-1}+(\partial_xT)T^{-1}=\epsilon\tilde A$. The engine works in exact arithmetic over $\mathbb{Q}(\epsilon)(x)$, so the matrices carry no numerical error. When the engine directory is not found, or when the engine stops short of an ε-form, the call raises NotImplementedError carrying the stop reason and the names of other programs for this step (CANONICA, Libra, Fuchsia, epsilon). If the bridge process itself fails, the call raises RuntimeError with Julia's error output. Inside factor_epsilon the engine first lowers any poles of order higher than one with a Barkatou–Moser reduction; a genuinely irregular singular point, which that reduction cannot make regular, stops the call. Such a stop usually means, for a Feynman system, that some master still lacks its leading-singularity prefactor. to_dlog needs no engine: it partial-fractions $\tilde A$ in $x$ and returns one constant matrix per letter of a supplied alphabet, plus a _residual matrix that is zero when the decomposition is complete.
Examples
Leading singularities and rotation from the command line. The package includes the one-loop massless box family as box1l.json; run this from tools/counterweight/canonical_form/:
python canonical_form.py box1l.json --point '{"s":-3,"t":-7}' \
--out out/box1l/ut_rotation.json
The script prints (and, with --out, also writes) one JSON record with the family name, the point, a leading_singularities object and the ut_rotation dictionary evaluated at the point. Each master's entry carries tag, sector, kind, expr, radicand, quartic, residual_vars and a note naming the function class. The bundled file lists the two bubble masters of the box family; the test suite uses the same family with the box master [1,1,1,1] added, whose leading singularity comes out proportional to $1/(st)$. The two bubbles are subsectors, so their automatic entries come back with kind sqrt and the spurious factors described above; they are the case the maxcut override is for.
The rotation from Python, passed to a fit. From the module README:
from canonical_form import Family, ut_rotation
from gatekeeper import heldout_certify, load_oracle_values
fam = Family(target["family"])
rot = ut_rotation(fam, target["masters"], point={"s": -3, "t": -7})
# rot = {'ut_box': {'box': '21/4'}, ...} ← exact heldout_certify format
values, kin, _ = load_oracle_values([oracle_dir])
res = heldout_certify(values, kin, weight=2, basis_fn=..., basis_names=...,
ut_rotation=rot)
Here target is a parsed target file like box1l.json. For the box master at $s=-3$, $t=-7$ the coefficient is the exact string '21/4', that is $st/4$. The Gatekeeper fit called on the last line multiplies the raw numerical values by these coefficients before it fits. When the automatic cut is degenerate, give the curve directly in the master entry, as the README does for the equal-mass sunrise:
masters = [{"tag": "sun", "indices": [1,1,1],
"maxcut": {"poly": "z*(z-4)*((tt-z)**2 - 4*z)", "vars": ["z"]}}]
leading_singularities then reports kind == "elliptic" with that quartic, and ut_rotation at a numerical value of tt returns $1/\varpi_0$.
Full ε-factorization through the engine. Also from the README:
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])
A is a sympy Matrix in the symbols x and eps; the bridge fixes those two names, so import eps from the module and name your variable x. Atilde and T come back as sympy matrices and report as a dictionary describing what the engine did. letters maps 'x' and 'x + 1' to constant matrices; confirm that letters['_residual'] is the zero matrix before using them.
Routines
Command line
canonical_form.py target.json [--point JSON] [--out FILE]— compute the leading singularities and the rotation for the masters in a target file (or a bare family file) and print them as JSON.
Family and leading singularities
Family(spec)— parse afamilyblock; its methodsbaikov_poly(),baikov_exponent()andsector_of(indices)return the Baikov polynomial, its exponent and a master's sector.leading_singularities(family, masters, *, maxcut_override=None)— maximal-cut leading singularity of each master; returns{tag: LSResult}.LSResult— result record withtag,sector,kind,expr,radicand,quartic,residual_vars,note, and ato_json()method.elliptic_period(quartic, var, point, dps=60)— holomorphic period $\varpi_0$ of $y^2=P(z)$ ($P$ cubic or quartic) at a kinematic point, via the complete elliptic integral, with root-finding and arithmetic atdpsdigits.
Rotation and its check
ut_rotation(family, masters, point=None, *, eps_power=None, dps=60, include_eps_shift=False)— the diagonal $1/\mathrm{LS}$ rotation as{ut_tag: {raw_tag: coeff}}; withinclude_eps_shift=Trueit also returns the recommended power of ε for each master (default $L$).ut_rotation_matrix(family, masters, point)— the same rotation as(matrix, row_names, col_names).verify_unit_pole(values, ut_rot, *, L, tol_digits=8)— apply the rotation to numerical values at several points and check that each rotated master's $\epsilon^{-L}$ coefficient is independent of the point.
ε-form (Counterweight engine) and dlog form
wrap_epsfactor(A, x, *, action="epsform", timeout=600)— the low-level bridge call;action="certify"only tests whetherAalready has simple poles (is Fuchsian) and is in ε-form,"epsform"runs the full reduction; returns the engine's JSON reply as a dict.fuchsify(A, x)— returns(A_new, T, report); stops withNotImplementedErrorand a written recipe if the engine's first check finds poles of order higher than one in the input as given. This wrapper does not hand back a Moser-reduced matrix; the reduction runs insidefactor_epsilon.factor_epsilon(A, x)— returns(Atilde, T, report)with $TAT^{-1}+(\partial_xT)T^{-1}=\epsilon\tilde A$; raisesNotImplementedErrorwith the engine's stop reason when no ε-form is reached.to_dlog(Atilde, x, alphabet)— decompose $\tilde A=\sum_a A_a\,\partial_x\log a$; returns{letter: matrix}plus_residualand, where poles are left over,_unmatched.eps— the sympy symbol the module and the bridge use for the regulator.counterweight_bridge.jl— the Julia script behindwrap_epsfactor,fuchsifyandfactor_epsilon: JSON in (matrix entries as strings plus anaction), JSON out (ok,fuchsian,epsform,dlog,singular_points,T,Atilde,report). The environment variableCOUNTERWEIGHT_ROOTpoints it at an engine checkout other than the parent directory.
Requirements and source
Python 3 with sympy and mpmath. verify_unit_pole imports its rotation helper from the Gatekeeper package, which sits beside Counterweight in the repository. The ε-form functions need Julia 1.11 or newer with the Counterweight engine set up once (julia --project=. -e "using Pkg; Pkg.instantiate()" in tools/counterweight/); everything else runs without Julia. Self-tests, from tools/counterweight/: python3 canonical_form/test_canonical_form.py runs five tests (box leading singularity and rotation format, exact to_dlog, the elliptic sunrise period, the numerical-values check, and the Julia bridge) and ends with OK: 5/5 passed. The last two tests skip cleanly when their optional data or Julia are absent, and CANONICAL_FORM_SKIP_JULIA=1 skips the bridge test explicitly. Source: the canonical_form/ subdirectory of tools/counterweight/ in BootLoops' bootloops-dev repository (GitHub organization BootLoops-ai), released under the MIT license; the engine it calls, and the engine's own tests, are described on the Counterweight page.