Skip to content

Commit bbdbfac

Browse files
Merge pull request #6947 from LMFDB/main
main -> dev
2 parents 61cc5b6 + 67957aa commit bbdbfac

60 files changed

Lines changed: 1544 additions & 170 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Development.md

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ Code Attribution
337337
----------------
338338

339339
Each file should begin with a short copyright information, mentioning the people
340-
who are mainly involved in coding this particular python file. In practice,
340+
who are mainly involved in coding this particular python file.
341341

342342

343343
Testing
@@ -360,6 +360,62 @@ Testing
360360
```
361361
it produces beautiful coverage scores in `lmfdb/cover/index.html`
362362

363+
Code Snippets
364+
-------------
365+
366+
Many of the LMFDB pages include code snippets which describe how to generate the objects on the page using computer algebra systems such as SageMath, Magma, pari/GP or Oscar. To add code snippets to a new page, the relevant code is placed in a file named `code.yaml`. The file should always define the languages used using the `prompt` tag, for example:
367+
```
368+
prompt:
369+
sage: 'sage'
370+
pari: 'gp'
371+
magma: 'magma'
372+
oscar: 'oscar'
373+
```
374+
375+
The code snippets should be formatted as template strings, which will then be formatted in the code when the page is loaded.
376+
```yaml # From number_fields/code.yaml
377+
field:
378+
comment: Define the number field
379+
sage: x = polygen(QQ); K.<a> = NumberField(%s)
380+
pari: K = bnfinit(%s, 1)
381+
magma: R<x> := PolynomialRing(Rationals()); K<a> := NumberField(%s);
382+
oscar: Qx, x = polynomial_ring(QQ); K, a = number_field(%s)
383+
```
384+
385+
### Snippet testing
386+
387+
Sometimes computer algebra systems make breaking changes which render the code snippets invalid. To catch this, there's a testing system for snippets implemented in `lmfdb/tests/generate_snippet_tests.py`. This runs the code line by line in the various CAS-es, excluding magma. It does not test correctness of the results, only consistency, by comparing with previous results stored in the `lmfdb/tests/snippet_tests` directory. Normally (and at the time of writing), this is run automatically by Github Actions, which generates the relevant log files. However, it is possible to run it manually using the CLI tool
388+
389+
```bash
390+
sage --python ./lmfdb/tests/generate_snippet_tests.py -h
391+
```
392+
which takes a number of arguments (of which one of the positional arguments `generate` and `test` are required).
393+
394+
To specify which code snippets should be tested in the Github Action, add a tag of the form
395+
396+
```yaml
397+
# e.g. in /lmfdb/number_fields/code.yaml
398+
...
399+
snippet_test:
400+
testQ:
401+
label: 1.1.1.1
402+
langs:
403+
- sage
404+
- magma
405+
- oscar
406+
- gp
407+
url: NumberField/1.1.1.1/download/{lang}
408+
```
409+
410+
The tag `langs` here is optional, and if omitted, all (available) languages in the `prompt` tag will be used.
411+
412+
### Snippet test and generate Github Actions
413+
414+
Part of the code snippet testing system is a pair of code actions to generate evaluation log files automatically. The first one runs when a change to a `code*.yaml` file is pulled to the main branch, and if so, regenerates the evaluation files. If the output is different from the previous evaluation files, it will make a pull request, allowing you to manually check that the output is as expected. The logic is in `.github/workflows/snippet_generate.yml`.
415+
416+
Additionally, there's a second CI which runs twice a month and checks that the evaluations are consistent - it can also be run on demand. This ensures that if and when SageMath (or another CAS) deprecates a function, we'll know without having to wait for someone to update the yaml files, rerun the code or submit a bug report. If this action generates a different output than what's stored in the evaluation files, it will error, and upload the diff as an artifact. This is in `.github/workflows/snippet_test.yml`. Furthermore, it will create/update an issue (see https://github.com/LMFDB/lmfdb/issues/6810 for example) which keeps track of all the evaluation errors.
417+
418+
363419
Pro Tip: Debugging
364420
-------------------
365421

lmfdb/artin_representations/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,7 @@ class ArtinSearchArray(SearchArray):
528528
jump_egspan = "e.g. 4.5648.6t13.b.a"
529529
jump_knowl = "artin.search_input"
530530
jump_prompt = "Label"
531+
has_diagram = False
531532

532533
def __init__(self):
533534
dimension = TextBox(

lmfdb/belyi/main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,7 @@ def labels_page():
888888
class BelyiCommonSearchArray(SearchArray):
889889
jump_knowl = "belyi.search_input"
890890
jump_label = "Label"
891+
has_diagram = False
891892

892893
def __init__(self):
893894
self.deg = TextBox(

lmfdb/bianchi_modular_forms/bianchi_modular_form.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ class BMFSearchArray(SearchArray):
706706
jump_egspan = "e.g. 2.0.4.1-65.2-a (single form) or 2.0.4.1-65.2 (space of forms at a level)"
707707
jump_prompt = "Label"
708708
jump_knowl = "mf.bianchi.search_input"
709+
has_diagram = False
709710

710711
def __init__(self):
711712
field = TextBox(

lmfdb/characters/code.yaml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ galois_orbit:
9494
order := Order(chi);
9595
{ chi^k : k in [1..order-1] | GCD(k,order) eq 1 };
9696
97+
value_field:
98+
comment: Field of values of chi
99+
sage: CyclotomicField(chi.multiplicative_order())
100+
pari: nfinit(polcyclo(charorder(g,chi)))
101+
magma: CyclotomicField(Order(chi));
102+
103+
kernel_field:
104+
comment: Fixed field
105+
sage: chi.fixed_field()
106+
97107
gauss_sum:
98108
comment: Gauss sum
99109
sage: chi.gauss_sum(a)
@@ -109,8 +119,9 @@ kloosterman_sum:
109119

110120
value:
111121
comment: Value of chi at x
112-
sage: chi(x) # x integer
113-
pari: chareval(g,chi,x) \\\\ x integer, value in Q/Z'
122+
sage: |
123+
chi(x) # x integer
124+
pari: chareval(g,chi,x) \\ x integer, value in Q/Z
114125
magma: chi(x)
115126

116127

lmfdb/characters/main.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,6 @@ def dirichlet_character_search(info, query):
308308
common_parse(info, query)
309309

310310

311-
@characters_page.route("/Dirichlet")
312311
@characters_page.route("/Dirichlet/")
313312
def render_DirichletNavigation():
314313
if request.args:
@@ -416,7 +415,6 @@ def make_webchar(args, get_bread=False):
416415
return WebSmallDirichletCharacter(**args)
417416

418417

419-
@characters_page.route("/Dirichlet/<modulus>")
420418
@characters_page.route("/Dirichlet/<modulus>/")
421419
@characters_page.route("/Dirichlet/<int:modulus>/<int:number>")
422420
@characters_page.route("/Dirichlet/<int:modulus>/<orbit_label>") # orbit_label is a Cremona_letter_code identifying the orbit
@@ -557,6 +555,8 @@ def dirchar_code_download(label, download_type):
557555
Render a text page to download all Magma/Sage/PariGP code snippets for the various Dirichlet character pages
558556
Returns code snippets for either the individual Dirichet character, character orbit, or character group, depending on label
559557
"""
558+
sorted_code_names = ['character_init', 'kronecker_symbol', 'modulus', 'conductor', 'order',
559+
'is_real', 'is_primitive', 'parity', 'value_field', 'kernel_field']
560560
try:
561561
if label.count(".") == 0:
562562
# Group of Dirichlet characters
@@ -567,12 +567,11 @@ def dirchar_code_download(label, download_type):
567567
# Orbit of Dirichlet characters
568568
modulus, orbit_label = label.split(".")
569569
dc = make_webchar({'type':'Dirichlet', 'modulus':modulus, 'orbit_label':orbit_label})
570-
sorted_code_names = ['character_init', 'kronecker_symbol', 'modulus', 'conductor', 'order', 'is_real', 'is_primitive', 'parity']
571570
elif label.count(".") == 2:
572571
# Individual Dirichlet character
573572
modulus, orbit_label, number = label.split(".")
574573
dc = make_webchar({'type':'Dirichlet', 'modulus':modulus, 'orbit_label':orbit_label, 'number':number})
575-
sorted_code_names = ['character_init', 'kronecker_symbol', 'modulus', 'conductor', 'order', 'is_real', 'is_primitive', 'parity', 'galois_orbit']
574+
sorted_code_names.insert(8, 'galois_orbit')
576575
else:
577576
return abort(404, f"Invalid label {label}")
578577
if label.count(".") > 0 and dc.symbol_numerator() is None:

lmfdb/characters/templates/CharacterCommon.html

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,13 @@ <h2>
8383
{% if valuefield %}
8484
<tr>
8585
<td> {{KNOWL('character.dirichlet.value_field',title='Field of values')}}:</td>
86-
<td> {{ valuefield | safe }} </td>
86+
<td> {{ valuefield | safe }} </td> <td>{{ place_code('value_field') }}</td>
8787
</tr>
8888
{% endif %}
8989
{% if kerfield %}
9090
<tr>
9191
<td>{{ KNOWL('character.dirichlet.field_cut_out',title='Fixed field') }}:</td>
92-
<td> {{ kerfield | safe }}
93-
</td>
92+
<td> {{ kerfield | safe }} </td> <td>{{ place_code('kernel_field') }}</td>
9493
</tr>
9594
{% endif %}
9695
</table>

lmfdb/characters/web_character.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,13 @@ def title(self):
800800
def order(self):
801801
return euler_phi(self.modulus)
802802

803+
@cached_method
804+
def code_snippets(self):
805+
code = super().code_snippets()
806+
# Adjust frontmatter for constructing group of Dirichlet characters
807+
code["frontmatter"]["all"] = code["frontmatter"]["all"].replace("character", "character group of modulus")
808+
return code
809+
803810

804811
class WebDBDirichletGroup(WebDirichletGroup, WebDBDirichlet):
805812
"""
@@ -994,6 +1001,8 @@ def code_snippets(self):
9941001
# Sage code in special case for modulus 1
9951002
if self.modulus == 1:
9961003
self._genvalues_for_code = []
1004+
# Sage throws error for "chi.fixed_field()" if modulus is 1
1005+
code['kernel_field'].pop('sage')
9971006

9981007
data = {'modulus': self.modulus, 'number' : self.number,
9991008
'symbol_num' : self.symbol_numerator(),

lmfdb/classical_modular_forms/main.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ def index():
168168
# hidden_search_type for prev/next buttons
169169
info['search_type'] = search_type = info.get('search_type', info.get('hst', ''))
170170

171-
if search_type in ['List', '', 'Random']:
171+
if search_type in ['List', '', 'Random', 'Diagram']:
172172
return newform_search(info)
173173
elif search_type in ['Spaces', 'RandomSpace']:
174174
return space_search(info)
@@ -931,7 +931,12 @@ def _AL_col(i, p):
931931
},
932932
url_for_label=url_for_label,
933933
bread=get_search_bread,
934-
learnmore=learnmore_list)
934+
learnmore=learnmore_list,
935+
diagram_opts={"x_axis_default": "level",
936+
"y_axis_default": "weight",
937+
"color_default": "dim",
938+
"extra_fields": ["level"],
939+
})
935940
def newform_search(info, query):
936941
newform_parse(info, query)
937942
set_info_funcs(info)
@@ -1231,7 +1236,11 @@ def dimension_space_search(info, query):
12311236
'download':CMF_download().download_spaces},
12321237
url_for_label=url_for_label,
12331238
bread=get_search_bread,
1234-
learnmore=learnmore_list)
1239+
learnmore=learnmore_list,
1240+
diagram_opts={"x_axis_default": "level",
1241+
"y_axis_default": "weight",
1242+
"color_default": "dim",
1243+
})
12351244
def space_search(info, query):
12361245
newspace_parse(info, query)
12371246
set_info_funcs(info)
@@ -1774,10 +1783,12 @@ def search_types(self, info):
17741783
basic = [('', 'List of forms'),
17751784
('Dimensions', 'Dimension table'),
17761785
('Traces', 'Traces table'),
1777-
('Random', 'Random form')]
1786+
('Random', 'Random form'),
1787+
('Diagram', 'Diagram search')]
17781788
spaces = [('Spaces', 'List of spaces'),
17791789
('SpaceDimensions', 'Dimension table'),
1780-
('RandomSpace', 'Random')]
1790+
('RandomSpace', 'Random'),
1791+
('Diagram', 'Diagram search')]
17811792
if info is None:
17821793
return basic
17831794
st = self._st(info)

lmfdb/ecnf/code.yaml

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,43 @@ localdata:
131131
sage: E.local_data()
132132
magma: LocalInformation(E);
133133

134+
135+
# Code snippets for elliptic curve isogeny class pages
136+
isogeny_class:
137+
comment: Define the isogeny class
138+
sage: E.isogeny_class()
139+
140+
curves:
141+
comment: List of curves in the isogeny class
142+
sage: E.isogeny_class().curves
143+
144+
isogeny_matrix:
145+
comment: Isogeny matrix
146+
sage: E.isogeny_class().matrix()
147+
148+
isogeny_graph:
149+
comment: Isogeny graph
150+
sage: E.isogeny_class().graph().plot(edge_labels=True)
151+
152+
153+
# Code snippet tests for elliptic curve pages and isogeny class pages
134154
snippet_test:
155+
test11.1-a:
156+
label: 11.1-a
157+
langs:
158+
- sage
159+
url: EllipticCurve/2.0.11.1/11.1/a/download/{lang}
135160
test11.1-a1:
136161
label: 11.1-a1
137-
langs:
138162
url: EllipticCurve/2.0.11.1/11.1/a/1/download/{lang}
163+
test81.1-CMa:
164+
label: 81.1-CMa
165+
langs:
166+
- sage
167+
url: EllipticCurve/2.0.3.1/81.1/CMa/download/{lang}
139168
test81.1-CMa1:
140169
label: 81.1-CMa1
141-
langs:
142170
url: EllipticCurve/2.0.3.1/81.1/CMa/1/download/{lang}
143171

144172

145-
173+

0 commit comments

Comments
 (0)