Skip to content

Commit 0e30ea7

Browse files
roed314claude
andcommitted
Modular curves: real Sage download code; fix skip guards querying the wrong table
Reviewer-requested changes: 1. The Sage download emitted Magma code with only the header text changed (a lang parameter on download_modular_curve_magma_str), post-processed by fragile string replacements that still left invalid Sage (Magma-style Pol<x,y,z> ring declaration, true/false booleans). Replace it with a single shared writer, download_modular_curve_str(label, lang), with proper per-language templates: comment character (// vs #), assignment (:= vs =), statement terminator, booleans (true/false vs True/False), and the polynomial ring constructor (Pol<x,y,z> := PolynomialRing(Rationals(), 3) vs Pol.<x,y,z> = PolynomialRing(QQ, 3)). While porting, fix small bugs inherited from main: the fiber-product comment was missing its newline (so the factors assignment was swallowed into the comment), the factors list kept Python-style single quotes, stray statement terminators after r/Ncusps/Nrat_cusps, and the polynomial ring is now also defined when a curve has maps but no stored models (e.g. the j-map of a P^1 curve uses x,y) or codomain equations needing more variables. The generated .sage files for 11.12.1.a.1, 2.3.0.a.1 and 60.11520.409-60.bwm.1.10 were verified to run under sage's load(). test_download_functionality now asserts the Sage download has # comments, no // or := anywhere, the Sage-style ring declaration, and actually executes it through the Sage preparser, checking the resulting values. 2. dims/mults/newforms moved from gps_gl2zhat to modcurve_decomposition (keyed by Gassmann class), but test_related_objects still guarded on db.gps_gl2zhat.lookup(label, "newforms"), a condition that would stay false forever and permanently skip the test. Split it into four focused tests (siblings 48, siblings 60, elliptic curve, genus 2 isogeny class), each guarded via a new _skip_if_not_populated helper on exactly the (table, label, column) triples it needs, querying the table that now owns each column; skip messages name the precise missing dependency. All original assertions are preserved. Audited the remaining skip guards: they only reference columns still owned by gps_gl2zhat. Also fix the same wrong-table bug in WebModCurve.friends: the sibling search compared gps_gl2zhat.newforms (now always null) against the decomposition newforms, so sibling links could never appear once trace_hash is populated; candidate newforms are now fetched from modcurve_decomposition by Gassmann class. Full test file: 39 passed, 22 skipped (all skips are unloaded devmirror rows/columns still owned by gps_gl2zhat), 0 failed; pyflakes clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 42bdede commit 0e30ea7

3 files changed

Lines changed: 237 additions & 163 deletions

File tree

lmfdb/modular_curves/main.py

Lines changed: 109 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -484,80 +484,119 @@ class ModCurve_download(Downloader):
484484
),
485485
}
486486

