Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions lmfdb/genus2_curves/code.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,37 @@ frontmatter:
{lang} code for working with genus 2 curve {label}.

curve:
comment: Define the curve
comment: Define the curve (minimal equation)
magma: |
R<x> := PolynomialRing(Rationals());
fh := %s;
f := R![a : a in fh[1]];
h := R![a : a in fh[2]];
C := HyperellipticCurve(f, h);
Cmin := HyperellipticCurve(f, h);

simple_curve:
comment: Simplified equation
magma: Csim := SimplifiedModel(Cmin);

aut:
comment: Automorphism group
magma: AutomorphismGroup(C);
magma: AutomorphismGroup(Cmin);

jacobian:
comment: Jacobian
magma: J := Jacobian(SimplifiedModel(C));
magma: J := Jacobian(Csim);

tors:
comment: Torsion subgroup
magma: TorsionSubgroup(J);

cond:
comment: Conductor
magma: Conductor(LSeries(C));
magma: Conductor(LSeries(Cmin%s));

disc:
comment: Discriminant
magma: Discriminant(C);
magma: Discriminant(Cmin);

ntors:
comment: Torsion order of Jacobian
Expand Down
2 changes: 1 addition & 1 deletion lmfdb/genus2_curves/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,7 @@ def labels_page():
learnmore=learnmore_list_remove("labels"),
)

sorted_code_names = ['curve', 'aut', 'jacobian', 'tors', 'cond', 'disc', 'ntors', 'mwgroup']
sorted_code_names = ['curve', 'simple_curve', 'aut', 'jacobian', 'tors', 'cond', 'disc', 'ntors', 'mwgroup']

Comment = {'magma': '//', 'sage': '#', 'gp': '\\\\', 'pari': '\\\\'}

Expand Down
82 changes: 82 additions & 0 deletions lmfdb/genus2_curves/test_genus2_curves.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import re

from sage.all import PolynomialRing, QQ

from lmfdb.tests import LmfdbTest
from lmfdb.genus2_curves.web_g2c import (comp_poly, simplify_hyperelliptic,
simplify_hyperelliptic_point,
simplify_hyperelliptic_scale)


class Genus2Test(LmfdbTest):
Expand Down Expand Up @@ -122,6 +129,23 @@ def test_download(self):
self.tc.get("/Genus2Curve/Q/?query={'abs_disc':3976}&download=sage")
self.tc.get("/Genus2Curve/Q/?query={'abs_disc':3976}&download=magma")

def test_code_download_conductor(self):
# Magma will not use Ogg's formula once v_2(disc) >= 12, so for those
# curves the conductor snippet has to hand it the L-factor at 2. The
# downloaded code needs the same treatment as the snippet on the page.
excfactors = "ExcFactors:=[*<2,Valuation(400,2),R![1]>*]"
page = self.tc.get("/Genus2Curve/Q/400/a/409600/1").get_data(as_text=True)
# the snippets on the page escape the angle brackets of the tuple
assert excfactors.replace("<", "&lt;").replace(">", "&gt;") in page
code = self.tc.get(
"/Genus2Curve/Q/400/a/409600/1/download/magma").get_data(as_text=True)
assert "Conductor(LSeries(Cmin: %s));" % excfactors in code
# curves with v_2(disc) < 12 get the plain call
code = self.tc.get(
"/Genus2Curve/Q/961/a/961/1/download/magma").get_data(as_text=True)
assert "Conductor(LSeries(Cmin));" in code
assert "ExcFactors" not in code

def test_rational_weierstrass_points_search(self):
L = self.tc.get("/Genus2Curve/Q/?num_rat_wpts=4")
assert "360.a.6480.1" in L.get_data(as_text=True)
Expand Down Expand Up @@ -238,6 +262,64 @@ def test_ratpts(self):
assert "rational points are known" in L.get_data(as_text=True)
assert "for this curve" in L.get_data(as_text=True)

