Lockpick
The content on this page was written by AI under human supervision.
Lockpick is BootLoops' package for integer-relation finding. It takes a number known to a hundred or more digits, or a function sampled to that precision at several points, and a list of candidate constants or basis functions. It looks for small whole-number coefficients that express the target exactly in terms of the candidates, and returns the relation with the numerical evidence for it, or a "no relation" answer that means something definite: no relation below the coefficient bound at the working precision, with the built-in checks passed. The parts are a checked PSLQ driver for single numbers, the multi-point lattice fitter mplll, a sample-point selector, and ring15, a library of high-precision constants attached to the field $\mathbb{Q}(\sqrt{-15})$ that serves as a ready-made candidate list. Two script sets built on these, sealrun and bilmine, run fixed, preregistered searches: one constant against a frozen candidate list, and exact bilinear relations among period matrices.
What it does
An integer relation among real numbers $v,b_1,\dots,b_n$ is a set of integers $(c_0,\dots,c_n)$, not all zero, with $c_0v+c_1b_1+\dots+c_nb_n=0$. If $v$ is an integral known to 200 digits and the $b_i$ are constants such as $\pi^2$, $\zeta(3)$ or $\log2$, a relation with small coefficients that holds to that precision is strong evidence for an exact formula for $v$. The two standard algorithms are PSLQthe Ferguson–Bailey integer-relation algorithm: from real numbers given to high precision it returns small integers combining them to zero, or reports none below a coefficient bound and lattice reductionLLL (Lenstra–Lenstra–Lovász) and its block version BKZ turn long, nearly parallel integer vectors into short, nearly orthogonal ones; an integer relation appears as the shortest vector of a suitably built lattice. Lockpick wraps both with the checks that make their answers, above all their negative answers, meaningful. The result is evidence, never a proof; confirm an accepted relation against an independent evaluation (Gatekeeper, PSLQ and LLL).
One number against a list of constants. The PSLQ module takes the target and the named candidates as decimal strings (200 digits or more) and a coefficient bound $H$ (default $10^6$). It refuses with a message in three cases that would otherwise give a silent, false "no relation". First, the target carries fewer digits than the working precision. Second, the search needs more digits than the 120-digit run provides, about $(n+1)\log_{10}H+20$ for $n$ candidates. Third, the candidates themselves satisfy an integer relation; PSLQ would return that relation with a zero coefficient on the target, so the module names the dependent constants and stops. A relation counts only if runs at 120 and at 200 digits give the same vector up to a common factor and sign. Before a "no relation" result is reported, a synthetic target $(3b_1-7b_2)/5$ must be recovered from the same list with the same settings, and the command-line program re-verifies every hit at 210 digits.
A function against a basis of functions. In the multi-point version the target $I$ and $K$ basis functions $F_i$ are known numerically at the same $N$ points, and one integer vector must work at all of them:
$$c_0\,I(x_j)-\sum_{i=1}^{K}c_i\,F_i(x_j)=0,\qquad j=1,\dots,N .$$
mplll builds one integer lattice from the whole data matrix and reduces it, so the relation appears as a single short vector instead of $K$ real least-squares coefficients each needing its own PSLQ. The default method qrbkz orthogonalizes the data matrix before BKZ reduction, removing the ill-conditioning that many sample points cause; it needs at least $K+1$ fit points. rawlll embeds the raw data as in arXiv:2507.17815 and works with fewer points; rawbkz reduces the same lattice with BKZ. graded treats a basis graded by transcendental weight ($\mathrm{Li}_2$ has weight 2, $\zeta(3)$ weight 3) one weight at a time and reports at which weights a relation was found (locked), localizing a failure. cvp rounds a least-squares solution to the nearest lattice point for a given denominator, batch mode fits many targets against one basis, and a precision sweep finds the fewest digits at which the relation is stable.
Every fit is judged the same way. Some points are withheld (the last two by default); the relation must reproduce the target there to at least 30 digits, and reappear unchanged when the fit is repeated at $d-20$ digits, for status HIT. Otherwise the status is PARTIAL, and NULL means no vector under the coefficient bound. A capacity entry says whether $N_{\rm fit}\cdot d$ digits can support the relation found. A synthetic target that must be found and a random one that must not be are fitted first (exit code 2 on failure). The field span_residual_d tells the two kinds of failure apart. A value above $d$ means the target lies in the span of the basis at the working precision, so a missing relation is a matter of coefficient size. A value below $d$ is the number of digits to which this basis can reproduce the target at all: a function is missing, and more digits will not help. A basis_relations entry means the basis is itself dependent; prune it first.
Choosing sample points. Basis functions are cheap to evaluate and the target usually is not. mplll_points selects, from candidate points and the basis values there, the $n$ points that maximize the determinant of the fit matrix (a greedy D-optimal design, which keeps the fit well conditioned); run it before computing any target values.
The conductor-15 constants. ring15 evaluates, at any mpmath precision, the constants expected when an integral's curve or surface has complex multiplicationan extra arithmetic symmetry of an elliptic curve or K3 surface present only at special parameter values; by the Chowla–Selberg formula its periods are then products of gamma-function values at rational arguments by $\mathbb{Q}(\sqrt{-15})$, as for the K3 surface of the equal-mass three-loop banana. The table holds Dirichlet $L$-values for the quadratic characters of discriminant $-15$, $-3$, $-4$ and $5$, and the Chowla–Selberg period $\Omega_D$, a fixed power of the product $\prod_a\Gamma(a/|D|)^{\chi_D(a)}$ divided by $\sqrt{2\pi|D|}$. It also holds the Dedekind-eta periods $\sqrt{\operatorname{Im}\tau}\,|\eta(\tau)|^2$ at the two CM points of discriminant $-15$, and the exact integer Fourier coefficients and the $L$-values at $s=1,2,3$ of the two CM newforms of weight 3 and level 15. Each module carries its own consistency checks (Catalan's constant, the lemniscate constant from the arithmetic–geometric mean, eta-product and PARI/GP values for the coefficients), and the orchestrator builds the table at 210 and at 120 digits and records their agreement.
Fixed-protocol searches. Once the targets are defined, a protocol frozen in advance replaces exploratory searching. sealrun decides, for one constant of at least 150 digits (300 is typical), between "a relation was found and confirmed" and "no relation with coefficients up to the printed bound" against a frozen list of 24 constants (powers of $\pi$ and $\log 2$, zeta values, Catalan's constant, Dirichlet $L$-values, and the periods and $L$-values of a weight-3 newform). Every run repeats the dependence audit and the two controls (a synthetic target that must be found, a pseudo-random one that must not), then climbs a coefficient ladder from $10^4$ to $10^{12}$, refusing rungs the digits cannot support. The fits use at most the target's digits minus 40, and a hit is re-verified on digits no fit used. A null result excludes only that list up to that bound. bilmine applies the same discipline to exact bilinear relations $C^{T}S_{\rm loc}\,C=S$ among numerically known period matrices $C$, with the integer entries of the constant matrices $S_{\rm loc}$ and $S$ unknown. One homogeneous lattice reduction finds them; the span found is compared between two precisions through its Hermite normal form; entries of $S$ withheld from the fit must come out as integers afterwards; and an exact stage confirms the result with integer linear algebra. A second stage allows coefficients of the form $a+b\sqrt{15}$.
Examples
Run the self-test. The PSLQ module writes its own test targets in the AMFlow result-file format with known answers, such as $(3+7\pi^2-5\zeta(3))/11$ split across two $\varepsilon$ orders so that the loader's $e^{3\gamma_E\varepsilon}$ normalization is exercised, and must recover them.
python3 tools/pslq_gate.py --selftest
Each of the sixteen checks prints one line (four are shown here; the last two of the run fit a synthetic target with the weight-graded multi-point method) and the run ends with a tally:
PASS refind planted M0[1111]eps0 = [11,-3,-7,5] over [1,pi^2,zeta3] (gamma normalization exercised across two orders) PASS capacity() rejects impossible scan (30d leg, height 1e8) PASS members_audit finds the height-12 MT internal relation (pi^2-12log2^2+12log2log3-6log3^2-12Li2(1/3)-6Li2(1/4)=0) PASS degenerate basket raises DegeneratePoolError (not silent NULL) SELFTEST PASS (16/16)
The third line is the dependent-list check at work: $\{\pi^2,\ \log^2 2,\ \log 2\log 3,\ \log^2 3,\ \mathrm{Li}_2(\tfrac13),\ \mathrm{Li}_2(\tfrac14)\}$ satisfy a dilogarithm identity, so a search against that list would return a false null, and the fourth line confirms the search refuses instead. The same run verifies the ring15 source files against their recorded checksums.
Identify one constant. From Python, run inside tools/, with pool a dictionary of decimal strings and target_str the number to identify:
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)
vec is the tuple $(c_0,c_1,c_2)$ with $c_0\,\text{target}+c_1+c_2\pi^2=0$, common factor removed and first nonzero entry positive, or None if no relation below maxcoeff holds at both precisions. Too few digits raises DigitsError, an over-ambitious maxcoeff raises CapacityError, a dependent pool raises DegeneratePoolError. Call members_audit(pool, names) first and reverify(...) on any hit. The command-line form reads the target from a file and searches subsets of the pool:
pslq_gate.py --target out.json:'M0[1111]eps0' \
--pool pool.json --protocol default
out.json is an AMFlow result file, whose $\varepsilon$-coefficients are loaded under names M<i>[<propagator indices>]eps<n>, or a flat JSON dictionary of value strings; pool.json is {"members": {name: value_string}, "weights": {name: int}} with the weights optional. The command audits the pool, runs the synthetic control, and tries single constants, pairs and triples of total weight at most --max-weight. It prints one JSON object whose status is HIT (with members, vector and the 210-digit residual), NULL, CONTROLS_FAILED or DEGENERATE_POOL; the exit codes are 0, 1, 2 and 3.
Fit a sampled function against a basis. The mplll command line, run from the repository root through its flat entry point (python3 tools/mplll_cli.py; python3 -m lockpick.mplll_cli or python -m lockpick.mplll from inside tools/ take the same flags):
python3 tools/mplll.py --basis B --target T --dps D --height H
--method {qrbkz|rawlll|rawbkz|graded|cvp} [--backend ...] [--weights FILE.json]
[--held-out ...] [--fit-points ...] [--dmin-sweep] [--no-controls]
B is a JSON file {"names": [...], "values": {name: [v_1, ..., v_N]}} with each basis function's values at the $N$ sample points as decimal strings. T is a JSON list of the target's $N$ values (complex values written (re+imj) are split and fitted jointly). --dps (default 100) is the number of digits the data support; --height (default $10^8$) is the weight $W$ on the coefficient block of the rawlll lattice and the largest accepted coefficient for graded and batch fits. --backend forces one reduction backend (fpylll, fplll-cli, nemo or pure), --no-controls skips the two control fits, and --dmin-sweep runs the precision sweep. --held-out and --fit-points take comma-separated point indices; by default the last two points are withheld. --weights gives each basis function's integer weight for graded; cvp also takes --denom D and optionally --ls-coeffs FILE.json; a target file {"targets": {label: [...]}} switches to batch mode. The command prints a JSON record with status, relation, c0 and coeffs (so that $c_0I=\sum_i\texttt{coeffs}[F_i]\,F_i$), heldout_min_d, heldout_per_pt, insample_min_d, two_prec_stable, capacity, span_residual_d, the backend used and its run time, and exits 0 on HIT (batch and sweep runs always exit 0).
Routines
Command-line programs
tools/mplll.pyortools/mplll_cli.py(orpython3 -m lockpick.mplll_cli/python -m lockpick.mplllfrom insidetools/) — the multi-point fitter of Example 3; the package module itself uses relative imports, so run it with-mrather than by its file path.tools/lockpick/mplll_points.py --from-json FILE --n-select N [--dps D] [--out FILE]— point selector;FILEis{"candidates": [x...], "basis_values": {name: [B_i(x_j)...]}}; prints the chosen indices and points, $\log_{10}$ of the design determinant, the gain per step and the condition number of the chosen fit matrix.tools/pslq_gate.py(--target FILE:key --pool pool.json [--max-weight W], or--selftest) — the single-number search of Examples 1 and 2; Python code imports the same module as in Example 2 or aslockpick.pslq_gate.python3 -m lockpick.ring15.ring15(from insidetools/) — rebuilds the conductor-15 table at 210 and 120 digits, writingRING15_partial.jsonafter each block andRING15.jsonat the end, in the directory named by the environment variableRING15_OUT(default: the current directory).tools/lockpick/sealrun/t6_close.py --target-string-file FILE [--label NAME] [--out DIR](or--target-json FILE:key.path) — the fixed-protocol search of one constant against the frozen listBASKET_T6.json; a complex value must be split into real and imaginary parts, one run each; writesCLOSE_<label>_<stamp>.jsonunder--out(default: the current directory); exit codes 0 relation found and confirmed, 1 none up to the printed bound, 2 controls failed (no result), 3 dependent list (a null would be void), 4 input or digit-count error.tools/lockpick/sealrun/t6_basket.py— rebuildsBASKET_T6.json(each constant computed two ways, the dependence audit and both controls recorded inside the file) from reference inputs in the directory named bySEALRUN_R1; those inputs are not part of the repository, and the frozen file is used as is.tools/lockpick/bilmine/— the bilinear-relation search as a chain of drivers,seal_prereg.py,run_control.py,run_exact.py,run_mine.py,run_global.py,emit_verdict.py,close_leg.py(plusremine_points.py), withf1/holding the $a+b\sqrt{15}$ counterparts andresume_mine_f1.py; inputs are named byBILMINE_*environment variables (BILMINE_LEG,BILMINE_T5,BILMINE_PFRAME,BILMINE_C1_PI,BILMINE_C1_N,BILMINE_F1_LEG,BILMINE_PARENT;BILMINE_PRODUCER_LINToptional), no default paths are provided, and a driver stops with a message when one is unset.bilmine_lib.pyandf1/bilmine_f1_lib.pyhold the shared routines;selftest.pyis the synthetic self-test.
Single-number relations (the module of Examples 1 and 2)
two_prec_stable(target_str, names, members, dps_pair=(120, 200), maxcoeff=10**6, maxsteps=120000)— the checked two-precision PSLQ search; canonical tuple orNone.members_audit(members, names=None, dps=160, h=1e4)— PSLQ on the candidate list alone;[]if independent up to coefficienth, else the internal relations found. Run before every search.canonicalize(vec)— divide out the gcd, make the first nonzero entry positive.capacity(dps, n_members, height, margin=20)— digits needed against digits available;{'required', 'available', 'ok'}.check_digits(target_str, dps_needed),ndigits(s)— digit-count guard.controls(members, proto=DEFAULT, known_hits=())— the synthetic control plus any known relations you pass, in the same pool and settings; warns when a control's coefficients sit within a factor 10 ofmaxcoeff.reverify(hit, members, dps=210, tol='1e-150')— relative residual of{'target', 'members', 'vector'}at high precision.graded_basket(weights, max_weight, sizes=(1, 2, 3))— candidate subsets by total weight.odd_zeta_pool(max_weight, dps=None)— ready-made pool of products of odd zeta values with their weights (from weight 11 these products no longer span the single-valued multiple zeta values).load_amflow_targets(fn, gamma_norm=True),load_target("FILE:key")— read targets from an AMFlow result file or a flat JSON dictionary.DigitsError,CapacityError,DegeneratePoolError— the three exceptions raised instead of a silent null; the last carries.relationand.members.DEFAULTholds the protocol settings;selftest()is Example 1.
Multi-point fits
mplll.mplll_fit(I, F, names, fit, ho, d, W=10**8, hmax=10**30, two_prec=True, backend=None, method="qrbkz", beta=None)— one fit;Ithe $N$ target values,Fa list of $K$ such lists,fit/hoindex lists;methodalso"rawlll"or"rawbkz"; returns the record of Example 3. Safe at any ambientmp.dps.mplll.controls(I, F, names, fit, ho, d, W, backend=None, method="qrbkz")— the synthetic and random-target checks.mplll.heldout_digits(rel, I, F, idx, d),mplll.capacity(rel, K, N_fit, d)— agreeing digits of a relation over pointsidx; digits a relation of this size needs.mplll_batch.qrbkz_batch(I_dict, F, names, fit, ho, d, hmax=10**30, backend=None)— many targets, one basis factorization; one record per label plus_meta.mplll_batch.cvp_fit(I, F, names, fit, ho, d, denom, c_LS=None)— nearest-lattice-point rounding with denominatordenom.mplll_batch.dmin_sweep(I, F, names, fit, ho, dps, W=10**4, method="qrbkz")— smallest precision reproducing the full-precision relation;{d_min, reference, d_tested}.mplll_graded.graded_fit(I, F, names, weights, fit, ho, d, hmax=10**30, backend=None)— weight-by-weight fit;per_weightrecords{K_w, locked, insample_d, floor_d}andall_locked. Withweights=Noneit guesses from the names viainfer_weight(name)and warns.mplll_lattice.reduce_rows(rows, backend=None, bkz_beta=0),reduce_rows_batch(mats, ...)— LLL or BKZ of integer row matrices through fpylll, thefplllexecutable, Nemo (Julia) or pure Python, in that order; return reduced rows, backend name, seconds.mplll_points.select_points(basis_fn, candidate_xs, K, n_select, dps=60),select_from_matrix(B_cols, n_select, dps=60),cond_E(B_cols, dps=60)— the point selector from Python; condition number of a fit matrix.
Conductor-15 constants (lockpick.ring15)
ring15_core.dirichlet_L(s, D),dirichlet_Lprime0(D)— $L(s,\chi_D)$ and $L'(0,\chi_D)$ for $D\in\{-15,-3,-4,5\}$; the characters arechi15,chi3,chi4,chi5.ring15_core.gamma_quotient(D),Omega(D)— the product $\prod_a\Gamma(a/|D|)^{\chi_D(a)}$ and the Chowla–Selberg period built from it.ring15_core.eta(tau),cm_period_eta(a, b, c)— the Dedekind eta function, and $\sqrt{\operatorname{Im}\tau}\,|\eta(\tau)|^2$ at the CM point of the quadratic form $[a,b,c]$.ring15_core.lerch_residual(D),disc4_controls()— the built-in consistency checks, returned as residuals that should vanish to working precision (the controls dictionary also carries the lemniscate constantvarpi_agmthey use).ring15_hecke.a_p(p, s),a_n_list(N, s)— exact coefficients of the newform with signs = ±1.ring15_hecke.eta_products(N),run_validation(NCMP=60)— the two eta-product expansions, and the exact comparison of the coefficients against them and against PARI/GP.ring15_lfun.L_values(NL=1400)— $L(f,s)$ for both forms at $s=1,2,3$ with split-point invariance and Fricke data; helpersLambda,fit_fricke,F_val,direct_sum_check.ring15.run(dps),ring15.main()— the orchestrator behind the rebuild command.
Used on this site
- Three-loop light-by-light — the lattice search showing that no combination of the candidate constants reproduces the crossed-box finite part.
- Non-planar hexa-box — the integer-relation search over the top-sector boundary constants, which found no relation, so those constants are reported numerically.
Requirements and source
Python 3 with mpmath; pytest for the regression tests. BKZ needs the fpylll module or the fplll executable (FPLLL overrides its path; MPLLL_FPLLL_TIMEOUT sets an optional time limit in seconds for one reduction, unset or 0 meaning none, and a reduction it ends raises MplllTimeoutExpired). Without them the fitter falls back to LLL through Julia with the Nemo and JSON packages (JULIA names the executable), then to a pure-Python routine imported from tools/gatekeeper/, which must sit beside this directory. ring15 needs only mpmath; bilmine also needs sympy and python-flint for its exact stage. Self-tests: the command of Example 1 (run from the repository root), and python3 -m pytest tools/lockpick/tests (the same relation whatever the caller's ambient precision, the rawbkz command line, the time-limit variable, and the synthetic control on a dependent basis). python3 tools/lockpick/bilmine/selftest.py recovers a synthetic bilinear relation exactly at two precisions, by lattice reduction and by PSLQ, finds nothing in a target built to have no relation, and checks the $\sqrt{15}$ detector, all in seconds. The sealrun pair takes minutes and writes its CLOSE_*.json records into the current directory unless --out DIR is given: python3 tools/lockpick/sealrun/t6_close.py --target-string-file tools/lockpick/sealrun/selftest_planted_target.txt --label planted must exit 0, and the same with selftest_null_target.txt --label null must exit 1. The code is tools/lockpick/ in BootLoops' bootloops-dev repository (GitHub organization BootLoops-ai), released under the MIT license, with one flat shim per module under tools/ (for example tools/mplll.py) keeping the old import names; sealrun/ and bilmine/ sit inside it and are run as scripts, not imported. Credits: PSLQ by Ferguson and Bailey as implemented in mpmath; LLL and BKZ by fplll and Nemo; the rawlll construction follows arXiv:2507.17815.