487-
def download_modular_curve_magma_str(self, label, lang="Magma"):
488-
s = ""
487+
def download_modular_curve_str(self, label, lang):
488+
"""
489+
The contents of the code download file for a modular curve, as a string.
490+
491+
INPUT:
492+
493+
- ``label`` -- the label of a modular curve
494+
- ``lang`` -- either "magma" or "sage"; determines the syntax used for
495+
the generated code (comment character, assignment operator, booleans,
496+
statement terminator and polynomial ring constructor)
497+
"""
489498
rec = combined_data(label)
490499
if rec is None:
491500
return abort(404, "Label not found: %s" % label)
492-
s += "// %s code for modular curve with label %s\n\n" % (lang, label)
501+
if lang == "magma":
502+
com = "//" # comment character
503+
asgn = ":=" # assignment operator
504+
eol = ";" # statement terminator
505+
true, false = "true", "false"
506+
elif lang == "sage":
507+
com = "#"
508+
asgn = "="
509+
eol = ""
510+
true, false = "True", "False"
511+
else:
512+
raise ValueError("Unsupported download language: %s" % lang)
513+
514+
def assign(name, value):
515+
return "%s %s %s%s\n" % (name, asgn, value, eol)
516+
517+
s = "%s %s code for modular curve with label %s\n\n" % (com, lang.capitalize(), label)
493518
if rec['name'] or rec['CPlabel'] or rec['Slabel'] or rec['SZlabel'] or rec['RZBlabel']:
494-
s += "// Other names and/or labels\n"
519+
s += "%s Other names and/or labels\n" % com
495520
if rec['name']:
496-
s += "// Curve name: %s\n" % rec['name']
521+
s += "%s Curve name: %s\n" % (com, rec['name'])
497522
if rec['CPlabel']:
498-
s += "// Cummins-Pauli label: %s\n" % rec['CPlabel']
523+
s += "%s Cummins-Pauli label: %s\n" % (com, rec['CPlabel'])
499524
if rec['RZBlabel']:
500-
s += "// Rouse-Zureick-Brown label: %s\n" % rec['RZBlabel']
525+
s += "%s Rouse-Zureick-Brown label: %s\n" % (com, rec['RZBlabel'])
501526
if rec['RSZBlabel']:
502-
s += "// Rouse-Sutherland-Zureick-Brown label: %s\n" % rec['RSZBlabel']
527+
s += "%s Rouse-Sutherland-Zureick-Brown label: %s\n" % (com, rec['RSZBlabel'])
503528
if rec['Slabel']:
504-
s += "// Sutherland label: %s\n" % rec['Slabel']
529+
s += "%s Sutherland label: %s\n" % (com, rec['Slabel'])
505530
if rec['SZlabel']:
506-
s += "// Sutherland-Zywina label: %s\n" % rec['SZlabel']
507-
s += "\n// Group data\n"
508-
s += "level := %s;\n" % rec['level']
509-
s += "// Elements that, together with Gamma(level), generate the group\n"
510-
s += "gens := %s;\n" % rec['generators']
511-
s += "// Group contains -1?\n"
512-
if rec['contains_negative_one']:
513-
s += "ContainsMinus1 := true;\n"
514-
else:
515-
s += "ContainsMinus1 := false;\n"
516-
s += "// Index in Gamma(1)\n"
517-
s += "index := %s;\n" % rec['index']
518-
s += "\n// Curve data\n"
519-
s += "conductor := %s;\n" % rec['conductor']
520-
s += "bad_primes := %s;\n" % rec['bad_primes']
521-
s += "// Genus\n"
522-
s += "g := %s;\n" % rec['genus']
523-
s += "// Rank\n"
524-
s += "r := %s\n;" % rec['rank']
531+
s += "%s Sutherland-Zywina label: %s\n" % (com, rec['SZlabel'])
532+
s += "\n%s Group data\n" % com
533+
s += assign("level", rec['level'])
534+
s += "%s Elements that, together with Gamma(level), generate the group\n" % com
535+
s += assign("gens", rec['generators'])
536+
s += "%s Group contains -1?\n" % com
537+
s += assign("ContainsMinus1", true if rec['contains_negative_one'] else false)
538+
s += "%s Index in Gamma(1)\n" % com
539+
s += assign("index", rec['index'])
540+
s += "\n%s Curve data\n" % com
541+
s += assign("conductor", rec['conductor'])
542+
s += assign("bad_primes", rec['bad_primes'])
543+
s += "%s Genus\n" % com
544+
s += assign("g", rec['genus'])
545+
s += "%s Rank\n" % com
546+
s += assign("r", rec['rank'])
525547
if rec['q_gonality'] != -1:
526-
s += "// Exact gonality known\n"
527-
s += "gamma := %s;\n" % rec['q_gonality']
548+
s += "%s Exact gonality known\n" % com
549+
s += assign("gamma", rec['q_gonality'])
528550
else:
529-
s += "// Exact gonality unknown, but contained in following interval\n"
530-
s += "gamma_int := %s;\n" % rec['q_gonality_bounds']
531-
s += "\n// Modular data\n"
532-
s += "// Number of cusps\n"
533-
s += "Ncusps := %s\n;" % rec['cusps']
534-
s += "// Number of rational cusps\n"
535-
s += "Nrat_cusps := %s\n;" % rec['rational_cusps']
536-
s += "// CM discriminants\n"
537-
s += "CM_discs := %s;\n" % rec['cm_discriminants']
551+
s += "%s Exact gonality unknown, but contained in following interval\n" % com
552+
s += assign("gamma_int", rec['q_gonality_bounds'])
553+
s += "\n%s Modular data\n" % com
554+
s += "%s Number of cusps\n" % com
555+
s += assign("Ncusps", rec['cusps'])
556+
s += "%s Number of rational cusps\n" % com
557+
s += assign("Nrat_cusps", rec['rational_cusps'])
558+
s += "%s CM discriminants\n" % com
559+
s += assign("CM_discs", rec['cm_discriminants'])
538560
if rec['factorization'] != []:
539-
s += "// Modular curve is a fiber product of the following curves"
540-
s += "factors := %s\n" % [f.replace("'", "\"") for f in rec['factorization']]
541-
s += "// Groups containing given group, corresponding to curves covered by given curve\n"
542-
parents_mag = "%s" % rec['parents']
543-
parents_mag = parents_mag.replace("'", "\"")
544-
s += "covers := %s;\n" % parents_mag
545-
546-
s += "\n// Models for this modular curve, if computed\n"
561+
s += "%s Modular curve is a fiber product of the following curves\n" % com
562+
s += assign("factors", str(rec['factorization']).replace("'", "\""))
563+
s += "%s Groups containing given group, corresponding to curves covered by given curve\n" % com
564+
s += assign("covers", str(rec['parents']).replace("'", "\""))
565+
547566
models = list(db.modcurve_models.search(
548567
{"modcurve": label, "model_type":{"$not":1}},
549568
["equation", "number_variables", "model_type", "smooth"]))
550-
if models:
551-
max_nb_variables = max([m["number_variables"] for m in models])
552-
variables = "xyzwtuvrsabcdefghiklmnopqj"[:max_nb_variables]
553-
s += "Pol<%s" % variables[0]
554-
for x in variables[1:]:
555-
s += ",%s" % x
556-
s += "> := PolynomialRing(Rationals(), %s);\n" % max_nb_variables
557-
558-
s += "// Isomorphic to P^1?\n"
559-
is_P1 = "true" if (rec['genus'] == 0 and rec['pointless'] is False) else "false"
560-
s += "is_P1 := %s;\n" % is_P1
569+
maps = list(db.modcurve_modelmaps.search(
570+
{"domain_label": label},
571+
["domain_model_type", "codomain_label", "codomain_model_type",
572+
"coordinates", "leading_coefficients"]))
573+
codomain_labels = [m["codomain_label"] for m in maps]
574+
codomain_models = list(db.modcurve_models.search(
575+
{"modcurve": {"$in": codomain_labels}},
576+
["equation", "modcurve", "model_type", "number_variables"]))
577+
578+
# The polynomial ring must contain the variables of the model equations,
579+
# of the coordinates of the maps (a map whose domain model is P^1 is
580+
# expressed in the coordinates x, y of P^1), and of the codomain
581+
# equations included with the maps.
582+
num_variables = max((m["number_variables"] for m in models), default=0)
583+
for m in maps:
584+
if m["domain_model_type"] == 1:
585+
num_variables = max(num_variables, 2)
586+
if m["codomain_label"] != "1.1.0.a.1" and m["codomain_model_type"] != 1:
587+
num_variables = max([num_variables] + [eq["number_variables"] for eq in codomain_models
588+
if eq["modcurve"] == m["codomain_label"] and eq["model_type"] == m["codomain_model_type"]])
589+
590+
s += "\n%s Models for this modular curve, if computed\n" % com
591+
if num_variables:
592+
variables = ",".join("xyzwtuvrsabcdefghiklmnopqj"[:num_variables])
593+
if lang == "magma":
594+
s += "Pol<%s> := PolynomialRing(Rationals(), %s);\n" % (variables, num_variables)
595+
else:
596+
s += "Pol.<%s> = PolynomialRing(QQ, %s)\n" % (variables, num_variables)
597+
598+
s += "%s Isomorphic to P^1?\n" % com
599+
s += assign("is_P1", true if (rec['genus'] == 0 and rec['pointless'] is False) else false)
561600
model_id = 0
562601
for m in models:
563602
if m["model_type"] == 0:
@@ -577,25 +616,12 @@ def download_modular_curve_magma_str(self, label, lang="Magma"):
577616
name = "Embedded model"
578617
else:
579618
name = "Other model"
580-
s += "\n// %s\n" % name
581-
s += "model_%s := [" % model_id
582-
s += ",".join(m['equation'])
583-
s += "];\n"
619+
s += "\n%s %s\n" % (com, name)
620+
s += assign("model_%s" % model_id, "[%s]" % ",".join(m['equation']))
584621
model_id += 1
585622

