Skip to content

Add more column options to p-adic field dynamic statistics - #7163

Open
roed-math wants to merge 6 commits into
LMFDB:mainfrom
roed-math:ai/t25-lfstats-columns
Open

Add more column options to p-adic field dynamic statistics#7163
roed-math wants to merge 6 commits into
LMFDB:mainfrom
roed-math:ai/t25-lfstats-columns

Conversation

@roed-math

Copy link
Copy Markdown
Contributor

Closes #6542.

The dynamic statistics page for p-adic fields previously offered only Galois group and Galois Artin slopes as columns. This extends LFStats.dynamic_cols to every searchable column (residue characteristic, degree, ramification index, residue field degree, discriminant exponent, Galois group, automorphisms, unramified/tame degree, top slope, Galois/visible/hidden slopes, indices of inseparability, associated inertia, jump set), with display metadata and click-through query formatters matched to the existing search boxes.

Sort keys map not-computed (NULL) values to sort first, so columns with NULLs no longer crash when selected unconstrained, and Galois-group handling tolerates fields with no computed Galois group. Default p/c buckets are provided, and the static statistics page is verified unchanged (discdisp pins empty buckets).

Links to "create your own" statistics are added from the browse index and the statistics page. Adds tests covering the links and rendering of all new columns.


Ported from roed-math#27, where the full write-up and comment history live.

🤖 Generated with Claude Code

roed314 and others added 5 commits July 19, 2026 11:41
Extend LFStats.dynamic_cols from just galois_label/slopes to every
searchable column (p, n, e, f, c, galois_label, aut, u, t, top_slope,
slopes, visible, hidden, ind_of_insep, associated_inertia, jump_set), so
users can build their own statistics and click through to matching
searches.  Supporting display metadata (short_display, top_titles,
knowls, formatters, sort_keys, query_formatters, default p/c buckets) is
added to match the search boxes.  sort_keys map None to a first-sorting
sentinel so columns with NULLs (slopes, hidden, u, t, galois_label) no
longer crash when picked unconstrained; query_formatters emit valid
search URLs (topslope=, slopes=...&slopes_quantifier=exactly, empty
constraints for un-searchable values).  galformatter/galsortkey/galquery
now tolerate the 3,996 NULL galois_labels.  discdisp pins buckets={} so
the static /stats page is unchanged.  Links to the dynamic stats page are
added from the browse index (short_summary) and /stats (summary).

