def hilbert_class_polynomial(D, algorithm=None): r""" Return the Hilbert class polynomial for discriminant `D`. INPUT: - ``D`` (int) -- a negative integer congruent to 0 or 1 modulo 4. - ``algorithm`` (string, default None). OUTPUT: (integer polynomial) The Hilbert class polynomial for the discriminant `D`. ALGORITHM: - If ``algorithm`` = "arb" (default): Use Arb's implementation which uses complex interval arithmetic. - If ``algorithm`` = "sage": Use complex approximations to the roots. - If ``algorithm`` = "magma": Call the appropriate Magma function (if available). AUTHORS: - Sage implementation originally by Eduardo Ocampo Alvarez and AndreyTimofeev - Sage implementation corrected by John Cremona (using corrected precision bounds from Andreas Enge) - Magma implementation by David Kohel EXAMPLES:: sage: hilbert_class_polynomial(-4) x - 1728 sage: hilbert_class_polynomial(-7) x + 3375 sage: hilbert_class_polynomial(-23) x^3 + 3491750*x^2 - 5151296875*x + 12771880859375 sage: hilbert_class_polynomial(-37*4) x^2 - 39660183801072000*x - 7898242515936467904000000 sage: hilbert_class_polynomial(-37*4, algorithm="magma") # optional - magma x^2 - 39660183801072000*x - 7898242515936467904000000 sage: hilbert_class_polynomial(-163) x + 262537412640768000 sage: hilbert_class_polynomial(-163, algorithm="sage") x + 262537412640768000 sage: hilbert_class_polynomial(-163, algorithm="magma") # optional - magma x + 262537412640768000 TESTS:: sage: all([hilbert_class_polynomial(d, algorithm="arb") == \ ....: hilbert_class_polynomial(d, algorithm="sage") \ ....: for d in range(-1,-100,-1) if d%4 in [0,1]]) True """ if algorithm is None: algorithm = "arb" D = Integer(D) if D >= 0: raise ValueError("D (=%s) must be negative" % D) if not (D % 4 in [0, 1]): raise ValueError("D (=%s) must be a discriminant" % D) if algorithm == "arb": import sage.libs.arb.arith return sage.libs.arb.arith.hilbert_class_polynomial(D) if algorithm == "magma": magma.eval("R<x> := PolynomialRing(IntegerRing())") f = str(magma.eval("HilbertClassPolynomial(%s)" % D)) return IntegerRing()['x'](f) if algorithm != "sage": raise ValueError("%s is not a valid algorithm" % algorithm) from sage.quadratic_forms.binary_qf import BinaryQF_reduced_representatives from sage.rings.all import RR, ComplexField from sage.functions.all import elliptic_j # get all primitive reduced quadratic forms, (necessary to exclude # imprimitive forms when D is not a fundamental discriminant): rqf = BinaryQF_reduced_representatives(D, primitive_only=True) # compute needed precision # # NB: [https://arxiv.org/abs/0802.0979v1], quoting Enge (2006), is # incorrect. Enge writes (2009-04-20 email to John Cremona) "The # source is my paper on class polynomials # [https://hal.inria.fr/inria-00001040] It was pointed out to me by # the referee after ANTS that the constant given there was # wrong. The final version contains a corrected constant on p.7 # which is consistent with your example. It says: # "The logarithm of the absolute value of the coefficient in front # of X^j is bounded above by # # log (2*k_2) * h + pi * sqrt(|D|) * sum (1/A_i) # # independently of j", where k_2 \approx 10.163. h = len(rqf) # class number c1 = 3.05682737291380 # log(2*10.63) c2 = sum([1 / RR(qf[0]) for qf in rqf], RR(0)) prec = c2 * RR(3.142) * RR(D).abs().sqrt() + h * c1 # bound on log prec = prec * 1.45 # bound on log_2 (1/log(2) = 1.44..) prec = 10 + prec.ceil() # allow for rounding error # set appropriate precision for further computing Dsqrt = D.sqrt(prec=prec) R = ComplexField(prec)['t'] t = R.gen() pol = R(1) for qf in rqf: a, b, c = list(qf) tau = (b + Dsqrt) / (a << 1) pol *= (t - elliptic_j(tau)) coeffs = [cof.real().round() for cof in pol.coefficients(sparse=False)] return IntegerRing()['x'](coeffs)
def hilbert_class_polynomial(D, algorithm=None): r""" Returns the Hilbert class polynomial for discriminant `D`. INPUT: - ``D`` (int) -- a negative integer congruent to 0 or 1 modulo 4. - ``algorithm`` (string, default None). OUTPUT: (integer polynomial) The Hilbert class polynomial for the discriminant `D`. ALGORITHM: - If ``algorithm`` = "arb" (default): Use Arb's implementation which uses complex interval arithmetic. - If ``algorithm`` = "sage": Use complex approximations to the roots. - If ``algorithm`` = "magma": Call the appropriate Magma function (if available). AUTHORS: - Sage implementation originally by Eduardo Ocampo Alvarez and AndreyTimofeev - Sage implementation corrected by John Cremona (using corrected precision bounds from Andreas Enge) - Magma implementation by David Kohel EXAMPLES:: sage: hilbert_class_polynomial(-4) x - 1728 sage: hilbert_class_polynomial(-7) x + 3375 sage: hilbert_class_polynomial(-23) x^3 + 3491750*x^2 - 5151296875*x + 12771880859375 sage: hilbert_class_polynomial(-37*4) x^2 - 39660183801072000*x - 7898242515936467904000000 sage: hilbert_class_polynomial(-37*4, algorithm="magma") # optional - magma x^2 - 39660183801072000*x - 7898242515936467904000000 sage: hilbert_class_polynomial(-163) x + 262537412640768000 sage: hilbert_class_polynomial(-163, algorithm="sage") x + 262537412640768000 sage: hilbert_class_polynomial(-163, algorithm="magma") # optional - magma x + 262537412640768000 TESTS:: sage: all([hilbert_class_polynomial(d, algorithm="arb") == \ ....: hilbert_class_polynomial(d, algorithm="sage") \ ....: for d in range(-1,-100,-1) if d%4 in [0,1]]) True """ if algorithm is None: algorithm = "arb" D = Integer(D) if D >= 0: raise ValueError("D (=%s) must be negative"%D) if not (D%4 in [0,1]): raise ValueError("D (=%s) must be a discriminant"%D) if algorithm == "arb": import sage.libs.arb.arith return sage.libs.arb.arith.hilbert_class_polynomial(D) if algorithm == "magma": magma.eval("R<x> := PolynomialRing(IntegerRing())") f = str(magma.eval("HilbertClassPolynomial(%s)"%D)) return IntegerRing()['x'](f) if algorithm != "sage": raise ValueError("%s is not a valid algorithm"%algorithm) from sage.quadratic_forms.binary_qf import BinaryQF_reduced_representatives from sage.rings.all import RR, ZZ, ComplexField from sage.functions.all import elliptic_j # get all primitive reduced quadratic forms, (necessary to exclude # imprimitive forms when D is not a fundamental discriminant): rqf = BinaryQF_reduced_representatives(D, primitive_only=True) # compute needed precision # # NB: [http://arxiv.org/abs/0802.0979v1], quoting Enge (2006), is # incorrect. Enge writes (2009-04-20 email to John Cremona) "The # source is my paper on class polynomials # [http://hal.inria.fr/inria-00001040] It was pointed out to me by # the referee after ANTS that the constant given there was # wrong. The final version contains a corrected constant on p.7 # which is consistent with your example. It says: # "The logarithm of the absolute value of the coefficient in front # of X^j is bounded above by # # log (2*k_2) * h + pi * sqrt(|D|) * sum (1/A_i) # # independently of j", where k_2 \approx 10.163. h = len(rqf) # class number c1 = 3.05682737291380 # log(2*10.63) c2 = sum([1/RR(qf[0]) for qf in rqf], RR(0)) prec = c2*RR(3.142)*RR(D).abs().sqrt() + h*c1 # bound on log prec = prec * 1.45 # bound on log_2 (1/log(2) = 1.44..) prec = 10 + prec.ceil() # allow for rounding error # set appropriate precision for further computing Dsqrt = D.sqrt(prec=prec) R = ComplexField(prec)['t'] t = R.gen() pol = R(1) for qf in rqf: a, b, c = list(qf) tau = (b+Dsqrt)/(a<<1) pol *= (t - elliptic_j(tau)) coeffs = [cof.real().round() for cof in pol.coefficients(sparse=False)] return IntegerRing()['x'](coeffs)