Skip to content

Validate Euler factor links to abelian varieties over Fq on L-function pages - #29

Closed
roed-math wants to merge 4 commits into
mainfrom
ai/t37-euler-avfq-links
Closed

Validate Euler factor links to abelian varieties over Fq on L-function pages#29
roed-math wants to merge 4 commits into
mainfrom
ai/t37-euler-avfq-links

Conversation

@roed-math

Copy link
Copy Markdown
Owner

L-function pages of motivic weight 1 link each good-prime Euler factor to the corresponding
isogeny class of abelian varieties over F_p (feature introduced in LMFDB#6573). This PR hardens that
linking: a new Lfactor_to_gq check confirms the factor is genuinely the reciprocal Weil
polynomial of an abelian variety over F_p (even degree, integral, lead p^g, self-dual) before a
label is displayed, so malformed or non-Weil factors now give an empty cell instead of a
ValueError-driven 500 or a wrong/garbage label, while out-of-range (g,q) still shows a plain
unlinked label. The av/Fq coverage lookup now happens lazily at first use instead of at module
import, and the row's prime is passed in so the label can be checked against it. Verified
against av_fq_isog data (11.a links 1.2.c at p=2 with 5 = #E(F_2) points; genus 2 curve 169.a
links 2.2.d_f, its reduced Jacobian) and covered by new tests in test_lfunctions.py; the full
lfunctions suite passes.
Addresses LMFDB#4535.
Addresses LMFDB#4922.

🤖 Generated with Claude Code

roed314 and others added 2 commits July 19, 2026 11:59
…FDB#4535, LMFDB#4922)

The isogeny class column of the L-function Euler factor table (added in
LMFDB#6573) derived (g, q) from the factor's leading coefficient via
nth_root, which raises ValueError (a 500 page) when the coefficient is
not a perfect g-th power, and produced garbage labels for factors that
are not genuine Weil polynomials (negative lead, non-self-dual).  Add
Lfactor_to_gq, which checks that the factor is the reciprocal Weil
polynomial of an abelian variety over F_p (even degree, integral,
constant term 1, leading coefficient p^g, self-duality
a_{2g-i} = q^{g-i} a_i) and pass the row's prime in from lfuncEPhtml,
leaving the cell empty otherwise; out-of-range (g, q) still shows a
plain unlinked label.  Also defer the AbvarFqStats query from module
import to first use.

Verified on 11.a (links 1.2.c at p=2, matching 5 = #E(F_2)), genus 2
curve 169.a (2.2.d_f at p=2, the reduced Jacobian, matching
av_fq_isog.poly and 19 points), an EC/NF and degree 6/8 CMF pages
(labels beyond av/Fq coverage stay unlinked), and EC/G2C isogeny class
pages; new tests in test_lfunctions.py plus the full suite (25 passed)
and pyflakes are clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…v/Fq links (LMFDB#4535)

Lfactor_to_gq checked reciprocity but not the root condition, so self-dual
non-Weil factors like [1, 10, 2] (x^2 + 10x + 2 violates the Hasse bound at
p = 2) produced links to wrong or nonexistent isogeny classes.  Now the
reversal must pass Sage's exact is_weil_polynomial (Sturm's theorem, no
floating point; 26-53us per call for g = 1..6).  For proper prime powers,
where Weil does not imply realizable (x^2 + 25 over F_25, Honda-Tate), the
link emitter confirms the label against av_fq_isog; pages always pass a
prime, where Weil is exactly Honda-Tate, so page renders add no queries.
polynomial_unroll now rejects empty/non-list payloads with ValueError
(previously IndexError escaped to a 500) and gives prod an initial
polynomial so exponent-0 factorizations no longer raise; Lfactor_to_gq
catches these and returns None.

Verified: new must-reject unit tests ([1,10,2], boundary (x+2)^2, malformed
flat/factored payloads); av/Fq link counts on six real L-function pages
(EC/Q 11.a, G2C 169.a, EC/NF deg 4, deg 6, deg 8) identical before/after;
full lfunctions suite 27 passed; pyflakes clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@roed-math

Copy link
Copy Markdown
Owner Author

Addressed both of the external review's P2 findings: commit b0a99a2.

Weil condition: the validator now requires the reversed factor to pass Sage's is_weil_polynomial() — exact arithmetic (squared-roots trace polynomial + Sturm counting via PARI polsturm, no floats). The reviewer's [1,10,2] example is rejected in both flat and factored form (regression tests added); the supersingular boundary case (x+2)² over F₄ is correctly accepted. Measured cost 26–53 µs/call, ~1 ms/page worst case.

Beyond the review: while verifying, the agent found that Weil ⟹ exists-in-DB only holds for prime q (Honda–Tate): x²+25 is Weil but 1.25.a doesn't exist. Labels for proper prime-power q are now confirmed against av_fq_isog before linking — a cold path, since current pages always pass prime p, so zero added queries on real pages.

Empty/malformed payloads: polynomial_unroll now raises ValueError on empty/non-list input instead of IndexError, an exponent-0 factorization no longer hits an uncaught NotImplementedError (a second 500 path found en route), and the caller catches these → empty cell, not a 500.

27/27 lfunctions tests pass; av/Fq links verified identical before/after on six real pages (11.a→1.2.c etc.).

roed314 and others added 2 commits August 4, 2026 17:23
Follow-up to the review of the malformed-Euler-factor hardening.  Behavior
for valid local factors is unchanged: valid in-range factors still link to
av/Fq, valid factors outside database coverage still show a plain label,
and bad primes still get no link.

Renderer: the nested row() in lfuncEPhtml indexed poly[0] and chose a
formatter before its try block, so an empty local factor raised IndexError
and a malformed factorization raised ValueError out of the page (a 500);
the fallback caught only IndexError, used the outer loop variable j instead
of the row's prime, and emitted duplicated closing tags with the wrong
number of cells.  Row preparation now lives in a module-level
euler_factor_row_data(), which validates the input shape inside the
exception boundary and returns (factors, gal_groups, isog_class); row()
builds the HTML once from that triple.  Malformed data give one valid row
with the row's own prime, the factor 'not available' and an empty isogeny
cell.  Only the exceptions expected from malformed factor payloads are
caught.  The show more/less rows now also account for the isogeny column.

polynomial_unroll: expanding a factorization with [fac] * exponent turns a
zero or negative exponent into an empty list, so a malformed factor could
silently vanish while a valid one remained, contradicting the contract that
exponent-zero factorizations are malformed.  Every entry is now validated
(two elements, nonempty coefficient list, strictly positive integer
exponent, bool rejected explicitly) and the factors are accumulated by
exponentiation rather than list expansion.

Tests: test_euler_factor_isogeny_class_out_of_range no longer assumes
dimension 3 data stop at q = 25 (that becomes false as av_fq_isog grows);
it patches AbvarExists instead.  New tests cover exponent validation, the
extracted row preparation, and lfuncEPhtml itself for both degree 2 (no
Galois column) and degree 4 (Galois column), checking the fallback row's
prime and the table's column count.  The three new tests fail on the
previous commit and pass here.

Verified: sage -python -m pytest lmfdb/lfunctions/test_lfunctions.py
(30 passed); pyflakes, pylint and ruff clean; the EC and genus 2 isogeny
class pages, which also call Lfactor_to_label, still render their av/Fq
links.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@roed-math

Copy link
Copy Markdown
Owner Author

Addressed the review in b3962a1. Behavior for valid local factors is unchanged throughout: valid in-range factors still link to av/Fq, valid factors outside database coverage still show a plain label, and bad primes still get no link.

Required change 1: the row renderer. row() inside lfuncEPhtml indexed poly[0] and picked a formatter before its try, so poly=[] raised IndexError and malformed factorizations like [[]] and [[[1,2,2]]] raised ValueError (which the except IndexError did not catch either) straight out of the page. The fallback also used the outer loop variable j instead of the row's prime, emitted duplicated closing tags, and produced the wrong number of cells.

Row preparation now lives in a module-level euler_factor_row_data(poly, p, display_galois, complex_coefficients, isogeny_label) returning (factors, gal_groups, isog_class). All indexing, the None in poly test, formatter selection and formatter invocation happen inside its exception boundary, which catches only TypeError/ValueError/ArithmeticError/IndexError (the same set Lfactor_to_gq already used) rather than a bare except Exception. row() builds the HTML once from that triple, so there is no second inconsistent row. Malformed data give one valid row with the row's own p, the factor not available, and an empty isogeny cell.

One extra fix in the same area, worth a look since it wasn't in the review: the show more / show less toggle rows built last_entry accounting for the Galois column but not the isogeny column, so those rows were one cell short of the header whenever the isogeny column was displayed. The new test asserts the toggle rows span the table, which is what caught it.

Required change 2: exponents. polynomial_unroll expanded factorizations with [fac] * exponent, and Python turns both zero and negative repetitions into an empty list, so a malformed factor could disappear while a valid one remained. Every entry is now validated (two elements, nonempty coefficient list, strictly positive integer exponent, bool rejected explicitly since it is an int subclass and True in ZZ), and the factors are accumulated by exponentiation rather than list expansion. Both cases from the review now return None:

Lfactor_to_gq([[[1, 2, 2], 1], [[1, 10, 2], 0]], 2) is None
Lfactor_to_gq([[[1, 2, 2], 1], [[1, 10, 2], -1]], 2) is None

Test hardening. test_euler_factor_isogeny_class_out_of_range no longer assumes dimension 3 data stop at q=25; it patches AbvarExists to return False and asserts the plain label with no anchor, then checks the unpatched call does produce the link. The real-page assertions for a known link are kept, minus the one that would break as av_fq_isog grows.

New tests: test_Lfactor_exponent_validation (zero, negative, nonintegral, True, plus the valid repeated-factor cases), test_euler_factor_row_data (the extracted helper, including that gal_groups is never empty since the caller indexes gal_groups[0]), and test_euler_product_table_malformed_factor, which goes through lfuncEPhtml itself for both 2-11-1.1-c1-0-0 (no Galois column) and 4-13e2-1.1-c1e2-0-0 (Galois column), asserting the fallback row uses the prime 3 and that the row and toggle rows match the header's column count. Those three tests fail on d415e64 and pass here.

Verification. sage -python -m pytest lmfdb/lfunctions/test_lfunctions.py gives 30 passed. pyflakes over lmfdb/, pylint and ruff (the tox -e lint targets) are clean. The elliptic curve and genus 2 isogeny class pages also call Lfactor_to_label/AbvarExists, but with unfactored coefficient lists, so they never reach the changed factorization path; I confirmed /EllipticCurve/Q/11/a/ and /Genus2Curve/Q/169/a/ still render with their av/Fq links.

@roed314

roed314 commented Aug 5, 2026

Copy link
Copy Markdown

GPT signed off.

@roed-math

Copy link
Copy Markdown
Owner Author

Superseded by LMFDB#7165, opened upstream from this same branch. Closing here; review continues upstream.

@roed-math roed-math closed this Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants