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 elliptic-quartic Magma adapter on top of the core runner (#76): for v^2 = Q(u) with Q a quartic with concrete integer coefficients, find a deterministic integral seed point P on the model, run Magma's two-argumentIntegralQuarticPoints(Q, P) on the encoded coefficient sequence and that seed, parse the returned integral points into a SolutionSet whose completeness, scope and assumptions state exactly what that routine establishes, verify every point against the original equation, and fall back to today's SolverUnavailable-with-templates on any failure — including a failed seed search. Opt-in only (solve(..., use_magma=True) / --use-magma).
This is the adapter that gives #50 a route to integral points: #65 lists it as the integral-points route, under the assumptions recorded here and subject to the seed applicability guard specified below.
Magma version floor: V2.29 (or an earlier version on which the two
signatures below have been specifically verified by running them; there is
no other supported configuration). The adapter records the Magma version
reported by the run in its Evidence.
The following is quoted verbatim from the Magma V2.29 Handbook (footer: V2.29, 3 August 2026), chapter Elliptic Curves over Q and Number Fields,
section Curves over the Rationals, subsection Integral and S-integral
Points, at https://magma.maths.usyd.edu.au/magma/handbook/text/1567
(fetched and read 2026-08-09). Cite the section title, not only the page
number: in the current V2.29 printing text/1566 is the Introduction of the
same chapter and the intrinsics below sit one page later, so a bare page
number drifts between printings.
IntegralQuarticPoints(Q) : [ RngIntElt ] -> [ SeqEnum ]
If Q is a sequence of five integers [a, b, c, d, e] where e is a
square, this function returns all integral points (modulo negation)
on the curve y^2 = a x^4 + b x^3 + c x^2 + d x + e.
IntegralQuarticPoints(Q, P) : [ RngIntElt ], [ RngIntElt ] -> [ SeqEnum ]
If Q is a list of five integers [a, b, c, d, e] defining the
hyperelliptic quartic y^2 = a x^4 + b x^3 + c x^2 + d x + e and P is a
sequence representing a rational point [x, y], this function
returns all integral points on Q.
The one-argument form is not usable here. Its documented hypothesis is
that the constant coefficient e is a square. The fixture equation has e = 7, which is not a square, so the one-argument call is outside the
documented hypothesis; and even where it does apply it returns points only modulo negation, which would silently halve a solution set this
repository is contractually required to return in full. This adapter
never calls the one-argument form.
The two-argument form needs a rational point P = [x, y] on the
quartic and then returns all integral points (no modulo-negation
qualifier). Supplying that point is this adapter's job; see Deterministic
seed search.
The same handbook subsection states the provenance and method of the routine
(verbatim):
The following algorithms use the technique of linear forms in complex
and p-adic elliptic logarithms. They were initially implemented by
Emmanuel Hermann in the late 90s, and then Steve Donnelly made various
improvements. The main theoretical reference is Stroeker and Tzanakis
[ST94], and Tzanakis [ST96] for the case of a quartic equation.
The main routine here is (S)IntegralPoints for an elliptic curves.
The functions listed afterwards, for determining integral points on
various other kinds of genus one curves, are applications of the main
routine.
and, for that main routine, IntegralPoints:
The algorithm involves first computing generators of the Mordell--Weil group,
using the tools available in Magma for this.
Since the handbook says in so many words that the quartic functions are
applications of IntegralPoints, this is the documentary basis for the two
assumptions recorded under Guarantee, scope and assumptions below: the
Mordell–Weil computation is performed by Magma itself and is not re-derived by
this package.
The handbook's own worked example on the same page fixes the return element
shape — a sequence of two-element integer sequences [x, y] (verbatim):
elliptic-quartic has no entry in CAPABILITIES/SOLVERS today and gains
none: with use_magma=False the family still has no wired solver.
Input regime
v^2 = Q(u), deg Q = 4, all coefficients concrete integers; integral
points, i.e. effective domain ZZ (or NN, handled by the existing _restrict_nonneg post-pass). Verified today (re-run against Sage 10.7 on 85f8e0a):
qcoeffs is [a, b, c, d, e]descending: Q(u) = a*u^4 + b*u^3 + c*u^2 + d*u + e (the convention used throughout #50, and — see above — exactly the
sequence order the pinned intrinsic documents). Match data values are
stringified, so the list is read with ast.literal_eval and each entry
converted exactly with ZZ(...); a non-integer entry declines the adapter.
The resulting list goes through encode_int_list, which is the only path
into the script. data["x"]/data["y"] give
the user's names for the curve's coordinates — the assignment is built from
those and then ordered with _ordered(cls, assignment), so the returned
tuples follow the unknowns' order of first appearance: for the fixture
equation that is (v, u), not(u, v).
Domain gate
The rational case is a different problem and this adapter never touches it.
With domain="QQ" the Magma route is skipped by the capability check and solve behaves exactly as today (verified: SolverUnavailable("no automatic solver for family 'elliptic-quartic' yet — ...")). Rational points on
genus-one quartics are #50's business, not this adapter's.
Guards (each one declines: MagmaFailure, then the standard fallback)
any coefficient not a concrete integer (parametric input, or a rational
coefficient — encode_int_list rejects both);
leading coefficient zero, i.e. deg Q != 4;
Q not separable (zero discriminant): the model is then not a smooth
genus-one curve and the routine's hypotheses do not apply;
the effective domain not in ("ZZ", "NN");
no integral seed point found within INTEGRAL_QUARTIC_SEED_BOUND (the
new guard; see immediately below). This guard declines before any Magma
process is started.
Deterministic seed search
The two-argument intrinsic requires a rational point on the quartic. This
adapter supplies an integral one, found by an exact bounded enumeration in
Sage before Magma is invoked. The helper is fully specified here; there is
nothing to choose at implementation time.
INTEGRAL_QUARTIC_SEED_BOUND=50# module-level constant of the adapterdef_integral_quartic_seed(coeffs):
""" Return an exact integral point ``(u, v)`` with ``v^2 == Q(u)``, or ``None``. ``coeffs`` is ``[a, b, c, d, e]`` descending, all ``ZZ``. ``Q(u) = a*u^4 + b*u^3 + c*u^2 + d*u + e``. The abscissas are tried in exactly this order:: u = 0, 1, -1, 2, -2, ..., B, -B with B = INTEGRAL_QUARTIC_SEED_BOUND For each ``u`` the value ``Q(u)`` is computed exactly in ``ZZ`` and accepted iff ``Q(u) >= 0`` and ``ZZ(Q(u)).is_square()``. The first accepted ``u`` yields ``(u, ZZ(Q(u)).sqrt())`` — the **nonnegative** square root — and the enumeration stops. If no ``u`` in the list is accepted the helper returns ``None``. """
Binding properties of this helper:
The enumeration order above is part of the contract, not an implementation
detail: it makes the seed a deterministic function of coeffs, so the
generated script is reproducible and snapshot-testable. The list has 1 + 2*B = 101 entries at the shipped bound.
The search is model-level. It enumerates over ZZ and ignores the
user's requested domain entirely; in particular a domain="NN" request may
still be seeded by a negative u. The seed is not a solution being
reported, it is an argument being handed to Magma, and the NN
restriction is applied to the result afterwards (see Parsing).
INTEGRAL_QUARTIC_SEED_BOUND is an applicability guard, not a
completeness bound. It bounds how hard the adapter looks for a way in;
it says nothing about how far IntegralQuarticPoints then searches, and it
never appears in a scope string.
A failed seed search is not an emptiness claim. If the helper returns None the adapter raises MagmaFailure(..., outcome="cas-failure") with
reason "no-seed-point" and the standard fallback runs, exactly as for any
other declined guard. The adapter must never turn "I could not find a point
to start from" into kind="empty", and must not start a Magma process in
this case.
The seed is re-verified exactly (v^2 == Q(u) in ZZ) immediately before
encoding, so a bug in the enumerator cannot put a non-point into the script.
The fixture's seed, computed by hand and machine-verified. For Q = [2, -3, 1, -5, 7], i.e. Q(u) = 2*u^4 - 3*u^3 + u^2 - 5*u + 7, the
declared enumeration order gives
step
u
Q(u)
exact square?
1
0
7
no
2
1
2 - 3 + 1 - 5 + 7 = 2
no
3
-1
2 + 3 + 1 + 5 + 7 = 18
no
4
2
32 - 24 + 4 - 10 + 7 = 9
yes, 9 = 3^2
so the helper returns exactly (2, 3) and stops; -2 is never reached. In
words, and this is the statement the test pins: Q(0) = 7 is not a square, Q(1) = 2 is not a square, Q(-1) = 18 is not a square, and Q(2) = 9 = 3^2
is. (Verified against Sage 10.7 during this edit: the declared enumeration
was run and the first hit is (2, 3), with both entries ZZ; disc(Q) = 147348 != 0, so the separability guard passes.) P = [2, 3] is the seed the
pinned fixture asserts.
Output and API contract
Script template
diophantine_classifier/data/magma/integral_quartic_points.m, assembled by
on top of prelude.m. Both placeholders are filled only with typed
integer-encoder output; there is no other path into the script.
// integral_quartic_points.m (prelude.m is prepended)
Q := <<coeffs>>; // [a, b, c, d, e], descending
P := <<seed>>; // [u, v] with v^2 = Q(u), v >= 0
try
pts := IntegralQuarticPoints(Q, P);
entries := [ JSONList([JSONInt(p[1]), JSONInt(p[2])]) : p in pts ];
DCEmit("ok", "", JSONObj(["points"], [JSONList(entries)]));
catch e
DCEmit("error", "routine-error", JSONObj([], []));
end try;
quit;
For the fixture this renders literally and completely as
Q := [2, -3, 1, -5, 7];
P := [2, 3];
try
pts := IntegralQuarticPoints(Q, P);
entries := [ JSONList([JSONInt(p[1]), JSONInt(p[2])]) : p in pts ];
DCEmit("ok", "", JSONObj(["points"], [JSONList(entries)]));
catch e
DCEmit("error", "routine-error", JSONObj([], []));
end try;
quit;
p[1], p[2] is the documented element shape: the intrinsic's return type is [ SeqEnum ], a sequence of two-element integer sequences [x, y], as the
handbook's own worked example quoted above shows.
The try/catch wrapper stays mandatory: a Magma-side error (a rank
condition the routine cannot settle, a Mordell–Weil computation that fails,
an unexpected model rejection) surfaces as status: "error" and takes the
fallback path — never a partial parse and never a completeness claim.
Payload schema (data)
Unchanged from the previous design: the two-argument intrinsic returns
the same shape as the one-argument one, so the payload stays a flat list of (u, v) pairs.
field
type
meaning
points
array of 2-element arrays of decimal strings
the integral points, as (u, v) in the curve's own coordinates
Integers are decimal strings; bare JSON numbers are rejected by the protocol
(#76 §2). The seed itself is not echoed in the payload — it is already
recorded in the evidence command string.
Parsing into a SolutionSet
decode_int each coordinate; build the assignment {data["x"]: u, data["y"]: v} and order it with _ordered(cls, ...).
sorted(set(...)) — deduplicate and sort. (The two-argument form returns
all integral points rather than one of each ± pair, so (u, v) and (u, -v) both arrive; deduplication is still applied because nothing in
the intrinsic's contract promises a duplicate-free sequence.)
No silent prefilter. The adapter does not drop, correct or quietly
discard any returned pair for any reason. solvers._verified is the
firewall; a point that fails ParsedEquation.accepts raises InternalConsistencyError (this equation carries no nonvanishing
conditions, so silent dropping is not permitted).
The ordinary NN restriction is applied afterwards, by the existing _restrict_nonneg post-pass — never as a prefilter inside the adapter,
and never inside the seed search.
kind = "finite-complete" when nonempty, "empty" otherwise.
representation is left to _derived_representation, which gives PartialSearch(solutions, scope) for a finite-complete result that is
not proved. It must not be overridden with a FiniteSet: that would
assert unconditional completeness.
scope_info = Scope(domain_scope="integral points of the affine model", ordering_convention="unknowns in order of first appearance in the input equation").
Guarantee, scope and assumptions
IntegralQuarticPoints bounds the integral points by elliptic-logarithm
estimates on the associated elliptic curve (handbook quote above), and those
estimates are computed from a Mordell–Weil basis that Magma itself
computes. The honest report is therefore:
completeness="conditional"scope="all integral points"assumptions=("Magma's Mordell-Weil computation for the associated curve ""is correct and saturated",
"the external Magma routine computed correctly")
conditional requires nonempty assumptions (docs/SEMANTICS.md §3), and
these are the two things the classifier cannot re-derive: it re-checks every
returned point against the user's equation, but not that the search bound was
valid or that the basis was saturated. This mirrors how _solve_weierstrass already reports a gens(proof=False) fallback
(assumptions=("the unproved Mordell-Weil basis is correct and saturated",)).
The seed bound does not enter scope. scope describes the set the
routine reports on; INTEGRAL_QUARTIC_SEED_BOUND only decides whether the
routine is invoked at all.
No statement is made — here or anywhere — that a Magma-backed answer settles
the family in general: the guarantee above belongs to this routine on this
input regime.
Evidence
result.as_evidence(
"Magma IntegralQuarticPoints(Q, P) (elliptic-logarithm bounds on the ""associated elliptic curve, seeded with an exact integral point)",
command="integral_quartic_points([2, -3, 1, -5, 7], seed=[2, 3])",
reference=("Tzanakis2013", "StroekerTzanakis1994"))
The command string records the seed, so a certificate identifies the exact
call that was made. Both keys already resolve in data/references.bib
(verified today): Tzanakis2013 is already cited by the elliptic-quartic
family (why: "solving quartic elliptic equations via elliptic logarithms"),
and StroekerTzanakis1994 (cited today by elliptic-weierstrass) is
precisely the [ST94] the handbook names as the routine's main theoretical
reference in the quotation above. No bibliography change is required. If the
implementation adds one, it 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 (including no seed point found), executable missing,
timeout, nonzero exit, status != "ok" (including an error raised on rank
conditions), oversized output or unparseable payload all fall through to
today's behavior for this family: _local_emptiness still runs first, and
then
SolverUnavailable("no automatic solver for family 'elliptic-quartic' yet — "
"magma: IntegralQuarticPoints([a4,...,a0]); sage: Jacobian "
"of genus-one models; code[magma (elliptic-quartic)]: "
"IntegralQuarticPoints([2, -3, 1, -5, 7]);; no small local "
"obstruction found either",
family="elliptic-quartic") # 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) and is
what the equivalence test pins.
Note the ordering, because the no-seed test depends on it: _local_emptiness
runs before the family's SolverUnavailable is raised, so an equation with
a real or small-modular obstruction never reaches this adapter at all. That is
why the no-seed fixture below is chosen to be an equation the local layer
cannot settle.
Acceptance criteria
Fixture (deterministic, fake runner, no Magma)
Equation: v^2 = 2*u^4 - 3*u^3 + u^2 - 5*u + 7 (the quartic already used as
the #50 fixture; qcoeffs == [2, -3, 1, -5, 7], verified).
Assertions on solve("v^2 = 2*u^4 - 3*u^3 + u^2 - 5*u + 7", use_magma=True)
with the fake runner:
result.variables == ('v', 'u');
result.solutions == [(-3, 2), (3, 2)] — the (u, v) payload pairs
reordered to the unknowns' order of first appearance and sorted;
result.kind == "finite-complete", result.completeness == "conditional", result.complete is False;
result.scope == "all integral points" and result.assumptions == ("Magma's Mordell-Weil computation for the associated curve is correct " "and saturated", "the external Magma routine computed correctly"); "50" does not appear in result.scope (the seed bound is not a
completeness bound);
exactly one Evidence with kind == "external-computation", software == "magma", version equal to the fake payload's version, and
every reference key 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 (decode_exact round-trips each coordinate);
an empty payload (data["points"] == []) yields kind == "empty", completeness == "conditional", the same assumptions, and representation.form == "empty";
domain="NN" on the same payload yields only (3, 2) (the existing _restrict_nonneg post-pass), with everything else unchanged — and the
generated script is byte-identical to the ZZ one, proving the seed
search is model-level and unaffected by the requested domain.
The five required seed/script/signature tests
Script snapshot — the generated script contains both Q and P.
Assert all of: "Q := [2, -3, 1, -5, 7];" in script, "P := [2, 3];" in script, "IntegralQuarticPoints(Q, P)" in script, and — guarding against a
regression to the unusable form — "IntegralQuarticPoints(Q);" not in script. Compare the whole script
against the literal rendering shown above, not only these substrings.
The fixture seed is exactly (2, 3). _integral_quartic_seed([2, -3, 1, -5, 7]) == (2, 3) exactly, with both
entries ZZ. Assert also the intermediate facts that pin the enumeration
order: Q(0) == 7, Q(1) == 2 and Q(-1) == 18 are each not squares
while Q(2) == 9 is, so no earlier abscissa can be returned and -2 is
never examined.
No seed → decline without starting Magma. Fixture equation v^2 = u^4 + 5203, i.e. qcoeffs == [1, 0, 0, 0, 5203] (verified today: classify gives that qcoeffs with unknowns == ('v', 'u'), and disc(Q) = 36057984109312 != 0, so every other guard passes). Verified
facts that make this fixture exactly right:
Q(u) is not a nonnegative square for any |u| <= 50, so _integral_quartic_seed([1, 0, 0, 0, 5203]) is None after examining all
101 declared abscissas;
the equation nevertheless has integral solutions — Q(51) = 6770404 = 2602^2, so (u, v) = (±51, ±2602) are genuine points —
which is what makes "a failed seed search is not an emptiness claim" a
real assertion rather than a vacuous one;
solve("v^2 = u^4 + 5203") today raises the family's SolverUnavailable with "no small local obstruction found either"
(verified), so the local layer is inconclusive and the adapter really is
reached.
Assert: _integral_quartic_seed(...) is None; the documented fallback runs
with reason "no-seed-point" and outcome == "cas-failure" recorded,
ending in the family's SolverUnavailable; no process is started at
all — assert this with a fake runner that fails the test if it is
invoked (a stub raising AssertionError when called), not merely by
inspecting output; and kind is never "empty" on this path.
(Do not use v^2 = -u^4 - 1 for this test. Verified today: it is
intercepted earlier by the real-obstruction layer, which returns kind="empty", completeness="proved" with the description "the equation
has no real solutions ...", so the adapter is never consulted and the test
would assert nothing about the seed guard.)
Invalid returned point trips InternalConsistencyError. Payload [["3", "5"]] on the main fixture — Q(3) = 82 and 5^2 = 25, so the
pair is not a point of the curve (verified) — makes solvers._verified
raise InternalConsistencyError; the bad point appears in no returned
solution set, and the adapter does not pre-filter it away.
Real-Magma test confirming the V2.29 signature. A magma-marked test,
skipped by default, runs the fixture against a real install and asserts
that the two-argument call is the one that works on the pinned version:
it calls IntegralQuarticPoints(Q, P) and requires success, and
separately asserts that the run's reported Magma version is V2.29 or
higher (or is an explicitly allow-listed earlier version recorded in the
test). Every returned point must verify against the equation. This test is
never a substitute for the deterministic tests above.
Failure-path tests (main fixture, fake runner)
malformed payload → the verbatim SolverUnavailable above, with the
reason appended; outcome == "unsupported-family", family == "elliptic-quartic";
status: "error" payload (standing in for a rank-condition error) →
same;
domain="QQ" with use_magma=True behaves exactly as today (the
capability gate skips the adapter; no process is started).
Unconditional and repository-wide
With use_magma=False, and with DIOPHANTINE_CLASSIFIER_MAGMA pointing
at the fake, solve("v^2 = 2*u^4 - 3*u^3 + u^2 - 5*u + 7") raises the
same SolverUnavailable message as on the pre-epic tree. Runs
unconditionally.
A new corpus row is not required (elliptic-quartic is already matched);
the adapter's fixture equations are added to the Magma adapter test
catalogue introduced by Core opt-in Magma process runner #76.
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. _integral_quartic_seed in particular has an EXAMPLES
block exhibiting the (2, 3) seed and a None return.
Quartics with rational non-integral coefficients (no clearing of
denominators here: v^2 = Q(u) with denominators is a different model and
a different integral-points question).
Minimisation or reduction of the quartic model before the call (T3: minimisation and reduction of genus-one models #51) —
including any attempt to move the model to one with square constant
coefficient so that the one-argument form would apply.
Searching for a seed beyond INTEGRAL_QUARTIC_SEED_BOUND, or seeding from
a non-integral rational point. Both are possible extensions; neither is in
this issue, and neither changes any claim made here.
Any change to the registry's display template or to Family.fill_code.
Note for the record: the registry's display snippet for this family is
today IntegralQuarticPoints([2, -3, 1, -5, 7]); (verified), i.e. the
one-argument form, which — as pinned above — does not satisfy its own
documented hypothesis for this fixture (e = 7 is not a square). The
executed script is the two-argument file specified in this issue and is
unaffected; correcting the display template is a separate registry change
and is not made here.
Goal
Add the
elliptic-quarticMagma adapter on top of the core runner (#76): forv^2 = Q(u)withQa quartic with concrete integer coefficients, find a deterministic integral seed pointPon the model, run Magma's two-argumentIntegralQuarticPoints(Q, P)on the encoded coefficient sequence and that seed, parse the returned integral points into aSolutionSetwhosecompleteness,scopeandassumptionsstate exactly what that routine establishes, verify every point against the original equation, and fall back to today'sSolverUnavailable-with-templates on any failure — including a failed seed search. Opt-in only (solve(..., use_magma=True)/--use-magma).This is the adapter that gives #50 a route to integral points: #65 lists it as the integral-points route, under the assumptions recorded here and subject to the seed applicability guard specified below.
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 API
Magma version floor: V2.29 (or an earlier version on which the two
signatures below have been specifically verified by running them; there is
no other supported configuration). The adapter records the Magma version
reported by the run in its
Evidence.The following is quoted verbatim from the Magma V2.29 Handbook (footer:
V2.29, 3 August 2026), chapter Elliptic Curves over Q and Number Fields,
section Curves over the Rationals, subsection Integral and S-integral
Points, at
https://magma.maths.usyd.edu.au/magma/handbook/text/1567
(fetched and read 2026-08-09). Cite the section title, not only the page
number: in the current V2.29 printing
text/1566is the Introduction of thesame chapter and the intrinsics below sit one page later, so a bare page
number drifts between printings.
Three consequences are binding for this adapter.
eis the constantcoefficient. That is exactly the
qcoeffsconvention of T3: elliptic quartics — Jacobian, two-covers, and transported point computations #50, so noreordering is performed anywhere.
that the constant coefficient
eis a square. The fixture equation hase = 7, which is not a square, so the one-argument call is outside thedocumented hypothesis; and even where it does apply it returns points only
modulo negation, which would silently halve a solution set this
repository is contractually required to return in full. This adapter
never calls the one-argument form.
P = [x, y]on thequartic and then returns all integral points (no modulo-negation
qualifier). Supplying that point is this adapter's job; see Deterministic
seed search.
The same handbook subsection states the provenance and method of the routine
(verbatim):
and, for that main routine,
IntegralPoints:Since the handbook says in so many words that the quartic functions are
applications of
IntegralPoints, this is the documentary basis for the twoassumptions recorded under Guarantee, scope and assumptions below: the
Mordell–Weil computation is performed by Magma itself and is not re-derived by
this package.
The handbook's own worked example on the same page fixes the return element
shape — a sequence of two-element integer sequences
[x, y](verbatim):(That example uses the one-argument form legitimately: its constant
coefficient is
1, a square. It is quoted here only to pinp[1],p[2].)Supported inputs
Registered as
MAGMA_SOLVERS["elliptic-quartic"]withelliptic-quartichas no entry inCAPABILITIES/SOLVERStoday and gainsnone: with
use_magma=Falsethe family still has no wired solver.Input regime
v^2 = Q(u),deg Q = 4, all coefficients concrete integers; integralpoints, i.e. effective domain
ZZ(orNN, handled by the existing_restrict_nonnegpost-pass). Verified today (re-run against Sage 10.7 on85f8e0a):qcoeffsis[a, b, c, d, e]descending:Q(u) = a*u^4 + b*u^3 + c*u^2 + d*u + e(the convention used throughout #50, and — see above — exactly thesequence order the pinned intrinsic documents). Match data values are
stringified, so the list is read with
ast.literal_evaland each entryconverted exactly with
ZZ(...); a non-integer entry declines the adapter.The resulting list goes through
encode_int_list, which is the only pathinto the script.
data["x"]/data["y"]givethe user's names for the curve's coordinates — the assignment is built from
those and then ordered with
_ordered(cls, assignment), so the returnedtuples follow the unknowns' order of first appearance: for the fixture
equation that is
(v, u), not(u, v).Domain gate
The rational case is a different problem and this adapter never touches it.
With
domain="QQ"the Magma route is skipped by the capability check andsolvebehaves exactly as today (verified:SolverUnavailable("no automatic solver for family 'elliptic-quartic' yet — ...")). Rational points ongenus-one quartics are #50's business, not this adapter's.
Guards (each one declines:
MagmaFailure, then the standard fallback)coefficient —
encode_int_listrejects both);deg Q != 4;Qnot separable (zero discriminant): the model is then not a smoothgenus-one curve and the routine's hypotheses do not apply;
("ZZ", "NN");INTEGRAL_QUARTIC_SEED_BOUND(thenew guard; see immediately below). This guard declines before any Magma
process is started.
Deterministic seed search
The two-argument intrinsic requires a rational point on the quartic. This
adapter supplies an integral one, found by an exact bounded enumeration in
Sage before Magma is invoked. The helper is fully specified here; there is
nothing to choose at implementation time.
Binding properties of this helper:
detail: it makes the seed a deterministic function of
coeffs, so thegenerated script is reproducible and snapshot-testable. The list has
1 + 2*B = 101entries at the shipped bound.ZZand ignores theuser's requested domain entirely; in particular a
domain="NN"request maystill be seeded by a negative
u. The seed is not a solution beingreported, it is an argument being handed to Magma, and the
NNrestriction is applied to the result afterwards (see Parsing).
INTEGRAL_QUARTIC_SEED_BOUNDis an applicability guard, not acompleteness bound. It bounds how hard the adapter looks for a way in;
it says nothing about how far
IntegralQuarticPointsthen searches, and itnever appears in a
scopestring.Nonethe adapter raisesMagmaFailure(..., outcome="cas-failure")withreason
"no-seed-point"and the standard fallback runs, exactly as for anyother declined guard. The adapter must never turn "I could not find a point
to start from" into
kind="empty", and must not start a Magma process inthis case.
v^2 == Q(u)inZZ) immediately beforeencoding, so a bug in the enumerator cannot put a non-point into the script.
The fixture's seed, computed by hand and machine-verified. For
Q = [2, -3, 1, -5, 7], i.e.Q(u) = 2*u^4 - 3*u^3 + u^2 - 5*u + 7, thedeclared enumeration order gives
uQ(u)0712 - 3 + 1 - 5 + 7 = 2-12 + 3 + 1 + 5 + 7 = 18232 - 24 + 4 - 10 + 7 = 99 = 3^2so the helper returns exactly
(2, 3)and stops;-2is never reached. Inwords, and this is the statement the test pins:
Q(0) = 7is not a square,Q(1) = 2is not a square,Q(-1) = 18is not a square, andQ(2) = 9 = 3^2is. (Verified against Sage 10.7 during this edit: the declared enumeration
was run and the first hit is
(2, 3), with both entriesZZ;disc(Q) = 147348 != 0, so the separability guard passes.)P = [2, 3]is the seed thepinned fixture asserts.
Output and API contract
Script template
diophantine_classifier/data/magma/integral_quartic_points.m, assembled byon top of
prelude.m. Both placeholders are filled only with typedinteger-encoder output; there is no other path into the script.
For the fixture this renders literally and completely as
p[1],p[2]is the documented element shape: the intrinsic's return type is[ SeqEnum ], a sequence of two-element integer sequences[x, y], as thehandbook's own worked example quoted above shows.
The
try/catchwrapper stays mandatory: a Magma-side error (a rankcondition the routine cannot settle, a Mordell–Weil computation that fails,
an unexpected model rejection) surfaces as
status: "error"and takes thefallback path — never a partial parse and never a completeness claim.
Payload schema (
data)Unchanged from the previous design: the two-argument intrinsic returns
the same shape as the one-argument one, so the payload stays a flat list of
(u, v)pairs.points(u, v)in the curve's own coordinatesIntegers are decimal strings; bare JSON numbers are rejected by the protocol
(#76 §2). The seed itself is not echoed in the payload — it is already
recorded in the evidence
commandstring.Parsing into a
SolutionSetdecode_inteach coordinate; build the assignment{data["x"]: u, data["y"]: v}and order it with_ordered(cls, ...).sorted(set(...))— deduplicate and sort. (The two-argument form returnsall integral points rather than one of each
±pair, so(u, v)and(u, -v)both arrive; deduplication is still applied because nothing inthe intrinsic's contract promises a duplicate-free sequence.)
discard any returned pair for any reason.
solvers._verifiedis thefirewall; a point that fails
ParsedEquation.acceptsraisesInternalConsistencyError(this equation carries no nonvanishingconditions, so silent dropping is not permitted).
NNrestriction is applied afterwards, by the existing_restrict_nonnegpost-pass — never as a prefilter inside the adapter,and never inside the seed search.
kind = "finite-complete"when nonempty,"empty"otherwise.representationis left to_derived_representation, which givesPartialSearch(solutions, scope)for afinite-completeresult that isnot
proved. It must not be overridden with aFiniteSet: that wouldassert unconditional completeness.
scope_info = Scope(domain_scope="integral points of the affine model", ordering_convention="unknowns in order of first appearance in the input equation").Guarantee, scope and assumptions
IntegralQuarticPointsbounds the integral points by elliptic-logarithmestimates on the associated elliptic curve (handbook quote above), and those
estimates are computed from a Mordell–Weil basis that Magma itself
computes. The honest report is therefore:
conditionalrequires nonemptyassumptions(docs/SEMANTICS.md§3), andthese are the two things the classifier cannot re-derive: it re-checks every
returned point against the user's equation, but not that the search bound was
valid or that the basis was saturated. This mirrors how
_solve_weierstrassalready reports agens(proof=False)fallback(
assumptions=("the unproved Mordell-Weil basis is correct and saturated",)).The seed bound does not enter
scope.scopedescribes the set theroutine reports on;
INTEGRAL_QUARTIC_SEED_BOUNDonly decides whether theroutine is invoked at all.
No statement is made — here or anywhere — that a Magma-backed answer settles
the family in general: the guarantee above belongs to this routine on this
input regime.
Evidence
The
commandstring records the seed, so a certificate identifies the exactcall that was made. Both keys already resolve in
data/references.bib(verified today):
Tzanakis2013is already cited by theelliptic-quarticfamily (
why: "solving quartic elliptic equations via elliptic logarithms"),and
StroekerTzanakis1994(cited today byelliptic-weierstrass) isprecisely the
[ST94]the handbook names as the routine's main theoreticalreference in the quotation above. No bibliography change is required. If the
implementation adds one, it follows the standard
rules (author/title/journal/volume/year, nonempty
why,doiverified atcommit time,
urlonly for legally free copies,make referencespassing).Failure and fallback semantics
Guard declined (including no seed point found), executable missing,
timeout, nonzero exit,
status != "ok"(including an error raised on rankconditions), oversized output or unparseable payload 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) and is
what the equivalence test pins.
Note the ordering, because the no-seed test depends on it:
_local_emptinessruns before the family's
SolverUnavailableis raised, so an equation witha real or small-modular obstruction never reaches this adapter at all. That is
why the no-seed fixture below is chosen to be an equation the local layer
cannot settle.
Acceptance criteria
Fixture (deterministic, fake runner, no Magma)
Equation:
v^2 = 2*u^4 - 3*u^3 + u^2 - 5*u + 7(the quartic already used asthe #50 fixture;
qcoeffs == [2, -3, 1, -5, 7], verified).Fake payload
data["points"] = [["2", "3"], ["2", "-3"]]— genuine integralpoints, since
Q(2) = 2*16 - 3*8 + 4 - 10 + 7 = 9 = (±3)^2(verified).Assertions on
solve("v^2 = 2*u^4 - 3*u^3 + u^2 - 5*u + 7", use_magma=True)with the fake runner:
result.variables == ('v', 'u');result.solutions == [(-3, 2), (3, 2)]— the(u, v)payload pairsreordered to the unknowns' order of first appearance and sorted;
result.kind == "finite-complete",result.completeness == "conditional",result.complete is False;result.scope == "all integral points"andresult.assumptions == ("Magma's Mordell-Weil computation for the associated curve is correct " "and saturated", "the external Magma routine computed correctly");"50"does not appear inresult.scope(the seed bound is not acompleteness bound);
Evidencewithkind == "external-computation",software == "magma",versionequal to the fake payload's version, andevery reference key resolvable in
bibliography();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 (
decode_exactround-trips each coordinate);data["points"] == []) yieldskind == "empty",completeness == "conditional", the sameassumptions, andrepresentation.form == "empty";domain="NN"on the same payload yields only(3, 2)(the existing_restrict_nonnegpost-pass), with everything else unchanged — and thegenerated script is byte-identical to the
ZZone, proving the seedsearch is model-level and unaffected by the requested domain.
The five required seed/script/signature tests
Script snapshot — the generated script contains both
QandP.Assert all of:
"Q := [2, -3, 1, -5, 7];" in script,"P := [2, 3];" in script,"IntegralQuarticPoints(Q, P)" in script, and — guarding against aregression to the unusable form —
"IntegralQuarticPoints(Q);" not in script. Compare the whole scriptagainst the literal rendering shown above, not only these substrings.
The fixture seed is exactly
(2, 3)._integral_quartic_seed([2, -3, 1, -5, 7]) == (2, 3)exactly, with bothentries
ZZ. Assert also the intermediate facts that pin the enumerationorder:
Q(0) == 7,Q(1) == 2andQ(-1) == 18are each not squareswhile
Q(2) == 9is, so no earlier abscissa can be returned and-2isnever examined.
No seed → decline without starting Magma. Fixture equation
v^2 = u^4 + 5203, i.e.qcoeffs == [1, 0, 0, 0, 5203](verified today:classifygives thatqcoeffswithunknowns == ('v', 'u'), anddisc(Q) = 36057984109312 != 0, so every other guard passes). Verifiedfacts that make this fixture exactly right:
Q(u)is not a nonnegative square for any|u| <= 50, so_integral_quartic_seed([1, 0, 0, 0, 5203]) is Noneafter examining all101 declared abscissas;
Q(51) = 6770404 = 2602^2, so(u, v) = (±51, ±2602)are genuine points —which is what makes "a failed seed search is not an emptiness claim" a
real assertion rather than a vacuous one;
solve("v^2 = u^4 + 5203")today raises the family'sSolverUnavailablewith"no small local obstruction found either"(verified), so the local layer is inconclusive and the adapter really is
reached.
Assert:
_integral_quartic_seed(...) is None; the documented fallback runswith reason
"no-seed-point"andoutcome == "cas-failure"recorded,ending in the family's
SolverUnavailable; no process is started atall — assert this with a fake runner that fails the test if it is
invoked (a stub raising
AssertionErrorwhen called), not merely byinspecting output; and
kindis never"empty"on this path.(Do not use
v^2 = -u^4 - 1for this test. Verified today: it isintercepted earlier by the real-obstruction layer, which returns
kind="empty",completeness="proved"with the description "the equationhas no real solutions ...", so the adapter is never consulted and the test
would assert nothing about the seed guard.)
Invalid returned point trips
InternalConsistencyError. Payload[["3", "5"]]on the main fixture —Q(3) = 82and5^2 = 25, so thepair is not a point of the curve (verified) — makes
solvers._verifiedraise
InternalConsistencyError; the bad point appears in no returnedsolution set, and the adapter does not pre-filter it away.
Real-Magma test confirming the V2.29 signature. A
magma-marked test,skipped by default, runs the fixture against a real install and asserts
that the two-argument call is the one that works on the pinned version:
it calls
IntegralQuarticPoints(Q, P)and requires success, andseparately asserts that the run's reported Magma version is
V2.29orhigher (or is an explicitly allow-listed earlier version recorded in the
test). Every returned point must verify against the equation. This test is
never a substitute for the deterministic tests above.
Failure-path tests (main fixture, fake runner)
SolverUnavailableabove, with thereason appended;
outcome == "unsupported-family",family == "elliptic-quartic";magma_timeout=1, sleeping fake) → same, reason mentionsresource-exceeded;status: "error"payload (standing in for a rank-condition error) →same;
domain="QQ"withuse_magma=Truebehaves exactly as today (thecapability gate skips the adapter; no process is started).
Unconditional and repository-wide
use_magma=False, and withDIOPHANTINE_CLASSIFIER_MAGMApointingat the fake,
solve("v^2 = 2*u^4 - 3*u^3 + u^2 - 5*u + 7")raises thesame
SolverUnavailablemessage as on the pre-epic tree. Runsunconditionally.
elliptic-quarticis already matched);the adapter's fixture equations are added to the Magma adapter test
catalogue introduced by Core opt-in Magma process runner #76.
make test,make doctest,make coverage(100%) andmake registry-docsclean without Magma installed; every newfunction has a Sage-convention docstring whose
EXAMPLESpasssage -twithout Magma.
_integral_quartic_seedin particular has anEXAMPLESblock exhibiting the
(2, 3)seed and aNonereturn.Out of scope
Jacobian/2-cover machinery of Binary-quartic invariants and Jacobian convention for elliptic quartics #61/Explicit two-cover map from a binary quartic to its Jacobian #62).
denominators here:
v^2 = Q(u)with denominators is a different model anda different integral-points question).
including any attempt to move the model to one with square constant
coefficient so that the one-argument form would apply.
INTEGRAL_QUARTIC_SEED_BOUND, or seeding froma non-integral rational point. Both are possible extensions; neither is in
this issue, and neither changes any claim made here.
Family.fill_code.Note for the record: the registry's display snippet for this family is
today
IntegralQuarticPoints([2, -3, 1, -5, 7]);(verified), i.e. theone-argument form, which — as pinned above — does not satisfy its own
documented hypothesis for this fixture (
e = 7is not a square). Theexecuted script is the two-argument file specified in this issue and is
unaffected; correcting the display template is a separate registry change
and is not made here.