586-
s += "\n// Maps from this modular curve, if computed\n"
587-
maps = list(db.modcurve_modelmaps.search(
588-
{"domain_label": label},
589-
["domain_model_type", "codomain_label", "codomain_model_type",
590-
"coordinates", "leading_coefficients"]))
591-
codomain_labels = [m["codomain_label"] for m in maps]
592-
codomain_models = list(db.modcurve_models.search(
593-
{"modcurve": {"$in": codomain_labels}},
594-
["equation", "modcurve", "model_type"]))
623+
s += "\n%s Maps from this modular curve, if computed\n" % com
595624
map_id = 0
596-
#if maps and is_P1: #variable t has not been introduced above
597-
# s += "Pol<t> := PolynomialRing(Rationals());\n"
598-
#This was using t twice when the genus was large enough that t was used above. Even when t is not defined above, t is not used below.
599625
for m in maps:
600626
prefix = "map_%s_" % map_id
601627
has_codomain_equation = False
@@ -630,35 +656,29 @@ def download_modular_curve_magma_str(self, label, lang="Magma"):
630656
else:
631657
name += " to another model of modular curve"
632658
name += " with label %s" % m["codomain_label"]
633-
s += "\n// %s\n" % name
659+
s += "\n%s %s\n" % (com, name)
634660
coord = m["coordinates"]
635661
if m["leading_coefficients"] is None:
636662
lead = [1]*len(coord)
637663
else:
638664
lead = m["leading_coefficients"]
639665
for j in range(len(coord)):
640-
s += "// Coordinate number %s:\n" % j
641-
s += prefix + ("coord_%s := " % j)
642-
s += "%s*(" % lead[j]
643-
s += "%s);\n" % coord[j]
666+
s += "%s Coordinate number %s:\n" % (com, j)
667+
s += assign(prefix + "coord_%s" % j, "%s*(%s)" % (lead[j], coord[j]))
644668
if has_codomain_equation:
645-
s += "// Codomain equation:\n"
669+
s += "%s Codomain equation:\n" % com
646670
eq = [eq for eq in codomain_models if eq["modcurve"] == m["codomain_label"] and eq["model_type"] == m["codomain_model_type"]][0]
647-
s += prefix + "codomain := " + "[%s];\n" % ",".join(eq["equation"])
671+
s += assign(prefix + "codomain", "[%s]" % ",".join(eq["equation"]))
648672
map_id += 1
649673
return s
650674

651675
def download_modular_curve_magma(self, label):
652-
s = self.download_modular_curve_magma_str(label)
653-
return self._wrap(s, label, lang="magma")
676+
return self._wrap(self.download_modular_curve_str(label, "magma"),
677+
label, lang="magma")
654678

655679
def download_modular_curve_sage(self, label):
656-
s = self.download_modular_curve_magma_str(label, lang="Sage")
657-
s = s.replace(":=", "=")
658-
s = s.replace(";", "")
659-
s = s.replace("//", "#")
660-
s = s.replace("K<", "K.<")
661-
return self._wrap(s, label, lang="sage")
680+
return self._wrap(self.download_modular_curve_str(label, "sage"),
681+
label, lang="sage")
662682

663683
def download_modular_curve(self, label, lang):
664684
if lang == "magma":

0 commit comments

Comments
 (0)