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 genus-two Magma adapter on top of the core runner (#76): for y^2 = f(x) with deg f in {5, 6} and concrete integer coefficients, call Magma V2.29's RationalPointsGenus2 intrinsic on the hyperelliptic curve and turn its three return values — the point set, the "these are all of them" flag, and the height bound — into a SolutionSet whose claim is decided by that flag and nothing else. When the flag is true the result is a conditional determination of the affine rational solutions; when it is false the result is a partial witness list, or today's fallback when there is nothing to witness. Opt-in only (solve(..., use_magma=True, domain="QQ") / --use-magma).
There is no hand-built Chabauty pipeline in this adapter. The previous design assembled a Jacobian / rank-bound / Chabauty sequence out of intrinsics whose signatures it never pinned, and derived its completeness claim from flags this repository would have had to infer; that is replaced in full by the single pinned intrinsic below, whose exact signature, parameter defaults, return values and documented failure modes are quoted from the handbook here.
Magma version floor: V2.29. There is no earlier-version fallback path in
this adapter; on an older Magma the call errors and the standard fallback
runs. The adapter records the Magma version reported by the run in its Evidence.
Quoted verbatim from the Magma V2.29 Handbook (footer: V2.29, 3 August
2026), chapter Hyperelliptic Curves, section Points, subsection Enumeration and Counting Points, at https://magma.maths.usyd.edu.au/magma/handbook/text/1613
(fetched and read 2026-08-09). Cite the section title, not only the page
number: in the current V2.29 printing text/1612 is the Function Field page
of the same chapter and this subsection sits one page later, so a bare page
number drifts between printings.
PointsGenus2(C) : CrvHyp -> SetIndx, BoolElt, RngIntElt
RationalPointsGenus2(C) : CrvHyp -> SetIndx, BoolElt, RngIntElt
Bound1: RngIntElt Default: 1000
Bound2: RngIntElt Default: 20000
Fast: BoolElt Default: false
RankBound: RngIntElt Default: Infinity()
PrimeCutoff: RngIntElt Default: 10000
For a curve C over Q of the form y^2 = f(x) try to determine the set of
rational points
This returns a set of rational points on the curve, a flag indicating whether
we know that these are all the points, and if this is not the case, a number
such that all the points of (multiplicative x-coordinate) height up to that
bound are included in the set.
Bound1 is an argument passed to RationalPoints.
The handbook continues, in the same paragraph, that Bound2 is passed to a
second RationalPoints call which is made after the first search for small
points has failed and after the routine's own local-solubility test has
passed; that Fast makes the routine use smaller bounds in its call to MordellWeilGroupGenus2 for the search on 2-coverings, so that it fails
faster instead of triggering a time-intensive search for rational points on
2-coverings; and that PrimeCutoff is passed on to TwoCoverDescent. Those
sentences are paraphrased rather than quoted only so that this issue does not
put a solubility phrase into the repository's own claim vocabulary — nothing
in the adapter depends on them, and this adapter makes no local-solubility
claim of its own.
The handbook's description of what the routine does internally, and of how it
can fail, is quoted verbatim:
This function uses a combination of techniques:
i) search for (relatively small) points,
ii) test for everywhere local solubility,
iii) two-cover descent,
iv) degree 2 and 3 elliptic subcovers of rank zero,
v) computation of generators of a finite-index subgroup of the
Mordell-Weil group,
vi) Chabauty when the rank is at most 1,
vii) a check whether the curve has rational divisors of odd degree,
viii)if the rank is at least 2 and no points were found, a Mordell-Weil
sieve computation.
The two possible failure modes are:
i) failure to determine the rank
ii) rank is at least 2 and rational points exist; in these cases the result
is false, and it returns the set of rational points up to the height
bound for the search.
The adapter does not know, and never claims, which of (i)–(viii) produced a
given answer. It reads the second return value and nothing else. That is
the whole point of the redesign: the completeness claim rests on a flag the
intrinsic itself sets, not on a flag this repository would have had to infer.
The list above is quoted as the handbook's own account of the routine, not as
an assertion by this repository that any particular step ran.
Three further pinned facts used by the script and the decoder, each fetched
and read on 2026-08-09 from the same V2.29 handbook.
HyperellipticCurve(f) : RngUPolElt -> CrvHyp, chapter Hyperelliptic
Curves, Creation Functions
(https://magma.maths.usyd.edu.au/magma/handbook/text/1610): "Given two
polynomials h and f ∈ R[x] where R is a field or integral domain, this
function returns the nonsingular hyperelliptic curve C: y^2 + h(x)y = f(x).
If h(x) is not given, then it is taken as zero. If R is an integral domain
rather than a field, the base field of the curve is taken to be the field
of fractions of R. An error is returned if the given curve C is singular."
So an integer coefficient sequence is legitimate input and produces the
curve over Q; a non-squarefree f errors inside the try.
Polynomial(Q) : [ RngElt ] -> RngUPolElt, chapter Univariate Polynomial
Rings (https://magma.maths.usyd.edu.au/magma/handbook/text/231): "Given
a sequence Q of elements from a ring R, create the polynomial over R whose
coefficients are given by Q. This is equivalent to PolynomialRing(Universe(Q))!Q." The coefficient order is ascending,
matching the magma_coeffs convention below. (This is also the call form
the repository's own registry template already emits — re-verified today: classify("y^2 = x^5 - x + 1").code() is {'magma (hyperelliptic)': 'C := HyperellipticCurve(Polynomial([1, -1, 0, 0, 0, 1]));\nJ := Jacobian(C); RankBound(J);\n'}.)
Weighted-projective coordinates. The head of the same Points page
states: "The hyperelliptic curve is embedded in a weighted projective space,
with weights 1, g + 1, and 1, respectively on x, y and z. Therefore point
triples satisfy the equivalence relation (x : y : z) = (μ x : μ^(g+1) y :
μ z), and the points at infinity are then normalized to take the form
(1 : y : 0)." For genus 2, g + 1 = 3, so a point (x : y : z) with z != 0 has affine coordinates
(x/z, y/z^3)
and z == 0 marks a point at infinity. The same page documents Eltseq(P) : PtHyp -> SeqEnum under Access Operations: "Given a point P
on a hyperelliptic curve, this returns a 3-element sequence consisting of
the coordinates of the point P." (Verified against the handbook's own worked
example on that page: for C: y^2 = x^6 + x^2 + 1 the call Points(C : Bound := 2) lists the point (-1 : -9 : 2), whose affine form
under this rule is (-1/2, -9/8); checked exactly in Sage 10.7 during this
edit, f(-1/2) = 81/64 = (-9/8)^2.)
These equal the intrinsic's documented V2.29 defaults. The adapter passes
them explicitly anyway, so that the exact search parameters appear in the
generated script and in the evidence command string, and so that a future
change to Magma's defaults cannot silently change what this repository
reports. Fast, RankBound and PrimeCutoff are not passed; their V2.29
defaults (false, Infinity(), 10000) are recorded here for the record
and are what any run of this adapter uses.
genus-two has no entry in CAPABILITIES/SOLVERS today and gains none:
with use_magma=False the family still has no wired solver.
Input regime
y^2 = f(x), deg f in {5, 6}, all coefficients concrete integers, rational points (effective domain QQ). Verified today (re-run against
Sage 10.7 on 85f8e0a):
magma_coeffs is ascending (Polynomial([...]) order in Magma, as
pinned above). Match data values are stringified, so the list is read with ast.literal_eval, each entry converted exactly with ZZ(...), and the
result passed through encode_int_list — the only path into the script. data["x"]/data["y"] give the user's coordinate names; the assignment
built from them is ordered with _ordered(cls, assignment), so tuples follow
the unknowns' order of first appearance — for the fixture equation (y, x), not(x, y).
Domain gate
Integral points on a genus-2 curve are a different problem with different
machinery, and this adapter never answers it. With the effective domain ZZ
or NN the capability check skips the Magma route and solve behaves
exactly as today (verified: SolverUnavailable("no automatic solver for family 'genus-two' yet — ...")).
Guards (each one declines: MagmaFailure, then the standard fallback)
any coefficient not a concrete integer (parametric input, or a rational
coefficient);
deg f outside {5, 6}, or f not squarefree (the model is then not a
smooth genus-2 curve, and HyperellipticCurve itself rejects it);
effective domain other than QQ.
Output and API contract
Script template
diophantine_classifier/data/magma/rational_points_genus2.m, assembled by
on top of prelude.m. All three placeholders are filled only with typed
encoder output. The file is complete as written — there is nothing left to
fill in at implementation time:
// rational_points_genus2.m (prelude.m is prepended)
coeffs := <<coeffs>>; // ascending integers, deg f in {5, 6}
try
C := HyperellipticCurve(Polynomial(coeffs));
pts, complete, bound := RationalPointsGenus2(
C : Bound1 := <<bound1>>, Bound2 := <<bound2>>);
entries := [];
for P in pts do
c := Eltseq(P);
if c[3] eq 0 then
Append(~entries, JSONObj(["kind"], ["infinite"]));
else
xx := c[1] / c[3];
yy := c[2] / c[3]^3; // weights are 1, g+1 = 3, 1
Append(~entries,
JSONObj(["kind", "x", "y"],
["affine",
JSONObj(["n", "d"],
[JSONInt(Numerator(xx)),
JSONInt(Denominator(xx))]),
JSONObj(["n", "d"],
[JSONInt(Numerator(yy)),
JSONInt(Denominator(yy))])]));
end if;
end for;
DCEmit("ok", "",
JSONObj(["points", "points_complete", "height_bound"],
[JSONList(entries),
complete select "true" else "false",
JSONInt(bound)]));
catch e
DCEmit("error", "pipeline-error", JSONObj([], []));
end try;
quit;
For the fixture this renders literally and completely as
coeffs := [1, -1, 0, 0, 0, 1];
try
C := HyperellipticCurve(Polynomial(coeffs));
pts, complete, bound := RationalPointsGenus2(
C : Bound1 := 1000, Bound2 := 20000);
entries := [];
for P in pts do
c := Eltseq(P);
if c[3] eq 0 then
Append(~entries, JSONObj(["kind"], ["infinite"]));
else
xx := c[1] / c[3];
yy := c[2] / c[3]^3; // weights are 1, g+1 = 3, 1
Append(~entries,
JSONObj(["kind", "x", "y"],
["affine",
JSONObj(["n", "d"],
[JSONInt(Numerator(xx)),
JSONInt(Denominator(xx))]),
JSONObj(["n", "d"],
[JSONInt(Numerator(yy)),
JSONInt(Denominator(yy))])]));
end if;
end for;
DCEmit("ok", "",
JSONObj(["points", "points_complete", "height_bound"],
[JSONList(entries),
complete select "true" else "false",
JSONInt(bound)]));
catch e
DCEmit("error", "pipeline-error", JSONObj([], []));
end try;
quit;
The rational leaves are built out of #76's own primitives — JSONObj over
two JSONInts — so that they land in exactly the {"n": ..., "d": ...}
shape the payload protocol specifies (#76 §2); no new emitter is introduced.
The try/catch wrapper is mandatory: a singular model, an unavailable
intrinsic (Magma older than V2.29), or any internal error becomes status: "error" and the standard fallback, never a partial parse and never
a completeness claim.
Payload schema (data)
field
type
meaning
points
array of objects
each {"kind": "affine", "x": {"n": …, "d": …}, "y": {"n": …, "d": …}} or {"kind": "infinite"}
points_complete
"true"/"false"
the intrinsic's second return value: whether it knows the returned set is all of C(Q)
height_bound
decimal string
the intrinsic's third return value
Those three keys are the whole payload, and the decoder asserts that the
key set is exactly {"points", "points_complete", "height_bound"}. The four
invented fields of the previous design — an upper bound for the rank of the
Jacobian, a flag saying that bound was proved to be the rank, a flag saying a
Chabauty computation ran, and a flag saying the Mordell–Weil subgroup used was
saturated — are removed everywhere: from the script, from the schema, from
the branch logic, from assumptions, and from the fixtures. RationalPointsGenus2 returns none of them, and this adapter reports only
what it is told.
Integers are decimal strings and rationals are {"n": …, "d": …} objects
(#76 §2); bare JSON numbers are rejected by the protocol.
height_bound is only meaningful when points_complete == "false" — the
handbook defines it as the bound reached in that case. It is recorded in the
payload unconditionally (it is what Magma returned) but is used only on the
incomplete branch; on the complete branch the adapter must not put it into
any scope or description string.
Parsing into a SolutionSet
Decode each point. Entries with kind == "infinite" are dropped from
the solution list — a point at infinity is a point of the projective
curve, not a solution of the affine equation the user typed — and their
count is recorded in the description. (For the fixture, deg f = 5, so
there is exactly one rational point at infinity.)
Decode affine coordinates exactly: QQ(n)/QQ(d) from the tagged objects,
never through float or int(...).
Build assignments {data["x"]: x, data["y"]: y} from the affine entries,
order them with _ordered(cls, ...), sorted(set(...)) — so tuples come
out in the unknowns' order of first appearance and are deduplicated and
sorted.
No pre-filtering: solvers._verified is the firewall; a point failing ParsedEquation.accepts raises InternalConsistencyError.
representation is left to _derived_representation
(PartialSearch(solutions, scope) for both the conditional and the
witness case, EmptySet for an empty conditional result). It must not be
overridden with a FiniteSet.
Result semantics — exactly three branches
(A) points_complete == "true".
kind="finite-complete"# or "empty" when there is no affine pointcompleteness="conditional"assumptions= ("the external Magma routine computed correctly",)
scope= ("all rational solutions of the equation as typed; the ""curve's rational points at infinity are points of the ""projective model and are not solutions of it")
scope_info=Scope(domain_scope="rational solutions of the affine equation",
ordering_convention="unknowns in order of first ""appearance in the input equation")
assumptions is exactly that one-element tuple, with that exact text — no
rank assumption, no saturation assumption, because the adapter no longer
inspects rank or saturation. conditional requires nonempty assumptions
(docs/SEMANTICS.md §3), and this entry supplies it. height_bound is not
mentioned anywhere on this branch.
(B) points_complete == "false" and at least one affine point.
kind="witness"completeness="partial"assumptions= ()
scope= ("rational solutions of the affine equation whose ""x-coordinate has multiplicative height at most "f"{height_bound}; nothing is claimed beyond that height")
scope_info=Scope(domain_scope="rational solutions of the affine equation",
ordering_convention="unknowns in order of first ""appearance in the input equation",
search_region=("RationalPointsGenus2(C : Bound1 := 1000, ""Bound2 := 20000), height bound "f"{height_bound}"))
The height in those strings is the payload's height_bound, decoded exactly
and rendered as a decimal integer — never POINT_SEARCH_BOUND1 or POINT_SEARCH_BOUND2, which are inputs to the search, not the guarantee it
came back with. witness results are never proved (contract test).
(C) points_complete == "false" and no affine point.
The adapter raises MagmaFailure(..., outcome="cas-failure") with reason "not-determined", so the caller gets today's behavior (which carries the
registry's runnable templates). It must not return an empty witness: a
user could read "no solutions found, completeness partial" as evidence that
the equation has no rational solutions, and this branch is exactly the case
where the intrinsic reports that it could not decide. Points at infinity in
the payload do not rescue this branch — they are not affine solutions.
No other combination produces a completeness claim, and there is no path on
which a blanket claim about the family is made.
where points_complete is the payload's own "true"/"false" enum string.
The evidence names the intrinsic that ran and the flag it returned. It does not name Chabauty, a rank bound, a Mordell–Weil sieve or a descent as
having been performed: the adapter cannot observe which of the intrinsic's
internal techniques was decisive, and asserting one would be a claim the run
does not support. The references describe the family of methods the intrinsic
combines; they are attribution, not a statement about this run.
Stoll2011 ("Rational points on curves", cited today by general-curve) and McCallumPoonen2012 (cited today by hyperelliptic, why: "the
Chabauty-Coleman method for rational points") both already resolve in data/references.bib (re-verified today); CasselsFlynn1996 (already cited
by genus-two) may be added as a third for the explicit Jacobian arithmetic.
No bibliography change is required; any addition follows the standard rules
(author/title/journal/volume/year, nonempty why, doi verified at commit
time, url only for legally free copies, make references passing).
Failure and fallback semantics
Guard declined, executable missing, Magma older than V2.29 (the intrinsic is
undefined and the call errors), timeout, nonzero exit, status != "ok",
oversized output, unparseable payload, and branch (C) above all fall through
to today's behavior for this family: _local_emptiness still runs first, and
then
SolverUnavailable("no automatic solver for family 'genus-two' yet — magma: "
"full pipeline (Jacobian, RankBound, Chabauty); sage: "
"HyperellipticCurve; igusa_clebsch_invariants; "
"code[magma (hyperelliptic)]: C := HyperellipticCurve("
"Polynomial([1, -1, 0, 0, 0, 1]));\nJ := Jacobian(C); "
"RankBound(J);; no small local obstruction found either",
family="genus-two") # outcome unsupported-family
is raised with "; the Magma route was attempted and did not produce a result (<outcome>): <reason>" appended (#76). The message above is today's
verbatim output for the fixture equation (re-verified during this edit — note
the template is inherited from the hyperelliptic ancestor, since genus-two
has no code: map of its own) and is what the equivalence test pins. Changing
the registry's inherited display template is out of scope here.
Acceptance criteria
Fixtures (deterministic, fake runner, no Magma)
Equation: y^2 = x^5 - x + 1 (the registry example for genus-two and the #52 fixture), solved with domain="QQ".
The six affine points used in the payloads are genuine: f(0) = 1, f(1) = 1, f(-1) = 1 (verified today), so (x, y) in {(0, ±1), (1, ±1), (-1, ±1)} all satisfy y^2 = x^5 - x + 1. The fixtures assert the adapter's
decoding, ordering, branch logic and verification — they do not assert that
this list is all of C(Q).
(i) Fake complete payload(required test 1). points_complete: "true", height_bound: "0", points = the six affine points (each as {"kind": "affine", "x": {"n": …, "d": "1"}, "y": {…}}) plus one {"kind": "infinite"} entry. Assert:
result.variables == ('y', 'x');
result.solutions == [(-1, -1), (-1, 0), (-1, 1), (1, -1), (1, 0), (1, 1)] — payload (x, y) data reordered to the unknowns' order and
sorted (verified: this is the sorted (y, x) list for those six points);
the point at infinity is absent;
"1 rational point at infinity" (or equivalent, exact wording pinned by
the test) appears in result.description;
result.kind == "finite-complete", result.completeness == "conditional", result.complete is False;
result.assumptions == ("the external Magma routine computed correctly",)
— exactly this one-element tuple;
result.scope states that the claim is about rational solutions of the
equation as typed and that points at infinity are not solutions of it; "height" does not appear in result.scope on this branch;
exactly one Evidence with kind == "external-computation", software == "magma", version from the payload, every reference key
resolvable in bibliography(), and "RationalPointsGenus2" in the
evidence description;
check_solver_contract(cls, result) (Core opt-in Magma process runner #76) passes; json.dumps(result.to_dict()) succeeds with schema_version == 3 and
exact tagged rationals (decode_exact round-trips each coordinate).
(ii) Fake incomplete payload with the exact returned height(required
test 2). Same six affine points, points_complete: "false", height_bound: "4096". Assert kind == "witness", completeness == "partial", assumptions == (), representation.form == "partial-search", the same six tuples, and that "4096" appears in result.scope while neither "1000" nor "20000"
does — the guarantee is the height the intrinsic returned, not the search
bounds it was given. Assert result.scope_info.search_region contains both
the returned height and the two Bound values.
(iii) Complete payload containing only points at infinity(required test
3). points_complete: "true", points = a single {"kind": "infinite"}
entry, height_bound: "0". Assert kind == "empty", completeness == "conditional", assumptions == ("the external Magma routine computed correctly",), representation.form == "empty", result.complete is False, and that result.description records the one rational point at infinity — so the
emptiness is visibly emptiness of the affine equation and visibly rests on
the external completeness assumption, never on a proved statement.
(iv) Incomplete payload containing only points at infinity → fallback (required test 4). Same payload with points_complete: "false", height_bound: "4096". Assert the verbatim SolverUnavailable above with the
reason appended, outcome == "unsupported-family", family == "genus-two",
and that noSolutionSet with kind == "empty" is produced on this path.
(v) Exact coordinate decoding and first-appearance ordering(required
test 5). A payload point with genuinely non-integral coordinates, decoded
exactly and ordered by first appearance: take x = -1/2, y = -9/8 on y^2 = x^6 + x^2 + 1 — the handbook's own example point (-1 : -9 : 2),
whose affine form under the weight-(1, 3, 1) rule is (-1/2, -9/8),
verified exactly in Sage 10.7 during this edit (f(-1/2) = 81/64 = (-9/8)^2; f is squarefree of degree 6, so the curve is a legitimate genus-2 model for
this adapter). Assert that the payload objects {"n": "-1", "d": "2"} and {"n": "-9", "d": "8"} decode to the exact QQ values, that the produced
tuple is (-9/8, -1/2) for unknown order ('y', 'x'), that json.dumps
round-trips them as tagged rationals, and that solvers._verified accepts
them. A second payload with the coordinates swapped (x = -9/8, y = -1/2,
not a point of that curve) must raise InternalConsistencyError, proving the
adapter does not pre-filter or silently reorder.
(vi) Real-Magma marked test on V2.29+(required test 6). A magma-marked test, skipped by default, runs the fixture curve against a real
install on V2.29 or newer: it asserts that the run's reported Magma
version is at least V2.29, that the three-value call signature works, that points_complete parses as one of the two enum strings, and that every
returned affine point verifies against the equation. It never asserts a
particular point count and is never a substitute for the deterministic tests
above.
(vii) Malformed payload (invalid JSON between the sentinels) → the same
fallback, reason mentions cas-failure. Likewise a timeout
(magma_timeout=1, sleeping fake) → fallback, reason mentions resource-exceeded; and status: "error" → fallback. A payload whose points_complete is neither "true" nor "false", or whose height_bound
is not a decimal integer, is malformed → fallback, never a default guess.
(viii) Verification firewall. A payload affine point (x, y) = (2, 3) — f(2) = 32 - 2 + 1 = 31 while 3^2 = 9 (verified) — makes solvers._verified raise InternalConsistencyError; the bad point appears in
no returned solution set, and the adapter does not pre-filter it away.
Script snapshot
(ix) The script generated for the fixture is asserted line-by-line
against the complete literal rendering above: it contains coeffs := [1, -1, 0, 0, 0, 1];, C := HyperellipticCurve(Polynomial(coeffs));, RationalPointsGenus2(, Bound1 := 1000, Bound2 := 20000,
and the three-value assignment pts, complete, bound :=.
Two regression guards against the removed design, both stated positively so
that they cannot drift:
the decoder rejects any payload whose key set is not exactly {"points", "points_complete", "height_bound"} — in particular a payload
carrying any of the four removed flags is malformed and takes the fallback,
never a completeness claim;
Jacobian(, RankBound( and Chabauty occur nowhere in the generated
script or in the adapter module. (They do still occur in the family's
inherited SolverUnavailable hint text, which is today's verbatim registry
template and is out of scope here; assert against the script and the module,
not against that message.)
Unconditional and repository-wide
(x) With use_magma=False, and with DIOPHANTINE_CLASSIFIER_MAGMA
pointing at the fake, solve("y^2 = x^5 - x + 1", domain="QQ") and solve("y^2 = x^5 - x + 1") raise the same SolverUnavailable messages as on
the pre-epic tree. Runs unconditionally.
(xi)domain="ZZ" with use_magma=True behaves exactly as today: the
capability gate skips the adapter and no process is started.
(xii)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
Integral points on genus-2 curves (Baker's method / Magma's IntegralPoints), and every domain other than QQ.
Running, re-running or second-guessing any individual step the intrinsic
performs internally — descent, Mordell–Weil computation, Chabauty, the
Mordell–Weil sieve — and any attempt to upgrade a conditional
determination to a proved one.
Tuning Bound1/Bound2 per input, or exposing them as user parameters;
they are module constants here.
Passing Fast, RankBound or PrimeCutoff; their V2.29 defaults are
recorded above and are what every run uses.
Reporting the points at infinity as solutions, or emitting a projective
solution set. Their count goes in the description; the affine equation is
what the user typed.
Goal
Add the
genus-twoMagma adapter on top of the core runner (#76): fory^2 = f(x)withdeg fin{5, 6}and concrete integer coefficients, call Magma V2.29'sRationalPointsGenus2intrinsic on the hyperelliptic curve and turn its three return values — the point set, the "these are all of them" flag, and the height bound — into aSolutionSetwhose claim is decided by that flag and nothing else. When the flag is true the result is a conditional determination of the affine rational solutions; when it is false the result is a partial witness list, or today's fallback when there is nothing to witness. Opt-in only (solve(..., use_magma=True, domain="QQ")/--use-magma).There is no hand-built Chabauty pipeline in this adapter. The previous design assembled a Jacobian / rank-bound / Chabauty sequence out of intrinsics whose signatures it never pinned, and derived its completeness claim from flags this repository would have had to infer; that is replaced in full by the single pinned intrinsic below, whose exact signature, parameter defaults, return values and documented failure modes are quoted from the handbook here.
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)
and table lookup). Genus 2: Igusa-invariant identification against curve tables #52 identifies a curve; this issue looks for its
rational points. Labels and tables stay entirely in Genus 2: Igusa-invariant identification against curve tables #52.
Pinned external API
Magma version floor: V2.29. There is no earlier-version fallback path in
this adapter; on an older Magma the call errors and the standard fallback
runs. The adapter records the Magma version reported by the run in its
Evidence.Quoted verbatim from the Magma V2.29 Handbook (footer: V2.29, 3 August
2026), chapter Hyperelliptic Curves, section Points, subsection
Enumeration and Counting Points, at
https://magma.maths.usyd.edu.au/magma/handbook/text/1613
(fetched and read 2026-08-09). Cite the section title, not only the page
number: in the current V2.29 printing
text/1612is the Function Field pageof the same chapter and this subsection sits one page later, so a bare page
number drifts between printings.
The handbook continues, in the same paragraph, that
Bound2is passed to asecond
RationalPointscall which is made after the first search for smallpoints has failed and after the routine's own local-solubility test has
passed; that
Fastmakes the routine use smaller bounds in its call toMordellWeilGroupGenus2for the search on 2-coverings, so that it failsfaster instead of triggering a time-intensive search for rational points on
2-coverings; and that
PrimeCutoffis passed on toTwoCoverDescent. Thosesentences are paraphrased rather than quoted only so that this issue does not
put a solubility phrase into the repository's own claim vocabulary — nothing
in the adapter depends on them, and this adapter makes no local-solubility
claim of its own.
The handbook's description of what the routine does internally, and of how it
can fail, is quoted verbatim:
The adapter does not know, and never claims, which of (i)–(viii) produced a
given answer. It reads the second return value and nothing else. That is
the whole point of the redesign: the completeness claim rests on a flag the
intrinsic itself sets, not on a flag this repository would have had to infer.
The list above is quoted as the handbook's own account of the routine, not as
an assertion by this repository that any particular step ran.
Three further pinned facts used by the script and the decoder, each fetched
and read on 2026-08-09 from the same V2.29 handbook.
HyperellipticCurve(f) : RngUPolElt -> CrvHyp, chapter HyperellipticCurves, Creation Functions
(https://magma.maths.usyd.edu.au/magma/handbook/text/1610): "Given two
polynomials h and f ∈ R[x] where R is a field or integral domain, this
function returns the nonsingular hyperelliptic curve C: y^2 + h(x)y = f(x).
If h(x) is not given, then it is taken as zero. If R is an integral domain
rather than a field, the base field of the curve is taken to be the field
of fractions of R. An error is returned if the given curve C is singular."
So an integer coefficient sequence is legitimate input and produces the
curve over
Q; a non-squarefreeferrors inside thetry.Polynomial(Q) : [ RngElt ] -> RngUPolElt, chapter Univariate PolynomialRings (https://magma.maths.usyd.edu.au/magma/handbook/text/231): "Given
a sequence Q of elements from a ring R, create the polynomial over R whose
coefficients are given by Q. This is equivalent to
PolynomialRing(Universe(Q))!Q." The coefficient order is ascending,matching the
magma_coeffsconvention below. (This is also the call formthe repository's own registry template already emits — re-verified today:
classify("y^2 = x^5 - x + 1").code()is{'magma (hyperelliptic)': 'C := HyperellipticCurve(Polynomial([1, -1, 0, 0, 0, 1]));\nJ := Jacobian(C); RankBound(J);\n'}.)Weighted-projective coordinates. The head of the same Points page
states: "The hyperelliptic curve is embedded in a weighted projective space,
with weights 1, g + 1, and 1, respectively on x, y and z. Therefore point
triples satisfy the equivalence relation (x : y : z) = (μ x : μ^(g+1) y :
μ z), and the points at infinity are then normalized to take the form
(1 : y : 0)." For genus 2,
g + 1 = 3, so a point(x : y : z)withz != 0has affine coordinatesand
z == 0marks a point at infinity. The same page documentsEltseq(P) : PtHyp -> SeqEnumunder Access Operations: "Given a point Pon a hyperelliptic curve, this returns a 3-element sequence consisting of
the coordinates of the point P." (Verified against the handbook's own worked
example on that page: for
C: y^2 = x^6 + x^2 + 1the callPoints(C : Bound := 2)lists the point(-1 : -9 : 2), whose affine formunder this rule is
(-1/2, -9/8); checked exactly in Sage 10.7 during thisedit,
f(-1/2) = 81/64 = (-9/8)^2.)Pinned module constants
These equal the intrinsic's documented V2.29 defaults. The adapter passes
them explicitly anyway, so that the exact search parameters appear in the
generated script and in the evidence
commandstring, and so that a futurechange to Magma's defaults cannot silently change what this repository
reports.
Fast,RankBoundandPrimeCutoffare not passed; their V2.29defaults (
false,Infinity(),10000) are recorded here for the recordand are what any run of this adapter uses.
Supported inputs
Registered as
MAGMA_SOLVERS["genus-two"]withgenus-twohas no entry inCAPABILITIES/SOLVERStoday and gains none:with
use_magma=Falsethe family still has no wired solver.Input regime
y^2 = f(x),deg fin{5, 6}, all coefficients concrete integers,rational points (effective domain
QQ). Verified today (re-run againstSage 10.7 on
85f8e0a):magma_coeffsis ascending (Polynomial([...])order in Magma, aspinned above). Match data values are stringified, so the list is read with
ast.literal_eval, each entry converted exactly withZZ(...), and theresult passed through
encode_int_list— the only path into the script.data["x"]/data["y"]give the user's coordinate names; the assignmentbuilt from them is ordered with
_ordered(cls, assignment), so tuples followthe unknowns' order of first appearance — for the fixture equation
(y, x),not
(x, y).Domain gate
Integral points on a genus-2 curve are a different problem with different
machinery, and this adapter never answers it. With the effective domain
ZZor
NNthe capability check skips the Magma route andsolvebehavesexactly as today (verified:
SolverUnavailable("no automatic solver for family 'genus-two' yet — ...")).Guards (each one declines:
MagmaFailure, then the standard fallback)coefficient);
deg foutside{5, 6}, orfnot squarefree (the model is then not asmooth genus-2 curve, and
HyperellipticCurveitself rejects it);QQ.Output and API contract
Script template
diophantine_classifier/data/magma/rational_points_genus2.m, assembled byon top of
prelude.m. All three placeholders are filled only with typedencoder output. The file is complete as written — there is nothing left to
fill in at implementation time:
For the fixture this renders literally and completely as
The rational leaves are built out of #76's own primitives —
JSONObjovertwo
JSONInts — so that they land in exactly the{"n": ..., "d": ...}shape the payload protocol specifies (#76 §2); no new emitter is introduced.
The
try/catchwrapper is mandatory: a singular model, an unavailableintrinsic (Magma older than V2.29), or any internal error becomes
status: "error"and the standard fallback, never a partial parse and nevera completeness claim.
Payload schema (
data)points{"kind": "affine", "x": {"n": …, "d": …}, "y": {"n": …, "d": …}}or{"kind": "infinite"}points_complete"true"/"false"C(Q)height_boundThose three keys are the whole payload, and the decoder asserts that the
key set is exactly
{"points", "points_complete", "height_bound"}. The fourinvented fields of the previous design — an upper bound for the rank of the
Jacobian, a flag saying that bound was proved to be the rank, a flag saying a
Chabauty computation ran, and a flag saying the Mordell–Weil subgroup used was
saturated — are removed everywhere: from the script, from the schema, from
the branch logic, from
assumptions, and from the fixtures.RationalPointsGenus2returns none of them, and this adapter reports onlywhat it is told.
Integers are decimal strings and rationals are
{"n": …, "d": …}objects(#76 §2); bare JSON numbers are rejected by the protocol.
height_boundis only meaningful whenpoints_complete == "false"— thehandbook defines it as the bound reached in that case. It is recorded in the
payload unconditionally (it is what Magma returned) but is used only on the
incomplete branch; on the complete branch the adapter must not put it into
any scope or description string.
Parsing into a
SolutionSetkind == "infinite"are dropped fromthe solution list — a point at infinity is a point of the projective
curve, not a solution of the affine equation the user typed — and their
count is recorded in the
description. (For the fixture,deg f = 5, sothere is exactly one rational point at infinity.)
QQ(n)/QQ(d)from the tagged objects,never through
floatorint(...).{data["x"]: x, data["y"]: y}from the affine entries,order them with
_ordered(cls, ...),sorted(set(...))— so tuples comeout in the unknowns' order of first appearance and are deduplicated and
sorted.
solvers._verifiedis the firewall; a point failingParsedEquation.acceptsraisesInternalConsistencyError.representationis left to_derived_representation(
PartialSearch(solutions, scope)for both the conditional and thewitness case,
EmptySetfor an empty conditional result). It must not beoverridden with a
FiniteSet.Result semantics — exactly three branches
(A)
points_complete == "true".assumptionsis exactly that one-element tuple, with that exact text — norank assumption, no saturation assumption, because the adapter no longer
inspects rank or saturation.
conditionalrequires nonemptyassumptions(
docs/SEMANTICS.md§3), and this entry supplies it.height_boundis notmentioned anywhere on this branch.
(B)
points_complete == "false"and at least one affine point.The height in those strings is the payload's
height_bound, decoded exactlyand rendered as a decimal integer — never
POINT_SEARCH_BOUND1orPOINT_SEARCH_BOUND2, which are inputs to the search, not the guarantee itcame back with.
witnessresults are neverproved(contract test).(C)
points_complete == "false"and no affine point.The adapter raises
MagmaFailure(..., outcome="cas-failure")with reason"not-determined", so the caller gets today's behavior (which carries theregistry's runnable templates). It must not return an empty witness: a
user could read "no solutions found, completeness partial" as evidence that
the equation has no rational solutions, and this branch is exactly the case
where the intrinsic reports that it could not decide. Points at infinity in
the payload do not rescue this branch — they are not affine solutions.
No other combination produces a completeness claim, and there is no path on
which a blanket claim about the family is made.
Evidence
where
points_completeis the payload's own"true"/"false"enum string.The evidence names the intrinsic that ran and the flag it returned. It does
not name Chabauty, a rank bound, a Mordell–Weil sieve or a descent as
having been performed: the adapter cannot observe which of the intrinsic's
internal techniques was decisive, and asserting one would be a claim the run
does not support. The references describe the family of methods the intrinsic
combines; they are attribution, not a statement about this run.
Stoll2011("Rational points on curves", cited today bygeneral-curve) andMcCallumPoonen2012(cited today byhyperelliptic,why: "theChabauty-Coleman method for rational points") both already resolve in
data/references.bib(re-verified today);CasselsFlynn1996(already citedby
genus-two) may be added as a third for the explicit Jacobian arithmetic.No bibliography change is required; any addition follows the standard rules
(author/title/journal/volume/year, nonempty
why,doiverified at committime,
urlonly for legally free copies,make referencespassing).Failure and fallback semantics
Guard declined, executable missing, Magma older than V2.29 (the intrinsic is
undefined and the call errors), timeout, nonzero exit,
status != "ok",oversized output, unparseable payload, and branch (C) above all fall through
to today's behavior for this family:
_local_emptinessstill runs first, andthen
is raised with
"; the Magma route was attempted and did not produce a result (<outcome>): <reason>"appended (#76). The message above is today'sverbatim output for the fixture equation (re-verified during this edit — note
the template is inherited from the
hyperellipticancestor, sincegenus-twohas no
code:map of its own) and is what the equivalence test pins. Changingthe registry's inherited display template is out of scope here.
Acceptance criteria
Fixtures (deterministic, fake runner, no Magma)
Equation:
y^2 = x^5 - x + 1(the registry example forgenus-twoand the#52 fixture), solved with
domain="QQ".The six affine points used in the payloads are genuine:
f(0) = 1,f(1) = 1,f(-1) = 1(verified today), so(x, y)in{(0, ±1), (1, ±1), (-1, ±1)}all satisfyy^2 = x^5 - x + 1. The fixtures assert the adapter'sdecoding, ordering, branch logic and verification — they do not assert that
this list is all of
C(Q).(i) Fake complete payload (required test 1).
points_complete: "true",height_bound: "0",points= the six affine points (each as{"kind": "affine", "x": {"n": …, "d": "1"}, "y": {…}}) plus one{"kind": "infinite"}entry. Assert:result.variables == ('y', 'x');result.solutions == [(-1, -1), (-1, 0), (-1, 1), (1, -1), (1, 0), (1, 1)]— payload(x, y)data reordered to the unknowns' order andsorted (verified: this is the sorted
(y, x)list for those six points);the point at infinity is absent;
"1 rational point at infinity"(or equivalent, exact wording pinned bythe test) appears in
result.description;result.kind == "finite-complete",result.completeness == "conditional",result.complete is False;result.assumptions == ("the external Magma routine computed correctly",)— exactly this one-element tuple;
result.scopestates that the claim is about rational solutions of theequation as typed and that points at infinity are not solutions of it;
"height"does not appear inresult.scopeon this branch;Evidencewithkind == "external-computation",software == "magma",versionfrom the payload, every reference keyresolvable in
bibliography(), and"RationalPointsGenus2"in theevidence description;
check_solver_contract(cls, result)(Core opt-in Magma process runner #76) passes;json.dumps(result.to_dict())succeeds withschema_version == 3andexact tagged rationals (
decode_exactround-trips each coordinate).(ii) Fake incomplete payload with the exact returned height (required
test 2). Same six affine points,
points_complete: "false",height_bound: "4096". Assertkind == "witness",completeness == "partial",assumptions == (),representation.form == "partial-search", the same six tuples, and that"4096"appears inresult.scopewhile neither"1000"nor"20000"does — the guarantee is the height the intrinsic returned, not the search
bounds it was given. Assert
result.scope_info.search_regioncontains boththe returned height and the two
Boundvalues.(iii) Complete payload containing only points at infinity (required test
3).
points_complete: "true",points= a single{"kind": "infinite"}entry,
height_bound: "0". Assertkind == "empty",completeness == "conditional",assumptions == ("the external Magma routine computed correctly",),representation.form == "empty",result.complete is False, and thatresult.descriptionrecords the one rational point at infinity — so theemptiness is visibly emptiness of the affine equation and visibly rests on
the external completeness assumption, never on a proved statement.
(iv) Incomplete payload containing only points at infinity → fallback
(required test 4). Same payload with
points_complete: "false",height_bound: "4096". Assert the verbatimSolverUnavailableabove with thereason appended,
outcome == "unsupported-family",family == "genus-two",and that no
SolutionSetwithkind == "empty"is produced on this path.(v) Exact coordinate decoding and first-appearance ordering (required
test 5). A payload point with genuinely non-integral coordinates, decoded
exactly and ordered by first appearance: take
x = -1/2,y = -9/8ony^2 = x^6 + x^2 + 1— the handbook's own example point(-1 : -9 : 2),whose affine form under the weight-
(1, 3, 1)rule is(-1/2, -9/8),verified exactly in Sage 10.7 during this edit (
f(-1/2) = 81/64 = (-9/8)^2;fis squarefree of degree 6, so the curve is a legitimate genus-2 model forthis adapter). Assert that the payload objects
{"n": "-1", "d": "2"}and{"n": "-9", "d": "8"}decode to the exactQQvalues, that the producedtuple is
(-9/8, -1/2)for unknown order('y', 'x'), thatjson.dumpsround-trips them as tagged rationals, and that
solvers._verifiedacceptsthem. A second payload with the coordinates swapped (
x = -9/8,y = -1/2,not a point of that curve) must raise
InternalConsistencyError, proving theadapter does not pre-filter or silently reorder.
(vi) Real-Magma marked test on V2.29+ (required test 6). A
magma-marked test, skipped by default, runs the fixture curve against a realinstall on V2.29 or newer: it asserts that the run's reported Magma
version is at least V2.29, that the three-value call signature works, that
points_completeparses as one of the two enum strings, and that everyreturned affine point verifies against the equation. It never asserts a
particular point count and is never a substitute for the deterministic tests
above.
(vii) Malformed payload (invalid JSON between the sentinels) → the same
fallback, reason mentions
cas-failure. Likewise a timeout(
magma_timeout=1, sleeping fake) → fallback, reason mentionsresource-exceeded; andstatus: "error"→ fallback. A payload whosepoints_completeis neither"true"nor"false", or whoseheight_boundis not a decimal integer, is malformed → fallback, never a default guess.
(viii) Verification firewall. A payload affine point
(x, y) = (2, 3)—f(2) = 32 - 2 + 1 = 31while3^2 = 9(verified) — makessolvers._verifiedraiseInternalConsistencyError; the bad point appears inno returned solution set, and the adapter does not pre-filter it away.
Script snapshot
(ix) The script generated for the fixture is asserted line-by-line
against the complete literal rendering above: it contains
coeffs := [1, -1, 0, 0, 0, 1];,C := HyperellipticCurve(Polynomial(coeffs));,RationalPointsGenus2(,Bound1 := 1000,Bound2 := 20000,and the three-value assignment
pts, complete, bound :=.Two regression guards against the removed design, both stated positively so
that they cannot drift:
{"points", "points_complete", "height_bound"}— in particular a payloadcarrying any of the four removed flags is malformed and takes the fallback,
never a completeness claim;
Jacobian(,RankBound(andChabautyoccur nowhere in the generatedscript or in the adapter module. (They do still occur in the family's
inherited
SolverUnavailablehint text, which is today's verbatim registrytemplate and is out of scope here; assert against the script and the module,
not against that message.)
Unconditional and repository-wide
(x) With
use_magma=False, and withDIOPHANTINE_CLASSIFIER_MAGMApointing at the fake,
solve("y^2 = x^5 - x + 1", domain="QQ")andsolve("y^2 = x^5 - x + 1")raise the sameSolverUnavailablemessages as onthe pre-epic tree. Runs unconditionally.
(xi)
domain="ZZ"withuse_magma=Truebehaves exactly as today: thecapability gate skips the adapter and no process is started.
(xii)
make test,make doctest,make coverage(100%) andmake registry-docsclean without Magma installed; every new function hasa Sage-convention docstring whose
EXAMPLESpasssage -twithout Magma.Out of scope
IntegralPoints), and every domain other thanQQ.performs internally — descent, Mordell–Weil computation, Chabauty, the
Mordell–Weil sieve — and any attempt to upgrade a conditional
determination to a proved one.
Bound1/Bound2per input, or exposing them as user parameters;they are module constants here.
Fast,RankBoundorPrimeCutoff; their V2.29 defaults arerecorded above and are what every run uses.
solution set. Their count goes in the
description; the affine equation iswhat the user typed.
those are Genus 2: Igusa-invariant identification against curve tables #52.
coefficients.