diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst
index bc96846353c..f020ff16cd3 100644
--- a/src/doc/en/reference/references/index.rst
+++ b/src/doc/en/reference/references/index.rst
@@ -1551,6 +1551,10 @@ REFERENCES:
and Monographs*. American Mathematical Society,
Providence, RI, 1999.
+.. [Cox1989] David A. Cox.
+ Primes of the form `x^2+ny^2`.
+ Wiley, 1989.
+
.. [CK2008] Derek G. Corneil and Richard M. Krueger, *A Unified View
of Graph Searching*, SIAM Jounal on Discrete Mathematics,
22(4), 1259–-1276, 2008.
@@ -1647,6 +1651,11 @@ REFERENCES:
.. [CrNa2020] \J.E. Cremona and F. Najman, `\QQ`-curves over odd degree number fields, :arxiv:`2004.10054`.
+.. [CreSuth2023] \J.E. Cremona and A.V. Sutherland.
+ *Computing the endomorphism ring of an elliptic curve
+ over a number field*.
+ :arxiv:`2301.11169`.
+
.. [CoCo1] J.H. Conway, H.S.M. Coxeter
*Triangulated polygons and frieze patterns*,
The Mathematical Gazette (1973) 57 p.87-94
@@ -3699,6 +3708,11 @@ REFERENCES:
.. [KMOY2007] \M. Kashiwara, K. C. Misra, M. Okado, D. Yamada.
*Perfect crystals for* `U_q(D_4^{(3)})`, J. Algebra. **317** (2007).
+.. [Klaise2012] Janis Klaise.
+ *Orders in imaginary quadratic fields of small class number*
+ University of Warwick Undergraduate Masters thesis, unpublished (2012).
+ https://warwick.ac.uk/fac/cross_fac/complexity/people/students/dtc/students2013/klaise/janis_klaise_ug_report.pdf
+
.. [KMR2012] \A. Kleshchev, A. Mathas, and A. Ram, *Universal Specht
modules for cyclotomic Hecke algebras*,
Proc. London Math. Soc. (2012) 105 (6): 1245-1289.
@@ -5860,6 +5874,12 @@ REFERENCES:
Mathematics of Computation **80** (2011), pp. 477-500.
:arxiv:`0809.3413v3`.
+.. [RouSuthZur2022] Jeremy Rouse, Andrew V. Sutherland, David Zureick-Brown.
+ *`\ell`-adic images of Galois for elliptic curves over `\Q`* (and an appendix with John Voight).
+ Forum of Mathematics, Sigma , Volume 10 , 2022.
+ :doi: `10.1017/fms.2022.38`.
+ :arxiv:`2106.11141`.
+
.. [SV1970] \H. Schneider and M. Vidyasagar. Cross-positive matrices. SIAM
Journal on Numerical Analysis, 7:508-519, 1970.
@@ -6177,6 +6197,11 @@ REFERENCES:
.. [Watkins] Mark Watkins, *Hypergeometric motives over Q and their
L-functions*, http://magma.maths.usyd.edu.au/~watkins/papers/known.pdf
+.. [Watkins2004] Mark Watkins.
+ *Class numbers of imaginary quadratic fields*.
+ Math. Comp. 73 (2004), 907-938.
+ https://www.ams.org/journals/mcom/2004-73-246/S0025-5718-03-01517-5/
+
.. [Wat2003] Joel Watson. *Strategy: an introduction to game
theory*. WW Norton, 2002.
diff --git a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst
index fb7697587c5..9e5b3b8fbf0 100644
--- a/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst
+++ b/src/doc/en/thematic_tutorials/explicit_methods_in_number_theory/nf_galois_groups.rst
@@ -188,7 +188,7 @@ field).
::
sage: w = [-1,-2,..,-200]
- sage: v = [D for D in w if is_fundamental_discriminant(D)]
+ sage: v = [D for D in w if D.is_fundamental_discriminant()]
sage: v
[-3, -4, -7, -8, -11, -15, -19, -20, ..., -195, -199]
@@ -222,7 +222,7 @@ class number :math:`1`!
::
sage: w = [1..1000]
- sage: v = [D for D in w if is_fundamental_discriminant(D)]
+ sage: v = [D for D in w if D.is_fundamental_discriminant()]
sage: len(v)
302
sage: len([D for D in v if QuadraticField(D,'a').class_number() == 1])
diff --git a/src/sage/quadratic_forms/binary_qf.py b/src/sage/quadratic_forms/binary_qf.py
index 4b85ad40ed7..7b4e93be75c 100755
--- a/src/sage/quadratic_forms/binary_qf.py
+++ b/src/sage/quadratic_forms/binary_qf.py
@@ -607,7 +607,7 @@ def has_fundamental_discriminant(self):
sage: Q.has_fundamental_discriminant()
False
"""
- return is_fundamental_discriminant(self.discriminant())
+ return self.discriminant().is_fundamental_discriminant()
def is_primitive(self):
r"""
@@ -1774,7 +1774,7 @@ def BinaryQF_reduced_representatives(D, primitive_only=False, proper=True):
# For a fundamental discriminant all forms are primitive so we need not check:
if primitive_only:
- primitive_only = not is_fundamental_discriminant(D)
+ primitive_only = not D.is_fundamental_discriminant()
form_list = []
diff --git a/src/sage/quadratic_forms/special_values.py b/src/sage/quadratic_forms/special_values.py
index dbfad45e31e..4d3ef49d437 100644
--- a/src/sage/quadratic_forms/special_values.py
+++ b/src/sage/quadratic_forms/special_values.py
@@ -163,7 +163,7 @@ def QuadraticBernoulliNumber(k, d):
Let us create a list of some odd negative fundamental discriminants::
- sage: test_set = [d for d in range(-163, -3, 4) if is_fundamental_discriminant(d)]
+ sage: test_set = [d for d in srange(-163, -3, 4) if d.is_fundamental_discriminant()]
In general, we have `B_{1, \chi_d} = -2 h/w` for odd negative fundamental
discriminants::
diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx
index 67a30c01001..f99e2a5add0 100644
--- a/src/sage/rings/integer.pyx
+++ b/src/sage/rings/integer.pyx
@@ -5590,7 +5590,8 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
- ``proof`` (boolean, default ``True``) -- if ``False`` then
for negative discriminants a faster algorithm is used by
the PARI library which is known to give incorrect results
- when the class group has many cyclic factors.
+ when the class group has many cyclic factors. However the
+ results are correct for discriminants `D` with `|D|\le 2\cdot10^{10}`.
OUTPUT:
@@ -5599,7 +5600,7 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
.. NOTE::
- This is not always equal to the number of classes of
+ For positive `D`, this is not always equal to the number of classes of
primitive binary quadratic forms of discriminant `D`, which
is equal to the narrow class number. The two notions are
the same when `D<0`, or `D>0` and the fundamental unit of
@@ -6034,6 +6035,73 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement):
"""
return self.__pari__().issquarefree()
+ def is_discriminant(self):
+ """
+ Returns True if this integer is a discriminant.
+
+ .. NOTE::
+
+ A discriminant is an integer congruent to 0 or 1 modulo 4.
+
+ EXAMPLES::
+
+ sage: (-1).is_discriminant()
+ False
+ sage: (-4).is_discriminant()
+ True
+ sage: 100.is_discriminant()
+ True
+ sage: 101.is_discriminant()
+ True
+
+ TESTS::
+
+ sage: 0.is_discriminant()
+ True
+ sage: 1.is_discriminant()
+ True
+ sage: len([D for D in srange(-100,100) if D.is_discriminant()])
+ 100
+ """
+ return self%4 in [0,1]
+
+ def is_fundamental_discriminant(self):
+ """
+ Returns True if this integer is a fundamental_discriminant.
+
+ .. NOTE::
+
+ A fundamental discriminant is a discrimimant, not 0 or 1 and not a square multiple of a smaller discriminant.
+
+ EXAMPLES::
+
+ sage: (-4).is_fundamental_discriminant()
+ True
+ sage: (-12).is_fundamental_discriminant()
+ False
+ sage: 101.is_fundamental_discriminant()
+ True
+
+ TESTS::
+
+ sage: 0.is_fundamental_discriminant()
+ False
+ sage: 1.is_fundamental_discriminant()
+ False
+ sage: len([D for D in srange(-100,100) if D.is_fundamental_discriminant()])
+ 61
+
+ """
+ if self in [0,1]:
+ return False
+ Dmod4 = self%4
+ if Dmod4 in [2,3]:
+ return False
+ if Dmod4 == 1:
+ return self.is_squarefree()
+ d = self//4
+ return d%4 in [2,3] and d.is_squarefree()
+
cpdef __pari__(self):
"""
Returns the PARI version of this integer.
diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py
index d157026ddf8..c65c477f353 100644
--- a/src/sage/rings/number_field/number_field.py
+++ b/src/sage/rings/number_field/number_field.py
@@ -12188,16 +12188,15 @@ def is_fundamental_discriminant(D):
EXAMPLES::
sage: [D for D in range(-15,15) if is_fundamental_discriminant(D)]
+ ...
+ DeprecationWarning: is_fundamental_discriminant(D) is deprecated; please use D.is_fundamental_discriminant()
+ ...
[-15, -11, -8, -7, -4, -3, 5, 8, 12, 13]
sage: [D for D in range(-15,15) if not is_square(D) and QuadraticField(D,'a').disc() == D]
[-15, -11, -8, -7, -4, -3, 5, 8, 12, 13]
"""
- d = D % 4
- if d not in [0, 1]:
- return False
- return D != 1 and D != 0 and \
- (arith.is_squarefree(D) or
- (d == 0 and (D // 4) % 4 in [2, 3] and arith.is_squarefree(D // 4)))
+ deprecation(35147, "is_fundamental_discriminant(D) is deprecated; please use D.is_fundamental_discriminant()")
+ return Integer(D).is_fundamental_discriminant()
###################
diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx
index 1ecd83db98b..1c52157fb79 100644
--- a/src/sage/rings/number_field/number_field_element.pyx
+++ b/src/sage/rings/number_field/number_field_element.pyx
@@ -4676,10 +4676,6 @@ cdef class NumberFieldElement_absolute(NumberFieldElement):
sage: a.absolute_charpoly(algorithm='pari') == a.absolute_charpoly(algorithm='sage')
True
"""
- # this hack is necessary because quadratic fields override
- # charpoly(), and they don't take the argument 'algorithm'
- if algorithm is None:
- return self.charpoly(var)
return self.charpoly(var, algorithm)
def absolute_minpoly(self, var='x', algorithm=None):
@@ -4707,10 +4703,6 @@ cdef class NumberFieldElement_absolute(NumberFieldElement):
sage: b.absolute_minpoly(algorithm='pari') == b.absolute_minpoly(algorithm='sage')
True
"""
- # this hack is necessary because quadratic fields override
- # minpoly(), and they don't take the argument 'algorithm'
- if algorithm is None:
- return self.minpoly(var)
return self.minpoly(var, algorithm)
def charpoly(self, var='x', algorithm=None):
diff --git a/src/sage/rings/number_field/number_field_element_quadratic.pyx b/src/sage/rings/number_field/number_field_element_quadratic.pyx
index 77661f1aca1..bcba57457e5 100644
--- a/src/sage/rings/number_field/number_field_element_quadratic.pyx
+++ b/src/sage/rings/number_field/number_field_element_quadratic.pyx
@@ -2129,10 +2129,16 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute):
# D = 1 mod 4
return mpz_fdiv_ui(self.D.value, 4) == 1
- def charpoly(self, var='x'):
+ def charpoly(self, var='x', algorithm=None):
r"""
The characteristic polynomial of this element over `\QQ`.
+ INPUT:
+
+ - ``var`` -- the minimal polynomial is defined over a polynomial ring
+ in a variable with this name. If not specified this defaults to ``x``
+ - ``algorithm`` -- for compatibility with general number field elements; ignored
+
EXAMPLES::
sage: K. = NumberField(x^2-x+13)
@@ -2147,15 +2153,16 @@ cdef class NumberFieldElement_quadratic(NumberFieldElement_absolute):
R = QQ[var]
return R([self.norm(), -self.trace(), 1])
- def minpoly(self, var='x'):
+ def minpoly(self, var='x', algorithm=None):
r"""
The minimal polynomial of this element over `\QQ`.
INPUT:
- - ``var`` -- the minimal polynomial is defined over a polynomial ring
- in a variable with this name. If not specified this defaults to
- ``x``.
+ - ``var`` -- the minimal polynomial is defined over a polynomial ring
+ in a variable with this name. If not specified this defaults to ``x``
+ - ``algorithm`` -- for compatibility with general number field elements: and ignored
+
EXAMPLES::
@@ -2651,11 +2658,18 @@ cdef class OrderElement_quadratic(NumberFieldElement_quadratic):
"""
return ZZ(NumberFieldElement_quadratic.trace(self))
- def charpoly(self, var='x'):
+ def charpoly(self, var='x', algorithm=None):
r"""
The characteristic polynomial of this element, which is over `\ZZ`
because this element is an algebraic integer.
+ INPUT:
+
+ - ``var`` -- the minimal polynomial is defined over a polynomial ring
+ in a variable with this name. If not specified this defaults to ``x``
+ - ``algorithm`` -- for compatibility with general number field elements; ignored
+
+
EXAMPLES::
sage: K. = NumberField(x^2 - 5)
@@ -2671,10 +2685,17 @@ cdef class OrderElement_quadratic(NumberFieldElement_quadratic):
R = ZZ[var]
return R([self.norm(), -self.trace(), 1])
- def minpoly(self, var='x'):
+ def minpoly(self, var='x', algorithm=None):
r"""
The minimal polynomial of this element over `\ZZ`.
+ INPUT:
+
+ - ``var`` -- the minimal polynomial is defined over a polynomial ring
+ in a variable with this name. If not specified this defaults to ``x``
+ - ``algorithm`` -- for compatibility with general number field elements; ignored
+
+
EXAMPLES::
sage: K. = NumberField(x^2 + 163)
diff --git a/src/sage/schemes/elliptic_curves/cm.py b/src/sage/schemes/elliptic_curves/cm.py
index 02a24908f1c..94b922a0365 100644
--- a/src/sage/schemes/elliptic_curves/cm.py
+++ b/src/sage/schemes/elliptic_curves/cm.py
@@ -4,11 +4,13 @@
This module implements the functions
- ``hilbert_class_polynomial``
+- ``is_HCP``
- ``cm_j_invariants``
- ``cm_orders``
- ``discriminants_with_bounded_class_number``
- ``cm_j_invariants_and_orders``
- ``largest_fundamental_disc_with_class_number``
+- ``is_cm_j_invariant``
AUTHORS:
@@ -41,6 +43,7 @@
from sage.rings.number_field.number_field import is_fundamental_discriminant
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+from sage.schemes.elliptic_curves.all import EllipticCurve
from sage.misc.cachefunc import cached_function
from sage.rings.number_field.number_field_element_base import NumberFieldElement_base
@@ -174,6 +177,154 @@ def hilbert_class_polynomial(D, algorithm=None):
return IntegerRing()['x'](coeffs)
+def is_HCP(f, check_monic_irreducible=True):
+ r"""
+ Determine whether a polynomial is a Hilbert Class Polynomial.
+
+ INPUT:
+
+ - ``f`` -- a polynomial in `\ZZ[X]`.
+ - ``check_monic_irreducible`` (boolean, default ``True``) -- if
+ ``True``, check that ``f`` is a monic, irreducible, integer
+ polynomial.
+
+ OUTPUT:
+
+ (integer) -- either `D` if ``f`` is the Hilbert Class Polynomial
+ `H_D` for discriminant `D`, or `0` if not an HCP.
+
+ ALGORITHM:
+
+ Cremona and Sutherland: Algorithm 2 of [CreSuth2023]_.
+
+ EXAMPLES:
+
+ Even for large degrees this is fast. We test the largest
+ discriminant of class number 100, for which the HCP has coefficients
+ with thousands of digits::
+
+ sage: from sage.schemes.elliptic_curves.cm import is_HCP
+ sage: D = -1856563
+ sage: D.class_number()
+ 100
+ sage: H = hilbert_class_polynomial(D)
+ sage: H.degree()
+ 100
+ sage: max(c for c in H).ndigits()
+ 2774
+ sage: is_HCP(H)
+ -1856563
+
+ Testing polynomials which are not HCPs is faster::
+
+ sage: is_HCP(H+1)
+ 0
+
+
+ TESTS::
+
+ sage: from sage.schemes.elliptic_curves.cm import is_HCP
+ sage: all(is_HCP(hilbert_class_polynomial(D))==D for D in srange(-4,-100,-1) if D.is_discriminant())
+ True
+ sage: all(not is_HCP(hilbert_class_polynomial(D)+1) for D in srange(-4,-100,-1) if D.is_discriminant())
+ True
+ """
+ zero = ZZ(0)
+ # optional check that input is monic and irreducible
+ if check_monic_irreducible:
+ try:
+ if not (all(c in ZZ for c in f) and f.is_monic()):
+ return zero
+ f = f.change_ring(ZZ)
+ except AttributeError:
+ return zero
+
+ from sage.rings.real_mpfr import RR
+ from sage.rings.finite_rings.finite_field_constructor import GF
+
+ h = f.degree()
+ h2list = [d for d in h.divisors() if (d-h)%2 == 0 and d.prime_to_m_part(2) == 1]
+ pmin = 33 * (h**2 * (RR(h+2).log().log()+2)**2).ceil()
+ # Guarantees 4*p > |D| for fundamental D under GRH
+ p = pmin-1
+ n = 0
+ from sage.arith.all import next_prime
+ while True:
+ p = next_prime(p)
+ n += 1
+ fp = f.change_ring(GF(p))
+ # Compute X^p-X mod fp
+ z = fp.parent().gen()
+ r = pow(z, p, fp) - z
+ d = r.gcd(fp).degree() # number of roots mod p
+ if d == 0:
+ continue
+ if not fp.is_squarefree():
+ continue
+ if d, <... 'sage.rings.integer.Integer'>)
sage: v = cm_orders(2); v
- [(-3, 7), (-3, 5), (-3, 4), (-4, 5), (-4, 4), (-4, 3), (-7, 4), (-8, 3), (-8, 2), (-11, 3), (-15, 2), (-15, 1), (-20, 1), (-24, 1), (-35, 1), (-40, 1), (-51, 1), (-52, 1), (-88, 1), (-91, 1), (-115, 1), (-123, 1), (-148, 1), (-187, 1), (-232, 1), (-235, 1), (-267, 1), (-403, 1), (-427, 1)]
+ [(-3, 4), (-3, 5), (-3, 7), (-4, 3), (-4, 4), (-4, 5), (-7, 4), (-8, 2), (-8, 3), (-11, 3), (-15, 1), (-15, 2), (-20, 1), (-24, 1), (-35, 1), (-40, 1), (-51, 1), (-52, 1), (-88, 1), (-91, 1), (-115, 1), (-123, 1), (-148, 1), (-187, 1), (-232, 1), (-235, 1), (-267, 1), (-403, 1), (-427, 1)]
sage: len(v)
29
sage: set([hilbert_class_polynomial(D*f^2).degree() for D,f in v])
{2}
- Any degree up to 100 is implemented, but may be prohibitively slow::
+ Any degree up to 100 is implemented, but may be slow::
sage: cm_orders(3)
- [(-3, 9), (-3, 6), (-11, 2), (-19, 2), (-23, 2), (-23, 1), (-31, 2), (-31, 1), (-43, 2), (-59, 1), (-67, 2), (-83, 1), (-107, 1), (-139, 1), (-163, 2), (-211, 1), (-283, 1), (-307, 1), (-331, 1), (-379, 1), (-499, 1), (-547, 1), (-643, 1), (-883, 1), (-907, 1)]
+ [(-3, 6), (-3, 9), (-11, 2), (-19, 2), (-23, 1), (-23, 2), (-31, 1), (-31, 2), (-43, 2), (-59, 1), (-67, 2), (-83, 1), (-107, 1), (-139, 1), (-163, 2), (-211, 1), (-283, 1), (-307, 1), (-331, 1), (-379, 1), (-499, 1), (-547, 1), (-643, 1), (-883, 1), (-907, 1)]
sage: len(cm_orders(4))
84
"""
@@ -334,13 +487,16 @@ def cm_orders(h, proof=None):
if h <= 0:
# trivial case
return []
- # Get information for all discriminants then throw away everything
- # but for h. If this is replaced by a table it will be faster,
- # but not now. (David Kohel is rumored to have a large table.)
- return discriminants_with_bounded_class_number(h, proof=proof)[h]
+
+ if h in hDf_dict:
+ return hDf_dict[h]
+ else: # Get all discriminants for all class numbers up to h (which will
+ # be stored in hDf_dict), and return just those with class number h.
+ return discriminants_with_bounded_class_number(h, proof=proof)[h]
# Table from Mark Watkins paper "Class numbers of imaginary quadratic fields".
-# I extracted this by cutting/pasting from the pdf, and running this program:
+
+# WAS extracted this by cutting/pasting from the pdf, and running this program:
# z = {}
# for X in open('/Users/wstein/tmp/a.txt').readlines():
# if len(X.strip()):
@@ -348,6 +504,10 @@ def cm_orders(h, proof=None):
# for i in range(5):
# z[v[3*i]]=(v[3*i+2], v[3*i+1])
+# The keys are integers 1--100 and the value for each h is (|D|,n)
+# where |D| is the largest absolute discriminant of an imaginary
+# quadratic field with class number h, and n is the number of such
+# fields. These are all *unconditional* (not dependent on GRH).
watkins_table = {1: (163, 9), 2: (427, 18), 3: (907, 16), 4: (1555, 54), 5: (2683, 25),
6: (3763, 51), 7: (5923, 31), 8: (6307, 131), 9: (10627, 34), 10:
@@ -377,13 +537,60 @@ def cm_orders(h, proof=None):
95:(1659067, 241), 96: (1684027, 3283), 97: (1842523, 185), 98: (2383747,580),
99: (1480627, 289), 100: (1856563, 1736)}
+# Table from Janis Klaise [Klaise2012]_
+
+# Extracted by converting pdf to text via pdf2ps and ps2txt, cutting/pasting
+# and running this code:
+
+# klaise_table = {}
+# for X in open('klaise_table.txt').readlines():
+# if len(X.strip()):
+# v = [int(a) for a in X.split()]
+# for i in range(4):
+# klaise_table[v[3*i]]=(v[3*i+2], v[3*i+1])
+
+# The keys are integers 1--100 and the value for each h is (|D|,n)
+# where |D| is the largest discriminant of an imaginary quadratic
+# order with class number h, and n is the number of such orders.
+# These are all *unconditional* (not dependent on GRH).
+
+klaise_table = {1: (163, 13), 2: (427, 29), 3: (907, 25), 4: (1555, 84), 5: (2683, 29), 6: (4075, 101),
+ 7: (5923, 38), 8: (7987, 208), 9: (10627, 55), 10: (13843, 123), 11: (15667, 46),
+ 12: (19723, 379), 13: (20563, 43), 14: (30067, 134), 15: (34483, 95), 16: (35275, 531),
+ 17: (37123, 50), 18: (48427, 291), 19: (38707, 59), 20: (58843, 502), 21: (61483, 118),
+ 22: (85507, 184), 23: (90787, 78), 24: (111763, 1042), 25: (93307, 101), 26: (103027, 227),
+ 27: (103387, 136), 28: (126043, 623), 29: (166147, 94), 30: (137083, 473), 31: (133387, 83),
+ 32: (164803, 1231), 33: (222643, 158), 34: (189883, 262), 35: (210907, 111), 36: (217627, 1306),
+ 37: (158923, 96), 38: (289963, 284), 39: (253507, 162), 40: (274003, 1418), 41: (296587, 125),
+ 42: (301387, 596), 43: (300787, 123), 44: (319867, 911), 45: (308323, 231), 46: (462883, 330),
+ 47: (375523, 117), 48: (335203, 2895), 49: (393187, 146), 50: (389467, 445), 51: (546067, 217),
+ 52: (457867, 1006), 53: (425107, 130), 54: (532123, 812), 55: (452083, 177), 56: (494323, 1812),
+ 57: (615883, 237), 58: (586987, 361), 59: (474307, 144), 60: (662803, 2361), 61: (606643, 149),
+ 62: (647707, 386), 63: (991027, 311), 64: (693067, 2919), 65: (703123, 192), 66: (958483, 861),
+ 67: (652723, 145), 68: (819163, 1228), 69: (888427, 292), 70: (821683, 704), 71: (909547, 176),
+ 72: (947923, 4059), 73: (886867, 137), 74: (951043, 474), 75: (916507, 353), 76: (1086187, 1384),
+ 77: (1242763, 236), 78: (1004347, 925), 79: (1333963, 200), 80: (1165483, 3856), 81: (1030723, 339),
+ 82: (1446547, 487), 83: (1074907, 174), 84: (1225387, 2998), 85: (1285747, 246), 86: (1534723, 555),
+ 87: (1261747, 313), 88: (1265587, 2771), 89: (1429387, 206), 90: (1548523, 1516), 91: (1391083, 249),
+ 92: (1452067, 1591), 93: (1475203, 354), 94: (1587763, 600), 95: (1659067, 273), 96: (1684027, 7276),
+ 97: (1842523, 208), 98: (2383747, 710), 99: (1480627, 396), 100: (1856563, 2311)}
+
def largest_fundamental_disc_with_class_number(h):
- """
- Return largest absolute value of any fundamental discriminant with
- class number `h`, and the number of fundamental discriminants with
- that class number. This is known for `h` up to 100, by work of Mark
- Watkins.
+ r"""
+ Return largest absolute value of any fundamental negative discriminant with
+ class number `h`, and the number of fundamental negative discriminants with
+ that class number. This is known (unconditionally) for `h` up to 100,
+ by work of Mark Watkins ([Watkins2004]_).
+
+ .. NOTE::
+
+ The class number of a fundamental negative discriminant `D` is
+ the same as the class number of the imaginary quadratic field
+ `\QQ(\sqrt{D})`, so this function gives the number of such
+ fields of each class number `h\le100`. It is easy to extend
+ this to larger class number conditional on the GRH, but much
+ harder to obtain unconditional results.
INPUT:
@@ -391,60 +598,127 @@ class number `h`, and the number of fundamental discriminants with
EXAMPLES::
- sage: sage.schemes.elliptic_curves.cm.largest_fundamental_disc_with_class_number(0)
+ sage: from sage.schemes.elliptic_curves.cm import largest_fundamental_disc_with_class_number
+ sage: largest_fundamental_disc_with_class_number(0)
(0, 0)
- sage: sage.schemes.elliptic_curves.cm.largest_fundamental_disc_with_class_number(1)
+ sage: largest_fundamental_disc_with_class_number(1)
(163, 9)
- sage: sage.schemes.elliptic_curves.cm.largest_fundamental_disc_with_class_number(2)
+ sage: largest_fundamental_disc_with_class_number(2)
(427, 18)
- sage: sage.schemes.elliptic_curves.cm.largest_fundamental_disc_with_class_number(10)
+ sage: largest_fundamental_disc_with_class_number(10)
(13843, 87)
- sage: sage.schemes.elliptic_curves.cm.largest_fundamental_disc_with_class_number(100)
+ sage: largest_fundamental_disc_with_class_number(100)
(1856563, 1736)
- sage: sage.schemes.elliptic_curves.cm.largest_fundamental_disc_with_class_number(101)
+ sage: largest_fundamental_disc_with_class_number(101)
Traceback (most recent call last):
...
- NotImplementedError: largest discriminant not known for class number 101
+ NotImplementedError: largest fundamental discriminant not available for class number 101
+
"""
h = Integer(h)
if h <= 0:
- # very easy special case
return Integer(0), Integer(0)
try:
- # simply look up the answer in Watkins's table.
B, c = watkins_table[h]
return (Integer(B), Integer(c))
except KeyError:
- # nobody knows, since I guess Watkins's is state of the art.
- raise NotImplementedError("largest discriminant not known for class number %s" % h)
+ raise NotImplementedError("largest fundamental discriminant not available for class number %s" % h)
+def largest_disc_with_class_number(h):
+ r"""
+ Return largest absolute value of any negative discriminant with
+ class number `h`, and the number of fundamental negative
+ discriminants with that class number. This is known
+ (unconditionally) for `h` up to 100, by work of Mark Watkins
+ [Watkins2004]_ for fundamental discriminants, extended to all
+ discriminants of class number `h\le100` by Klaise [Klaise2012]_.
+
+ .. NOTE::
+
+ The class number of a negative discriminant `D` is
+ the same as the class number of the unique imaginary quadratic order
+ of discriminant `D`, so this function gives the number of such
+ orders of each class number `h\le100`. It is easy to extend
+ this to larger class number conditional on the GRH, but much
+ harder to obyain unconditional results.
+
+ INPUT:
+
+ - `h` -- integer
+
+ EXAMPLES::
+
+ sage: from sage.schemes.elliptic_curves.cm import largest_disc_with_class_number
+ sage: largest_disc_with_class_number(0)
+ (0, 0)
+ sage: largest_disc_with_class_number(1)
+ (163, 13)
+ sage: largest_disc_with_class_number(2)
+ (427, 29)
+ sage: largest_disc_with_class_number(10)
+ (13843, 123)
+ sage: largest_disc_with_class_number(100)
+ (1856563, 2311)
+ sage: largest_disc_with_class_number(101)
+ Traceback (most recent call last):
+ ...
+ NotImplementedError: largest discriminant not available for class number 101
+
+ For most `h\le100`, the largest fundamental discriminant with
+ class number `h` is also the largest discriminant, but this is not
+ the case for some `h`::
+
+ sage: from sage.schemes.elliptic_curves.cm import largest_disc_with_class_number, largest_fundamental_disc_with_class_number
+ sage: [h for h in range(1,101) if largest_disc_with_class_number(h)[0] != largest_fundamental_disc_with_class_number(h)[0]]
+ [6, 8, 12, 16, 20, 30, 40, 42, 52, 70]
+ sage: largest_fundamental_disc_with_class_number(6)
+ (3763, 51)
+ sage: largest_disc_with_class_number(6)
+ (4075, 101)
+
+ """
+ h = Integer(h)
+ if h <= 0:
+ return Integer(0), Integer(0)
+ try:
+ B, c = klaise_table[h]
+ return (Integer(B), Integer(c))
+ except KeyError:
+ raise NotImplementedError("largest discriminant not available for class number %s" % h)
+
+# This dict has class numbers h as keys, the value at h is a complete
+# list of pairs (D0,f) such that D=D0*f**2 has class number h. We
+# initialise it with h=1 only; other values will be added by calls to
+# discriminants_with_bounded_class_number().
+
+hDf_dict = {ZZ(1): [(ZZ(D), ZZ(h)) for D,h in
+ [(-3, 1), (-3, 2), (-3, 3), (-4, 1), (-4, 2), (-7, 1), (-7, 2),
+ (-8, 1), (-11, 1), (-19, 1), (-43, 1), (-67, 1), (-163, 1)]]}
-@cached_function
def discriminants_with_bounded_class_number(hmax, B=None, proof=None):
- r"""
- Return dictionary with keys class numbers `h\le hmax` and values the
- list of all pairs `(D, f)`, with `D<0` a fundamental discriminant such
- that `Df^2` has class number `h`. If the optional bound `B` is given,
- return only those pairs with fundamental `|D| \le B`, though `f` can
- still be arbitrarily large.
+ r"""Return a dictionary with keys class numbers `h\le hmax` and values the
+ list of all pairs `(D_0, f)`, with `D_0<0` a fundamental discriminant such
+ that `D=D_0f^2` has class number `h`. If the optional bound `B` is given,
+ return only those pairs with `|D| \le B`.
INPUT:
- ``hmax`` -- integer
- `B` -- integer or None; if None returns all pairs
- ``proof`` -- this code calls the PARI function :pari:`qfbclassno`, so it
- could give wrong answers when ``proof``==``False``. The default is
- whatever ``proof.number_field()`` is. If ``proof==False`` and `B` is
- ``None``, at least the number of discriminants is correct, since it
- is double checked with Watkins's table.
+ could give wrong answers when ``proof``==``False`` (though only for
+ discriminants greater than `2\cdot10^{10}`). The default is
+ the current value of ``proof.number_field()``.
OUTPUT:
- dictionary
- In case `B` is not given, we use Mark Watkins's: "Class numbers of
- imaginary quadratic fields" to compute a `B` that captures all `h`
- up to `hmax` (only available for `hmax\le100`).
+ .. NOTE::
+
+ In case `B` is not given, then ``hmax`` must be at most 100; we
+ use the tables from [Watkins2004]_ and [Klaise2012]_ to compute
+ a `B` that captures all `h` up to `hmax`.
EXAMPLES::
@@ -452,11 +726,11 @@ def discriminants_with_bounded_class_number(hmax, B=None, proof=None):
sage: sorted(v)
[1, 2, 3]
sage: v[1]
- [(-3, 3), (-3, 2), (-3, 1), (-4, 2), (-4, 1), (-7, 2), (-7, 1), (-8, 1), (-11, 1), (-19, 1), (-43, 1), (-67, 1), (-163, 1)]
+ [(-3, 1), (-3, 2), (-3, 3), (-4, 1), (-4, 2), (-7, 1), (-7, 2), (-8, 1), (-11, 1), (-19, 1), (-43, 1), (-67, 1), (-163, 1)]
sage: v[2]
- [(-3, 7), (-3, 5), (-3, 4), (-4, 5), (-4, 4), (-4, 3), (-7, 4), (-8, 3), (-8, 2), (-11, 3), (-15, 2), (-15, 1), (-20, 1), (-24, 1), (-35, 1), (-40, 1), (-51, 1), (-52, 1), (-88, 1), (-91, 1), (-115, 1), (-123, 1), (-148, 1), (-187, 1), (-232, 1), (-235, 1), (-267, 1), (-403, 1), (-427, 1)]
+ [(-3, 4), (-3, 5), (-3, 7), (-4, 3), (-4, 4), (-4, 5), (-7, 4), (-8, 2), (-8, 3), (-11, 3), (-15, 1), (-15, 2), (-20, 1), (-24, 1), (-35, 1), (-40, 1), (-51, 1), (-52, 1), (-88, 1), (-91, 1), (-115, 1), (-123, 1), (-148, 1), (-187, 1), (-232, 1), (-235, 1), (-267, 1), (-403, 1), (-427, 1)]
sage: v[3]
- [(-3, 9), (-3, 6), (-11, 2), (-19, 2), (-23, 2), (-23, 1), (-31, 2), (-31, 1), (-43, 2), (-59, 1), (-67, 2), (-83, 1), (-107, 1), (-139, 1), (-163, 2), (-211, 1), (-283, 1), (-307, 1), (-331, 1), (-379, 1), (-499, 1), (-547, 1), (-643, 1), (-883, 1), (-907, 1)]
+ [(-3, 6), (-3, 9), (-11, 2), (-19, 2), (-23, 1), (-23, 2), (-31, 1), (-31, 2), (-43, 2), (-59, 1), (-67, 2), (-83, 1), (-107, 1), (-139, 1), (-163, 2), (-211, 1), (-283, 1), (-307, 1), (-331, 1), (-379, 1), (-499, 1), (-547, 1), (-643, 1), (-883, 1), (-907, 1)]
sage: v = sage.schemes.elliptic_curves.cm.discriminants_with_bounded_class_number(8, proof=False)
sage: sorted(len(v[h]) for h in v)
[13, 25, 29, 29, 38, 84, 101, 208]
@@ -464,129 +738,130 @@ def discriminants_with_bounded_class_number(hmax, B=None, proof=None):
Find all class numbers for discriminant up to 50::
sage: sage.schemes.elliptic_curves.cm.discriminants_with_bounded_class_number(hmax=5, B=50)
- {1: [(-3, 3), (-3, 2), (-3, 1), (-4, 2), (-4, 1), (-7, 2), (-7, 1), (-8, 1), (-11, 1), (-19, 1), (-43, 1)], 2: [(-3, 7), (-3, 5), (-3, 4), (-4, 5), (-4, 4), (-4, 3), (-7, 4), (-8, 3), (-8, 2), (-11, 3), (-15, 2), (-15, 1), (-20, 1), (-24, 1), (-35, 1), (-40, 1)], 3: [(-3, 9), (-3, 6), (-11, 2), (-19, 2), (-23, 2), (-23, 1), (-31, 2), (-31, 1), (-43, 2)], 4: [(-3, 13), (-3, 11), (-3, 8), (-4, 10), (-4, 8), (-4, 7), (-4, 6), (-7, 8), (-7, 6), (-7, 3), (-8, 6), (-8, 4), (-11, 5), (-15, 4), (-19, 5), (-19, 3), (-20, 3), (-20, 2), (-24, 2), (-35, 3), (-39, 2), (-39, 1), (-40, 2), (-43, 3)], 5: [(-47, 2), (-47, 1)]}
+ {1: [(-3, 1), (-3, 2), (-3, 3), (-4, 1), (-4, 2), (-7, 1), (-7, 2), (-8, 1), (-11, 1), (-19, 1), (-43, 1)], 2: [(-3, 4), (-4, 3), (-8, 2), (-15, 1), (-20, 1), (-24, 1), (-35, 1), (-40, 1)], 3: [(-11, 2), (-23, 1), (-31, 1)], 4: [(-39, 1)], 5: [(-47, 1)]}
+
"""
+ hmax = Integer(hmax)
+ global hDf_dict
+
+ # Easy case where we have already computed and cached the relevant values
+ if hDf_dict and hmax <= max(hDf_dict):
+ T = {h:Dflist for h,Dflist in hDf_dict.items() if h<=hmax}
+ if B:
+ for h in T:
+ T[h] = [Df for Df in T[h] if Df[0].abs()*Df[1]**2<=B]
+ return T
+
# imports that are needed only for this function
+ from sage.arith.srange import xsrange
from sage.structure.proof.proof import get_flag
-
- # deal with input defaults and type checking
proof = get_flag(proof, 'number_field')
- hmax = Integer(hmax)
-
- # T stores the output
- T = {}
-
- # Easy case -- instead of giving error, give meaningful output
- if hmax < 1:
- return T
if B is None:
- # Determine how far we have to go by applying Watkins's theorem.
- v = [largest_fundamental_disc_with_class_number(h) for h in range(1, hmax+1)]
- B = max([b for b,_ in v])
- fund_count = [0] + [cnt for _,cnt in v]
+ if hmax <= 100:
+ # Determine how far we have to go by applying Watkins + Klaise's results.
+ v = [largest_disc_with_class_number(h) for h in range(1, hmax+1)]
+ B = max([b for b,_ in v])
+ #print("Testing all discriminants up to {}".format(B))
+ count = [0] + [cnt for _,cnt in v]
+ else:
+ raise ValueError("if hmax>100 you must specify a discriminant bound B")
else:
# Nothing to do -- set to None so we can use this later to know not
# to do a double check about how many we find.
- fund_count = None
+ count = None
B = Integer(B)
if B <= 2:
# This is an easy special case, since there are no fundamental discriminants
# this small.
- return T
-
- # This lower bound gets used in an inner loop below.
- from math import log
-
- def lb(f):
- """Lower bound on euler_phi."""
- # 1.79 > e^gamma = 1.7810724...
- if f <= 1:
- return 0 # don't do log(log(1)) = log(0)
- llf = log(log(f))
- return f/(1.79*llf + 3.0/llf)
-
- for D in range(-B, -2):
- D = Integer(D)
- if is_fundamental_discriminant(D):
- h_D = D.class_number(proof)
- # For each fundamental discriminant D, loop through the f's such
- # that h(D*f^2) could possibly be <= hmax. As explained to me by Cremona,
- # we have h(D*f^2) >= (1/c)*h(D)*phi_D(f) >= (1/c)*h(D)*euler_phi(f), where
- # phi_D(f) is like euler_phi(f) but the factor (1-1/p) is replaced
- # by a factor of (1-kr(D,p)*p), where kr(D/p) is the Kronecker symbol.
- # The factor c is 1 unless D=-4 and f>1 (when c=2) or D=-3 and f>1 (when c=3).
- # Since (1-1/p) <= 1 and (1-1/p) <= (1+1/p), we see that
- # euler_phi(f) <= phi_D(f).
- #
- # We have the following analytic lower bound on euler_phi:
- #
- # euler_phi(f) >= lb(f) = f / (exp(euler_gamma)*log(log(n)) + 3/log(log(n))).
- #
- # See Theorem 8 of Peter Clark's
- # http://math.uga.edu/~pete/4400arithmeticorders.pdf
- # which is a consequence of Theorem 15 of
- # [Rosser and Schoenfeld, 1962].
- #
- # By Calculus, we see that the lb(f) is an increasing function of f >= 2.
- #
- # NOTE: You can visibly "see" that it is a lower bound in Sage with
- # lb(n) = n/(exp(euler_gamma)*log(log(n)) + 3/log(log(n)))
- # plot(lb, (n, 1, 10^4), color='red') + plot(lambda x: euler_phi(int(x)), 1, 10^4).show()
- #
- # So we consider f=1,2,..., until the first f with lb(f)*h_D > c*h_max.
- # (Note that lb(f) is <= 0 for f=1,2, so nothing special is needed there.)
- #
- # TODO: Maybe we could do better using a bound for phi_D(f).
- #
- f = Integer(1)
- chmax=hmax
- if D==-3:
- chmax*=3
+ return {}
+
+ # T stores the values found, to be returned. It will also be used
+ # to update the global hDf_dict *provided that* the parameter B
+ # was not provided. Note that we only reach this point if we have
+ # not already computed data for at least one class number, hmax.
+
+ # To avoid recomputing class numbers, we initialise T with the
+ # global hDf_dict, only including those (D,f) for which |D|*f**2
+ # <= B if B was provided:
+
+ # h_dict caches the class number h of all discriminants previously
+ # encountered; we will use the function OrderClassNumber() to
+ # quicky compute the class number of non-fundamental discriminants
+ # from the fundamental ones. Note that in the initialisation, the
+ # keys of h_dict include nonfundamental discriminants, but we only
+ # update it with fundamental ones.
+
+ from collections import defaultdict
+ T = defaultdict(set)
+ h_dict = {}
+ for h, Dflist in hDf_dict.items():
+ for D0,f in Dflist:
+ h_dict[D0*f**2] = h
+ if not count:
+ Dflist = [Df for Df in Dflist if Df[0].abs()*Df[1]**2<=B]
+ T[h] = set(Dflist)
+
+ # We do not need to certify the class number from :pari:`qfbclassno` for discriminants under 2*10^10
+ if B < 2*10**10:
+ proof = False
+
+ for D in xsrange(-3, -B-1, -1):
+ if not D.is_discriminant():
+ continue
+ D0 = D.squarefree_part()
+ if D0%4 !=1:
+ D0 *= 4
+ f = (D//D0).isqrt()
+
+ # Now D0 is the fundamental discriminant and f the conductor
+
+ if D in h_dict:
+ h = h_dict[D]
+ else:
+ if f == 1: # D itself is fundamental
+ h = D.class_number(proof)
+ h_dict[D] = h
else:
- if D==-4:
- chmax*=2
- while lb(f)*h_D <= chmax:
- if f == 1:
- h = h_D
- else:
- h = (D*f*f).class_number(proof)
- # If the class number of this order is within the range, then
- # use it. (NOTE: In some cases there is a simple relation between
- # the class number for D and D*f^2, and this could be used to
- # optimize this inner loop a little.)
- if h <= hmax:
- z = (D, f)
- if h in T:
- T[h].append(z)
- else:
- T[h] = [z]
- f += 1
+ h = OrderClassNumber(D0,h_dict[D0],f)
+
+ # If the class number of this order is within the range, then store (D0,f)
+ if h <= hmax:
+ T[h].add((D0,f))
+
+ # sort each list of (D,f) pairs by (|D|,f)
for h in T:
- T[h] = list(reversed(T[h]))
+ T[h] = list(T[h])
+ T[h].sort(key=lambda Df: (Df[0].abs(), Df[1]))
- if fund_count is not None:
- # Double check that we found the right number of fundamental
- # discriminants; we might as well, since Watkins provides this
- # data.
+ # count is None precisely when the user provided a value of B
+ if count is not None:
+ # 1. Check that we found the right number of discriminants
for h in T:
- if len([DD for DD, ff in T[h] if ff == 1]) != fund_count[h]:
+ if len(T[h]) != count[h]:
raise RuntimeError("number of discriminants inconsistent with Watkins's table")
+ # 2. Update the global dict
+ hDf_dict.update(dict(T))
return T
@cached_function
-def is_cm_j_invariant(j, method='new'):
- """
- Return whether or not this is a CM `j`-invariant.
+def is_cm_j_invariant(j, algorithm='CremonaSutherland', method=None):
+ r"""Return whether or not this is a CM `j`-invariant, and the CM discriminant if it is.
INPUT:
- ``j`` -- an element of a number field `K`
+ - ``algorithm`` (string, default 'CremonaSutherland') -- the algorithm
+ used, either 'CremonaSutherland' (the default, very much faster
+ for all but very small degrees), 'exhaustive' or 'reduction'
+
+ - ``method`` (string) -- deprecated name for ``algorithm``
+
OUTPUT:
A pair (bool, (d,f)) which is either (False, None) if `j` is not a
@@ -594,15 +869,24 @@ def is_cm_j_invariant(j, method='new'):
imaginary quadratic order of discriminant `D=df^2` where `d` is
the associated fundamental discriminant and `f` the index.
- .. NOTE::
+ ALGORITHM:
+
+ The default algorithm used is to test whether the minimal
+ polynomial of ``j`` is a Hilbert CLass Polynomail, using
+ :func:`is_HCP` which implements Algorithm 2 of [CreSuth2023]_ by
+ Cremona and Sutherland.
- The current implementation makes use of the classification of
- all orders of class number up to 100, and hence will raise an
- error if `j` is an algebraic integer of degree greater than
- this. It would be possible to implement a more general
- version, using the fact that `d` must be supported on the
- primes dividing the discriminant of the minimal polynomial of
- `j`.
+ Two older algorithms are available, both of which are much slower
+ except for very small degrees.
+
+ Method 'exhaustive' makes use of the complete and unconditionsl classification of
+ all orders of class number up to 100, and hence will raise an
+ error if `j` is an algebraic integer of degree greater than
+ this.
+
+ Method 'reduction' constructs an elliptic curve over the number
+ field `\QQ(j)` and computes its traces of Frobenius at several
+ primes of degree 1.
EXAMPLES::
@@ -619,12 +903,29 @@ def is_cm_j_invariant(j, method='new'):
sage: is_cm_j_invariant(31710790944000*a^2 + 39953093016000*a + 50337742902000)
(True, (-3, 6))
+ An example of large degree. This is only possible using the default algorithm::
+
+ sage: from sage.schemes.elliptic_curves.cm import is_cm_j_invariant
+ sage: D = -1856563
+ sage: H = hilbert_class_polynomial(D)
+ sage: H.degree()
+ 100
+ sage: K. = NumberField(H)
+ sage: is_cm_j_invariant(j)
+ (True, (-1856563, 1))
+
TESTS::
sage: from sage.schemes.elliptic_curves.cm import is_cm_j_invariant
sage: all(is_cm_j_invariant(j) == (True, (d,f)) for d,f,j in cm_j_invariants_and_orders(QQ))
True
+
"""
+ if method:
+ if not algorithm:
+ algorithm = method
+ raise DeprecationWarning("'method' is deprecated, use 'algorithm instead'")
+
# First we check that j is an algebraic number:
if not isinstance(j, NumberFieldElement_base) and j not in QQ:
raise NotImplementedError("is_cm_j_invariant() is only implemented for number field elements")
@@ -643,32 +944,48 @@ def is_cm_j_invariant(j, method='new'):
if j in QQ:
return False, None
- # Now j has degree at least 2. If it is not integral so is not CM:
+ # Next we find its minimal polynomial of j:
- if not j.is_integral():
+ if j.parent().absolute_degree() == 2:
+ jpol = j.absolute_minpoly() # no algorithm parameter
+ else:
+ jpol = j.absolute_minpoly(algorithm='pari')
+
+ # If it does not have integer coefficients then j is not integral, hence not CM:
+
+ if not all(c in ZZ for c in jpol):
return False, None
- # Next we find its minimal polynomial and degree h, and if h is
- # less than the degree of j.parent() we recreate j as an element
- # of Q(j):
+ # Otherwise test whether it is a Hilbert Class Polynomial
+ # (using the fact that we know that it is monic and irreducible):
- jpol = PolynomialRing(QQ,'x')([-j,1]) if j in QQ else j.absolute_minpoly()
- h = jpol.degree()
+ if algorithm == 'CremonaSutherland':
+ D = is_HCP(jpol, check_monic_irreducible=False)
+ if D:
+ D0 = D.squarefree_part()
+ if D0%4 !=1:
+ D0 *= 4
+ f = ZZ(D//D0).isqrt()
+ return (True, (D0,f))
+ else:
+ return (False, None)
- # This will be used as a fall-back if we cannot determine the
- # result using local data. For this to be necessary there would
- # have to be very few primes of degree 1 and norm under 1000,
- # since we only need to find one prime of degree 1, good
- # reduction for which a_P is nonzero.
- if method=='old':
+ h = jpol.degree()
+ if algorithm in ['exhaustive', 'old']:
if h>100:
raise NotImplementedError("CM data only available for class numbers up to 100")
for d,f in cm_orders(h):
if jpol == hilbert_class_polynomial(d*f**2):
- return True, (d,f)
- return False, None
+ return (True, (d,f))
+ return (False, None)
+
+ if algorithm not in ['reduction', 'new']:
+ raise ValueError("Invalid algorithm {} in is_cm_j_invariant".format(algorithm))
+
+ # Now we use the reduction algorithm
- # replace j by a clone whose parent is Q(j), if necessary:
+ # If the degree h is less than the degree of j.parent() we recreate j as an element
+ # of Q(j, and replace j by a clone whose parent is Q(j), if necessary:
K = j.parent()
if h < K.absolute_degree():
@@ -680,7 +997,6 @@ def is_cm_j_invariant(j, method='new'):
# Construct an elliptic curve with j-invariant j, with
# integral model:
- from sage.schemes.elliptic_curves.all import EllipticCurve
E = EllipticCurve(j=j).integral_model()
D = E.discriminant()
prime_bound = 1000 # test primes of degree 1 up to this norm
@@ -718,16 +1034,16 @@ def is_cm_j_invariant(j, method='new'):
DP = aP**2 - 4*P.norm()
dP = DP.squarefree_part()
fP = ZZ(DP//dP).isqrt()
- if cmd==0: # first one, so store d and f
+ if cmd == 0: # first one, so store d and f
cmd = dP
cmf = fP
elif cmd != dP: # inconsistent with previous
- return False, None
+ return (False, None)
else: # consistent d, so update f
cmf = cmf.gcd(fP)
- if cmd==0: # no conclusion, we found no degree 1 primes, revert to old method
- return is_cm_j_invariant(j, method='old')
+ if cmd == 0: # no conclusion, we found no degree 1 primes, revert to default algorithm
+ return is_cm_j_invariant(j)
# it looks like cm by disc cmd * f**2 where f divides cmf
@@ -737,11 +1053,10 @@ def is_cm_j_invariant(j, method='new'):
# Now we must check if h(cmd*f**2)==h for f|cmf; if so we check
# whether j is a root of the associated Hilbert class polynomial.
+ h0 = cmd.class_number()
for f in cmf.divisors(): # only positive divisors
- d = cmd*f**2
- if h != d.class_number():
+ if h != OrderClassNumber(cmd,h0,f):
continue
- pol = hilbert_class_polynomial(d)
- if pol(j) == 0:
- return True, (cmd, f)
- return False, None
+ if jpol == hilbert_class_polynomial(cmd*f**2):
+ return (True, (cmd, f))
+ return (False, None)
diff --git a/src/sage/schemes/elliptic_curves/ell_finite_field.py b/src/sage/schemes/elliptic_curves/ell_finite_field.py
index d0279e553f1..5b0f07a11e7 100644
--- a/src/sage/schemes/elliptic_curves/ell_finite_field.py
+++ b/src/sage/schemes/elliptic_curves/ell_finite_field.py
@@ -703,6 +703,19 @@ def frobenius_endomorphism(self):
"""
return self.frobenius_isogeny(self.base_field().degree())
+ def frobenius_discriminant(self):
+ r"""
+ Return the discriminant of the ring `\ZZ[\pi_E]` where `\pi_E` is the Frobenius endomorphism.
+
+ EXAMPLES::
+
+ sage: F. = GF(11^4)
+ sage: E = EllipticCurve([t,t])
+ sage: E.frobenius_discriminant()
+ -57339
+ """
+ return self.frobenius_polynomial().discriminant()
+
def cardinality_pari(self):
r"""
Return the cardinality of ``self`` using PARI.
@@ -1423,6 +1436,152 @@ def _fetch_cached_order(self, other):
if n is not None:
self._order = n
+ def height_above_floor(self, ell, e):
+ r"""
+ Return the height of the `j`-invariant of this ordinary elliptic curve on its `\ell`-volcano.
+
+ INPUT:
+
+ - ``ell`` -- a prime number
+ - ``e`` -- a non-negative integer, the ell-adic valuation of
+ the conductor the Frobenius order
+
+
+ .. NOTE::
+
+ For an ordinary `E/\GF{q}`, and a prime `\ell`, the height
+ `e` of the `\ell`-volcano containing `j(E)` is the `\ell`-adic
+ valuation of the conductor of the order generated by the
+ Frobenius `\pi_E`; the height of `j(E)` on its
+ ell-volcano is the `\ell`-adic valuation of the conductor
+ of the order `\text{End}(E)`.
+
+ ALGORITHM:
+
+ See [RouSuthZur2022]_.
+
+ EXAMPLES::
+
+ sage: F = GF(312401)
+ sage: E = EllipticCurve(F,(0, 0, 0, 309381, 93465))
+ sage: D = E.frobenius_discriminant(); D
+ -687104
+ sage: D.factor()
+ -1 * 2^10 * 11 * 61
+ sage: E.height_above_floor(2,8)
+ 5
+
+ """
+ if self.is_supersingular():
+ raise ValueError("{} is not ordinary".format(self))
+ if e == 0:
+ return 0
+ j = self.j_invariant()
+ if j in [0, 1728]:
+ return e
+ F = j.parent()
+ x = polygen(F)
+ from sage.rings.polynomial.polynomial_ring import polygens
+ from sage.libs.pari.convert_sage import gen_to_sage
+ from sage.libs.pari.all import pari
+ X,Y = polygens(F,['X', 'Y'],2)
+ phi = gen_to_sage(pari.polmodular(ell),{'x':X, 'y':Y})
+ j1 = phi([x,j]).roots(multiplicities=False)
+ nj1 = len(j1)
+ on_floor = self.two_torsion_rank() < 2 if ell==2 else nj1 <= ell
+ if on_floor:
+ return 0
+ if e == 1 or nj1 != ell+1: # double roots can only happen at the surface
+ return e
+ if nj1 < 3:
+ return 0
+ j0 = [j,j,j]
+ h = 1
+ while True:
+ for i in range(3):
+ r = (phi([x,j1[i]])//(x-j0[i])).roots(multiplicities=False)
+ if not r:
+ return h
+ j0[i] = j1[i]
+ j1[i] = r[0]
+ h += 1
+
+ def endomorphism_discriminant_from_class_number(self, h):
+ r"""
+ Return the endomorphism order discriminant of this ordinary elliptic curve, given its class number ``h``.
+
+ INPUT:
+
+ - ``h`` -- a positive integer
+
+ OUTPUT:
+
+ (integer) The discriminant of the endomorphism ring `\text{End}(E)`, if
+ this has class number ``h``. If `\text{End}(E)` does not have class
+ number ``h``, a ``ValueError`` is raised.
+
+ ALGORITHM:
+
+ Compute the trace of Frobenius and hence the discriminant
+ `D_0` and class number `h_0` of the maximal order containing
+ the endomorphism order. From the given value of `h`, which
+ must be a multiple of `h_0`, compute the possible conductors,
+ using :meth:`height_above_floor` for each prime `\ell`
+ dividing the quotient `h/h_0`. If exactly one conductor `f`
+ remains, return `f^2D_0`, otherwise raise a ``ValueError``;
+ this can onlyhappen when the input value of `h` was incorrect.
+
+ .. NOTE::
+
+ Adapted from [RouSuthZur2022]_. The application for which
+ one knows the class number in advance is in the
+ recognition of Hilbert Class Polynomials: see
+ :func:`sage.schemes.elliptic_curves.cm.is_HCP`.
+
+ EXAMPLES::
+
+ sage: F = GF(312401)
+ sage: E = EllipticCurve(F,(0, 0, 0, 309381, 93465))
+ sage: E.endomorphism_discriminant_from_class_number(30)
+ -671
+
+ We check that this is the correct discriminant, and the input value of `h` was correct::
+
+ sage: H = hilbert_class_polynomial(-671)
+ sage: H(E.j_invariant()) == 0 and H.degree()==30
+ True
+
+ """
+ F = self.base_field()
+ if not F.is_finite():
+ raise ValueError("Base field {} must be finite".format(F))
+ if self.is_supersingular():
+ raise ValueError("Elliptic curve ({}) must be ordinary".format(self))
+ D1 = self.frobenius_discriminant()
+ D0 = D1.squarefree_part()
+ if D0%4 !=1:
+ D0 *= 4
+ v = ZZ(D1//D0).isqrt()
+ h0 = D0.class_number()
+ if h%h0:
+ raise ValueError("Incorrect class number {}".format(h))
+ from sage.schemes.elliptic_curves.cm import OrderClassNumber
+ cs = [v//f for f in v.divisors() if OrderClassNumber(D0,h0,f) == h] # cofactors c=v/f compatible with h(f**2D0)=h
+ if not cs:
+ raise ValueError("Incorrect class number {}".format(h))
+ if len(cs) == 1:
+ return (v//cs[0])**2 * D0
+ from sage.sets.set import Set
+ L = sorted(set(sum([c.prime_factors() for c in cs], [])))
+ for ell in L:
+ e = self.height_above_floor(ell,v.valuation(ell))
+ cs = [c for c in cs if c.valuation(ell) == e]
+ if not cs:
+ raise ValueError("Incorrect class number {}".format(h))
+ if len(cs) == 1:
+ return (v//cs[0])**2 * D0
+ raise ValueError("Incorrect class number {}".format(h))
+
def twists(self):
r"""
Return a list of `k`-isomorphism representatives of all
@@ -1899,7 +2058,6 @@ def curves_with_j_0_char3(K):
return [EllipticCurve(K, a4a6) for a4a6 in
[[1,0], [1,i*b], [a,0], [a**2,0], [a**2,c], [a**3,0]]]
-
# dict to hold precomputed coefficient vectors of supersingular j values (excluding 0, 1728):
supersingular_j_polynomials = {}
diff --git a/src/sage/schemes/elliptic_curves/heegner.py b/src/sage/schemes/elliptic_curves/heegner.py
index ebbfcb00afa..e4e19cf154b 100644
--- a/src/sage/schemes/elliptic_curves/heegner.py
+++ b/src/sage/schemes/elliptic_curves/heegner.py
@@ -5704,9 +5704,9 @@ def red(P, ell):
def best_heegner_D(ell_1, ell_2):
# return the first Heegner D satisfy all hypothesis such that
# both ell_1 and ell_2 are inert
- D = -5
+ D = ZZ(-5)
while True:
- if number_field.is_fundamental_discriminant(D) and \
+ if D.is_fundamental_discriminant() and \
D % ell_1 and D % ell_2 and \
E.satisfies_heegner_hypothesis(D) and \
is_inert(D, ell_1) and is_inert(D, ell_2) and \
@@ -6075,10 +6075,9 @@ def class_number(D):
...
ValueError: D (=-5) must be a fundamental discriminant
"""
- if not number_field.is_fundamental_discriminant(D):
+ if not D.is_fundamental_discriminant():
raise ValueError("D (=%s) must be a fundamental discriminant" % D)
- return QuadraticField(D, 'a').class_number()
-
+ return D.class_number()
def is_inert(D, p):
r"""
@@ -6226,7 +6225,7 @@ def satisfies_weak_heegner_hypothesis(N, D):
sage: EllipticCurve('37a').heegner_discriminants_list(10)
[-7, -11, -40, -47, -67, -71, -83, -84, -95, -104]
"""
- if not number_field.is_fundamental_discriminant(D):
+ if not D.is_fundamental_discriminant():
return False
if D >= 0:
return False
@@ -6422,7 +6421,7 @@ def ell_heegner_discriminants(self, bound):
sage: E.heegner_discriminants(30) # indirect doctest
[-7, -8, -19, -24]
"""
- return [-D for D in range(1, bound)
+ return [ZZ(-D) for D in range(1, bound)
if self.satisfies_heegner_hypothesis(-D)]
@@ -6445,7 +6444,7 @@ def ell_heegner_discriminants_list(self, n):
[-7, -8, -19, -24]
"""
v = []
- D = -5
+ D = ZZ(-5)
while len(v) < n:
while not self.satisfies_heegner_hypothesis(D):
D -= 1
@@ -7174,7 +7173,7 @@ def _heegner_forms_list(self, D, beta=None, expected_count=None):
[389*x^2 + 313*x*y + 63*y^2, 1167*x^2 + 313*x*y + 21*y^2, 3501*x^2 + 313*x*y + 7*y^2]
"""
if expected_count is None:
- expected_count = number_field.QuadraticField(D, 'a').class_number()
+ expected_count = D.class_number()
N = self.conductor()
if beta is None:
beta = Integers(4*N)(D).sqrt(extend=False)
@@ -7234,11 +7233,11 @@ def satisfies_heegner_hypothesis(self, D):
sage: E.satisfies_heegner_hypothesis(-11)
False
"""
- if not number_field.is_fundamental_discriminant(D):
- return False
D = ZZ(D)
if D >= 0:
return False
+ if not D.is_fundamental_discriminant():
+ return False
if D.gcd(self.conductor()) != 1:
return False
for p, _ in self.conductor().factor():