Verified: sage -python -m pytest lmfdb/local_fields/test_localfields.py
(10 passed, incl. new test_stats_pages/test_dynamic_stats); /padicField/stats
byte-identical to base except the added "create your own" link (fixed
PYTHONHASHSEED diff); formatter/sort-key/query-formatter sweep over all
distinct DB values of the new columns with no errors; new-style
click-through URLs resolve (e.g. slopes exact match returns 10 fields);
pyflakes clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mic stats (LMFDB#6542)

P2: the dynamic-statistics query_formatters encoded not-computed/null buckets
as empty url parameters (gal=, u=, slopes=, associated_inertia=), which the
search parsers silently ignore -- so clicking a null bucket's count opened an
UNFILTERED search returning every field instead of the counted records.  There
is no url-level null-search syntax (empty params are dropped; $exists:False is
not reachable from any parser used by common_parse), so the drill-down link is
now suppressed for such buckets.  The null query_formatters return a new
NO_SEARCH_QUERY sentinel; LFStats.display_data (via the testable
_suppress_null_links helper) blanks any assembled drill-down url containing it,
and stat_1d.html now guards the link with {% if c.query %} exactly as
stat_2d.html already did.

P3: formatbracketcol handled empty lists/strings but not None, so ind_of_insep/
associated_inertia could render a literal $None$ bucket label; None now maps to
the standard "not computed" presentation.

Verified (sage -python, devmirror): pytest lmfdb/local_fields/test_localfields.py
-> 12 passed, incl. two new tests (unit coverage of the sentinel query_formatters,
_suppress_null_links for counts+grid, and formatbracketcol(None/''/[]); page-level
coverage that the galois_label/slopes/hidden not-computed buckets render with no
empty-parameter link and no sentinel leak, while non-null buckets still filter
correctly).  Confirmed the not-computed galois_label bucket (3996 fields) now has
a blank query, and non-null drill-downs (p=2,n=4 -> 12 cyclic quartics; n x e)
still produce correct filtered links.  pyflakes clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three ways a count on the dynamic statistics page could link to a search
returning different records than the count, all of them in the generic
statistics framework rather than the p-adic tables:

Cells of a two-dimensional grid with no records were synthesized by
KeyedDefaultDict from the formatted row and column headers, so their urls were
built from displayed values (TeX, html, "not computed") rather than stored ones.
The totaler intersects the urls of a row to find the row's constraint, so a
sparse row lost it: the "not nilpotent" total on the abstract groups statistics
page linked to /Groups/Abstract/? and returned all 1.5 million groups instead of
the 455903 counted.  display_data now indexes counts by the stored values and
builds every url, empty cells included, from those, so a formatter is free to
produce TeX or html.  Values that display identically share a row, adding their
counts, rather than one silently replacing the other.

Drill-down urls were serialized from the parsed query, whose columns are often
not parameters the search page accepts: dynamic statistics constrained by Artin
slopes produced slopes_tmp=2A, which the p-adic parser ignores, so clicking a
count opened a broader search.  dynamic_setup now passes the search boxes the
user filled in as link_constraint, which reproduce the same query by
construction, and a test asserts they re-parse to it.

Bucket endpoints for top_slope were compared as text against the fixed-width
decimal encoding the column is stored in, so a bucket of 1-2 counted the wrong
fields, and topslope_query sliced 12 characters off every endpoint, turning the
bucket 1-2 into topslope=-.  Buckets are now encoded through a bucket_encoders
hook before they reach the backend and decoded for display and links, and an
endpoint that is not a rational is rejected with the usual message.

Along the way: the sentinel for unsearchable values moves into the framework, so
a null bucket is linkless in every table rather than only in p-adic ones; totals
that share no constraint are no longer linked; total urls list their parameters
in a fixed order rather than set order; invalid input to the dynamic statistics
page flashes an error instead of raising a 500; the default p and c buckets are
left open above so no field is omitted as the database grows; a not-computed
jump set is distinguished from an empty one; and nilp_qformatter maps the stored
-1 to nilpotent=no, since nilpotency_class=-1 matches nothing.

Every statistics page in LMFDB renders identically to before apart from those
fixes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s it

The total row appended the bare column name to the url as a marker, which is
not a search constraint: an empty parameter is ignored by the parsers, so the
link returned every record rather than the ones counted, and the parameter is
often not even one the search page accepts (galois_label rather than gal).

The statistics backend computes a total over the records where the column is
not null, so the discrepancy is real wherever a column is not computed for
every record.  On the classical modular forms statistics page the analytic rank
block totalled the 281885 forms whose analytic rank is known and linked to a
search returning all 1141510 newforms; dynamic statistics for Galois groups with
p=7 and n=21 totalled 1324 of 3784 fields.

A total is now linked only when the search page can describe exactly the records
it counts: the constraint itself, which is right when the column is computed for
every record satisfying it.  Totals over buckets (which cover only the buckets
displayed), over split lists (which count entries rather than records), and over
a column the constraint also restricts (whose parameters the urls leave out, so
that each count can constrain it) are rendered without a link.

Also fixes the test helper that reads a one-dimensional table off the page: it
matched nothing, so two page-level loops were passing vacuously.  With it
working, the pairing of an addl_row_title row with its values turns out to have
been repaired too, by the same change that made the headers carry their own
values: on the genus 2 statistics page the real geometric endomorphism algebras
were rotated against the counts underneath them, labelling the 6 curves with
endomorphism algebra C x C as M_2(R).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LMFDB uses American spelling, so "labelled" becomes "labeled" and
"totalled" becomes "totaled".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

More column options in p-adic field dynamic statistics

2 participants