Add Gauss-sum portraits to Dirichlet character pages (#3996) - #38
Add Gauss-sum portraits to Dirichlet character pages (#3996)#38roed-math wants to merge 3 commits into
Conversation
New self-contained module lmfdb/characters/portraits.py draws, for each residue a mod N, the partial Gauss sums S_a(k) as rainbow radial segments (early terms darkened), the complete Gauss sums as dots, and a circle of radius sqrt(N), following Alex Best's demo from the issue; rendered as a single matplotlib LineCollection and embedded via encode_plot in the properties box (the elliptic curve pattern). Computed on the fly for modulus up to 300, skipped above. Hook is a single 4-line call in render_Dirichletwebpage; any portrait failure is logged and swallowed. Verified: complete sums match pari znchargauss to 1e-13 for ~18 odd/even/primitive/imprimitive/trivial characters, |tau_a|=sqrt(N) for primitive chi at coprime a, 27.8 matches the issue's demo image; pages checked via flask test client for N=1..300 and skip for N>300; DirichletCharactersTest (17 tests incl. new test_portrait) green; pyflakes clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
This looks really cool, many thanks for adding this! The portraits all look good to me, it seems to pretty accurately recreate the pictures given in https://alexjbest.github.io/dirich/ . I think the Maybe we can cache the portraits? Since the runtime depends on Maybe one can just add a description of the portraits ("Picture description") in the Learn more box, as done for number fields and Galois groups ? One optional thing to add would be pictures also for orbits of Dirichlet characters (though certainly no need to add it for this PR). |
Portraits were guarded by a modulus cutoff, but their cost scales with the number of segments drawn, N*phi(N), which is far from monotone in N: the prime 293 draws 85556 of them and takes some eight times as long as the larger 300, whose phi is only 80, and a reviewer measured seconds of latency on the 293.17 page. The cutoff is now on the segment count (25000, which keeps 300 and drops 293), consulted before any character is constructed or any array allocated, and exposed as portrait_complexity/portrait_is_enabled so the policy can be tested without rendering. Completed portraits are kept in a bounded lru_cache(maxsize=64), so repeat visits and crawlers cost microseconds; moduli with no portrait are rejected before the cache, and a failure is not cached. Adds the accessibility and explanatory pieces asked for in review: a stable dirichlet-character-portrait class and alt text on the image, a /Character/Dirichlet/Pictures page rendering the knowl portrait.character.dirichlet, and a "Picture description" entry in the Learn more box, following the number field and Galois group pattern. The knowl itself still has to be created in the knowl database. Also corrects the module docstring, which said every k = 1, ..., N-1 was drawn: only the k coprime to N are, since chi(n) = 0 off the units makes the other stages repeat the segment before them. Tests: complete sums against pari znchargauss for primitive, imprimitive, non-real and composite-modulus characters; |tau_a| = sqrt(N) on units and 0 off them for a primitive character; the N = 1 point data; the 293/300 workload pair; the cache; and the portrait's own class on the character page, rather than the presence of any base64 image. Rendered portraits are byte-identical to before. lmfdb/characters/test_characters.py green (32), pyflakes/pylint/ruff clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Thanks for the review! Pushed 547265f, which takes up all four points. The cutoff is now on work, not modulusYou were right that Timings on this machine,
283 and 271 sit at 0.58s and 0.48s, also skipped. Your "few seconds" for 293.17 is consistent: the CachingCompleted portraits go through a bounded A separate cacheable PNG endpoint would be better still (the browser could cache the image, and the Picture descriptionThere is now a
Draft knowl:
|
|
I added the knowl; GPT signed off. |
|
Superseded by LMFDB#7172, opened upstream from this same branch. Closing here; review continues upstream. (The draft |
Adds a "portrait" visualization to each individual Dirichlet character homepage, in the properties
box alongside the other basic data, following the design proposed in the issue (Alex Best's demo).
For a character chi of modulus N, and every residue a, the partial Gauss sums
S_a(k) = sum_{n<=k} chi(n) e(a n / N)are drawn as rainbow radial segments (early terms darkened),the complete Gauss sums
tau_a(chi)as dots, and a grey circle of radiussqrt(N); the coprime dotslie on the circle iff chi is primitive, and rotational/reflective symmetry reveals the order and
reality of the character. The plot is a new self-contained module
lmfdb/characters/portraits.pythat renders all segments as a single matplotlib
LineCollectionand embeds the result viaencode_plot(the elliptic-curve pattern). The only hook into existing code is a one-line call inrender_Dirichletwebpage.The picture has
N * phi(N)segments, and that count (not the modulus) is what its cost scaleswith, so that is what is capped: portraits are drawn when
N * phi(N) <= 25000, which keepsN = 300at about 0.1s while dropping prime-like cases such asN = 293, whose 85556 segments takesome eight times longer. The check happens before any character is constructed or any array
allocated, and is exposed as
portrait_complexity/portrait_is_enabledso the policy can betested without rendering. Completed portraits are kept in a bounded
lru_cache(maxsize=64), so arepeat visit costs microseconds; a modulus with no portrait is a quiet no-op that never reaches the
cache, and any failure while building one is logged and swallowed, since the picture must never
break the page.
A new
/Character/Dirichlet/Picturespage and a "Picture description" entry in the Learn more boxexplain what the picture shows, as for number fields and Galois groups, and the image carries a
stable
class="dirichlet-character-portrait"plus alt text. That page renders the knowlportrait.character.dirichlet, which still needs to be created in the knowl database (draft text ina comment below).
Tested: the complete Gauss sums agree with pari's
znchargaussto machine precision for primitive,imprimitive, real, non-real and composite-modulus characters;
|tau_a| = sqrt(N)on units andtau_a = 0off them for a primitive character; theN = 1special case; the workload cutoff on thenon-monotone 293/300 pair; the cache; and the portrait's own CSS class on the character page.
Addresses LMFDB#3996.
🤖 Generated with Claude Code