Skip to content

Identify abelian number fields in the jump box without polredabs (LMFDB#5471) - #42

Closed
roed-math wants to merge 4 commits into
mainfrom
ai/t43-nf-abelian-lookup
Closed

Identify abelian number fields in the jump box without polredabs (LMFDB#5471)#42
roed-math wants to merge 4 commits into
mainfrom
ai/t43-nf-abelian-lookup

Conversation

@roed-math

Copy link
Copy Markdown
Owner

Looking up a number field by polynomial always ran polredbest+polredabs, which can hang — e.g. kernel polynomials of Dirichlet characters of large degree run for many minutes without producing an answer. This implements the abelian-case identification proposed by @jwj61: after polredbest, certify the field abelian with galoisinit on an order maximal at the small primes of the discriminant (the discriminant is never factored), read off the field discriminant from that order, query nf_fields by degree/signature/discriminant, and confirm candidates by exhibiting a root of their defining polynomial in the input field, so any label returned is provably correct; on any failure we fall back to the unchanged polredabs path. In addition, polynomials whose degree exceeds every field in nf_fields now return "not in the database" immediately: a degree-94 kernel polynomial of conductor 283 that previously ran polredabs for >300s now answers in 0.15s, and the degree-47 example from the issue is identified from a non-reduced defining polynomial in ~2s with a certificate. Tested on abelian fields of degree 8, 16 and 47 entered via deliberately non-reduced polynomials, plus non-Galois/non-abelian negatives; all 36 number_fields tests pass. Addresses LMFDB#5471.

🤖 Generated with Claude Code

roed314 and others added 3 commits July 19, 2026 14:33
…LMFDB#5471)

Add a fast path to WebNumberField.from_polynomial implementing jwj61's
abelian identification: after polredbest, certify the field abelian with
galoisinit over an order maximal at the small primes of the discriminant
(never factoring the discriminant), compute the field discriminant from
that order, query nf_fields by degree/signature/discriminant, and confirm
candidates by exhibiting a verified root of their defining polynomial, so
any label returned is provably correct; all failures fall back to the
unchanged polredabs path.  Inputs whose degree exceeds every field in
nf_fields now return "not in database" immediately instead of running
polredabs (a degree 94 kernel polynomial went from >300s to 0.15s; the
issue's degree 47 field is identified from a non-reduced polynomial in ~2s).

Verified: all 36 number_fields tests pass (3 new: identified jumps for the
issue's degree 47 field and Q(zeta_32) from non-reduced polynomials,
abelian_nf_label unit checks, fast large-degree jump); pyflakes clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…LMFDB#5471)

Two review fixes, both of which could make the abelian fast path return
None and fall back to the expensive polredabs path (neither could produce
a wrong label, which the exact root check still rules out).

Recognize a large ramified prime that appears in the discriminant as a
prime power: in a field of degree at least 4 a ramified p contributes p^e
with e > 1, and the index contributes further powers, so the cofactor left
after dividing out the small primes is typically p^e rather than p and
is_pseudoprime() rejected it, leaving p out of S and making DK (hence the
database query) wrong.  Use is_pseudoprime_power(get_data=True) and append
the base; a cofactor with several large primes is still left unfactored.
The selection of these primes moves to a helper _known_discriminant_primes.

Give nfroots the defining polynomial rather than the conditional nf
structure: nfinit([T, S]) is only certified maximal at the primes of S,
and the PARI manual warns that nfroots may miss a root when handed such a
structure, while it recovers in polynomial time from nf.pol.  The exact
substitution certificate and the polredabs fallback are unchanged.

Verified: all 38 number_fields tests pass, including two new regressions
(a discriminant whose large ramified prime occurs as p^7, and an abelian
field entered with an index divisible by two primes above 10^5, so that
nfroots runs against a conditional order); pyflakes and ruff clean.  On
the degree 47 conductor 283 example nfroots goes from 0.37s to 0.78s and
the whole fast path takes 1.5s, against more than 300s for polredabs.

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

Copy link
Copy Markdown
Owner Author

Pushed fd85f3d, which addresses both review points. Only web_number_field.py and test_numberfield.py changed; the exact substitution certificate and the polredabs fallback are untouched.

1. nfroots no longer gets the conditional structure

field_pol = nf.getattr("pol") is taken right after nfinit([T, S]) and passed to nfroots in the candidate loop. (cypari2 has no nf.pol(): getattr is its accessor for PARI member functions.) The caveat is in the installed PARI 2.17.2 documentation too, which says of the polynomial form that "nfroots is able to recover in polynomial time in this case", instead of potentially missing a factor. Both forms return the roots as t_POLMOD, so gpol.subst("x", rt) == 0 behaves identically.

Cost on the flagship example (degree 47, conductor 283): nfroots goes from 0.37s to 0.78s, and abelian_nf_label as a whole takes 1.5s, against more than 300s for polredabs.

One caveat on the regression test, since the review asked for a test that fails before the change. I could not construct an input where PARI 2.17.2 actually misses a root when handed the conditional nf. The suggested Q(zeta_20) scaled by 100003 * 100019 returns all 8 roots either way, and so does every variant I tried: cyclotomic degrees 8, 16, 24 and 32, a multiquadratic field, and cyclic subfields of Q(zeta_13) and Q(zeta_17), scaled by that product and by its powers, with indices up to 1200 digits. The order really is conditional in all of them (the roots have denominator divisible by the omitted primes, so they are not even in nf.zk), and PARI recovers anyway. The new test therefore guards the conditional-order path rather than reproducing a miss; the change itself is the documented-correct call and removes the theoretical gap, at the measured cost above. Happy to drop the test if you would rather not pay 0.3s for a case that currently cannot fail.

2. Large ramified prime appearing as a prime power

Confirmed and fixed. The cofactor test is now

p, e = C.is_pseudoprime_power(get_data=True)
if e:
    S.append(p)

and the selection moved to a small helper _known_discriminant_primes(D) so it can be tested without a suitable high-conductor field in nf_fields. A cofactor with several large primes is still left unfactored, and D is never fully factored. The comment now says why the cofactor is a prime power rather than a prime: a ramified p contributes p^e with e > 1 once the degree is at least 4, and the index of Z[x]/(T) contributes further powers.

Validation

sage -python -m pytest lmfdb/number_fields/test_numberfield.py: 38 passed in 18m55s. pyflakes and ruff check --preview --select=E722 clean on both files.

  1. Degree 47 conductor 283 jump still takes the fast path and returns its label (1.5s in abelian_nf_label).
  2. Degree 16 and degree 8 abelian tests still pass.
  3. Non-Galois and Galois-but-nonabelian inputs still return None.
  4. New prime-power-cofactor test passes, and fails as expected under the old is_pseudoprime() (for D = 2^20 * 5^10 * 100003^7 the cofactor 100003^7 is not a pseudoprime, so the prime was dropped).
  5. New conditional-order root test passes; as described above it does not fail under nfroots(nf, gpol) on PARI 2.17.2.
  6. No test weakens or removes the exact root-substitution certificate.

…MFDB#5471)

Passing nf.pol to nfroots for every candidate protects against the root
that a conditional nf structure can miss, but it also slows down the
candidates that succeed, which is the case the fast path exists for: on
the degree 47 conductor 283 example nfroots went from 0.37s to 0.78s.

A missed root can only show up as an empty result, so try the nf form
first and ask again with the defining polynomial only when nothing came
back, which is exactly the situation that would otherwise fall through to
polredabs.  The degree 47 lookup is back to 1.18s end to end, and a
candidate that really is not isomorphic to K costs a second nfroots call
that returns immediately (0.00s at degree 8 and 16).

Verified: all 38 number_fields tests pass; pyflakes and ruff clean.

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

Copy link
Copy Markdown
Owner Author

Correction to the timing note in my previous comment: passing nf.pol to nfroots for every candidate was slowing down the successful lookups, which is the case this PR exists to make fast. Fixed in 23f7ab0.

A root missed because of the conditional order can only surface as an empty result, so the nf form now runs first and the defining polynomial is used only when nothing came back, which is exactly the situation that would otherwise fall through to polredabs.

Degree 47, conductor 283, abelian_nf_label end to end:

total nfroots
before the review 1.18s 0.37s
always nf.pol (fd85f3d) 1.53s 0.78s
retry on empty (23f7ab0) 1.18s 0.37s

That query returns a single candidate and it matches, so the second call never runs. Degree 8 and 16 are unaffected either way (nfroots under 10ms in both forms).

The trade is not free in every shape: the degree 16 query returns 16 candidates, so the 15 non-isomorphic ones now do two nfroots calls instead of one. That is immaterial here (0.31s for the whole path either way, and a non-matching candidate returns immediately in both forms), and it seems the right way round, since many candidates sharing a discriminant is a small-discriminant phenomenon while the expensive nfroots calls happen at large degree and large discriminant, where the candidate is unique.

Verified: all 38 number_fields tests pass (16m08s); pyflakes and ruff check --preview --select=E722 clean. Everything else from the previous comment stands, including the caveat that I could not construct an input where PARI 2.17.2 actually misses a root, so the added conditional-order test guards the path rather than reproducing a failure.

@roed314

roed314 commented Aug 5, 2026

Copy link
Copy Markdown

GPT signed off.

@roed-math

Copy link
Copy Markdown
Owner Author

Superseded by LMFDB#7176, 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