def test_simplified_model_points(self):
# 400.a.409600.1 has h = 0 and content(4f+h^2) = 4, so y-coordinates are
# divided by sqrt(4) = 2 and not by the content 4: the simplified equation
# equals the minimal one here, and the points must come through unchanged.
fh = [[1, 0, 4, 0, 4, 0, 1], []]
assert simplify_hyperelliptic_scale(fh) == 2
R = PolynomialRing(QQ, ['x', 'z'])
x, z = R.gens()
g = sum(c*x**i*z**(6-i) for i, c in enumerate(simplify_hyperelliptic(fh)))
pts = [[1, -1, 0], [1, 1, 0], [0, -1, 1], [0, 1, 1]]
for pt in pts:
X, Y, Z = simplify_hyperelliptic_point(fh, pt)
assert [X, Y, Z] == pt
assert Y**2 == g(X, Z)
L = self.tc.get("/Genus2Curve/Q/400/a/409600/1")
page = L.get_data(as_text=True)
# the simplified-model snippet builds its points on Csim, and they are the
# points above rather than the halved ones the content used to produce
for pt in pts:
assert "Csim![%s,%s,%s]" % tuple(pt) in page
assert "(0 : -1/2 : 1)" not in page
# nor does the simplified Mordell-Weil table pick up a factor of 1/2
assert "1/2z^3" not in page

def test_simplified_model_mw_gens(self):
# 336.a.172032.1 is y^2 + (x^3 + xz^2)y = -x^6 + 15x^4z^2 - 75x^2z^4 - 56z^6
# with a generator recorded as 3x^2 - 32z^2 = 0, 6y = -35xz^2. Since
# 6y = yD clears a denominator, the simplified relation multiplies h by 6
# too: 6Y = 2*(-35xz^2) + 6*(x^3 + xz^2) = 6x^3 - 64xz^2.
fh = [[-56, 0, -75, 0, 15, 0, -1], [0, 1, 0, 1]]
assert simplify_hyperelliptic_scale(fh) == 1
R = PolynomialRing(QQ, ['x', 'z'])
x, z = R.gens()
yD = comp_poly(fh, 6)(x, -35*x*z**2, z)
assert yD == 6*x**3 - 64*x*z**2
assert yD != x**3 - 69*x*z**2 # only one copy of h, the old behavior
# the generator is 2-torsion, so yD vanishes on 3x^2 - 32z^2 = 0
assert yD == 2*x*(3*x**2 - 32*z**2)
L = self.tc.get("/Genus2Curve/Q/336/a/172032/1")
page = L.get_data(as_text=True)
assert "-35xz^2" in page # minimal model, unchanged
assert "6x^3 - 64xz^2" in page
assert "x^3 - 69xz^2" not in page

def test_model_code_snippets(self):
url = "/Genus2Curve/Q/169/a/169/1"
page = self.tc.get(url).get_data(as_text=True)
assert "Cmin := HyperellipticCurve(R![0, 0, 0, 0, 1, 1], R![1, 1, 0, 1])" in page
assert "Csim, pi := SimplifiedModel(Cmin);" in page
# every point is built on the model it belongs to, with no stale bare C
assert "Cmin![" in page and "Csim![" in page
assert not re.search(r"(?<![A-Za-z])C!\[", page)
# the download defines each object before it is used
magma = self.tc.get(url + "/download/magma").get_data(as_text=True)
assert (magma.index("Cmin := HyperellipticCurve(f, h);")
< magma.index("Csim := SimplifiedModel(Cmin);")
< magma.index("J := Jacobian(Csim);"))

