You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add the first family adapter on top of the core runner (#76): a thue-mahler route that calls the Gherga–Siksek Magma implementation at a pinned immutable commit, parses its output into a SolutionSet whose completeness, scope and assumptions state exactly what that run establishes, and falls back to today's PARI box search on any failure. Opt-in only (solve(..., use_magma=True) / --use-magma); with the switch off, nothing about the thue-mahler solver changes.
Everything in this section was read directly off the package at the pinned
commit on 2026-08-09 (repository tree and file contents fetched from api.github.com / raw.githubusercontent.com); nothing here is transcribed
from memory, and there is nothing left to look up at implementation time.
solveThueMahler(alist, a, primelist : verb, coprime)
return type
SetEnum of sequences [X, Y, z_1, ..., z_v]
exponents returned?
yes, one per prime, in primelist order
solutions returned
primitive only: gcd(X, Y) = 1
license file
none at the pinned commit (see Provenance)
The authors' own pointer to this directory is in the paper
(GhergaSiksek2022, arXiv:2207.14492, published as Algebra & Number Theory 19 (2025), no. 4, 667–714): "We have implemented the algorithm described
in this paper in the computer algebra system Magma; our implementation is
available from https://github.com/adelagherga/ThueMahler/tree/master/Code/TMSolver".
There is no spec file: the package is loaded, not AttachSpeced
The recursive tree at the pinned commit contains no *.spec file for Code/TMSolver. The only file named like one is Code/Old/spec.m, which
belongs to the superseded Code/Old/ tree and whose entire content is an
absolute path on the author's own machine pointing at a file that does not
exist in the repository:
So AttachSpec is not the entry route, and the previous draft of this issue
(which called AttachSpec(<<pkg>>)) could never have worked. The package's own Code/TMSolver/README.txt documents the real usage verbatim:
// Below is an example of how to use the Thue--Mahler solver,
// for instance, to solve
// 3 X^3 + 2 X^2 Y + 7 X Y^2 + 2 Y^3 = 2^{z_1} 3^{z_2} 7^{z_3} 41^{z_4}
// under the assumptions that gcd(X,Y) = gcd(a_0,Y) = 1:
load "solveThueMahler.m";
alist:=[3,2,7,2];
a:=1;
primelist:=[2,3,7,41];
time sols:=solveThueMahler(alist,a,primelist);
sols;
and solveThueMahler.m itself begins with relative loads of its seven
siblings:
The process's working directory must be the package directory, because
those ./ loads resolve against it. The script therefore begins with ChangeDirectory(<pkgdir>); — ChangeDirectory(s) : MonStgElt ->, "Change
to the directory specified by the string s. Tilde expansion is allowed.",
Magma V2.29 Handbook, chapter Input and Output, section System Calls, https://magma.maths.usyd.edu.au/magma/handbook/text/42 (fetched
2026-08-09). Doing it in the script keeps Core opt-in Magma process runner #76's runner API unchanged: the
runner still just writes one script and executes it.
load is a top-level directive and cannot go inside try/catch. The
handbook lists load "filename"; as a "Special top level directive"
(chapter Input and Output, section Loading Program Files, https://magma.maths.usyd.edu.au/magma/handbook/text/38), and Load(F)
"may only be used in a simple statement at the top level". The ChangeDirectory and load lines therefore sit before the try block.
If the directory or the file is wrong, the script dies before emitting
anything between the sentinels; Core opt-in Magma process runner #76's runner sees no payload and the
standard fallback runs. The adapter additionally checks in Python that the
entry file exists before starting a process, so the ordinary declined path
never relies on that crash.
Exact callable contract
Quoted verbatim from Code/TMSolver/solveThueMahler.m at the pinned commit:
solveThueMahler:=function(alist,a,primelist : verb:=false,coprime:=true)
/*
Solves a_0 X^d + ... + a_d Y^d = a p_1^{z_1} ... p_v^{z_v}
subject to the assumptions that X, Y are integers and
gcd(X,Y) = 1, with a_0, Y optionally coprime.
Parameters
alist: SeqEnum
A list of coefficients a_0, a_1,...,a_d.
a: RngIntElt
primelist: SeqEnum
A list of rational primes p_1, p_2,...,p_v.
verb: BoolElt
A true/false value. If set to true, this function returns status
updates as it proceeds.
coprime: BoolElt
A true/false value. If set to true, this function returns all
solutions of the Thue--Mahler form under the added assumption that
gcd(a_0,Y) = 1.
Returns
sols: SetEnum
A list of solutions [X,Y,z_1,...,z_v] to the Thue-Mahler
equation.
*/
Reading that off precisely:
Argument order and types.alist is a SeqEnum of the integer
coefficients a_0, a_1, ..., a_d of F(X, Y) = a_0 X^d + a_1 X^{d-1} Y + ... + a_d Y^d, i.e. descending in X — the same convention Core opt-in Magma process runner #76's encode_int_list will emit from the match data. a is a single RngIntElt. primelist is a SeqEnum of rational primes.
Return type. A SetEnum whose elements are sequences of length 2 + v: [X, Y, z_1, ..., z_v]. Exponents are returned, and z_i
belongs to primelist[i] — the pairing is positional, so the order in which
this adapter encodes primelist is load-bearing (pinned below).
coprime must be passed as false. With its default true the routine
imposes the extra hypothesis gcd(a_0, Y) = 1 and therefore returns only
a subset of the primitive solutions when |a_0| > 1. With coprime := false the routine runs makeMonic/recoverXY and returns the primitive
solutions of the original form. This adapter always passes coprime := false, so its scope statement is correct for every accepted input. (For the
catalogue fixture a_0 = 1, so the two settings agree; the adapter does not
special-case that.)
Primitivity is enforced inside the package.recoverXY rejects any
candidate with GCD(sol[1], sol[2]) ne 1, so the returned set contains
primitive pairs only.
Sign handling is the package's, not ours.recoverXY returns {sol} or {[-X, -Y]} for odd d (whichever makes the right-hand side positive) and
both {sol, [-X, -Y]} for even d. The adapter therefore must not assume
that returned pairs come in ± couples, and must not add or remove any.
Documented hypotheses (the completeness contract)
The paper states the problem it solves as F(X,Y) = a · p_1^{z_1} ⋯ p_v^{z_v}
with X, Y ∈ Z, gcd(X,Y) = 1, F "an irreducible binary form of degree at
least 3 with integer coefficients", a "a non-zero integer", p_1, …, p_v
rational primes, and the condition p_i ∤ a; the working form of the equation
adds gcd(a_0, Y) = 1, "where a₀ is the leading coefficient of F"
(arXiv:2207.14492, Introduction, fetched 2026-08-09).
The package enforces exactly these hypotheses as asserts in Code/TMSolver/equationsInK.m (verbatim, at the pinned commit):
assert &and[IsPrime(p) : p in primelist];
assert &and[Valuation(a,p) eq 0 : p in primelist];
assert &and[a_i in Integers() : a_i in alist];
a0:=Integers()!alist[1];
assert a0 ne 0;
d:=#alist-1;
assert d ge 3;
// [four lines omitted: they build QUV, Qx, the homogeneous form F, and
// f := a0^(d-1)*Evaluate(F,[x/a0,1]), then assert IsHomogeneous(F)]
assert IsMonic(f);
assert Degree(f) eq d;
assert IsIrreducible(f);
A violated assert is a Magma runtime error, i.e. status: "error" and the
fallback — but this adapter guards on all of them in Python first, so the
guarded cases decline without starting a process. Note in particular Valuation(a, p) eq 0: no listed prime may divide m. That is a new
guard relative to the previous draft of this issue.
Supported Magma versions, provenance, license
The package states no minimum Magma version anywhere — not in README.md, not in Code/TMSolver/README.txt, not in any source header.
The only version-specific remark in the whole Code/TMSolver tree is a
comment in reducedBound.m: // ClosestVectors is error-prone in Magma V2.26-10. (the code uses CloseVectorsProcess instead). That records a
historical workaround, not a supported floor.
Provenance/license. At the pinned commit the repository has no license
file and the GitHub API reports no license. The package is therefore located, never vendored, never fetched, never redistributed by this
repository — consistent with the existing no-web-dependencies rule. The
adapter reads a user-supplied checkout; nothing is downloaded, and no
package source is copied into this repository. The pinned commit is
recorded so that a certificate identifies which revision produced a result.
The package writes progress output unconditionally
coprimeThueMahler in solveThueMahler.m calls printf on every run —
banner lines, the equation being solved, the number of S-unit equations, the
ranks, per-equation progress and time lines — regardless of verb. It also
executes SetAutoColumns(false); SetColumns(235); at load time. Two binding
consequences for #76's protocol:
the payload extractor must locate the sentinels inside an arbitrarily long
stream of preceding text, and must not assume the emitted object is the
first or only thing on stdout;
the runner's oversized-output guard must be sized for this adapter (or
applied to the extracted payload rather than to raw stdout); a large but
successful run must not be discarded as oversized. A test with a fake runner
that emits several kilobytes of banner noise before the sentinels pins this.
Supported inputs
The adapter is registered as MAGMA_SOLVERS["thue-mahler"] with MAGMA_CAPABILITIES["thue-mahler"] = SolverCapability(slug="thue-mahler", domains=frozenset(("ZZ", "NN")), goals=frozenset(("enumerate", "exists", "find-one")), kinds=frozenset(("finite-complete", "empty")), completeness=frozenset(("conditional",)), guards=(see "Guards" below)).
CAPABILITIES["thue-mahler"] and its doctest (completeness == frozenset({'partial'})) are unchanged: they describe the PARI solver,
which keeps its own contract.
Input regime
An irreducible binary form of degree at least 3 with concrete integer
coefficients, a concrete nonzero integer m divisible by none of the listed
primes, and a concrete list of distinct rational primes. The matcher already
supplies exactly this. Verified today (re-run against Sage 10.7 on 85f8e0a)
for the catalogue row x^3 + 2*y^3 = 5^a * 11^b:
data["primes"][i] is paired with data["exponents"][i] — here 11 ↔ b
and 5 ↔ a;
the adapter encodes primelist in exactly the order of data["primes"],
so the package's returned z_i is the exponent of data["primes"][i] and
binds to data["exponents"][i]. For the fixture that means primelist = [11, 5], z_1 ↔ b, z_2 ↔ a. No sorting, ever;
the solution tuple is ordered by first appearance of the unknowns in the
input, ('x', 'y', 'a', 'b'), and is built with _ordered(cls, assignment)
— never by the order the primes happen to have in the match data.
alist for the fixture is [1, 0, 0, 2]: F(X, Y) = 1·X^3 + 0·X^2 Y + 0·X Y^2 + 2·Y^3 = x^3 + 2 y^3 (verified). Degree-d forms give a (d + 1)-entry alist; missing monomials are explicit zeros.
Guards (each one declines: the adapter raises MagmaFailure and the PARI solver runs)
any form coefficient, or m, not a concrete integer (parametric input);
m == 0;
degree < 3;
the binary form not irreducible over QQ (the reducible case is a
different problem and the external solver's hypotheses do not cover it);
any listed prime failing encode_prime_list (not a rational prime, or
repeated);
any listed prime dividing m — the package asserts Valuation(a, p) eq 0 for every p in primelist and the paper hypothesizes p_i ∤ a; this is
the new guard, and it declines rather than relying on a Magma assertion
failure;
the Gherga–Siksek package not located (below).
Locating the external package
IntegralQuarticPoints and RationalPointsGenus2 are Magma intrinsics, but
the Thue–Mahler solver is a third-party Magma package (Gherga–Siksek,
cited in the registry as software.magma: "Gherga-Siksek ThueMahler (GitHub)") that must be present on the machine. Following the same rule the
runner uses for the executable: a second environment variable, DIOPHANTINE_CLASSIFIER_THUEMAHLER, may only locate the checked-out
repository root. It never activates anything — with use_magma=False it is
not read at all.
Concretely, with root the value of that variable:
the package directory is os.path.join(root, "Code", "TMSolver");
the entry file that must exist is os.path.join(root, "Code", "TMSolver", "solveThueMahler.m");
the adapter checks that file with os.path.isfilebefore starting a
process; if the variable is unset, or the file is absent, the adapter
declines and today's PARI result is returned with the reason recorded
("package-not-found").
The variable and its "locates, never activates" role are documented in docs/SEMANTICS.md next to DIOPHANTINE_CLASSIFIER_MAGMA, together with the
pinned commit above and the note that this repository never downloads it.
Output and API contract
Script template
diophantine_classifier/data/magma/thue_mahler.m, assembled by
on top of prelude.m. Placeholders are <<name>> and are filled only with
encoder output; encode_magma_path renders a Magma string literal and rejects
quotes, backslashes and newlines.
// thue_mahler.m (prelude.m is prepended)
// Gherga-Siksek ThueMahler, github.com/adelagherga/ThueMahler
// pinned commit 164a4eb9f53c4ccc565379594251c0503952a70b
// `load` is a top-level directive: it cannot appear inside try/catch.
ChangeDirectory(<<pkgdir>>);
load "solveThueMahler.m";
alist := <<coeffs>>;
a := <<m>>;
primes := <<primes>>;
try
sols := solveThueMahler(alist, a, primes : verb := false,
coprime := false);
entries := [ JSONList([JSONInt(s[i]) : i in [1..#s]]) : s in sols ];
DCEmit("ok", "", JSONObj(["solutions"], [JSONList(entries)]));
catch e
DCEmit("error", "solver-error", JSONObj([], []));
end try;
quit;
For the catalogue fixture this renders literally and completely as — taking /opt/ThueMahler as the located checkout, i.e. the value of DIOPHANTINE_CLASSIFIER_THUEMAHLER; only that one path varies between
machines, and it is the only part of the script the snapshot test substitutes:
ChangeDirectory("/opt/ThueMahler/Code/TMSolver");
load "solveThueMahler.m";
alist := [1, 0, 0, 2];
a := 1;
primes := [11, 5];
try
sols := solveThueMahler(alist, a, primes : verb := false,
coprime := false);
entries := [ JSONList([JSONInt(s[i]) : i in [1..#s]]) : s in sols ];
DCEmit("ok", "", JSONObj(["solutions"], [JSONList(entries)]));
catch e
DCEmit("error", "solver-error", JSONObj([], []));
end try;
quit;
verb and coprime are passed explicitly even though verb := false is the
package's default, so that the generated script records the exact call and a
future change to the package's defaults cannot silently change what this
repository reports. The try/catch wrapper is mandatory for the call
itself: a violated assert inside the package, or any other runtime error,
becomes status: "error" and the standard fallback — never a crash and never
a partial parse.
Payload schema (data)
field
type
meaning
solutions
array of (2 + v)-element arrays of decimal strings
the sequences [X, Y, z_1, ..., z_v] the package returned, with z_i the exponent of primes[i] in the order the script sent them
That is the whole payload. Integers are decimal strings; no bare JSON numbers
(#76 §2 rejects them).
The previous draft's primitive and solver_complete fields are removed. solveThueMahler returns neither: it exposes no completeness flag of its own,
and primitivity is a property of the pinned package contract (recorded in
Python, in scope and Scope.primitive), not a value the run reports. As in #79, the payload carries only what the external routine actually returns.
Parsing into a SolutionSet
Reject any entry whose length is not 2 + len(data["primes"]); that is a
malformed payload, not a solution.
Decode every entry with decode_int: X, Y, and the exponents z_1, ..., z_v.
Recompute the exponents independently in Python and require exact
agreement. Compute F(X, Y) / m in QQ; require it to be a positive
integer whose prime factorization is supported on the listed primes; take z'_i = the valuation at primes[i]. If the quotient is not such an
integer, or if z' != z, the payload is inconsistent with the equation:
raise MagmaFailure(..., outcome="cas-failure") and fall back. The
package's exponents are used as the reported values only after they have
been re-derived here; the classifier never forwards an external number it
has not itself checked.
Require gcd(X, Y) == 1 for every returned pair, matching the package's
own recoverXY check. A non-primitive pair is a payload inconsistency,
handled exactly as in step 3.
Build the assignment (the form's two unknowns x, y from the parsed
equation, plus data["exponents"][i] ↦ z_i) and order it with _ordered(cls, assignment).
sorted(set(...)), as the PARI solver already does.
kind = "finite-complete" when nonempty, "empty" otherwise.
Leave representation to _derived_representation: for a finite-complete result that is not proved, that is PartialSearch(solutions, scope). Do not override it with a FiniteSet, which would assert unconditional completeness.
scope_info = Scope(domain_scope="integer solutions", primitive="gcd(x, y) = 1", ordering_convention="unknowns in order of first appearance in the input equation").
Guarantee, scope and assumptions
completeness="conditional"assumptions=("the external Gherga-Siksek Magma implementation computed ""correctly",)
conditional requires nonempty assumptions (docs/SEMANTICS.md §3), and
this is the honest report for an uncertified external run: the classifier
re-checks each returned point against the equation, but it cannot re-derive
that nothing was missed.
Scope, stated precisely. The pinned package resolves the equation for coprime(X, Y) — that is the hypothesis in its own docstring, in the
paper, and in the recoverXY gcd check. The qualifier is not decoration:
without it the claim would be false, because the full integer solution set can
be infinite while the primitive one is finite. For the catalogue equation, (x, y, a, b) = (-5^k, 5^k, 3k, 0) is a solution for every k >= 0 — (-5^k)^3 + 2·(5^k)^3 = 5^{3k} (verified for k = 0, 1, 2, 3) — and each has gcd = 5^k, so exactly one of them (k = 0) is primitive. Today's PARI box
search returns 40 solutions for that equation, of which 7 are coprime
(re-verified today).
Therefore:
scope=("all integer solutions with gcd(x, y) = 1 and right-hand side ""supported on the prime set {5, 11}") # primes filled from the match
and the description contains exactly this sentence about the rest:
This adapter enumerates only the primitive solutions covered by the external
package. Imprimitive solutions are outside this SolutionSet scope.
Do not restore the previous, false characterization that every imprimitive
solution is a scaling of a primitive one by primes of the listed set. For a
homogeneous form F(g·X, g·Y) = g^degree · F(X, Y), an imprimitive solution
with gcd(X, Y) = g reduces to a primitive pair solving a different
equation, and g may involve primes dividing the fixed multiplier m.
Concrete counterexample, verified: for F(X, Y) = X^3 + 2 Y^3, m = 24 and
prime set {5}, the pair (2, 2) satisfies F(2, 2) = 24 = 24·5^0 and has gcd = 2, while (1, 1) — the primitive pair it scales from — has F(1, 1) = 3, which is not 24·5^z for any z; and 2 divides m, not the
prime set. A more detailed scaling characterization may be added later only if
it includes the support of m and exact exponent constraints.
Evidence
result.as_evidence(
"Thue-Mahler resolution via the Gherga-Siksek Magma implementation ""(github.com/adelagherga/ThueMahler @ 164a4eb9f53c4ccc565379594251c0503952a70b, ""solveThueMahler(alist, a, primelist : coprime := false))",
command="thue_mahler([1, 0, 0, 2], 1, [11, 5])",
reference=("GhergaSiksek2022",))
plus a second Evidence(..., kind="theorem", reference=("GhergaSiksek2022",))
recording which algorithm was run. The evidence names the pinned commit, so a
certificate identifies the exact revision that produced the result. GhergaSiksek2022 already resolves in data/references.bib and is already
cited by the thue-mahler family (why: "the modern efficient implementation
(Magma code)"), so no bibliography change is required. Its entry is currently @unpublished{... note = {Preprint}, year = {2022}, eprint = {2207.14492}}
(verified today); it may optionally be refreshed to the published version — Algebra & Number Theory19 (2025), no. 4, 667–714 — under the standard
rules (author/title/journal/volume/year, nonempty why, doi verified at
commit time, url only for legally free copies, make references passing).
That refresh is not required by this issue.
Failure and fallback semantics
Any of: package not located (DIOPHANTINE_CLASSIFIER_THUEMAHLER unset, or Code/TMSolver/solveThueMahler.m absent under it), guard declined, timeout,
nonzero exit, status != "ok", unparseable payload, wrong entry length,
exponent disagreement, a non-primitive returned pair — falls through to the
existing SOLVERS["thue-mahler"] (the PARI per-exponent box search), whose
result is returned unchanged except for "; the Magma route was attempted and did not produce a result (<outcome>): <reason>" appended to its description (#76). That fallback result keeps
its own completeness="partial" and scope "solutions with all prime exponents in [0, 6]"; the box search stays the unconditional partial answer.
A point that survives exponent recovery but still fails ParsedEquation.accepts is an internal-consistency error by the repository's
contract: solvers._verified raises InternalConsistencyError, and the
adapter must not pre-filter to hide it.
Acceptance criteria
Fixture (deterministic, fake runner, no Magma)
Equation: the existing catalogue row x^3 + 2*y^3 = 5^a * 11^b
(tests/test_classify.py, tests/test_solver_contract.py), so alist = [1, 0, 0, 2], m = 1, primes = [11, 5].
Fake payload data["solutions"] = the seven primitive solutions in the
package's own [X, Y, z_1, z_2] shape, with z_1 the exponent of 11 and z_2 the exponent of 5 (every line re-verified today):
(These are genuine primitive solutions — each has gcd(X, Y) = 1 and each
quotient is exactly 1 after the two valuations are divided out. The fixture
asserts the adapter's parsing, exponent cross-check, ordering and
verification — it does not assert that this list is the complete primitive
solution set.)
Assertions on solve("x^3 + 2*y^3 = 5^a * 11^b", use_magma=True) with the
fake runner:
result.variables == ('x', 'y', 'a', 'b');
result.solutions == [(-1, 1, 0, 0), (1, 0, 0, 0), (1, 3, 1, 1), (3, -2, 0, 1), (3, -1, 2, 0), (19, 2, 4, 1), (29, -23, 1, 1)] — the
payload exponents re-derived and re-ordered, sorted; in particular (3, -2) gives a = 0, b = 1 because 3^3 + 2·(-2)^3 = 11 = 5^0 · 11^1, which pins the primes ↔ exponents ↔ z_i pairing;
result.kind == "finite-complete", result.completeness == "conditional", result.complete is False;
result.assumptions == ("the external Gherga-Siksek Magma implementation computed correctly",);
"gcd(x, y) = 1" in result.scope, and result.scope_info.primitive == "gcd(x, y) = 1";
exactly one Evidence with kind == "external-computation", software == "magma", version equal to the fake payload's version, and "GhergaSiksek2022" among the evidence references (resolvable in bibliography());
result.representation.form == "partial-search";
check_solver_contract(cls, result) (Core opt-in Magma process runner #76) passes, and json.dumps(result.to_dict()) succeeds with schema_version == 3 and
exact tagged integers.
The six required package-contract tests
Generated-script snapshot against the pinned package API. The script
built for the fixture equals the literal rendering above, line for line,
with the located path substituted. Assert specifically: 'ChangeDirectory("' in script, 'load "solveThueMahler.m";' in script, "alist := [1, 0, 0, 2];" in script, "a := 1;" in script, "primes := [11, 5];" in script, "solveThueMahler(alist, a, primes : verb := false," in script, "coprime := false" in script. Regression guards against the superseded
draft: "AttachSpec" not in script, and the script contains no unresolved <<name>> marker after assembly (assert "<<" not in script), which also
rules out the superseded draft's unfilled entry-point and flag markers.
Assert also that load occurs before the try line (it is a top-level
directive) and that primes is not sorted ("[5, 11]" not in script).
Package-path validation. With DIOPHANTINE_CLASSIFIER_THUEMAHLER
pointing at a directory that lacks Code/TMSolver/solveThueMahler.m, the
adapter declines with reason "package-not-found" and starts no process
(assert with a runner stub that raises if invoked); the PARI result is
returned. With it pointing at a directory that has the file, the ChangeDirectory argument in the generated script is exactly that
directory's Code/TMSolver path. A path containing a quote, backslash or
newline is rejected by encode_magma_path and declines.
Payload parsing for the actual return shape. The (2 + v)-element
arrays above parse into the tuples of assertion 2. An entry of length 2 (the superseded [X, Y] shape) is malformed → fallback; an entry of
length 2 + v whose exponents disagree with the recomputed valuations —
e.g. ["3", "-2", "0", "1"], which asserts 11^0 · 5^1 = 5 instead of
the true 11 — is a payload inconsistency → MagmaFailure with outcome == "cas-failure" and fallback, and the bad pair appears in no
returned solution set.
Primitive scope.result.scope contains "gcd(x, y) = 1"; result.scope_info.primitive == "gcd(x, y) = 1"; result.description
contains the exact two-sentence wording pinned above
("This adapter enumerates only the primitive solutions covered by the external package." and "Imprimitive solutions are outside this SolutionSet scope."). Assert also that the description does not
contain the removed claim — no occurrence of "scaling" together with "listed primes", and no assertion that the imprimitive solutions are
obtained from the returned ones. A payload pair with gcd(X, Y) > 1 (for
instance ["-5", "5", "0", "3"], a genuine solution with gcd = 5) is a
payload inconsistency → fallback, never a silently accepted extra tuple.
Failure fallback. Malformed payload → the PARI result is returned,
with 40 solutions, kind witness, completeness partial, and the reason
appended to description; timeout (magma_timeout=1, sleeping fake) →
same PARI result, reason mentions resource-exceeded; status: "error"
payload (standing in for a violated package assert) → same PARI result;
a payload preceded by several kilobytes of the package's own banner and time output still parses correctly and is not discarded as oversized
(this pins the note about unconditional progress output above).
Real-package marked test at the pinned commit. A magma-marked test,
skipped by default, resolves the fixture equation against a real Magma
(V2.29+) and a real checkout of github.com/adelagherga/ThueMahler at commit 164a4eb9f53c4ccc565379594251c0503952a70b. It asserts that the
three-argument call with coprime := false runs, that every returned
entry has length 2 + v, that every returned pair is primitive, that the
package's exponents agree with the independently recomputed valuations,
and that every tuple verifies against the equation. It is skipped when the
environment variable is unset, and is never a substitute for the
deterministic tests above.
Further failure-path tests (same fixture, fake runner)
DIOPHANTINE_CLASSIFIER_THUEMAHLER unset → adapter declines without
starting a process; same PARI result.
New guard: an equation whose multiplier shares a prime with the prime
set — for instance a matched input with m = 5 and prime set containing 5 — declines before starting a process, with the reason naming the p | m guard, and the PARI result is returned. (The package would
otherwise fail its own assert Valuation(a,p) eq 0.)
Verification firewall. Constructing the adapter's SolutionSet
directly with an injected point that satisfies the exponent bookkeeping
but not the equation and passing it through solvers._verified(cls, ...)
raises InternalConsistencyError (the equation carries no nonvanishing
conditions, so filtering is not permitted).
Unconditional and repository-wide
With use_magma=False (and with DIOPHANTINE_CLASSIFIER_MAGMA pointing
at the fake), solve("x^3 + 2*y^3 = 5^a * 11^b") is identical to today's
result in solutions, kind, completeness, scope and description.
This runs unconditionally.
make test, make doctest, make coverage (100%) and make registry-docs clean without Magma installed; every new
function has a Sage-convention docstring whose EXAMPLES pass sage -t
without Magma.
Out of scope
Reconstructing the imprimitive solutions into a full solution set for the
equation as typed. The scope string, Scope.primitive and the description record the restriction; enumerating anything outside the
primitive set is not part of this issue.
Calling the package with its default coprime := true (the narrower gcd(a_0, Y) = 1 regime), or exposing verb, as user-facing options.
Any change to the PARI box search, its bound B = 6, or its capability
declaration.
Vendoring, downloading, installing or redistributing the Gherga–Siksek
package (no web dependencies; the package is located, never fetched — and
it carries no license file at the pinned commit).
Using anything in the repository outside Code/TMSolver/ — in particular Code/Old/, Code/TdWCode/ and the elliptic-curve drivers, which are not
this adapter's entry point.
Thue–Mahler equations over number fields, and forms that are reducible or
of degree below 3.
Goal
Add the first family adapter on top of the core runner (#76): a
thue-mahlerroute that calls the Gherga–Siksek Magma implementation at a pinned immutable commit, parses its output into aSolutionSetwhosecompleteness,scopeandassumptionsstate exactly what that run establishes, and falls back to today's PARI box search on any failure. Opt-in only (solve(..., use_magma=True)/--use-magma); with the switch off, nothing about thethue-mahlersolver changes.Target base
review-architecture@85f8e0a(the composedtree on the
roed-mathfork: wave-1 code = PRs Packaging, CI, design notes, and the annotated bibliography #2–Family: waring — Waring-type diagonal representation #48, plus the wave-2 andarchitecture-review series, which are not yet opened as PRs).
must land after them before this issue's work can merge to
main.Implementation happens on the composed tree, not on a split branch.
Dependencies
MAGMA_SOLVERS, the payloadprotocol)
Pinned external package
Everything in this section was read directly off the package at the pinned
commit on 2026-08-09 (repository tree and file contents fetched from
api.github.com/raw.githubusercontent.com); nothing here is transcribedfrom memory, and there is nothing left to look up at implementation time.
master164a4eb9f53c4ccc565379594251c0503952a70bUpdating paper examples)Code/TMSolver/Code/TMSolver/solveThueMahler.mload— notAttachSpec(see below)solveThueMahler(alist, a, primelist : verb, coprime)SetEnumof sequences[X, Y, z_1, ..., z_v]primelistordergcd(X, Y) = 1The authors' own pointer to this directory is in the paper
(
GhergaSiksek2022, arXiv:2207.14492, published as Algebra & Number Theory19 (2025), no. 4, 667–714): "We have implemented the algorithm described
in this paper in the computer algebra system Magma; our implementation is
available from https://github.com/adelagherga/ThueMahler/tree/master/Code/TMSolver".
There is no spec file: the package is
loaded, notAttachSpecedThe recursive tree at the pinned commit contains no
*.specfile forCode/TMSolver. The only file named like one isCode/Old/spec.m, whichbelongs to the superseded
Code/Old/tree and whose entire content is anabsolute path on the author's own machine pointing at a file that does not
exist in the repository:
So
AttachSpecis not the entry route, and the previous draft of this issue(which called
AttachSpec(<<pkg>>)) could never have worked. The package's ownCode/TMSolver/README.txtdocuments the real usage verbatim:and
solveThueMahler.mitself begins with relative loads of its sevensiblings:
Two binding consequences.
those
./loads resolve against it. The script therefore begins withChangeDirectory(<pkgdir>);—ChangeDirectory(s) : MonStgElt ->, "Changeto the directory specified by the string s. Tilde expansion is allowed.",
Magma V2.29 Handbook, chapter Input and Output, section System Calls,
https://magma.maths.usyd.edu.au/magma/handbook/text/42 (fetched
2026-08-09). Doing it in the script keeps Core opt-in Magma process runner #76's runner API unchanged: the
runner still just writes one script and executes it.
loadis a top-level directive and cannot go insidetry/catch. Thehandbook lists
load "filename";as a "Special top level directive"(chapter Input and Output, section Loading Program Files,
https://magma.maths.usyd.edu.au/magma/handbook/text/38), and
Load(F)"may only be used in a simple statement at the top level". The
ChangeDirectoryandloadlines therefore sit before thetryblock.If the directory or the file is wrong, the script dies before emitting
anything between the sentinels; Core opt-in Magma process runner #76's runner sees no payload and the
standard fallback runs. The adapter additionally checks in Python that the
entry file exists before starting a process, so the ordinary declined path
never relies on that crash.
Exact callable contract
Quoted verbatim from
Code/TMSolver/solveThueMahler.mat the pinned commit:Reading that off precisely:
alistis aSeqEnumof the integercoefficients
a_0, a_1, ..., a_dofF(X, Y) = a_0 X^d + a_1 X^{d-1} Y + ... + a_d Y^d, i.e. descending inX— the same convention Core opt-in Magma process runner #76'sencode_int_listwill emit from the match data.ais a singleRngIntElt.primelistis aSeqEnumof rational primes.SetEnumwhose elements are sequences of length2 + v:[X, Y, z_1, ..., z_v]. Exponents are returned, andz_ibelongs to
primelist[i]— the pairing is positional, so the order in whichthis adapter encodes
primelistis load-bearing (pinned below).coprimemust be passed asfalse. With its defaulttruethe routineimposes the extra hypothesis
gcd(a_0, Y) = 1and therefore returns onlya subset of the primitive solutions when
|a_0| > 1. Withcoprime := falsethe routine runsmakeMonic/recoverXYand returns the primitivesolutions of the original form. This adapter always passes
coprime := false, so its scope statement is correct for every accepted input. (For thecatalogue fixture
a_0 = 1, so the two settings agree; the adapter does notspecial-case that.)
recoverXYrejects anycandidate with
GCD(sol[1], sol[2]) ne 1, so the returned set containsprimitive pairs only.
recoverXYreturns{sol}or{[-X, -Y]}for oddd(whichever makes the right-hand side positive) andboth
{sol, [-X, -Y]}for evend. The adapter therefore must not assumethat returned pairs come in
±couples, and must not add or remove any.Documented hypotheses (the completeness contract)
The paper states the problem it solves as
F(X,Y) = a · p_1^{z_1} ⋯ p_v^{z_v}with
X, Y ∈ Z,gcd(X,Y) = 1,F"an irreducible binary form of degree atleast 3 with integer coefficients",
a"a non-zero integer",p_1, …, p_vrational primes, and the condition
p_i ∤ a; the working form of the equationadds
gcd(a_0, Y) = 1, "where a₀ is the leading coefficient of F"(arXiv:2207.14492, Introduction, fetched 2026-08-09).
The package enforces exactly these hypotheses as
asserts inCode/TMSolver/equationsInK.m(verbatim, at the pinned commit):A violated
assertis a Magma runtime error, i.e.status: "error"and thefallback — but this adapter guards on all of them in Python first, so the
guarded cases decline without starting a process. Note in particular
Valuation(a, p) eq 0: no listed prime may dividem. That is a newguard relative to the previous draft of this issue.
Supported Magma versions, provenance, license
README.md, not inCode/TMSolver/README.txt, not in any source header.The only version-specific remark in the whole
Code/TMSolvertree is acomment in
reducedBound.m:// ClosestVectors is error-prone in Magma V2.26-10.(the code usesCloseVectorsProcessinstead). That records ahistorical workaround, not a supported floor.
(Core opt-in Magma process runner #76, Integral elliptic-quartic Magma adapter (IntegralQuarticPoints) #78, Genus-2 rational-points Magma adapter (RationalPointsGenus2) #79) rather than inventing a package-specific one, and records
the run's reported Magma version in its
Evidenceso a certificate alwaysnames what actually ran.
file and the GitHub API reports no license. The package is therefore
located, never vendored, never fetched, never redistributed by this
repository — consistent with the existing no-web-dependencies rule. The
adapter reads a user-supplied checkout; nothing is downloaded, and no
package source is copied into this repository. The pinned commit is
recorded so that a certificate identifies which revision produced a result.
The package writes progress output unconditionally
coprimeThueMahlerinsolveThueMahler.mcallsprintfon every run —banner lines, the equation being solved, the number of S-unit equations, the
ranks, per-equation progress and
timelines — regardless ofverb. It alsoexecutes
SetAutoColumns(false); SetColumns(235);at load time. Two bindingconsequences for #76's protocol:
stream of preceding text, and must not assume the emitted object is the
first or only thing on stdout;
applied to the extracted payload rather than to raw stdout); a large but
successful run must not be discarded as oversized. A test with a fake runner
that emits several kilobytes of banner noise before the sentinels pins this.
Supported inputs
The adapter is registered as
MAGMA_SOLVERS["thue-mahler"]withMAGMA_CAPABILITIES["thue-mahler"] = SolverCapability(slug="thue-mahler", domains=frozenset(("ZZ", "NN")), goals=frozenset(("enumerate", "exists", "find-one")), kinds=frozenset(("finite-complete", "empty")), completeness=frozenset(("conditional",)), guards=(see "Guards" below)).CAPABILITIES["thue-mahler"]and its doctest (completeness == frozenset({'partial'})) are unchanged: they describe the PARI solver,which keeps its own contract.
Input regime
An irreducible binary form of degree at least 3 with concrete integer
coefficients, a concrete nonzero integer
mdivisible by none of the listedprimes, and a concrete list of distinct rational primes. The matcher already
supplies exactly this. Verified today (re-run against Sage 10.7 on
85f8e0a)for the catalogue row
x^3 + 2*y^3 = 5^a * 11^b:Note the three orderings and keep them apart:
data["primes"][i]is paired withdata["exponents"][i]— here11 ↔ band
5 ↔ a;primelistin exactly the order ofdata["primes"],so the package's returned
z_iis the exponent ofdata["primes"][i]andbinds to
data["exponents"][i]. For the fixture that meansprimelist = [11, 5],z_1 ↔ b,z_2 ↔ a. No sorting, ever;input,
('x', 'y', 'a', 'b'), and is built with_ordered(cls, assignment)— never by the order the primes happen to have in the match data.
alistfor the fixture is[1, 0, 0, 2]:F(X, Y) = 1·X^3 + 0·X^2 Y + 0·X Y^2 + 2·Y^3 = x^3 + 2 y^3(verified). Degree-dforms give a(d + 1)-entryalist; missing monomials are explicit zeros.Guards (each one declines: the adapter raises
MagmaFailureand the PARI solver runs)m, not a concrete integer (parametric input);m == 0;degree < 3;QQ(the reducible case is adifferent problem and the external solver's hypotheses do not cover it);
encode_prime_list(not a rational prime, orrepeated);
m— the package assertsValuation(a, p) eq 0for everyp in primelistand the paper hypothesizesp_i ∤ a; this isthe new guard, and it declines rather than relying on a Magma assertion
failure;
Locating the external package
IntegralQuarticPointsandRationalPointsGenus2are Magma intrinsics, butthe Thue–Mahler solver is a third-party Magma package (Gherga–Siksek,
cited in the registry as
software.magma: "Gherga-Siksek ThueMahler (GitHub)") that must be present on the machine. Following the same rule therunner uses for the executable: a second environment variable,
DIOPHANTINE_CLASSIFIER_THUEMAHLER, may only locate the checked-outrepository root. It never activates anything — with
use_magma=Falseit isnot read at all.
Concretely, with
rootthe value of that variable:os.path.join(root, "Code", "TMSolver");os.path.join(root, "Code", "TMSolver", "solveThueMahler.m");os.path.isfilebefore starting aprocess; if the variable is unset, or the file is absent, the adapter
declines and today's PARI result is returned with the reason recorded
(
"package-not-found").The variable and its "locates, never activates" role are documented in
docs/SEMANTICS.mdnext toDIOPHANTINE_CLASSIFIER_MAGMA, together with thepinned commit above and the note that this repository never downloads it.
Output and API contract
Script template
diophantine_classifier/data/magma/thue_mahler.m, assembled byon top of
prelude.m. Placeholders are<<name>>and are filled only withencoder output;
encode_magma_pathrenders a Magma string literal and rejectsquotes, backslashes and newlines.
For the catalogue fixture this renders literally and completely as — taking
/opt/ThueMahleras the located checkout, i.e. the value ofDIOPHANTINE_CLASSIFIER_THUEMAHLER; only that one path varies betweenmachines, and it is the only part of the script the snapshot test substitutes:
verbandcoprimeare passed explicitly even thoughverb := falseis thepackage's default, so that the generated script records the exact call and a
future change to the package's defaults cannot silently change what this
repository reports. The
try/catchwrapper is mandatory for the callitself: a violated
assertinside the package, or any other runtime error,becomes
status: "error"and the standard fallback — never a crash and nevera partial parse.
Payload schema (
data)solutions(2 + v)-element arrays of decimal strings[X, Y, z_1, ..., z_v]the package returned, withz_ithe exponent ofprimes[i]in the order the script sent themThat is the whole payload. Integers are decimal strings; no bare JSON numbers
(#76 §2 rejects them).
The previous draft's
primitiveandsolver_completefields are removed.solveThueMahlerreturns neither: it exposes no completeness flag of its own,and primitivity is a property of the pinned package contract (recorded in
Python, in
scopeandScope.primitive), not a value the run reports. As in#79, the payload carries only what the external routine actually returns.
Parsing into a
SolutionSet2 + len(data["primes"]); that is amalformed payload, not a solution.
decode_int:X,Y, and the exponentsz_1, ..., z_v.agreement. Compute
F(X, Y) / minQQ; require it to be a positiveinteger whose prime factorization is supported on the listed primes; take
z'_i =the valuation atprimes[i]. If the quotient is not such aninteger, or if
z' != z, the payload is inconsistent with the equation:raise
MagmaFailure(..., outcome="cas-failure")and fall back. Thepackage's exponents are used as the reported values only after they have
been re-derived here; the classifier never forwards an external number it
has not itself checked.
gcd(X, Y) == 1for every returned pair, matching the package'sown
recoverXYcheck. A non-primitive pair is a payload inconsistency,handled exactly as in step 3.
x,yfrom the parsedequation, plus
data["exponents"][i] ↦ z_i) and order it with_ordered(cls, assignment).sorted(set(...)), as the PARI solver already does.kind = "finite-complete"when nonempty,"empty"otherwise.representationto_derived_representation: for afinite-completeresult that is notproved, that isPartialSearch(solutions, scope). Do not override it with aFiniteSet, which would assert unconditional completeness.scope_info = Scope(domain_scope="integer solutions", primitive="gcd(x, y) = 1", ordering_convention="unknowns in order of first appearance in the input equation").Guarantee, scope and assumptions
conditionalrequires nonemptyassumptions(docs/SEMANTICS.md§3), andthis is the honest report for an uncertified external run: the classifier
re-checks each returned point against the equation, but it cannot re-derive
that nothing was missed.
Scope, stated precisely. The pinned package resolves the equation for
coprime
(X, Y)— that is the hypothesis in its own docstring, in thepaper, and in the
recoverXYgcd check. The qualifier is not decoration:without it the claim would be false, because the full integer solution set can
be infinite while the primitive one is finite. For the catalogue equation,
(x, y, a, b) = (-5^k, 5^k, 3k, 0)is a solution for everyk >= 0—(-5^k)^3 + 2·(5^k)^3 = 5^{3k}(verified fork = 0, 1, 2, 3) — and each hasgcd = 5^k, so exactly one of them (k = 0) is primitive. Today's PARI boxsearch returns 40 solutions for that equation, of which 7 are coprime
(re-verified today).
Therefore:
and the
descriptioncontains exactly this sentence about the rest:Do not restore the previous, false characterization that every imprimitive
solution is a scaling of a primitive one by primes of the listed set. For a
homogeneous form
F(g·X, g·Y) = g^degree · F(X, Y), an imprimitive solutionwith
gcd(X, Y) = greduces to a primitive pair solving a differentequation, and
gmay involve primes dividing the fixed multiplierm.Concrete counterexample, verified: for
F(X, Y) = X^3 + 2 Y^3,m = 24andprime set
{5}, the pair(2, 2)satisfiesF(2, 2) = 24 = 24·5^0and hasgcd = 2, while(1, 1)— the primitive pair it scales from — hasF(1, 1) = 3, which is not24·5^zfor anyz; and2dividesm, not theprime set. A more detailed scaling characterization may be added later only if
it includes the support of
mand exact exponent constraints.Evidence
plus a second
Evidence(..., kind="theorem", reference=("GhergaSiksek2022",))recording which algorithm was run. The evidence names the pinned commit, so a
certificate identifies the exact revision that produced the result.
GhergaSiksek2022already resolves indata/references.biband is alreadycited by the
thue-mahlerfamily (why: "the modern efficient implementation(Magma code)"), so no bibliography change is required. Its entry is currently
@unpublished{... note = {Preprint}, year = {2022}, eprint = {2207.14492}}(verified today); it may optionally be refreshed to the published version —
Algebra & Number Theory 19 (2025), no. 4, 667–714 — under the standard
rules (author/title/journal/volume/year, nonempty
why,doiverified atcommit time,
urlonly for legally free copies,make referencespassing).That refresh is not required by this issue.
Failure and fallback semantics
Any of: package not located (
DIOPHANTINE_CLASSIFIER_THUEMAHLERunset, orCode/TMSolver/solveThueMahler.mabsent under it), guard declined, timeout,nonzero exit,
status != "ok", unparseable payload, wrong entry length,exponent disagreement, a non-primitive returned pair — falls through to the
existing
SOLVERS["thue-mahler"](the PARI per-exponent box search), whoseresult is returned unchanged except for
"; the Magma route was attempted and did not produce a result (<outcome>): <reason>"appended to itsdescription(#76). That fallback result keepsits own
completeness="partial"and scope"solutions with all prime exponents in [0, 6]"; the box search stays the unconditional partial answer.A point that survives exponent recovery but still fails
ParsedEquation.acceptsis an internal-consistency error by the repository'scontract:
solvers._verifiedraisesInternalConsistencyError, and theadapter must not pre-filter to hide it.
Acceptance criteria
Fixture (deterministic, fake runner, no Magma)
Equation: the existing catalogue row
x^3 + 2*y^3 = 5^a * 11^b(
tests/test_classify.py,tests/test_solver_contract.py), soalist = [1, 0, 0, 2],m = 1,primes = [11, 5].Fake payload
data["solutions"]= the seven primitive solutions in thepackage's own
[X, Y, z_1, z_2]shape, withz_1the exponent of11andz_2the exponent of5(every line re-verified today):(These are genuine primitive solutions — each has
gcd(X, Y) = 1and eachquotient is exactly
1after the two valuations are divided out. The fixtureasserts the adapter's parsing, exponent cross-check, ordering and
verification — it does not assert that this list is the complete primitive
solution set.)
Assertions on
solve("x^3 + 2*y^3 = 5^a * 11^b", use_magma=True)with thefake runner:
result.variables == ('x', 'y', 'a', 'b');result.solutions == [(-1, 1, 0, 0), (1, 0, 0, 0), (1, 3, 1, 1), (3, -2, 0, 1), (3, -1, 2, 0), (19, 2, 4, 1), (29, -23, 1, 1)]— thepayload exponents re-derived and re-ordered, sorted; in particular
(3, -2)givesa = 0,b = 1because3^3 + 2·(-2)^3 = 11 = 5^0 · 11^1, which pins theprimes ↔ exponents ↔ z_ipairing;result.kind == "finite-complete",result.completeness == "conditional",result.complete is False;result.assumptions == ("the external Gherga-Siksek Magma implementation computed correctly",);"gcd(x, y) = 1"inresult.scope, andresult.scope_info.primitive == "gcd(x, y) = 1";Evidencewithkind == "external-computation",software == "magma",versionequal to the fake payload's version, and"GhergaSiksek2022"among the evidence references (resolvable inbibliography());result.representation.form == "partial-search";check_solver_contract(cls, result)(Core opt-in Magma process runner #76) passes, andjson.dumps(result.to_dict())succeeds withschema_version == 3andexact tagged integers.
The six required package-contract tests
built for the fixture equals the literal rendering above, line for line,
with the located path substituted. Assert specifically:
'ChangeDirectory("' in script,'load "solveThueMahler.m";' in script,"alist := [1, 0, 0, 2];" in script,"a := 1;" in script,"primes := [11, 5];" in script,"solveThueMahler(alist, a, primes : verb := false," in script,"coprime := false" in script. Regression guards against the supersededdraft:
"AttachSpec" not in script, and the script contains no unresolved<<name>>marker after assembly (assert"<<" not in script), which alsorules out the superseded draft's unfilled entry-point and flag markers.
Assert also that
loadoccurs before thetryline (it is a top-leveldirective) and that
primesis not sorted ("[5, 11]" not in script).DIOPHANTINE_CLASSIFIER_THUEMAHLERpointing at a directory that lacks
Code/TMSolver/solveThueMahler.m, theadapter declines with reason
"package-not-found"and starts no process(assert with a runner stub that raises if invoked); the PARI result is
returned. With it pointing at a directory that has the file, the
ChangeDirectoryargument in the generated script is exactly thatdirectory's
Code/TMSolverpath. A path containing a quote, backslash ornewline is rejected by
encode_magma_pathand declines.(2 + v)-elementarrays above parse into the tuples of assertion 2. An entry of length
2(the superseded[X, Y]shape) is malformed → fallback; an entry oflength
2 + vwhose exponents disagree with the recomputed valuations —e.g.
["3", "-2", "0", "1"], which asserts11^0 · 5^1 = 5instead ofthe true
11— is a payload inconsistency →MagmaFailurewithoutcome == "cas-failure"and fallback, and the bad pair appears in noreturned solution set.
result.scopecontains"gcd(x, y) = 1";result.scope_info.primitive == "gcd(x, y) = 1";result.descriptioncontains the exact two-sentence wording pinned above
(
"This adapter enumerates only the primitive solutions covered by the external package."and"Imprimitive solutions are outside this SolutionSet scope."). Assert also that the description does notcontain the removed claim — no occurrence of
"scaling"together with"listed primes", and no assertion that the imprimitive solutions areobtained from the returned ones. A payload pair with
gcd(X, Y) > 1(forinstance
["-5", "5", "0", "3"], a genuine solution withgcd = 5) is apayload inconsistency → fallback, never a silently accepted extra tuple.
with 40 solutions, kind
witness, completenesspartial, and the reasonappended to
description; timeout (magma_timeout=1, sleeping fake) →same PARI result, reason mentions
resource-exceeded;status: "error"payload (standing in for a violated package
assert) → same PARI result;a payload preceded by several kilobytes of the package's own banner and
timeoutput still parses correctly and is not discarded as oversized(this pins the note about unconditional progress output above).
magma-marked test,skipped by default, resolves the fixture equation against a real Magma
(V2.29+) and a real checkout of
github.com/adelagherga/ThueMahlerat commit164a4eb9f53c4ccc565379594251c0503952a70b. It asserts that thethree-argument call with
coprime := falseruns, that every returnedentry has length
2 + v, that every returned pair is primitive, that thepackage's exponents agree with the independently recomputed valuations,
and that every tuple verifies against the equation. It is skipped when the
environment variable is unset, and is never a substitute for the
deterministic tests above.
Further failure-path tests (same fixture, fake runner)
DIOPHANTINE_CLASSIFIER_THUEMAHLERunset → adapter declines withoutstarting a process; same PARI result.
set — for instance a matched input with
m = 5and prime set containing5— declines before starting a process, with the reason naming thep | mguard, and the PARI result is returned. (The package wouldotherwise fail its own
assert Valuation(a,p) eq 0.)SolutionSetdirectly with an injected point that satisfies the exponent bookkeeping
but not the equation and passing it through
solvers._verified(cls, ...)raises
InternalConsistencyError(the equation carries no nonvanishingconditions, so filtering is not permitted).
Unconditional and repository-wide
use_magma=False(and withDIOPHANTINE_CLASSIFIER_MAGMApointingat the fake),
solve("x^3 + 2*y^3 = 5^a * 11^b")is identical to today'sresult in
solutions,kind,completeness,scopeanddescription.This runs unconditionally.
make test,make doctest,make coverage(100%) andmake registry-docsclean without Magma installed; every newfunction has a Sage-convention docstring whose
EXAMPLESpasssage -twithout Magma.
Out of scope
equation as typed. The
scopestring,Scope.primitiveand thedescriptionrecord the restriction; enumerating anything outside theprimitive set is not part of this issue.
coprime := true(the narrowergcd(a_0, Y) = 1regime), or exposingverb, as user-facing options.B = 6, or its capabilitydeclaration.
package (no web dependencies; the package is located, never fetched — and
it carries no license file at the pinned commit).
Code/TMSolver/— in particularCode/Old/,Code/TdWCode/and the elliptic-curve drivers, which are notthis adapter's entry point.
of degree below 3.