PLD and SOFIA (external)
The content on this page was written by AI under human supervision.
PLD.jl is public software by Claudia Fevola, Sebastian Mizera and Simon Telen; SOFIA is public software by Miguel Correia, Mathieu Giroux and Sebastian Mizera. Both take a Feynman diagram, given as its internal lines with their masses and the vertices where the external particles attach. They return polynomials in the kinematic invariants whose zeros are the places where the corresponding integral can be singular. BootLoops uses them at the start of a calculation to fix the list of candidate symbol letters, and as independent checks on each other and on its own Landau Alphabet engine. It adds two pieces of code: SOFIA.jl, a reimplementation of SOFIA in Julia that runs without Mathematica and can call PLD.jl as an optional backend, and a bridge script that runs PLD.jl (or, where PLD.jl cannot load, a lighter numerical check) on Landau Alphabet's input files.
What it does
A Feynman integral depends analytically on its invariants (Mandelstam variables such as $s$ and $t$, and the squared masses) except on surfaces defined by polynomial equations, its Landau singularitiessolutions of the Landau equations: the kinematic configurations at which the integration contour is trapped between poles of the integrand, producing thresholds and other branch points. The one-loop bubble with internal masses $m_1$, $m_2$, for example, is singular where the Källén polynomial $\lambda=s^2+m_1^4+m_2^4-2sm_1^2-2sm_2^2-2m_1^2m_2^2$ vanishes. A bootstrap calculation needs the complete list first, because the irreducible polynomials in it are the candidate lettersthe arguments W(s,t,...) of the logarithms from which the iterated integrals in the answer are built; the finite set of them is the symbol alphabet of the answer. The two programs derive that list from the graph alone by algorithms that share no code, so agreement between them is strong evidence that it is right.
PLD.jl computes the principal Landau determinant. It builds the Symanzik polynomials of the graph (polynomials in the Feynman parameters $x_i$) and works face by face through a polytope attached to them. Each face corresponds to one way a subset of the $x_i$ can scale to zero or infinity. On each face it eliminates the $x_i$ and returns a discriminant in the kinematic variables; the irreducible factors of all these discriminants are the candidates. Elimination is symbolic (method = :sym, through the computer-algebra system Oscar) or numerical (:num, through HomotopyContinuation.jl). No face has a time limit, so the result is complete when the run finishes, and the run can be very heavy on multi-loop graphs with several masses. BootLoops runs PLD.jl as released, through pld_bridge.jl and, optionally, from inside SOFIA.jl (below). The script reads a Landau Alphabet graph specification (a JSON file of edges, leg attachments and kinematics), calls PLD.jl's getPLD, and writes the result in Landau Alphabet's output format, so the two can be compared on identical input. If getPLD fails part-way the file is still written, with "complete": false and the error text, and the script exits with code 2.
PLD.jl's Oscar dependency does not load on Julia 1.11 or newer. When PLD.jl or Oscar fails to load, or with --fallback, pld_bridge.jl runs a lighter numerical check that needs only HomotopyContinuation.jl. On each face it solves the critical-point equations of the Lee-Pomeransky polynomial $U+F$ (the sum of the two Symanzik polynomials) along a seeded generic line through invariant space; the solutions are numerical witnesses of singular surfaces. With --check FILE each witness is matched against a candidate alphabet, and the script exits with code 2 if one matches no candidate, which means the candidate list is missing a letter. Witnesses are interior pinches only: they can include second-type surfaces that a final alphabet drops (on the massless box, $s+t$) and they never appear for letters such as the box's $s$ and $t$. This mode therefore confirms letters but cannot certify completeness, and always writes complete: false.
SOFIA writes the diagram loop by loop in Baikov variablesa change of integration variables in which the propagator denominators themselves, plus a few extra scalar products, become the variables and sets the propagator variables to zero (the maximal cut). It eliminates the remaining variables from the resulting Gram determinants one at a time with resultants and discriminants, intersecting over elimination orders (the authors' FastFubini procedure). It repeats this for every subtopology, that is, every diagram obtained by contracting internal lines, and returns the union, factored and deduplicated. A separate routine then builds from that list the "odd" letters $(P-\sqrt Q)/(P+\sqrt Q)$ that go with each square root $\sqrt{Q}$ in the alphabet. Here $P^2-Q$ must be a multiple of a product of other letters (the Effortless construction of Matijašić and Miczajka). Upstream SOFIA is a Mathematica package. SOFIA.jl, which BootLoops runs, reimplements the algorithms in Julia on Nemo/FLINT and Graphs.jl, reusing no upstream code and leaving out the diagram-drawing interface; tests that upstream does by random sampling (proportionality, perfect squares, letter independence) are exact. In both implementations the candidate set on the maximal cut depends on choices made during elimination, such as which lines carry the loop momenta; SOFIA.jl's routes=N option repeats the elimination over up to N deterministic variants and returns the union, a superset of any single route. Subtopologies related by a relabeling are computed once (symmetries=true), which changes run time only.
Either program returns candidates: the list can contain polynomials on which the integral is regular, and it is not sorted into the first-entry and last-entry classes a bootstrap needs. Landau Alphabet does that sorting, and Leviathan tests candidates by a third method. SOFIA.jl has a known performance limit: on heavy multi-scale diagrams (a fully massive penta-box, several massive double-box configurations) an elimination meets an intermediate polynomial whose discriminant does not finish in practical time, where upstream SOFIA, on a different route, finishes in seconds to minutes. Two options make every run terminate: time_budget (a time limit in seconds on each subtopology's eliminations) and maxopterms (skip single resultants or discriminants whose inputs exceed a term count). The price is missing deeper candidates on such graphs; for those, use upstream SOFIA or PLD.jl. On the upstream worked examples that complete, SOFIA.jl reproduces every recorded singularity in most cases, often with extra candidates, and misses one or two entries in two cases (validation/REPORT.md). SOFIA.jl can also hand a diagram's Landau system to PLD.jl (pld_singularities, the counterpart of upstream's Solver->momentumPLD option). The caller loads PLD.jl and passes the module in, so PLD.jl and Oscar never become dependencies of the package, and the discriminants come back as polynomials in the kinematic ring.
Examples
Massless double box with SOFIA.jl. From the package README: seven internal lines (vertex pair, mass label, here all zero) and four external legs (vertex, mass label).
using SOFIA
import Nemo
# end to end: candidate Landau singularities of the massless double box
mz = BigInt(0)
dbox = Diagram([((1,2),mz),((2,3),mz),((3,6),mz),((6,5),mz),
((5,4),mz),((4,1),mz),((2,5),mz)],
[(1,mz),(3,mz),(4,mz),(6,mz)])
sing, ring = sofia_singularities(dbox) # -> {s12, s23, s12+s23} = {s, t, s+t}
ring is a Nemo polynomial ring generated by the invariants SOFIA chose, which here include the cyclic Mandelstam invariants s12 and s23 (massive legs and lines add squared-mass generators MM1, ... and mm1, ...). sing contains s12, s23 and s12 + s23: the integral can be singular only at $s=0$, $t=0$ and $u=-s-t=0$. These are also this diagram's entry in the PLD reference set included with the package: loadsingularities("reference/PLD_database/dbox_zero_zero.m") returns the polynomials s, s + t, t together with their ring and variable keys.
Masses and odd letters. Mass labels are symbols; lines with the same label share a mass. The first block continues the README example, the second is the odd-letter test from test/runtests.jl.
# with masses: labels are symbols/indexed atoms; same label = same scale bub = Diagram([((1,2),:m1), ((1,2),:m2)], [(1,mz),(2,mz)]) sing, ring = sofia_singularities(bub) # contains Källén λ(s, mm1, mm2) # route-union mode: superset over elimination routes (see PORTING_NOTES) sing, ring = sofia_singularities(bub; routes=12) # optional PLD.jl backend (upstream Solver->momentumPLD): if PLD.jl is # installed, pass the module in # import PLD # sing, ring = pld_singularities(PLD, bub)
R, (s, mm1, mm2) = Nemo.polynomial_ring(Nemo.QQ, ["s", "mm1", "mm2"]) kallen = s^2 + mm1^2 + mm2^2 - 2s * mm1 - 2s * mm2 - 2mm1 * mm2 alphabet = [s, mm1, mm2, kallen] odd = find_odd_letters(kallen, alphabet) indep = independent_letters(odd, alphabet)
For the bubble sing contains the Källén polynomial $\lambda$ of the first section, with the squared masses appearing as the generators mm1 and mm2. With routes=12 the call returns the union over up to twelve elimination routes, which contains the single-route list and on larger diagrams can add entries. The commented lines run the same bubble through PLD.jl instead when it is installed, returning (polynomials, ring) again so the two lists can be compared. find_odd_letters returns three OddLetter records whose P fields are proportional to $s-m_1^2-m_2^2$, $s+m_1^2-m_2^2$ and $s-m_1^2+m_2^2$, each storing Q (here $\lambda$) and the witness c, prod in $P^2=Q+c\cdot\mathrm{prod}$; independent_letters keeps the two whose logarithmic derivatives are independent given the even letters. effortless_odd_letters(list) runs the whole construction on a singularity list, taking every non-square entry (and, by default, pairwise products of them) as a root.
PLD.jl on a Landau Alphabet graph file. From the Landau Alphabet README, with PLD.jl in its own Julia 1.10 environment:
PLD_ENV=<your PLD julia env> julia pld_bridge.jl \
graph_specs/k4box3l.json k4box3l_alphabet_pld.json --method sym
k4box3l.json is the three-loop crossed massive box (six lines, one mass symbol m2, four massless legs), a heavy case; graph_specs/ also has box1l.json and sunrise2l.json for a first run. The output JSON carries alphabet (the distinct discriminant factors as strings), faces (one record per PLD.jl entry), complete, and a status block (under the key honesty) with the run time and any error text; first_entry and last_entry stay empty because PLD.jl does not classify letters. PLD.jl names the invariants itself ($s,t$ for four legs, $s_{ij}$ for more), so comparing with Landau Alphabet's own output may need relabeling. To resume a long run, --save_output LOG records per-face results as they arrive and --load_output LOG --codim_start C --face_start I restarts from that point.
Without PLD.jl or Oscar, the same script cross-checks a sympy-backend alphabet numerically. From the README, on the one-loop box:
julia pld_bridge.jl graph_specs/box1l.json box1l_hc.json \
--fallback --check box1l_alphabet.json --seed 0
box1l_alphabet.json is Landau Alphabet's own output for the box (python landau_alphabet.py graph_specs/box1l.json --out box1l_alphabet.json). The script writes box1l_hc.json in the format above, with method set to hc-fallback-numerical and any unmatched_witnesses under honesty, and prints whether every witness matched. Here the leading face gives a witness on $s+t=0$, absent from the sympy backend's {s, t}, so the run exits with code 2 and a "CROSS-VALIDATION CATCH" message; with s + t added to the candidate file's alphabet list every witness matches and the exit code is 0. python3 selftest.py --full in tools/landau-alphabet/ runs exactly this pair.
Routines
SOFIA.jl (using SOFIA; all exported)
Diagram(edges, nodes)— a diagram from[((u,v), mass), ...]internal lines and[(vertex, mass), ...]external legs (Edge,Nodeare the element types).sofia_singularities(d; include_subtopologies, maxcut, routes, symmetries, time_budget, maxopterms, limit, include_isps, loopedges, progress)— candidate Landau singularities ofdwith all subtopologies; returns(polynomials, ring).singularities_seed(d; ...)— the same for one diagram, no subtopologies.prepare_landau_system(d; maxcut=true)— the Gram determinants after the maximal-cut substitution and the variables left to eliminate.lbl(d; loopedges)— the loop-by-loop Baikov construction (BaikovSystemornothing);fix_loop_edges(d; order)chooses the lines that carry loop momenta.generate_kinematics(n)— genericn-point kinematics as aKinematicsrecord (ring, cyclic Mandelstam basis, table of $p_i\cdot p_j$).nloops,subtopologies,contract_edge,delete_tadpoles,one_vertex_irreducible— the graph operations behind the subtopology enumeration.fubini,fastfubini,runningfubini— the elimination engines on(polys, vars; limit, ...): variable by variable, the FastFubini subset-lattice version, and its intersection over variable orders;intersect_proportionalintersects result lists.isproportional,dedup_proportional,irreducible_factors,factor_list_unique— exact polynomial utilities (comparison up to a constant, factoring, deduplication).find_odd_letters(Q, alphabet),independent_letters(odd, evens),effortless_odd_letters(sing),OddLetter— the Effortless odd-letter construction.pld_singularities(PLD, d; maxcut, loopedges, factor_result, method, codim_start, face_start, ...)— candidate singularities of one diagram through the optional PLD.jl backend (pass the loadedPLDmodule);pld_system(d)andpld_delta(polys, active_idx)build thePLDSystemit sends, andpld_discriminants(PLD, sys; ...)makes thegetSpecializedPADcall and returns(discriminants, ring).symmetry_map(A, B),symmetry_quotient(tops)— relabeling maps between diagrams and the classes that avoid recomputing equivalent subtopologies.loadsingularities(path),wlparsefile(path),wlparse(str),WLCall— read the Wolfram-formatPLD_databasereference files into Nemo polynomials.validation/run_notebook_cases.jl—julia --project=. validation/run_notebook_cases.jl [case indices...]replays the upstream worked examples;extract_notebook.pyandmake_report.pybeside it rebuild the case list andREPORT.md.
PLD.jl bridge (in the Landau Alphabet package)
julia pld_bridge.jl SPEC.json OUT.json [--method sym|num]— rungetPLDon a graph specification and write Landau Alphabet's output format;--codim_start,--face_start,--load_output,--save_outputpass through PLD.jl's resume options;PLD_ENVnames the Julia environment holding PLD.jl,PLD_SRCa PLD source checkout to add to the load path. Exit code 2 if PLD.jl did not finish.julia pld_bridge.jl SPEC.json OUT.json --fallback [--check CANDIDATES.json] [--seed N] [--tol X]— the HomotopyContinuation-only numerical mode (also chosen automatically when PLD.jl or Oscar fails to load): per-face witnesses on a seeded generic line, matched against the candidate letters inCANDIDATES.jsonwith toleranceX; exit code 2 if a face fails or a witness matches no candidate;completeis always false.
The similarly named pld_groebner_backend.py is BootLoops' own Gröbner-basis backend for hard faces, not part of PLD.jl; the Landau Alphabet page documents it.
Used on this site
- Kite — SOFIA's singularity analysis found the algebraic threshold letter $s^2-12s+4$.
- Central-mass double box — SOFIA and PLD supplied the singularity analysis of the family, whose final alphabet is the seven rational letters of the principal $A$-determinant.
- gg → Zγ — the Landau/PLD singularity analysis identified the $\Gamma_0(4)$ curve and its five deformed cusps.
- Feynman diagrams — step 1 of the pipeline described there: where the answer can be singular, read from the graph alone.
Requirements and source
SOFIA.jl needs Julia 1.10 or newer with the registered packages Nemo, AbstractAlgebra, Graphs and Combinatorics; nothing proprietary, and PLD.jl only if you want the optional backend. It lives in upgrades/SOFIA.jl/ of BootLoops' bootloops-dev repository (GitHub organization BootLoops-ai), released under the MIT license. There, julia --project=. -e 'using Pkg; Pkg.instantiate()' installs the dependencies and julia --project=. -e 'using Pkg; Pkg.test()' runs test/runtests.jl: the parser on every PLD_database file, the elimination engines, kinematics, the end-to-end bubble and box, odd letters and symmetry maps. The same suite tests the PLD.jl backend against a mock solver, plus a live run when PLD.jl is installed (otherwise a skip message names it). PATCHES.md and docs/PORTING_NOTES.md list every difference from upstream, and reference/ holds copies of the upstream sources the port was checked against, under their own MIT terms, together with the PLD_database reference set. Upstream: github.com/StrangeQuark007/SOFIA; please cite M. Correia, M. Giroux and S. Mizera, Comput. Phys. Commun. 320 (2026) 109970, arXiv:2503.16601, and for the odd letters Effortless by A. Matijašić and J. Miczajka (github.com/antonela-matijasic/Effortless).
PLD.jl is not bundled with BootLoops; it comes from its authors at mathrepo.mis.mpg.de/PLD (C. Fevola, S. Mizera and S. Telen, Comput. Phys. Commun. 303 (2024) 109278, arXiv:2311.16219; companion paper arXiv:2311.14669). The bootloops-dev repository's THIRD_PARTY.md records its terms as MIT for the code and CC BY 4.0 for the page content; confirm them at the source. It depends on Oscar and HomotopyContinuation.jl and, because of Oscar's GAP component, loads on Julia 1.10 but not on 1.11 or newer; keep it in a separate Julia 1.10 environment named in PLD_ENV (the bridge's header records Oscar 1.7.3 with HomotopyContinuation 2.20 as a working combination). pld_bridge.jl itself needs the JSON and HomotopyContinuation packages (enough on their own for the --fallback mode, which also runs on Julia 1.11 and newer) and lives in tools/landau-alphabet/ in the bootloops-dev repository. There python3 selftest.py runs that package's default self-test suite and python3 selftest.py --full adds the bridge's fallback check on the one-loop box (skipped with a message when Julia or HomotopyContinuation.jl is missing); the Landau Alphabet page has the input format and the rest of the package.