def test_endo_search(self):
# first result for every search
for endo, text in [
Expand Down
102 changes: 63 additions & 39 deletions lmfdb/genus2_curves/web_g2c.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,27 +67,37 @@ def simplify_hyperelliptic(fh):
return f.coefficients(sparse=False)


def simplify_hyperelliptic_point(fh, pt):
def simplify_hyperelliptic_scale(fh):
# The simplified model is y^2 = g with g = squarefree_part(n)*(4f+h^2)/n,
# where n is the content of 4f+h^2, so the y-coordinate of a point on the
# minimal model transforms as y -> (2y+h)/s, where s = sqrt(n/squarefree_part(n))
xR = PolynomialRing(QQ, 'x')
f = 4*xR(fh[0]) + xR(fh[1])**2
f1 = xR(fh[1])
n = gcd(f.coefficients())
n = ZZ(gcd(f.coefficients()))
return (n // integer_squarefree_part(n)).isqrt()


def simplify_hyperelliptic_point(fh, pt):
s = simplify_hyperelliptic_scale(fh)
f1 = PolynomialRing(QQ, 'x')(fh[1])
xzR = PolynomialRing(QQ,['x', 'z'])
z = xzR('z')
f1 = (xzR(f1)*z**(4-len(fh[1]))).homogenize(z)
return [pt[0], (2*pt[1] + f1([pt[0],pt[2]])) / n, pt[2]]
return [pt[0], (2*pt[1] + f1([pt[0],pt[2]])) / s, pt[2]]


def comp_poly(fh):
xR = PolynomialRing(QQ, 'x')
f = 4*xR(fh[0]) + xR(fh[1])**2
f1 = xR(fh[1])
n = gcd(f.coefficients())
def comp_poly(fh, d):
# Mordell-Weil generators are recorded with denominators cleared, as a relation
# d*y = yD(x,z) on the minimal model. Since y -> (2*y + h(x,z))/s, that same
# relation reads d*Y = (2*yD + d*h(x,z))/s on the simplified model, so the
# polynomial to substitute for yD depends on the denominator d.
s = simplify_hyperelliptic_scale(fh)
f1 = PolynomialRing(QQ, 'x')(fh[1])
xyzR = PolynomialRing(QQ,['x', 'y', 'z'])
y = xyzR('y')
z = xyzR('z')
f1 = (xyzR(f1)*z**(4-len(fh[1]))).homogenize(z)
return (2*y + f1) / n
return (2*y + d*f1) / s


def min_eqns_pretty(fh):
Expand Down Expand Up @@ -609,7 +619,9 @@ def point_string(P):
return '(' + ' : '.join(map(str, P)) + ')'


def mw_gens_table(invs,gens,hts,pts,comp=PolynomialRing(QQ,['x', 'y', 'z'])('y')):
# comp takes the cleared denominator of a generator's y-coordinate and returns the
# polynomial in x, y, z to substitute for that y-coordinate; None leaves it alone.
def mw_gens_table(invs,gens,hts,pts,comp=None):
def divisor_data(P):
R = PolynomialRing(QQ, ['x', 'z'])
x = R('x')
Expand All @@ -620,7 +632,8 @@ def divisor_data(P):
if str(xD.factor())[:4] == "(-1)":
xD = -xD
yD = sum([ZZ(yden)*ZZ(yP[i][0])/ZZ(yP[i][1])*x**i*z**(len(yP)-i-1) for i in range(len(yP))])
yD = comp(x, yD, z)
if comp is not None:
yD = comp(yden)(x, yD, z)
return [make_bigint(elt, 10) for elt in [str(xD.factor()).replace("**","^").replace("*",""), str(yden)+"y" if yden > 1 else "y", str(yD).replace("**","^").replace("*","")]], xD, yD, yden
if not invs:
return ''
Expand All @@ -647,7 +660,7 @@ def divisor_data(P):

def mw_gens_simple_table(invs, gens, hts, pts, fh):
spts = [simplify_hyperelliptic_point(fh, pt) for pt in pts]
return mw_gens_table(invs, gens, hts, spts, comp=comp_poly(fh))
return mw_gens_table(invs, gens, hts, spts, comp=lambda d: comp_poly(fh, d))


def local_table(N, D, tama, bad_lpolys, bad_lfactors, cluster_pics, root_numbers):
Expand Down Expand Up @@ -768,6 +781,21 @@ def augment_galrep_and_nonsurj(galrep, nonsurj):
output.append({'prime': p, 'modell_image' : 'not computed'})
return output

def magma_cond_option(data):
"""Options for Magma's Conductor(LSeries(...)), as a string to be inserted
just before the closing parenthesis of LSeries.

Magma refuses to apply Ogg's formula once v_2(disc) >= 12, so for those
curves we hand it the local L-factor at 2 (which is in the database) rather
than letting it try. Both the code snippets on the curve page and the
downloaded code need this, so it lives here.
"""
if data['abs_disc'] % 4096 == 0:
ind2 = [a[0] for a in data['bad_lfactors']].index(2)
bad2 = data['bad_lfactors'][ind2][1]
return ': ExcFactors:=[*<2,Valuation(%s,2),R!%s>*]' % (data['cond'], bad2)
return ''


###############################################################################
# Genus 2 curve class definition
Expand Down Expand Up @@ -1120,34 +1148,29 @@ def make_object(self, curve, endo, tama, ratpts, clus, galrep, nonsurj, is_curve
code['show'] = {'sage':'', 'magma':''} # use default show names
f,h = fh = data['min_eqn']
g = simplify_hyperelliptic(fh)
code['curve'] = {'sage':'R.<x> = PolynomialRing(QQ); C = HyperellipticCurve(R(%s), R(%s));' % (f, h),
'magma':'R<x> := PolynomialRing(Rationals()); C := HyperellipticCurve(R!%s, R!%s);' % (f, h) }
code['simple_curve'] = {'sage':'X = HyperellipticCurve(R(%s))' % (g), 'magma':'X,pi:= SimplifiedModel(C);' }
if data['abs_disc'] % 4096 == 0:
ind2 = [a[0] for a in data['bad_lfactors']].index(2)
bad2 = data['bad_lfactors'][ind2][1]
magma_cond_option = ': ExcFactors:=[*<2,Valuation('+str(data['cond'])+',2),R!'+str(bad2)+'>*]'
else:
magma_cond_option = ''
code['cond'] = {'magma': 'Conductor(LSeries(C%s)); Factorization($1);' % magma_cond_option}
code['disc'] = {'magma':'Discriminant(C); Factorization(Integers()!$1);'}
code['geom_inv'] = {'sage':'C.igusa_clebsch_invariants(); [factor(a) for a in _]',
'magma':'IgusaClebschInvariants(C); IgusaInvariants(C); G2Invariants(C);'}
code['aut'] = {'magma':'AutomorphismGroup(C); IdentifyGroup($1);'}
code['autQbar'] = {'magma':'AutomorphismGroup(ChangeRing(C,AlgebraicClosure(Rationals()))); IdentifyGroup($1);'}
code['num_rat_wpts'] = {'magma':'#Roots(HyperellipticPolynomials(SimplifiedModel(C)));'}
code['curve'] = {'sage':'R.<x> = PolynomialRing(QQ); Cmin = HyperellipticCurve(R(%s), R(%s)) # minimal equation' % (f, h),
'magma':'R<x> := PolynomialRing(Rationals()); Cmin := HyperellipticCurve(R!%s, R!%s); // minimal equation' % (f, h) }
code['simple_curve'] = {'sage':'Csim = HyperellipticCurve(R(%s)); Csim # simplified equation' % (g),
'magma':'Csim, pi := SimplifiedModel(Cmin); Csim; // simplified equation' }
code['cond'] = {'magma': 'Conductor(LSeries(Cmin%s)); Factorization($1);' % magma_cond_option(data)}
code['disc'] = {'magma':'Discriminant(Cmin); Factorization(Integers()!$1);'}
code['geom_inv'] = {'sage':'Cmin.igusa_clebsch_invariants(); [factor(a) for a in _]',
'magma':'IgusaClebschInvariants(Cmin); IgusaInvariants(Cmin); G2Invariants(Cmin);'}
code['aut'] = {'magma':'AutomorphismGroup(Cmin); IdentifyGroup($1);'}
code['autQbar'] = {'magma':'AutomorphismGroup(ChangeRing(Cmin,AlgebraicClosure(Rationals()))); IdentifyGroup($1);'}
code['num_rat_wpts'] = {'magma':'#Roots(HyperellipticPolynomials(SimplifiedModel(Cmin)));'}
if ratpts:
code['rat_pts'] = {'magma': '[' + ','.join("C![%s,%s,%s]" % (p[0], p[1], p[2]) for p in ratpts['rat_pts']) + ']; // minimal model'}
code['rat_pts_simp'] = {'magma': '[' + ','.join(["C![%s,%s,%s]" % (p[0], p[1], p[2]) for p in [simplify_hyperelliptic_point(data['min_eqn'], pt) for pt in ratpts['rat_pts']]]) + ']; // simplified model'}
code['mw_group'] = {'magma':'MordellWeilGroupGenus2(Jacobian(C));'}
code['two_selmer'] = {'magma':'TwoSelmerGroup(Jacobian(C)); NumberOfGenerators($1);'}
code['has_square_sha'] = {'magma':'HasSquareSha(Jacobian(C));'}
code['locally_solvable'] = {'magma':'f,h:=HyperellipticPolynomials(C); g:=4*f+h^2; HasPointsEverywhereLocally(g,2) and (#Roots(ChangeRing(g,RealField())) gt 0 or LeadingCoefficient(g) gt 0);'}
code['torsion_subgroup'] = {'magma':'TorsionSubgroup(Jacobian(SimplifiedModel(C))); AbelianInvariants($1);'}
code['decomp'] = {'magma':'HeuristicDecompositionFactors(C);'}
code['rat_pts'] = {'magma': '[' + ','.join("Cmin![%s,%s,%s]" % (p[0], p[1], p[2]) for p in ratpts['rat_pts']) + ']; // minimal model'}
code['rat_pts_simp'] = {'magma': '[' + ','.join(["Csim![%s,%s,%s]" % (p[0], p[1], p[2]) for p in [simplify_hyperelliptic_point(data['min_eqn'], pt) for pt in ratpts['rat_pts']]]) + ']; // simplified model'}
code['mw_group'] = {'magma':'MordellWeilGroupGenus2(Jacobian(Cmin));'}
code['two_selmer'] = {'magma':'TwoSelmerGroup(Jacobian(Cmin)); NumberOfGenerators($1);'}
code['has_square_sha'] = {'magma':'HasSquareSha(Jacobian(Cmin));'}
code['locally_solvable'] = {'magma':'f,h:=HyperellipticPolynomials(Cmin); g:=4*f+h^2; HasPointsEverywhereLocally(g,2) and (#Roots(ChangeRing(g,RealField())) gt 0 or LeadingCoefficient(g) gt 0);'}
code['torsion_subgroup'] = {'magma':'TorsionSubgroup(Jacobian(SimplifiedModel(Cmin))); AbelianInvariants($1);'}
code['decomp'] = {'magma':'HeuristicDecompositionFactors(Cmin);'}
code['endos0'] = {'magma':'//Please install CHIMP (https://github.com/edgarcosta/CHIMP) if you want to run this code'}
code['endos1'] = {'magma':'HeuristicIsGL2(C); HeuristicEndomorphismDescription(C); HeuristicEndomorphismFieldOfDefinition(C);'}
code['endos2'] = {'magma':'HeuristicIsGL2(C : Geometric := true); HeuristicEndomorphismDescription(C : Geometric := true); HeuristicEndomorphismLatticeDescription(C);'}
code['endos1'] = {'magma':'HeuristicIsGL2(Cmin); HeuristicEndomorphismDescription(Cmin); HeuristicEndomorphismFieldOfDefinition(Cmin);'}
code['endos2'] = {'magma':'HeuristicIsGL2(Cmin : Geometric := true); HeuristicEndomorphismDescription(Cmin : Geometric := true); HeuristicEndomorphismLatticeDescription(Cmin);'}

self._code = None

Expand All @@ -1161,5 +1184,6 @@ def get_code(self):
# Fill in placeholders for this specific curve:
for lang in ['magma']: #TODO: 'sage', 'pari',
self._code['curve'][lang] = self._code['curve'][lang] % (self.data['min_eqn'])
self._code['cond'][lang] = self._code['cond'][lang] % magma_cond_option(self.data)

return self._code
Loading