PSLQ and LLL (external)
The content on this page was written by AI under human supervision.
PSLQ and LLL are public algorithms with public implementations: BootLoops runs PSLQ from the mpmath Python library (Fredrik Johansson and collaborators) and LLL and BKZ lattice reduction from the fplll library, or from Nemo in Julia code. It uses them for the last step of a calculation, turning a number known to many digits into an exact formula in known constants. BootLoops adds the checks around them, in Lockpick and Gatekeeper. Every search runs at two precisions, the coefficient bound is matched to the digits available, a synthetic target must be found before a negative answer counts, and an LLL change of basis repairs ill-conditioned fits.
What it does
An integer relation among real numbers $x_0,\dots,x_n$ is a vector of integers $(c_0,\dots,c_n)$, not all zero, with
$$c_0x_0+c_1x_1+\dots+c_nx_n=0 .$$
Take $x_0$ to be a Feynman integral computed numerically to 100 or 200 digits and $x_1,\dots,x_n$ to be candidate constants such as $\pi^2$, $\zeta(3)$, $\log 2$ or an elliptic period. A relation with small coefficients that holds to all those digits says $x_0=-(c_1x_1+\dots+c_nx_n)/c_0$: an exact formula read off from decimals. Applied to the floating-point coefficients of a least-squares fit, the same step turns them into exact rationals.
PSLQ (Ferguson, Bailey and Arno) takes the numbers at a set working precision and a bound on the coefficients, and returns either an integer vector whose combination vanishes to within a tolerance or None. LLL (Lenstra, Lenstra and Lovász) reaches the same answer through lattice reductionreplacing a set of long, nearly parallel integer vectors that span a lattice by short, nearly orthogonal ones spanning the same lattice. Each number, multiplied by $10^p$ and rounded, is appended to a unit vector; after reduction a genuine relation appears as a short vector whose last entry is nearly zero and whose other entries are the $c_i$. BKZ is a stronger and slower block version of LLL that fplll provides. LLL handles many numbers at once and copes better with ill-conditioned input; PSLQ is simpler to call and cheap for a handful of constants. BootLoops usually runs both on the same data, because they fail in different ways.
The output of either algorithm is numerical evidence, never a proof, and its strength rests on three things the algorithms do not check. Digits: telling a true relation with coefficients up to $H$ among $n$ candidates apart from a chance near-miss needs roughly $(n+1)\log_{10}H$ digits plus a margin; with fewer, the search returns nothing or a spurious vector of large integers. Independence: if the candidates themselves satisfy a relation, PSLQ returns that one, with a zero coefficient on the target, which is easy to misread as "no formula exists" (Example 1). Confirmation: an accepted relation should reappear at a higher precision and reproduce values that were not used to find it. The BootLoops wrappers enforce these points and stop with a message when they cannot be met; the raw library calls do not. An integer-relation search is the wrong tool when the quantity can be derived symbolically or when the digits available fall short of the count above, and it only recognizes combinations of the constants it is given.
Examples
Raw PSLQ in mpmath (from the mpmath documentation). Verify Machin's formula $\pi/4=4\,\mathrm{acot}\,5-\mathrm{acot}\,239$, then try to discover a formula for $\pi$ from $\mathrm{acot}\,2,\dots,\mathrm{acot}\,10$.
>>> from mpmath import * >>> mp.dps = 30 >>> pslq([pi/4, acot(5), acot(239)]) [1, -4, 1] >>> pslq([pi] + [acot(n) for n in range(2,11)]) [0, 1, -1, 0, 0, 0, -1, 0, 0, 0] >>> pslq([pi] + [acot(n) for n in range(2,11) if n not in (3, 5)]) [1, -8, 0, 0, 4, 0, 0, 0]
pslq(x) returns integers $c$ with $\sum_k c_kx_k=0$ to the tolerance (three quarters of the working precision by default), or None when no relation with coefficients below maxcoeff (default 1000) is found. The second call shows the dependent-list pitfall: $\mathrm{acot}\,2=\mathrm{acot}\,3+\mathrm{acot}\,7$ holds among the candidates alone, so PSLQ returns that relation with a zero in the slot for $\pi$. With the dependent entries removed, the third call finds $\pi=8\,\mathrm{acot}\,2-4\,\mathrm{acot}\,7$. Lockpick's members_audit runs this independence check before any search, and its search raises DegeneratePoolError rather than returning the misleading vector.
An integer relation by LLL (Gatekeeper's test T5). Build a number from five constants with known integer coefficients and recover them by lattice reduction. Run it from the repository's tools/ directory so that import gatekeeper works.
import mpmath as mp
from gatekeeper import pslq_with_lll
mp.mp.dps = 80
b = [mp.zeta(3), mp.pi ** 2 * mp.log(2), mp.log(2) ** 3,
mp.mpf(1), mp.catalan * mp.log(2)]
target = 7 * b[0] - 2 * b[1] + 11 * b[2] - 5 * b[3] + 3 * b[4]
rel, method = pslq_with_lll([target] + b, prec=60, max_coeff_bits=20)
The test suite prints T5 pslq_with_lll: PASS (method=lll, rel=[1, -7, 2, -11, 5, -3]), the relation $\mathrm{target}-7b_0+2b_1-11b_2+5b_3-3b_4=0$ found by LLL on the numbers scaled to 60 digits. method is "lll" when a reduced lattice vector passes a residual check at full precision with every coefficient below $2^{20}$ (max_coeff_bits), "pslq" when only the mpmath fallback succeeded, and None with rel = None when neither did.
A checked PSLQ search (Lockpick's library form, from the module's usage text). Identify one number, given as the decimal string target_str, against named constants.
from pslq_gate import two_prec_stable, controls, reverify
pool = {'1': '1.000...', 'pi^2': '9.8696...'} # >=200-digit strings
vec = two_prec_stable(target_str, ['1','pi^2'], pool, dps_pair=(120,200),
maxcoeff=10**6)
two_prec_stable runs mpmath.pslq at 120 and at 200 digits and returns $(c_0,c_1,c_2)$, common factor removed and first nonzero entry positive, only if both runs agree; otherwise None. It raises DigitsError if a string carries fewer than 200 digits, CapacityError if maxcoeff needs more digits than the 120-digit run holds, and DegeneratePoolError if the pool is dependent. controls(pool) must recover the synthetic target $(3b_1-7b_2)/5$ with the same settings before a null result is reported, and reverify recomputes an accepted relation at 210 digits. The command-line form and the multi-point lattice fitter are on the Lockpick page.
Routines
BootLoops adds no code inside mpmath or fplll; both are installed from upstream. BootLoops code written directly around them:
Around mpmath.pslq
tools/lockpick/pslq_gate.py— the checked single-number search of Example 3 (two_prec_stable,members_audit,capacity,controls,reverify,canonicalize,graded_basket,odd_zeta_pool,load_amflow_targets). As a command it takes--target file.json:key --pool pool.json, or--selftestto run its self-tests (from the repository root:python3 tools/pslq_gate.py --selftest); signatures on the Lockpick page.gatekeeper.pslq_with_lll(values, prec=None, max_coeff_bits=64)— Example 2: LLL on the relation lattice first,mpmath.pslqas fallback; returns(relation, method).tools/lockpick/sealrun/t6_close.py --target-string-file FILE [--label NAME] [--out DIR](or--target-json FILE:key.path) — a fixed-protocol form of the same search: one real constant, given to at least 150 digits, against a frozen list of 24 constants (BASKET_T6.json). Every run repeats the independence check and the synthetic controls, steps the coefficient bound from $10^4$ to $10^{12}$ (refusing the steps the digits cannot support), and re-verifies a hit on digits the search never used. It writesCLOSE_<label>_<stamp>.jsonand exits 0 (formula found), 1 (none below the printed bound), 2 (controls failed, no result), 3 (dependent list) or 4 (input error). Its two self-test targets and how to adapt it to another list are on the Lockpick page.- Scripts that call
mpmath.pslqon their own output to recognize rationals or short combinations: PMflow, Vopclose, SubTropica'sfibrate.py, and Lockpick'sring15table builder. Coalescer'spslq_calpha.pyruns Lockpick's checked search (Example 3) in thet6_close.pyform rather than callingmpmath.pslqitself.
Around LLL and BKZ
lockpick.mplll_lattice.reduce_rows(rows, backend=None, bkz_beta=0),reduce_rows_batch(mats, ...)— reduce a lattice given as a list of integer vectors; return the reduced vectors, the backend used and the seconds taken. Backends in order: thefpylllmodule, thefplllexecutable (-a bkz -b β -f mpfr -p bits, or-a lll -m proved), Nemo'slllthrough a Julia subprocess, and a pure-Python LLL (Gatekeeper's). Environment variablesFPLLLandJULIAname the executables.MPLLL_FPLLL_TIMEOUTsets an optional time limit in seconds on each fplll or Nemo reduction: unset or0(the default) means no limit, a positive integer ends a reduction that runs longer withMplllTimeoutExpired(asubprocess.TimeoutExpiredwhose message names the variable), and any other value is refused with aValueError.lockpick.mplll.mplll_fit(...), withlockpick.mplll_batchandlockpick.mplll_graded— the multi-point fitter: one lattice from a target function and a basis sampled at many points, reduced with the routine above; see Lockpick.tools/lockpick/bilmine/— scripts built onreduce_rowsand Lockpick'scanonicalizethat search for exact integer bilinear relations among the entries of period matrices, with the same capacity, two-precision and control checks.python3 tools/lockpick/bilmine/selftest.pyrecovers a deliberately constructed quadric relation and returns nothing for a vector built to have no relation, in seconds; the data-driven scripts read their inputs fromBILMINE_*environment variables with no defaults. See Lockpick.gatekeeper.auto_condition(basis_matrix, target_cond=1e6, prec=None, drop_tol_digits=None)— for a basis matrix with condition number abovetarget_cond, LLL-reduces its column lattice and drops columns that are exact integer combinations of the others. Returns(U, cond_before, cond_after, AU): the integer change of basis and the reduced matrix.gatekeeper.heldout_certify(..., auto_cond=True, cond_target=1e6)applies it on its own.lattice_fit(fvals, Bvals; digits, height_bound=ZZ(10)^12),integer_relation(vals; digits, height_bound=ZZ(10)^10),fit_residual(fr, fvals, Bvals)in Landau Alphabet (Julia) — the same constructions on Arb ball values with Nemo's LLL.FitResult.okis the coefficient-size check alone; the residual and the values left out of the fit are checked separately.
Used on this site
- Banana — identified the $7\zeta_3$ boundary constant and the exact word coefficients.
- Kite — recognized the five equal-mass coefficients after the fit to high-precision values, and ran the searches on the unequal-mass boundary constants that found no relation.
- Sunrise — integer-relation fits over the ring of cusp values, which recognized the $L$-value boundary constant.
- Three-loop light-by-light — identification of the boundary constants, and the negative searches.
Requirements and source
mpmath is pure Python (BSD license), one of the core dependencies of BootLoops' bootloops-dev repository (GitHub organization BootLoops-ai; pip install mpmath); mpmath.org. fplll is a C++ library and command-line program (LGPL) with Python bindings fpylll; github.com/fplll/fplll. Without either form of fplll, Lockpick falls back to Nemo (Julia) or pure Python and loses BKZ. Self-tests that exercise both algorithms: python3 gatekeeper/test_gatekeeper.py from the repository's tools/ directory (T4 recovers exact coefficients from a basis matrix with condition number above $10^{61}$, where least squares plus PSLQ finds nothing; T5 is Example 2), and the --selftest command in the Routines list, which repeats the checks of Example 3 on inputs built with known answers. The wrappers are tools/lockpick/, tools/gatekeeper/ and tools/landau-alphabet/ in the bootloops-dev repository, released under the MIT license; mpmath and fplll are not bundled and keep their own licenses. References: H. R. P. Ferguson, D. H. Bailey and S. Arno, Math. Comp. 68 (1999) 351; A. K. Lenstra, H. W. Lenstra Jr. and L. Lovász, Math. Ann. 261 (1982) 515; Lockpick's rawlll multi-point construction follows arXiv:2507.17815.