def quadratic_L_function__numerical(n, d, num_terms=1000): """ Evaluate the Dirichlet L-function (for quadratic character) numerically (in a very naive way). EXAMPLES: First, let us test several values for a given character:: sage: RR = RealField(100) sage: for i in range(5): ....: print("L({}, (-4/.)): {}".format(1+2*i, RR(quadratic_L_function__exact(1+2*i, -4)) - quadratic_L_function__numerical(RR(1+2*i),-4, 10000))) L(1, (-4/.)): 0.000049999999500000024999996962707 L(3, (-4/.)): 4.99999970000003...e-13 L(5, (-4/.)): 4.99999922759382...e-21 L(7, (-4/.)): ...e-29 L(9, (-4/.)): ...e-29 This procedure fails for negative special values, as the Dirichlet series does not converge here:: sage: quadratic_L_function__numerical(-3,-4, 10000) Traceback (most recent call last): ... ValueError: the Dirichlet series does not converge here Test for several characters that the result agrees with the exact value, to a given accuracy :: sage: for d in range(-20,0): # long time (2s on sage.math 2014) ....: if abs(RR(quadratic_L_function__numerical(1, d, 10000) - quadratic_L_function__exact(1, d))) > 0.001: ....: print("Oops! We have a problem at d = {}: exact = {}, numerical = {}".format(d, RR(quadratic_L_function__exact(1, d)), RR(quadratic_L_function__numerical(1, d)))) """ # Set the correct precision if it is given (for n). if is_RealField(n.parent()): R = n.parent() else: R = RealField() if n < 0: raise ValueError('the Dirichlet series does not converge here') d1 = fundamental_discriminant(d) ans = R.zero() for i in range(1, num_terms): ans += R(kronecker_symbol(d1, i) / R(i)**n) return ans
def quadratic_L_function__numerical(n, d, num_terms=1000): """ Evaluate the Dirichlet L-function (for quadratic character) numerically (in a very naive way). EXAMPLES: First, let us test several values for a given character:: sage: RR = RealField(100) sage: for i in range(5): ....: print("L({}, (-4/.)): {}".format(1+2*i, RR(quadratic_L_function__exact(1+2*i, -4)) - quadratic_L_function__numerical(RR(1+2*i),-4, 10000))) L(1, (-4/.)): 0.000049999999500000024999996962707 L(3, (-4/.)): 4.99999970000003...e-13 L(5, (-4/.)): 4.99999922759382...e-21 L(7, (-4/.)): ...e-29 L(9, (-4/.)): ...e-29 This procedure fails for negative special values, as the Dirichlet series does not converge here:: sage: quadratic_L_function__numerical(-3,-4, 10000) Traceback (most recent call last): ... ValueError: the Dirichlet series does not converge here Test for several characters that the result agrees with the exact value, to a given accuracy :: sage: for d in range(-20,0): # long time (2s on sage.math 2014) ....: if abs(RR(quadratic_L_function__numerical(1, d, 10000) - quadratic_L_function__exact(1, d))) > 0.001: ....: print("Oops! We have a problem at d = {}: exact = {}, numerical = {}".format(d, RR(quadratic_L_function__exact(1, d)), RR(quadratic_L_function__numerical(1, d)))) """ # Set the correct precision if it is given (for n). if is_RealField(n.parent()): R = n.parent() else: R = RealField() if n < 0: raise ValueError('the Dirichlet series does not converge here') d1 = fundamental_discriminant(d) ans = R.zero() for i in range(1,num_terms): ans += R(kronecker_symbol(d1,i) / R(i)**n) return ans
def deriv_at1(self, k=None, prec=None): r""" Compute `L'(E,1)` using `k` terms of the series for `L'(E,1)`, under the assumption that `L(E,1) = 0`. The algorithm used is from Section 7.5.3 of Henri Cohen's book *A Course in Computational Algebraic Number Theory*. INPUT: - ``k`` -- number of terms of the series. If zero or ``None``, use `k = \sqrt{N}`, where `N` is the conductor. - ``prec`` -- numerical precision in bits. If zero or ``None``, use a reasonable automatic default. OUTPUT: A tuple of real numbers ``(L1, err)`` where ``L1`` is an approximation for `L'(E,1)` and ``err`` is a bound on the error in the approximation. .. WARNING:: This function only makes sense if `L(E)` has positive order of vanishing at 1, or equivalently if `L(E,1) = 0`. ALGORITHM: - Compute the root number eps. If it is 1, return 0. - Compute the Fourier coefficients `a_n`, for `n` up to and including `k`. - Compute the sum .. MATH:: 2 \cdot \sum_{n=1}^{k} (a_n / n) \cdot E_1(2 \pi n/\sqrt{N}), where `N` is the conductor of `E`, and `E_1` is the exponential integral function. - Compute a bound on the tail end of the series, which is .. MATH:: 2 e^{-2 \pi (k+1) / \sqrt{N}} / (1 - e^{-2 \pi/\sqrt{N}}). For a proof see [Grigorov-Jorza-Patrascu-Patrikis-Stein]. This is exactly the same as the bound for the approximation to `L(E,1)` produced by :meth:`at1`. EXAMPLES:: sage: E = EllipticCurve('37a') sage: E.lseries().deriv_at1() (0.3059866, 0.000801045) sage: E.lseries().deriv_at1(100) (0.3059997738340523018204836833216764744526377745903, 1.52493e-45) sage: E.lseries().deriv_at1(1000) (0.305999773834052301820483683321676474452637774590771998..., 2.75031e-449) With less numerical precision, the error is bounded by numerical accuracy:: sage: L,err = E.lseries().deriv_at1(100, prec=64) sage: L,err (0.305999773834052302, 5.55318e-18) sage: parent(L) Real Field with 64 bits of precision sage: parent(err) Real Field with 24 bits of precision and rounding RNDU Rank 2 and rank 3 elliptic curves:: sage: E = EllipticCurve('389a1') sage: E.lseries().deriv_at1() (0.0000000, 0.000000) sage: E = EllipticCurve((1, 0, 1, -131, 558)) # curve 59450i1 sage: E.lseries().deriv_at1() (-0.00010911444, 0.142428) sage: E.lseries().deriv_at1(4000) (6.990...e-50, 1.31318e-43) """ sqrtN = sqrt(self.__E.conductor()) if k: k = int(k) else: k = int(ceil(sqrtN)) if prec: prec = int(prec) else: # Estimate number of bits for the computation, based on error # estimate below (the denominator of that error is close enough # to 1 that we can ignore it). # 9.065 = 2*Pi/log(2) # 1.443 = 1/log(2) # 12 is an arbitrary extra number of bits (it is chosen # such that the precision is 24 bits when the conductor # equals 11 and k is the default value 4) prec = int(9.065 * k / sqrtN + 1.443 * log(k)) + 12 R = RealField(prec) # Compute error term with bounded precision of 24 bits and # round towards +infinity Rerror = RealField(24, rnd='RNDU') if self.__E.root_number() == 1: # Order of vanishing at 1 of L(E) is even and assumed to be # positive, so L'(E,1) = 0. return (R.zero(), Rerror.zero()) an = self.__E.anlist(k) # list of Sage Integers pi = R.pi() sqrtN = R(self.__E.conductor()).sqrt() v = exp_integral.exponential_integral_1(2 * pi / sqrtN, k) # Compute series sum and accumulate floating point errors L = R.zero() error = Rerror.zero() # Sum of |an[n]|/n sumann = Rerror.zero() for n in xrange(1, k + 1): term = (v[n - 1] * an[n]) / n L += term error += term.epsilon(Rerror) * 5 + L.ulp(Rerror) sumann += Rerror(an[n].abs()) / n L *= 2 # Add error term for exponential_integral_1() errors. # Absolute error for 2*v[i] is 4*max(1, v[0])*2^-prec if v[0] > 1.0: sumann *= Rerror(v[0]) error += (sumann >> (prec - 2)) # Add series error (we use (-2)/(z-1) instead of 2/(1-z) # because this causes 1/(1-z) to be rounded up) z = (-2 * pi / sqrtN).exp() zpow = ((-2 * (k + 1)) * pi / sqrtN).exp() error += ((-2) * Rerror(zpow)) / Rerror(z - 1) return (L, error)
def at1(self, k=None, prec=None): r""" Compute `L(E,1)` using `k` terms of the series for `L(E,1)` as explained in Section 7.5.3 of Henri Cohen's book *A Course in Computational Algebraic Number Theory*. If the argument `k` is not specified, then it defaults to `\sqrt{N}`, where `N` is the conductor. INPUT: - ``k`` -- number of terms of the series. If zero or ``None``, use `k = \sqrt{N}`, where `N` is the conductor. - ``prec`` -- numerical precision in bits. If zero or ``None``, use a reasonable automatic default. OUTPUT: A tuple of real numbers ``(L, err)`` where ``L`` is an approximation for `L(E,1)` and ``err`` is a bound on the error in the approximation. This function is disjoint from the PARI ``elllseries`` command, which is for a similar purpose. To use that command (via the PARI C library), simply type ``E.pari_mincurve().elllseries(1)``. ALGORITHM: - Compute the root number eps. If it is -1, return 0. - Compute the Fourier coefficients `a_n`, for `n` up to and including `k`. - Compute the sum .. MATH:: 2 \cdot \sum_{n=1}^{k} \frac{a_n}{n} \cdot \exp(-2*pi*n/\sqrt{N}), where `N` is the conductor of `E`. - Compute a bound on the tail end of the series, which is .. MATH:: 2 e^{-2 \pi (k+1) / \sqrt{N}} / (1 - e^{-2 \pi/\sqrt{N}}). For a proof see [Grigov-Jorza-Patrascu-Patrikis-Stein]. EXAMPLES:: sage: L, err = EllipticCurve('11a1').lseries().at1() sage: L, err (0.253804, 0.000181444) sage: parent(L) Real Field with 24 bits of precision sage: E = EllipticCurve('37b') sage: E.lseries().at1() (0.7257177, 0.000800697) sage: E.lseries().at1(100) (0.7256810619361527823362055410263965487367603361763, 1.52469e-45) sage: L,err = E.lseries().at1(100, prec=128) sage: L 0.72568106193615278233620554102639654873 sage: parent(L) Real Field with 128 bits of precision sage: err 1.70693e-37 sage: parent(err) Real Field with 24 bits of precision and rounding RNDU Rank 1 through 3 elliptic curves:: sage: E = EllipticCurve('37a1') sage: E.lseries().at1() (0.0000000, 0.000000) sage: E = EllipticCurve('389a1') sage: E.lseries().at1() (-0.001769566, 0.00911776) sage: E = EllipticCurve('5077a1') sage: E.lseries().at1() (0.0000000, 0.000000) """ sqrtN = sqrt(self.__E.conductor()) if k: k = int(k) else: k = int(ceil(sqrtN)) if prec: prec = int(prec) else: # Use the same precision as deriv_at1() below for # consistency prec = int(9.065 * k / sqrtN + 1.443 * log(k)) + 12 R = RealField(prec) # Compute error term with bounded precision of 24 bits and # round towards +infinity Rerror = RealField(24, rnd='RNDU') if self.__E.root_number() == -1: return (R.zero(), Rerror.zero()) an = self.__E.anlist(k) # list of Sage Integers pi = R.pi() sqrtN = R(self.__E.conductor()).sqrt() z = (-2 * pi / sqrtN).exp() zpow = z # Compute series sum and accumulate floating point errors L = R.zero() error = Rerror.zero() for n in xrange(1, k + 1): term = (zpow * an[n]) / n zpow *= z L += term # We express relative error in units of epsilon, where # epsilon is a number divided by 2^precision. # Instead of multiplying the error by 2 after the loop # (to account for L *= 2), we already multiply it now. # # For multiplication and division, the relative error # in epsilons is bounded by (1+e)^n - 1, where n is the # number of operations (assuming exact inputs). # exp(x) additionally multiplies this error by abs(x) and # adds one epsilon. The inputs pi and sqrtN each contribute # another epsilon. # Assuming that 2*pi/sqrtN <= 2, the relative error for z is # 7 epsilon. This implies a relative error of (8n-1) epsilon # for zpow. We add 2 for the computation of term and 1/2 to # compensate for the approximation (1+e)^n = 1+ne. # # The error of the addition is at most half an ulp of the # result. # # Multiplying everything by two gives: error += term.epsilon(Rerror) * (16 * n + 3) + L.ulp(Rerror) L *= 2 # Add series error (we use (-2)/(z-1) instead of 2/(1-z) # because this causes 1/(1-z) to be rounded up) error += ((-2) * Rerror(zpow)) / Rerror(z - 1) return (L, error)
def deriv_at1(self, k=None, prec=None): r""" Compute `L'(E,1)` using `k` terms of the series for `L'(E,1)`, under the assumption that `L(E,1) = 0`. The algorithm used is from Section 7.5.3 of Henri Cohen's book ``A Course in Computational Algebraic Number Theory.'' INPUT: - ``k`` -- number of terms of the series. If zero or ``None``, use `k = \sqrt(N)`, where `N` is the conductor. - ``prec`` -- numerical precision in bits. If zero or ``None``, use a reasonable automatic default. OUTPUT: A tuple of real numbers ``(L1, err)`` where ``L1`` is an approximation for `L'(E,1)` and ``err`` is a bound on the error in the approximation. .. WARNING:: This function only makes sense if `L(E)` has positive order of vanishing at 1, or equivalently if `L(E,1) = 0`. ALGORITHM: - Compute the root number eps. If it is 1, return 0. - Compute the Fourier coefficients `a_n`, for `n` up to and including `k`. - Compute the sum .. MATH:: 2 * \sum_{n=1}^{k} (a_n / n) * E_1(2 \pi n/\sqrt{N}), where `N` is the conductor of `E`, and `E_1` is the exponential integral function. - Compute a bound on the tail end of the series, which is .. MATH:: 2 e^{-2 \pi (k+1) / \sqrt{N}} / (1 - e^{-2 \pi/\sqrt{N}}). For a proof see [Grigorov-Jorza-Patrascu-Patrikis-Stein]. This is exactly the same as the bound for the approximation to `L(E,1)` produced by :meth:`at1`. EXAMPLES:: sage: E = EllipticCurve('37a') sage: E.lseries().deriv_at1() (0.3059866, 0.000801045) sage: E.lseries().deriv_at1(100) (0.3059997738340523018204836833216764744526377745903, 1.52493e-45) sage: E.lseries().deriv_at1(1000) (0.305999773834052301820483683321676474452637774590771998..., 2.75031e-449) With less numerical precision, the error is bounded by numerical accuracy:: sage: L,err = E.lseries().deriv_at1(100, prec=64) sage: L,err (0.305999773834052302, 5.55318e-18) sage: parent(L) Real Field with 64 bits of precision sage: parent(err) Real Field with 24 bits of precision and rounding RNDU Rank 2 and rank 3 elliptic curves:: sage: E = EllipticCurve('389a1') sage: E.lseries().deriv_at1() (0.0000000, 0.000000) sage: E = EllipticCurve((1, 0, 1, -131, 558)) # curve 59450i1 sage: E.lseries().deriv_at1() (-0.00010911444, 0.142428) sage: E.lseries().deriv_at1(4000) (6.9902290...e-50, 1.31318e-43) """ sqrtN = sqrt(self.__E.conductor()) if k: k = int(k) else: k = int(ceil(sqrtN)) if prec: prec = int(prec) else: # Estimate number of bits for the computation, based on error # estimate below (the denominator of that error is close enough # to 1 that we can ignore it). # 9.065 = 2*Pi/log(2) # 1.443 = 1/log(2) # 12 is an arbitrary extra number of bits (it is chosen # such that the precision is 24 bits when the conductor # equals 11 and k is the default value 4) prec = int(9.065*k/sqrtN + 1.443*log(k)) + 12 R = RealField(prec) # Compute error term with bounded precision of 24 bits and # round towards +infinity Rerror = RealField(24, rnd='RNDU') if self.__E.root_number() == 1: # Order of vanishing at 1 of L(E) is even and assumed to be # positive, so L'(E,1) = 0. return (R.zero(), Rerror.zero()) an = self.__E.anlist(k) # list of Sage Integers pi = R.pi() sqrtN = R(self.__E.conductor()).sqrt() v = exp_integral.exponential_integral_1(2*pi/sqrtN, k) # Compute series sum and accumulate floating point errors L = R.zero() error = Rerror.zero() # Sum of |an[n]|/n sumann = Rerror.zero() for n in xrange(1,k+1): term = (v[n-1] * an[n])/n L += term error += term.epsilon(Rerror)*5 + L.ulp(Rerror) sumann += Rerror(an[n].abs())/n L *= 2 # Add error term for exponential_integral_1() errors. # Absolute error for 2*v[i] is 4*max(1, v[0])*2^-prec if v[0] > 1.0: sumann *= Rerror(v[0]) error += (sumann >> (prec - 2)) # Add series error (we use (-2)/(z-1) instead of 2/(1-z) # because this causes 1/(1-z) to be rounded up) z = (-2*pi/sqrtN).exp() zpow = ((-2*(k+1))*pi/sqrtN).exp() error += ((-2)*Rerror(zpow)) / Rerror(z - 1) return (L, error)
def at1(self, k=None, prec=None): r""" Compute `L(E,1)` using `k` terms of the series for `L(E,1)` as explained in Section 7.5.3 of Henri Cohen's book "A Course in Computational Algebraic Number Theory". If the argument `k` is not specified, then it defaults to `\sqrt(N)`, where `N` is the conductor. INPUT: - ``k`` -- number of terms of the series. If zero or ``None``, use `k = \sqrt(N)`, where `N` is the conductor. - ``prec`` -- numerical precision in bits. If zero or ``None``, use a reasonable automatic default. OUTPUT: A tuple of real numbers ``(L, err)`` where ``L`` is an approximation for `L(E,1)` and ``err`` is a bound on the error in the approximation. This function is disjoint from the PARI ``elllseries`` command, which is for a similar purpose. To use that command (via the PARI C library), simply type ``E.pari_mincurve().elllseries(1)``. ALGORITHM: - Compute the root number eps. If it is -1, return 0. - Compute the Fourier coefficients `a_n`, for `n` up to and including `k`. - Compute the sum .. MATH:: 2 * sum_{n=1}^{k} (a_n / n) * exp(-2*pi*n/Sqrt(N)), where `N` is the conductor of `E`. - Compute a bound on the tail end of the series, which is .. MATH:: 2 e^{-2 \pi (k+1) / \sqrt{N}} / (1 - e^{-2 \pi/\sqrt{N}}). For a proof see [Grigov-Jorza-Patrascu-Patrikis-Stein]. EXAMPLES:: sage: L, err = EllipticCurve('11a1').lseries().at1() sage: L, err (0.253804, 0.000181444) sage: parent(L) Real Field with 24 bits of precision sage: E = EllipticCurve('37b') sage: E.lseries().at1() (0.7257177, 0.000800697) sage: E.lseries().at1(100) (0.7256810619361527823362055410263965487367603361763, 1.52469e-45) sage: L,err = E.lseries().at1(100, prec=128) sage: L 0.72568106193615278233620554102639654873 sage: parent(L) Real Field with 128 bits of precision sage: err 1.70693e-37 sage: parent(err) Real Field with 24 bits of precision and rounding RNDU Rank 1 through 3 elliptic curves:: sage: E = EllipticCurve('37a1') sage: E.lseries().at1() (0.0000000, 0.000000) sage: E = EllipticCurve('389a1') sage: E.lseries().at1() (-0.001769566, 0.00911776) sage: E = EllipticCurve('5077a1') sage: E.lseries().at1() (0.0000000, 0.000000) """ sqrtN = sqrt(self.__E.conductor()) if k: k = int(k) else: k = int(ceil(sqrtN)) if prec: prec = int(prec) else: # Use the same precision as deriv_at1() below for # consistency prec = int(9.065*k/sqrtN + 1.443*log(k)) + 12 R = RealField(prec) # Compute error term with bounded precision of 24 bits and # round towards +infinity Rerror = RealField(24, rnd='RNDU') if self.__E.root_number() == -1: return (R.zero(), Rerror.zero()) an = self.__E.anlist(k) # list of Sage Integers pi = R.pi() sqrtN = R(self.__E.conductor()).sqrt() z = (-2*pi/sqrtN).exp() zpow = z # Compute series sum and accumulate floating point errors L = R.zero() error = Rerror.zero() for n in xrange(1,k+1): term = (zpow * an[n])/n zpow *= z L += term # We express relative error in units of epsilon, where # epsilon is a number divided by 2^precision. # Instead of multiplying the error by 2 after the loop # (to account for L *= 2), we already multiply it now. # # For multiplication and division, the relative error # in epsilons is bounded by (1+e)^n - 1, where n is the # number of operations (assuming exact inputs). # exp(x) additionally multiplies this error by abs(x) and # adds one epsilon. The inputs pi and sqrtN each contribute # another epsilon. # Assuming that 2*pi/sqrtN <= 2, the relative error for z is # 7 epsilon. This implies a relative error of (8n-1) epsilon # for zpow. We add 2 for the computation of term and 1/2 to # compensate for the approximation (1+e)^n = 1+ne. # # The error of the addition is at most half an ulp of the # result. # # Multiplying everything by two gives: error += term.epsilon(Rerror)*(16*n + 3) + L.ulp(Rerror) L *= 2 # Add series error (we use (-2)/(z-1) instead of 2/(1-z) # because this causes 1/(1-z) to be rounded up) error += ((-2)*Rerror(zpow)) / Rerror(z - 1) return (L, error)