diff --git a/lmfdb/ecnf/WebEllipticCurve.py b/lmfdb/ecnf/WebEllipticCurve.py index bd8651fcc0..22b64cb83b 100644 --- a/lmfdb/ecnf/WebEllipticCurve.py +++ b/lmfdb/ecnf/WebEllipticCurve.py @@ -2,7 +2,7 @@ from flask import url_for from urllib.parse import quote from markupsafe import Markup, escape -from sage.all import (Infinity, PolynomialRing, QQ, RDF, ZZ, KodairaSymbol, +from sage.all import (Infinity, PolynomialRing, QQ, RDF, ZZ, RR, KodairaSymbol, implicit_plot, plot, prod, rainbow, sqrt, text, var) from lmfdb import db from lmfdb.utils import (encode_plot, names_and_urls, web_latex, display_knowl, @@ -494,7 +494,8 @@ def make_E(self): self.mw_struct = "unknown" # Torsion - self.ntors = web_latex(self.torsion_order) + BSDntors = self.torsion_order + self.ntors = web_latex(BSDntors) self.tr = len(self.torsion_structure) if self.tr == 0: self.tor_struct_pretty = "$0$" @@ -563,18 +564,28 @@ def make_E(self): self.bsd_status = "missing_gens" # Regulator only in conditional/unconditional cases, or when we know the rank: + BSDReg = None if self.bsd_status in ["conditional", "unconditional"]: if self.ar == 0: - self.reg = web_latex(1) # otherwise we only get 1.00000... + BSDReg = 1 + self.reg = self.NTreg = web_latex(BSDReg) # otherwise we only get 1.00000... else: try: - self.reg = web_latex(self.reg) + R = self.reg + BSDReg = R * K.degree()**self.rank + self.reg = web_latex(R) + self.NTreg = web_latex(BSDReg) except AttributeError: self.reg = "not available" + self.NTreg = "not available" elif self.rk != "not available": - self.reg = web_latex(self.reg) if self.rank else web_latex(1) + R = self.reg + BSDReg = R * K.degree()**self.rank + self.reg = web_latex(R) if self.rank else web_latex(1) + self.NTreg = web_latex(BSDReg) if self.rank else web_latex(1) else: self.reg = "not available" + self.NTreg = "not available" # Generators try: @@ -585,17 +596,31 @@ def make_E(self): self.gens = [] self.gens_and_heights = [] - # Global period + # Global period -- see issue #5409 for why we multiply by + # 2**nc in most cases. However, data computed after + # 2024-07-09 (including all data for imaginary quadratic + # fields of absolute discriminant > 600 as well as some larger + # conductors for other IQFs) already has the extra factor of + # 2. As a fail-safe until we fix the data in all cases, we + # will test (using the BSD formula) whether to remove the + # factor of 2 added here. + BSDomega = None try: - self.omega = web_latex(self.omega) + BSDomega = self.omega + nc = self.signature[1] # number of complex places + if nc: + BSDomega *= 2**nc + self.omega = web_latex(BSDomega) except AttributeError: self.omega = "not available" # L-value + BSDLvalue = None try: r = int(self.analytic_rank) # lhs = "L(E,1) = " if r==0 else "L'(E,1) = " if r==1 else "L^{{({})}}(E,1)/{}! = ".format(r,r) - self.Lvalue = web_latex(self.Lvalue) + BSDLvalue = self.Lvalue + self.Lvalue = web_latex(BSDLvalue) except (TypeError, AttributeError): self.Lvalue = "not available" @@ -607,14 +632,79 @@ def make_E(self): self.tamagawa_factors = r'\cdot'.join(cp_fac) else: self.tamagawa_factors = None - self.tamagawa_product = web_latex(prod(tamagawa_numbers,1)) + BSDprodcp = prod(tamagawa_numbers,1) + self.tamagawa_product = web_latex(BSDprodcp) # Analytic Sha + BSDsha = None try: - self.sha = web_latex(self.sha) + " (rounded)" + BSDsha = self.sha + self.sha = web_latex(BSDsha) + " (rounded)" except AttributeError: self.sha = "not available" + # Check analytic Sha value compatible with formula in the knowl (see issue #5409) + + BSDrootdisc = RR(K.discriminant().abs()).sqrt() + BSDok = True + if BSDLvalue and BSDsha and BSDReg and (self.rank is not None): + BSDsha_numerator = BSDrootdisc * BSDntors**2 + BSDsha_denominator = BSDReg * BSDomega * BSDprodcp + BSDsha_from_formula = BSDLvalue * BSDsha_numerator / BSDsha_denominator + BSDsha_from_formula_rounded = BSDsha_from_formula.round() + BSDok = (BSDsha_from_formula_rounded == BSDsha) and ((BSDsha_from_formula_rounded -BSDsha_from_formula).abs() < 0.001) + #print(f"{BSDsha_from_formula=}") + #print(f"{BSDsha_from_formula_rounded=}") + #print(f"{BSDsha=}") + #print(f"{BSDok=}") + if not BSDok: + # this means that we doubled BSDomega when we should + # not have, so BSDsha_denominator is doubled and + # BSDsha_from formula is halved + print(f"BSD normalization: adjusting Omega for {self.label}: stored Sha = {BSDsha} but formula gives {BSDsha_from_formula}") + BSDok = ((BSDsha/BSDsha_from_formula)-2).abs() < 0.01 + if not BSDok: + print(f"BSD normalization issue with {self.label}: stored Sha = {BSDsha} but formula gives {BSDsha_from_formula}") + BSDomega /= 2 + BSDsha_denominator /= 2 + BSDsha_from_formula *= 2 + BSDsha_from_formula_rounded = BSDsha_from_formula.round() + BSDLvalue_from_formula = BSDsha * BSDsha_denominator / BSDsha_numerator + self.BSDsha = web_latex(BSDsha_from_formula) + self.BSDLvalue = web_latex(BSDLvalue_from_formula) + + # The BSD formula for display + + dot = '\\cdot' + approx = '\\approx' + eq_query = '\\overset{?}{=}' + frac = '\\frac' + Sha = '\\# Ш(E/K)' + Om = '\\Omega(E/K)' + Reg = '\\mathrm{Reg}_{\\mathrm{NT}}(E/K)' + prodcp = '\\prod_{\\mathfrak{p}} c_{\\mathfrak{p}}' + tors2 = '\\#E(K)_{\\mathrm{tor}}^2' + rootD = '\\left|d_K\\right|^{1/2}' + + lder_name = rf"L^{{({r})}}(E/K,1)/{r}!" if r>=2 else "L'(E/K,1)" if r else "L(E/K,1)" + lhs_num = rf'{Sha} {dot} {Om} {dot} {Reg} {dot} {prodcp}' + lhs_den = rf'{tors2} {dot} {rootD}' + lhs = rf'{frac}{{ {lhs_num} }} {{ {lhs_den} }}' + rhs_num = rf'{BSDsha} {dot} {BSDomega:0.6f} {dot} {BSDReg} {dot} {BSDprodcp}' + if r: + rhs_num = rf'{BSDsha} {dot} {BSDomega:0.6f} {dot} {BSDReg:0.6f} {dot} {BSDprodcp}' + rhs_den = rf'{{{BSDntors}^2 {dot} {BSDrootdisc:0.6f}}}' + rhs = rf'{frac}{{ {rhs_num} }} {{ {rhs_den} }}' + self.bsd_formula = rf'{BSDLvalue:0.9f} {approx} {lder_name} {eq_query} {lhs} {approx} {rhs} {approx} {BSDLvalue_from_formula:0.9f}' + + else: + self.BSDsha = "not available" + self.BSDLvalue = "not available" + self.bsd_formula = None + + if not BSDok: # don't display the formula if it is not correct + self.bsd_formula = None + # Local data # The Kodaira symbol is stored as an int in pari encoding. The diff --git a/lmfdb/ecnf/templates/ecnf-curve.html b/lmfdb/ecnf/templates/ecnf-curve.html index d5a48fa4d7..e288520570 100644 --- a/lmfdb/ecnf/templates/ecnf-curve.html +++ b/lmfdb/ecnf/templates/ecnf-curve.html @@ -274,7 +274,7 @@
* Conditional on {{ KNOWL('ec.bsdconjecture', title='BSD') }}: assuming rank = analytic rank. @@ -364,10 +393,22 @@
+$\displaystyle {{ ec.bsd_formula|safe }}$ +
+