def _check_muqt(mu, q, t, pi=None): """ EXAMPLES:: sage: from sage.combinat.sf.ns_macdonald import _check_muqt sage: P, q, t, n, R, x = _check_muqt([0,0,1],None,None) sage: P Fraction Field of Multivariate Polynomial Ring in q, t over Rational Field sage: q q sage: t t sage: n Nonattacking fillings of [0, 0, 1] sage: R Multivariate Polynomial Ring in x0, x1, x2 over Fraction Field of Multivariate Polynomial Ring in q, t over Rational Field sage: x (x0, x1, x2) :: sage: q,t = var('q,t') sage: P, q, t, n, R, x = _check_muqt([0,0,1],q,None) Traceback (most recent call last): ... ValueError: you must specify either both q and t or neither of them :: sage: P, q, t, n, R, x = _check_muqt([0,0,1],q,2) Traceback (most recent call last): ... ValueError: the parents of q and t must be the same """ if q is None and t is None: P = PolynomialRing(QQ, 'q,t').fraction_field() q, t = P.gens() elif q is not None and t is not None: if q.parent() != t.parent(): raise ValueError("the parents of q and t must be the same") P = q.parent() else: raise ValueError( "you must specify either both q and t or neither of them") n = NonattackingFillings(mu, pi) R = PolynomialRing(P, len(n._shape), 'x') x = R.gens() return P, q, t, n, R, x
def _sage_(self): """ EXAMPLES: sage: m = lie('[[1,0,3,3],[12,4,-4,7],[-1,9,8,0],[3,-5,-2,9]]') # optional - lie sage: m.sage() # optional - lie [ 1 0 3 3] [12 4 -4 7] [-1 9 8 0] [ 3 -5 -2 9] """ t = self.type() if t == "grp": raise ValueError, "cannot convert Lie groups to native Sage objects" elif t == "mat": import sage.matrix.constructor return sage.matrix.constructor.matrix(eval(str(self).replace("\n", "").strip())) elif t == "pol": import sage.misc.misc from sage.rings.all import PolynomialRing, QQ # Figure out the number of variables s = str(self) open_bracket = s.find("[") close_bracket = s.find("]") nvars = len(s[open_bracket:close_bracket].split(",")) # create the polynomial ring R = PolynomialRing(QQ, nvars, "x") x = R.gens() pol = R(0) # Split up the polynomials into terms terms = [] for termgrp in s.split(" - "): # The first entry in termgrp has # a negative coefficient termgrp = "-" + termgrp.strip() terms += termgrp.split("+") # Make sure we don't accidentally add a negative # sign to the first monomial if s[0] != "-": terms[0] = terms[0][1:] # go through all the terms in s for term in terms: xpos = term.find("X") coef = eval(term[:xpos].strip()) exps = eval(term[xpos + 1 :].strip()) monomial = sage.misc.misc.prod(map(lambda i: x[i] ** exps[i], range(nvars))) pol += coef * monomial return pol elif t == "tex": return repr(self) elif t == "vid": return None else: return ExpectElement._sage_(self)
def generator_relations(self, K): """ An ideal `I` in a polynomial ring `R` over `K`, such that the associated ring is `R / I` surjects onto the ring of modular forms with coefficients in `K`. INPUT: - `K` -- A ring. OUTPUT: An ideal in a polynomial ring. TESTS:: sage: from psage.modform.fourier_expansion_framework.modularforms.modularform_testtype import * sage: t = ModularFormTestType_scalar() sage: t.generator_relations(QQ) Ideal (g1^2 - g2, g1^3 - g3, g1^4 - g4, g1^5 - g5) of Multivariate Polynomial Ring in g1, g2, g3, g4, g5 over Rational Field """ if K.has_coerce_map_from(ZZ): R = PolynomialRing(K, self._generator_names(K)) g1 = R.gen(0) return R.ideal([ g1**i - g for (i, g) in list(enumerate([None] + list(R.gens())))[2:] ]) raise NotImplementedError
def _check_muqt(mu, q, t, pi=None): """ EXAMPLES:: sage: from sage.combinat.sf.ns_macdonald import _check_muqt sage: P, q, t, n, R, x = _check_muqt([0,0,1],None,None) sage: P Fraction Field of Multivariate Polynomial Ring in q, t over Rational Field sage: q q sage: t t sage: n Nonattacking fillings of [0, 0, 1] sage: R Multivariate Polynomial Ring in x0, x1, x2 over Fraction Field of Multivariate Polynomial Ring in q, t over Rational Field sage: x (x0, x1, x2) :: sage: q,t = var('q,t') sage: P, q, t, n, R, x = _check_muqt([0,0,1],q,None) Traceback (most recent call last): ... ValueError: you must specify either both q and t or neither of them :: sage: P, q, t, n, R, x = _check_muqt([0,0,1],q,2) Traceback (most recent call last): ... ValueError: the parents of q and t must be the same """ if q is None and t is None: P = PolynomialRing(QQ,'q,t').fraction_field() q,t = P.gens() elif q is not None and t is not None: if q.parent() != t.parent(): raise ValueError("the parents of q and t must be the same") P = q.parent() else: raise ValueError("you must specify either both q and t or neither of them") n = NonattackingFillings(mu, pi) R = PolynomialRing(P, len(n._shape), 'x') x = R.gens() return P, q, t, n, R, x
def _sage_(self): """ EXAMPLES: sage: m = lie('[[1,0,3,3],[12,4,-4,7],[-1,9,8,0],[3,-5,-2,9]]') # optional - lie sage: m.sage() # optional - lie [ 1 0 3 3] [12 4 -4 7] [-1 9 8 0] [ 3 -5 -2 9] """ t = self.type() if t == 'grp': raise ValueError, "cannot convert Lie groups to native Sage objects" elif t == 'mat': import sage.matrix.constructor return sage.matrix.constructor.matrix( eval( str(self).replace('\n','').strip()) ) elif t == 'pol': import sage.misc.misc from sage.rings.all import PolynomialRing, QQ #Figure out the number of variables s = str(self) open_bracket = s.find('[') close_bracket = s.find(']') nvars = len(s[open_bracket:close_bracket].split(',')) #create the polynomial ring R = PolynomialRing(QQ, nvars, 'x') x = R.gens() pol = R(0) #Split up the polynomials into terms terms = [] for termgrp in s.split(' - '): #The first entry in termgrp has #a negative coefficient termgrp = "-"+termgrp.strip() terms += termgrp.split('+') #Make sure we don't accidentally add a negative #sign to the first monomial if s[0] != "-": terms[0] = terms[0][1:] #go through all the terms in s for term in terms: xpos = term.find('X') coef = eval(term[:xpos].strip()) exps = eval(term[xpos+1:].strip()) monomial = sage.misc.misc.prod(map(lambda i: x[i]**exps[i] , range(nvars))) pol += coef * monomial return pol elif t == 'tex': return repr(self) elif t == 'vid': return None else: return ExpectElement._sage_(self)
def _sage_(self): """ EXAMPLES:: sage: m = lie('[[1,0,3,3],[12,4,-4,7],[-1,9,8,0],[3,-5,-2,9]]') # optional - lie sage: m.sage() # optional - lie [ 1 0 3 3] [12 4 -4 7] [-1 9 8 0] [ 3 -5 -2 9] """ t = self.type() if t == 'grp': raise ValueError("cannot convert Lie groups to native Sage objects") elif t == 'mat': import sage.matrix.constructor return sage.matrix.constructor.matrix( eval( str(self).replace('\n','').strip()) ) elif t == 'pol': from sage.rings.all import PolynomialRing, QQ #Figure out the number of variables s = str(self) open_bracket = s.find('[') close_bracket = s.find(']') nvars = len(s[open_bracket:close_bracket].split(',')) #create the polynomial ring R = PolynomialRing(QQ, nvars, 'x') x = R.gens() pol = R(0) #Split up the polynomials into terms terms = [] for termgrp in s.split(' - '): #The first entry in termgrp has #a negative coefficient termgrp = "-"+termgrp.strip() terms += termgrp.split('+') #Make sure we don't accidentally add a negative #sign to the first monomial if s[0] != "-": terms[0] = terms[0][1:] #go through all the terms in s for term in terms: xpos = term.find('X') coef = eval(term[:xpos].strip()) exps = eval(term[xpos+1:].strip()) monomial = prod([x[i]**exps[i] for i in range(nvars)]) pol += coef * monomial return pol elif t == 'tex': return repr(self) elif t == 'vid': return None else: return ExpectElement._sage_(self)
def velu(kernel, domain=None): E = kernel[0].curve() Q = PolynomialRing(E.base_field(), ['x', 'y']) x, y = Q.gens() X = x Y = y R = [] v = 0 w = 0 for P in kernel: g_x = 3 * P[0]**2 + E.a4() g_y = -2 * P[1] u_P = g_y**2 if 2 * P == E(0, 1, 0): R.append(P) v_P = g_x else: if not -P in R: R.append(P) v_P = 2 * g_x else: continue v += v_P w += (u_P + P[0] * v_P) X += (v_P / (x - P[0]) + u_P / (x - P[0])**2) Y -= (2 * u_P * y / (x - P[0])**3 + v_P * (y - P[1]) / (x - P[0])**2 - g_x * g_y / (x - P[0])**2) a = E.a4() - 5 * v b = E.a6() - 7 * w if domain != None: try: a = domain.base_field()(a) b = domain.base_field()(b) codomain = EllipticCurve(domain.base_field(), [a, b]) R = PolynomialRing(E.base_field(), ['x', 'y']) Rf = R.fraction_field() X = Rf(X) Y = Rf(Y) f = standard_form((X, Y), domain) except: codomain = EllipticCurve(E.base_field(), [a, b]) f = standard_form((X, Y), E) else: codomain = EllipticCurve(E.base_field(), [a, b]) f = standard_form((X, Y), E) return codomain, f
def tiny_integrals_on_basis(self, P, Q): r""" Evaluate the integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` by formally integrating a power series in a local parameter `t`. `P` and `Q` MUST be in the same residue disc for this result to make sense. INPUT: - P a point on self - Q a point on self (in the same residue disc as P) OUTPUT: The integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` EXAMPLES:: sage: K = pAdicField(17, 5) sage: E = EllipticCurve(K, [-31/3, -2501/108]) # 11a sage: P = E(K(14/3), K(11/2)) sage: TP = E.teichmuller(P); sage: E.tiny_integrals_on_basis(P, TP) (17 + 14*17^2 + 17^3 + 8*17^4 + O(17^5), 16*17 + 5*17^2 + 8*17^3 + 14*17^4 + O(17^5)) :: sage: K = pAdicField(11, 5) sage: x = polygen(K) sage: C = HyperellipticCurve(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) sage: P = C.lift_x(11^(-2)) sage: Q = C.lift_x(3*11^(-2)) sage: C.tiny_integrals_on_basis(P,Q) (3*11^3 + 7*11^4 + 4*11^5 + 7*11^6 + 5*11^7 + O(11^8), 3*11 + 10*11^2 + 8*11^3 + 9*11^4 + 7*11^5 + O(11^6), 4*11^-1 + 2 + 6*11 + 6*11^2 + 7*11^3 + O(11^4), 11^-3 + 6*11^-2 + 2*11^-1 + 2 + O(11^2)) Note that this fails if the points are not in the same residue disc:: sage: S = C(0,1/4) sage: C.tiny_integrals_on_basis(P,S) Traceback (most recent call last): ... ValueError: (11^-2 + O(11^3) : 11^-5 + 8*11^-2 + O(11^0) : 1 + O(11^5)) and (0 : 3 + 8*11 + 2*11^2 + 8*11^3 + 2*11^4 + O(11^5) : 1 + O(11^5)) are not in the same residue disc """ if P == Q: V = VectorSpace(self.base_ring(), 2 * self.genus()) return V(0) R = PolynomialRing(self.base_ring(), ['x', 'y']) x, y = R.gens() return self.tiny_integrals([x**i for i in range(2 * self.genus())], P, Q)
def normalize_map(f, E): Q = PolynomialRing(E.base_field(), ['x', 'y']) x, y = Q.gens() u, v = f[0].numerator(), f[0].denominator() s, t = f[1].numerator(), f[1].denominator() k = u.numerator().coefficient({x: u.numerator().degree(x)}) if k != 0: u /= k v /= k l = s.numerator().coefficient({x: s.numerator().degree(x), y: 1}) if l != 0: s /= l t /= l return u / v, s / t
def tiny_integrals_on_basis(self, P, Q): r""" Evaluate the integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` by formally integrating a power series in a local parameter `t`. `P` and `Q` MUST be in the same residue disc for this result to make sense. INPUT: - P a point on self - Q a point on self (in the same residue disc as P) OUTPUT: The integrals `\{\int_P^Q x^i dx/2y \}_{i=0}^{2g-1}` EXAMPLES:: sage: K = pAdicField(17, 5) sage: E = EllipticCurve(K, [-31/3, -2501/108]) # 11a sage: P = E(K(14/3), K(11/2)) sage: TP = E.teichmuller(P); sage: E.tiny_integrals_on_basis(P, TP) (17 + 14*17^2 + 17^3 + 8*17^4 + O(17^5), 16*17 + 5*17^2 + 8*17^3 + 14*17^4 + O(17^5)) :: sage: K = pAdicField(11, 5) sage: x = polygen(K) sage: C = HyperellipticCurve(x^5 + 33/16*x^4 + 3/4*x^3 + 3/8*x^2 - 1/4*x + 1/16) sage: P = C.lift_x(11^(-2)) sage: Q = C.lift_x(3*11^(-2)) sage: C.tiny_integrals_on_basis(P,Q) (3*11^3 + 7*11^4 + 4*11^5 + 7*11^6 + 5*11^7 + O(11^8), 3*11 + 10*11^2 + 8*11^3 + 9*11^4 + 7*11^5 + O(11^6), 4*11^-1 + 2 + 6*11 + 6*11^2 + 7*11^3 + O(11^4), 11^-3 + 6*11^-2 + 2*11^-1 + 2 + O(11^2)) Note that this fails if the points are not in the same residue disc:: sage: S = C(0,1/4) sage: C.tiny_integrals_on_basis(P,S) Traceback (most recent call last): ... ValueError: (11^-2 + O(11^3) : 11^-5 + 8*11^-2 + O(11^0) : 1 + O(11^5)) and (0 : 3 + 8*11 + 2*11^2 + 8*11^3 + 2*11^4 + O(11^5) : 1 + O(11^5)) are not in the same residue disc """ if P == Q: V = VectorSpace(self.base_ring(), 2*self.genus()) return V(0) R = PolynomialRing(self.base_ring(), ['x', 'y']) x, y = R.gens() return self.tiny_integrals([x**i for i in range(2*self.genus())], P, Q)
def reduce_mod_curve(poly, E): Q = PolynomialRing(E.base_field(), ['x', 'y']) x, y = Q.gens() a, b = E.a4(), E.a6() equation = x ** 3 + a * x + b exponents = poly.exponents() # list of (i,j) for each term x**i*y**j in poly for e_x, e_y in exponents: if e_y < 2: continue coef = poly.coefficient({x: e_x, y: e_y}) e = e_y % 2 poly -= coef * x ** (e_x) * y ** (e_y) poly += coef * x ** (e_x) * y ** e * equation ** ((e_y // 2)) return poly
def generator_relations(self, K) : """ An ideal `I` in a polynomial ring `R` over `K`, such that the associated ring is `R / I` surjects onto the ring of modular forms with coefficients in `K`. INPUT: - `K` -- A ring. OUTPUT: An ideal in a polynomial ring. TESTS:: sage: from psage.modform.fourier_expansion_framework.modularforms.modularform_testtype import * sage: t = ModularFormTestType_scalar() sage: t.generator_relations(QQ) Ideal (g1^2 - g2, g1^3 - g3, g1^4 - g4, g1^5 - g5) of Multivariate Polynomial Ring in g1, g2, g3, g4, g5 over Rational Field """ if K.has_coerce_map_from(ZZ) : R = PolynomialRing(K, self._generator_names(K)) g1 = R.gen(0) return R.ideal([g1**i - g for (i,g) in list(enumerate([None] + list(R.gens())))[2:]]) raise NotImplementedError
def has_rational_point(self, point = False, algorithm = 'default', read_cache = True): r""" Returns True if and only if the conic ``self`` has a point over its base field `F(t)`, which is a field of rational functions. If ``point`` is True, then returns a second output, which is a rational point if one exists. Points are cached whenever they are found. Cached information is used if and only if ``read_cache`` is True. The default algorithm does not (yet) work for all base fields `F`. In particular, sage is required to have: * an algorithm for finding the square root of elements in finite extensions of `F`; * a factorization and gcd algorithm for `F[t]`; * an algorithm for solving conics over `F`. ALGORITHM: The parameter ``algorithm`` specifies the algorithm to be used: * ``'default'`` -- use a native Sage implementation, based on the algorithm Conic in [HC2006]_. * ``'magma'`` (requires Magma to be installed) -- delegates the task to the Magma computer algebra system. EXAMPLES: We can find points for function fields over (extensions of) `\QQ` and finite fields:: sage: K.<t> = FractionField(PolynomialRing(QQ, 't')) sage: C = Conic(K, [t^2-2, 2*t^3, -2*t^3-13*t^2-2*t+18]) sage: C.has_rational_point(point=True) (True, (-3 : (t + 1)/t : 1)) sage: R.<t> = FiniteField(23)[] sage: C = Conic([2, t^2+1, t^2+5]) sage: C.has_rational_point() True sage: C.has_rational_point(point=True) (True, (5*t : 8 : 1)) sage: F.<i> = QuadraticField(-1) sage: R.<t> = F[] sage: C = Conic([1,i*t,-t^2+4]) sage: C.has_rational_point(point = True) verbose 0 (3369: multi_polynomial_ideal.py, groebner_basis) Warning: falling back to very slow toy implementation. ... (True, (-t - 2*i : -2*i : 1)) It works on non-diagonal conics as well:: sage: K.<t> = QQ[] sage: C = Conic([4, -4, 8, 1, -4, t + 4]) sage: C.has_rational_point(point=True) (True, (1/2 : 1 : 0)) If no point exists output still depends on the argument ``point``:: sage: K.<t> = QQ[] sage: C = Conic(K, [t^2, (t-1), -2*(t-1)]) sage: C.has_rational_point() False sage: C.has_rational_point(point=True) (False, None) Due to limitations in Sage of algorithms we depend on, it is not yet possible to find points on conics over multivariate function fields (see the requirements above):: sage: F.<t1> = FractionField(QQ['t1']) sage: K.<t2> = FractionField(F['t2']) sage: a = K(1) sage: b = 2*t2^2+2*t1*t2-t1^2 sage: c = -3*t2^4-4*t1*t2^3+8*t1^2*t2^2+16*t1^3-t2-48*t1^4 sage: C = Conic([a,b,c]) sage: C.has_rational_point() ... Traceback (most recent call last): ... NotImplementedError: is_square() not implemented for elements of Univariate Quotient Polynomial Ring in tbar over Fraction Field of Univariate Polynomial Ring in t1 over Rational Field with modulus tbar^2 + t1*tbar - 1/2*t1^2 In some cases, the algorithm requires us to be able to solve conics over `F`. In particular, the following does not work:: sage: P.<u> = QQ[] sage: E = P.fraction_field() sage: Q.<Y> = E[] sage: F.<v> = E.extension(Y^2 - u^3 - 1) sage: R.<t> = F[] sage: K = R.fraction_field() sage: C = Conic(K, [u, v, 1]) sage: C.has_rational_point() ... Traceback (most recent call last): ... NotImplementedError: has_rational_point not implemented for conics over base field Univariate Quotient Polynomial Ring in v over Fraction Field of Univariate Polynomial Ring in u over Rational Field with modulus v^2 - u^3 - 1 ``has_rational_point`` fails for some conics over function fields over finite fields, due to :trac:`20003`:: sage: K.<t> = PolynomialRing(GF(7)) sage: C = Conic([5*t^2+4, t^2+3*t+3, 6*t^2+3*t+2, 5*t^2+5, 4*t+3, 4*t^2+t+5]) sage: C.has_rational_point() ... Traceback (most recent call last): ... TypeError: self (=Scheme morphism: From: Projective Conic Curve over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 7 defined by (5*t^2 + 4)*x^2 + ((6*t^3 + 3*t^2 + 5*t + 5)/(t + 3))*y^2 + ((6*t^6 + 3*t^5 + t^3 + 6*t^2 + 6*t + 2)/(t^4 + t^3 + 4*t^2 + 3*t + 1))*z^2 To: Projective Conic Curve over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 7 defined by (5*t^2 + 4)*x^2 + (t^2 + 3*t + 3)*x*y + (5*t^2 + 5)*y^2 + (6*t^2 + 3*t + 2)*x*z + (4*t + 3)*y*z + (4*t^2 + t + 5)*z^2 Defn: Defined on coordinates by sending (x : y : z) to (x + ((2*t + 5)/(t + 3))*y + ((3*t^4 + 2*t^3 + 5*t^2 + 5*t + 3)/(t^4 + t^3 + 4*t^2 + 3*t + 1))*z : y + ((6*t^3 + 6*t^2 + 3*t + 6)/(t^3 + 4*t^2 + 2*t + 2))*z : z)) domain must equal right (=Scheme morphism: From: Projective Conic Curve over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 7 defined by (5*t^3 + 6*t^2 + 3*t + 3)*x^2 + (t + 4)*y^2 + (6*t^7 + 2*t^5 + t^4 + 2*t^3 + 3*t^2 + 6*t + 6)*z^2 To: Projective Conic Curve over Fraction Field of Univariate Polynomial Ring in t over Finite Field of size 7 defined by (5/(t^3 + 4*t^2 + 2*t + 2))*x^2 + (1/(t^3 + 3*t^2 + 5*t + 1))*y^2 + ((6*t^6 + 3*t^5 + t^3 + 6*t^2 + 6*t + 2)/(t^9 + 5*t^8 + t^7 + 6*t^6 + 3*t^5 + 4*t^3 + t^2 + 5*t + 3))*z^2 Defn: Defined on coordinates by sending (x : y : z) to ((t^3 + 4*t^2 + 2*t + 2)*x : (t^2 + 5)*y : (t^5 + 4*t^4 + t^2 + 3*t + 3)*z)) codomain TESTS:: sage: K.<t> = FractionField(PolynomialRing(QQ, 't')) sage: a = (2*t^2 - 3/2*t + 1)/(37/3*t^2 + t - 1/4) sage: b = (1/2*t^2 + 1/3)/(-73*t^2 - 2*t + 11/4) sage: c = (6934/3*t^6 + 8798/3*t^5 - 947/18*t^4 + 3949/9*t^3 + 20983/18*t^2 + 28/3*t - 131/3)/(-2701/3*t^4 - 293/3*t^3 + 301/6*t^2 + 13/4*t - 11/16) sage: C = Conic([a,b,c]) sage: C.has_rational_point(point=True) (True, (4*t + 4 : 2*t + 2 : 1)) A long time test:: sage: K.<t> = FractionField(PolynomialRing(QQ, 't')) sage: a = (-1/3*t^6 - 14*t^5 - 1/4*t^4 + 7/2*t^2 - 1/2*t - 1)/(24/5*t^6 - t^5 - 1/4*t^4 + t^3 - 3*t^2 + 8/5*t + 5) sage: b = (-3*t^3 + 8*t + 1/2)/(-1/3*t^3 + 3/2*t^2 + 1/12*t + 1/2) sage: c = (1232009/225*t^25 - 1015925057/8100*t^24 + 1035477411553/1458000*t^23 + 7901338091/30375*t^22 - 1421379260447/729000*t^21 + 266121260843/972000*t^20 + 80808723191/486000*t^19 - 516656082523/972000*t^18 + 21521589529/40500*t^17 + 4654758997/21600*t^16 - 20064038625227/9720000*t^15 - 173054270347/324000*t^14 + 536200870559/540000*t^13 - 12710739349/50625*t^12 - 197968226971/135000*t^11 - 134122025657/810000*t^10 + 22685316301/120000*t^9 - 2230847689/21600*t^8 - 70624099679/270000*t^7 - 4298763061/270000*t^6 - 41239/216000*t^5 - 13523/36000*t^4 + 493/36000*t^3 + 83/2400*t^2 + 1/300*t + 1/200)/(-27378/125*t^17 + 504387/500*t^16 - 97911/2000*t^15 + 1023531/4000*t^14 + 1874841/8000*t^13 + 865381/12000*t^12 + 15287/375*t^11 + 6039821/6000*t^10 + 599437/1500*t^9 + 18659/250*t^8 + 1218059/6000*t^7 + 2025127/3000*t^6 + 1222759/6000*t^5 + 38573/200*t^4 + 8323/125*t^3 + 15453/125*t^2 + 17031/500*t + 441/10) sage: C = Conic([a,b,c]) sage: C.has_rational_point(point = True) # long time (4 seconds) (True, ((-2/117*t^8 + 304/1053*t^7 + 40/117*t^6 - 1/27*t^5 - 110/351*t^4 - 2/195*t^3 + 11/351*t^2 + 1/117)/(t^4 + 2/39*t^3 + 4/117*t^2 + 2/39*t + 14/39) : -5/3*t^4 + 19*t^3 : 1)) """ from constructor import Conic if read_cache: if self._rational_point is not None: return (True, self._rational_point) if point else True if algorithm != 'default': return ProjectiveConic_field.has_rational_point(self, point, algorithm, read_cache) # Default algorithm if self.base_ring().characteristic() == 2: raise NotImplementedError("has_rational_point not implemented \ for function field of characteristic 2.") new_conic, transformation, inverse = self.diagonalization() coeff = new_conic.coefficients() if coeff[0] == 0: return (True, transformation([1,0,0])) if point else True elif coeff[3] == 0: return (True, transformation([0,1,0])) if point else True elif coeff[5] == 0: return (True, transformation([0,0,1])) if point else True # We save the coefficients of the reduced form in coeff # A zero of the reduced conic can be multiplied by multipliers # to get a zero of the old conic (coeff, multipliers) = new_conic._reduce_conic() new_conic = Conic(coeff) transformation = transformation \ * new_conic.hom(diagonal_matrix(multipliers)) if coeff[0].degree() % 2 == coeff[1].degree() % 2 and \ coeff[1].degree() % 2 == coeff[2].degree() % 2: case = 0 else: case = 1 t, = self.base_ring().base().gens() # t in F[t] supp = [] roots = [[], [], []] remove = None # loop through the coefficients and find a root of f_i (as in # [HC2006]) modulo each element in the coefficients' support for i in (0,1,2): supp.append(list(coeff[i].factor())) for p in supp[i]: if p[1] != 1: raise ValueError("Expected factor of exponent 1.") # Convert to monic factor x = p[0]/list(p[0])[-1] N = p[0].base_ring().extension(x, 'tbar') R = PolynomialRing(N, 'u') u, = R.gens() # If p[0] has degree 1, sage might forget the "defining # polynomial" of N, so we define our own modulo operation if p[0].degree() == 1: mod = t.parent().hom([-x[0]]) else: mod = N if i == 0: x = -mod(coeff[2])/mod(coeff[1]) elif i == 1: x = -mod(coeff[0])/mod(coeff[2]) else: x = -mod(coeff[1])/mod(coeff[0]) if x.is_square(): root = N(x.sqrt()) else: return (False, None) if point else False # if case == 0 and p[0] has degree 1, we switch to case # 1 and remove this factor out of the support. In [HC2006] # this is done later, in FindPoint. if case == 0 and p[0].degree() == 1: case = 1 # remove later so the loop iterator stays in place. remove = (i,p) else: roots[i].append(root) if remove: supp[remove[0]].remove(remove[1]) supp = [[p[0] for p in supp[i]] for i in (0,1,2)] if case == 0: # Find a solution of (5) in [HC2006] leading_conic = Conic(self.base_ring().base_ring(), [coeff[0].leading_coefficient(), coeff[1].leading_coefficient(), coeff[2].leading_coefficient()]) has_point = leading_conic.has_rational_point(True) if has_point[0]: if point: pt = new_conic.find_point(supp, roots, case, has_point[1]) else: pt = True return (True, transformation(pt)) if point else True else: return (False, None) if point else False # case == 1: if point: pt = new_conic.find_point(supp, roots, case) else: pt = True return (True, transformation(pt)) if point else True
def parametrization(self, point=None, morphism=True): r""" Return a parametrization `f` of ``self`` together with the inverse of `f`. If ``point`` is specified, then that point is used for the parametrization. Otherwise, use ``self.rational_point()`` to find a point. If ``morphism`` is True, then `f` is returned in the form of a Scheme morphism. Otherwise, it is a tuple of polynomials that gives the parametrization. ALGORITHM: Uses the PARI/GP function ``qfparam``. EXAMPLES :: sage: c = Conic([1,1,-1]) sage: c.parametrization() (Scheme morphism: From: Projective Space of dimension 1 over Rational Field To: Projective Conic Curve over Rational Field defined by x^2 + y^2 - z^2 Defn: Defined on coordinates by sending (x : y) to (2*x*y : x^2 - y^2 : x^2 + y^2), Scheme morphism: From: Projective Conic Curve over Rational Field defined by x^2 + y^2 - z^2 To: Projective Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x : y : z) to (1/2*x : -1/2*y + 1/2*z)) An example with ``morphism = False`` :: sage: R.<x,y,z> = QQ[] sage: C = Curve(7*x^2 + 2*y*z + z^2) sage: (p, i) = C.parametrization(morphism = False); (p, i) ([-2*x*y, x^2 + 7*y^2, -2*x^2], [-1/2*x, 1/7*y + 1/14*z]) sage: C.defining_polynomial()(p) 0 sage: i[0](p) / i[1](p) x/y A ``ValueError`` is raised if ``self`` has no rational point :: sage: C = Conic(x^2 + 2*y^2 + z^2) sage: C.parametrization() Traceback (most recent call last): ... ValueError: Conic Projective Conic Curve over Rational Field defined by x^2 + 2*y^2 + z^2 has no rational points over Rational Field! A ``ValueError`` is raised if ``self`` is not smooth :: sage: C = Conic(x^2 + y^2) sage: C.parametrization() Traceback (most recent call last): ... ValueError: The conic self (=Projective Conic Curve over Rational Field defined by x^2 + y^2) is not smooth, hence does not have a parametrization. """ if (not self._parametrization is None) and not point: par = self._parametrization else: if not self.is_smooth(): raise ValueError( "The conic self (=%s) is not smooth, hence does not have a parametrization." % self) if point is None: point = self.rational_point() point = Sequence(point) Q = PolynomialRing(QQ, 'x,y') [x, y] = Q.gens() gens = self.ambient_space().gens() M = self.symmetric_matrix() M *= lcm([t.denominator() for t in M.list()]) par1 = qfparam(M, point) B = Matrix([[par1[i][j] for j in range(3)] for i in range(3)]) # self is in the image of B and does not lie on a line, # hence B is invertible A = B.inverse() par2 = [sum([A[i, j] * gens[j] for j in range(3)]) for i in [1, 0]] par = ([Q(pol(x / y) * y**2) for pol in par1], par2) if self._parametrization is None: self._parametrization = par if not morphism: return par P1 = ProjectiveSpace(self.base_ring(), 1, 'x,y') return P1.hom(par[0], self), self.Hom(P1)(par[1], check=False)
def parametrization(self, point=None, morphism=True): r""" Return a parametrization `f` of ``self`` together with the inverse of `f`. If ``point`` is specified, then that point is used for the parametrization. Otherwise, use ``self.rational_point()`` to find a point. If ``morphism`` is True, then `f` is returned in the form of a Scheme morphism. Otherwise, it is a tuple of polynomials that gives the parametrization. EXAMPLES: An example over a finite field :: sage: c = Conic(GF(2), [1,1,1,1,1,0]) sage: c.parametrization() (Scheme morphism: From: Projective Space of dimension 1 over Finite Field of size 2 To: Projective Conic Curve over Finite Field of size 2 defined by x^2 + x*y + y^2 + x*z + y*z Defn: Defined on coordinates by sending (x : y) to (x*y + y^2 : x^2 + x*y : x^2 + x*y + y^2), Scheme morphism: From: Projective Conic Curve over Finite Field of size 2 defined by x^2 + x*y + y^2 + x*z + y*z To: Projective Space of dimension 1 over Finite Field of size 2 Defn: Defined on coordinates by sending (x : y : z) to (y : x)) An example with ``morphism = False`` :: sage: R.<x,y,z> = QQ[] sage: C = Curve(7*x^2 + 2*y*z + z^2) sage: (p, i) = C.parametrization(morphism = False); (p, i) ([-2*x*y, x^2 + 7*y^2, -2*x^2], [-1/2*x, 1/7*y + 1/14*z]) sage: C.defining_polynomial()(p) 0 sage: i[0](p) / i[1](p) x/y A ``ValueError`` is raised if ``self`` has no rational point :: sage: C = Conic(x^2 + y^2 + 7*z^2) sage: C.parametrization() Traceback (most recent call last): ... ValueError: Conic Projective Conic Curve over Rational Field defined by x^2 + y^2 + 7*z^2 has no rational points over Rational Field! A ``ValueError`` is raised if ``self`` is not smooth :: sage: C = Conic(x^2 + y^2) sage: C.parametrization() Traceback (most recent call last): ... ValueError: The conic self (=Projective Conic Curve over Rational Field defined by x^2 + y^2) is not smooth, hence does not have a parametrization. """ if (not self._parametrization is None) and not point: par = self._parametrization else: if not self.is_smooth(): raise ValueError("The conic self (=%s) is not smooth, hence does not have a parametrization." % self) if point is None: point = self.rational_point() point = Sequence(point) B = self.base_ring() Q = PolynomialRing(B, 'x,y') [x, y] = Q.gens() gens = self.ambient_space().gens() P = PolynomialRing(B, 4, ['X', 'Y', 'T0', 'T1']) [X, Y, T0, T1] = P.gens() c3 = [j for j in range(2,-1,-1) if point[j] != 0][0] c1 = [j for j in range(3) if j != c3][0] c2 = [j for j in range(3) if j != c3 and j != c1][0] L = [0,0,0] L[c1] = Y*T1*point[c1] + Y*T0 L[c2] = Y*T1*point[c2] + X*T0 L[c3] = Y*T1*point[c3] bezout = P(self.defining_polynomial()(L) / T0) t = [bezout([x,y,0,-1]),bezout([x,y,1,0])] par = (tuple([Q(p([x,y,t[0],t[1]])/y) for p in L]), tuple([gens[m]*point[c3]-gens[c3]*point[m] for m in [c2,c1]])) if self._parametrization is None: self._parametrization = par if not morphism: return par P1 = ProjectiveSpace(self.base_ring(), 1, 'x,y') return P1.hom(par[0],self), self.Hom(P1)(par[1], check = False)
def segre_embedding(self, PP=None, var='u'): r""" Return the Segre embedding of this space into the appropriate projective space. INPUT: - ``PP`` -- (default: ``None``) ambient image projective space; this is constructed if it is not given. - ``var`` -- string, variable name of the image projective space, default `u` (optional). OUTPUT: Hom -- from this space to the appropriate subscheme of projective space. .. TODO:: Cartesian products with more than two components. EXAMPLES:: sage: X.<y0,y1,y2,y3,y4,y5> = ProductProjectiveSpaces(ZZ, [2, 2]) sage: phi = X.segre_embedding(); phi Scheme morphism: From: Product of projective spaces P^2 x P^2 over Integer Ring To: Closed subscheme of Projective Space of dimension 8 over Integer Ring defined by: -u5*u7 + u4*u8, -u5*u6 + u3*u8, -u4*u6 + u3*u7, -u2*u7 + u1*u8, -u2*u4 + u1*u5, -u2*u6 + u0*u8, -u1*u6 + u0*u7, -u2*u3 + u0*u5, -u1*u3 + u0*u4 Defn: Defined by sending (y0 : y1 : y2 , y3 : y4 : y5) to (y0*y3 : y0*y4 : y0*y5 : y1*y3 : y1*y4 : y1*y5 : y2*y3 : y2*y4 : y2*y5). :: sage: T = ProductProjectiveSpaces([1, 2], CC, 'z') sage: T.segre_embedding() Scheme morphism: From: Product of projective spaces P^1 x P^2 over Complex Field with 53 bits of precision To: Closed subscheme of Projective Space of dimension 5 over Complex Field with 53 bits of precision defined by: -u2*u4 + u1*u5, -u2*u3 + u0*u5, -u1*u3 + u0*u4 Defn: Defined by sending (z0 : z1 , z2 : z3 : z4) to (z0*z2 : z0*z3 : z0*z4 : z1*z2 : z1*z3 : z1*z4). :: sage: T = ProductProjectiveSpaces([1, 2, 1], QQ, 'z') sage: T.segre_embedding() Scheme morphism: From: Product of projective spaces P^1 x P^2 x P^1 over Rational Field To: Closed subscheme of Projective Space of dimension 11 over Rational Field defined by: -u9*u10 + u8*u11, -u7*u10 + u6*u11, -u7*u8 + u6*u9, -u5*u10 + u4*u11, -u5*u8 + u4*u9, -u5*u6 + u4*u7, -u5*u9 + u3*u11, -u5*u8 + u3*u10, -u5*u8 + u2*u11, -u4*u8 + u2*u10, -u3*u8 + u2*u9, -u3*u6 + u2*u7, -u3*u4 + u2*u5, -u5*u7 + u1*u11, -u5*u6 + u1*u10, -u3*u7 + u1*u9, -u3*u6 + u1*u8, -u5*u6 + u0*u11, -u4*u6 + u0*u10, -u3*u6 + u0*u9, -u2*u6 + u0*u8, -u1*u6 + u0*u7, -u1*u4 + u0*u5, -u1*u2 + u0*u3 Defn: Defined by sending (z0 : z1 , z2 : z3 : z4 , z5 : z6) to (z0*z2*z5 : z0*z2*z6 : z0*z3*z5 : z0*z3*z6 : z0*z4*z5 : z0*z4*z6 : z1*z2*z5 : z1*z2*z6 : z1*z3*z5 : z1*z3*z6 : z1*z4*z5 : z1*z4*z6). """ N = self._dims M = prod([n + 1 for n in N]) - 1 CR = self.coordinate_ring() vars = list(self.coordinate_ring().variable_names()) + [ var + str(i) for i in range(M + 1) ] R = PolynomialRing(self.base_ring(), self.ngens() + M + 1, vars, order='lex') #set-up the elimination for the segre embedding mapping = [] k = self.ngens() index = self.num_components() * [0] for count in range(M + 1): mapping.append( R.gen(k + count) - prod([CR(self[i].gen(index[i])) for i in range(len(index))])) for i in range(len(index) - 1, -1, -1): if index[i] == N[i]: index[i] = 0 else: index[i] += 1 break #only increment once #change the defining ideal of the subscheme into the variables I = R.ideal(list(self.defining_polynomials()) + mapping) J = I.groebner_basis() s = set(R.gens()[:self.ngens()]) n = len(J) - 1 L = [] while s.isdisjoint(J[n].variables()): L.append(J[n]) n = n - 1 #create new subscheme if PP is None: PS = ProjectiveSpace(self.base_ring(), M, R.variable_names()[self.ngens():]) Y = PS.subscheme(L) else: if PP.dimension_relative() != M: raise ValueError( "projective Space %s must be dimension %s") % (PP, M) S = PP.coordinate_ring() psi = R.hom([0] * k + list(S.gens()), S) L = [psi(l) for l in L] Y = PP.subscheme(L) #create embedding for points mapping = [] index = self.num_components() * [0] for count in range(M + 1): mapping.append( prod([CR(self[i].gen(index[i])) for i in range(len(index))])) for i in range(len(index) - 1, -1, -1): if index[i] == N[i]: index[i] = 0 else: index[i] += 1 break #only increment once phi = self.hom(mapping, Y) return phi
def _biquadratic_syzygy_quartic(quadratic1, quadratic2, variables=None): r""" Helper function for the Weierstrass form of a biquadratic in $`\mathbb{P}^3$ The invariants and covariants of a quaternary biquadratic satisfy the relation :meth:`sage.rings.invariant_theory.TwoQuaternaryQuadratics.syzygy`, which is (modulo the two quadratic equations) of the form $J^2 = p_4(T, T')$ where * $J$, $T$, $T'$ are the covariants of the biquadratic. * $p_4$ is some quartic polynomial whose coefficients are invariants of the biquadratic. INPUT: See :func:`WeierstrassForm_P3` OUTPUT: A triple consisting of - The quaternary biquadratic as an algebraic form :class:`~sage.rings.invariant_theory.TwoQuaternaryQuadratics` - The binary quartic $p_4$ as a :class:`~sage.rings.invariant_theory.BinaryQuartic` - The dictionary of variable substitutions from the variables of the quartic to the variables of the biquadratic. EXAMPLES:: sage: from sage.schemes.toric.weierstrass_higher import _biquadratic_syzygy_quartic sage: R.<w,x,y,z> = QQ[] sage: _biquadratic_syzygy_quartic(w^2+x^2+y^2, z^2) (Joint quaternary quadratic with coefficients (1, 1, 1, 0, 0, 0, 0, 0, 0, 0) and quaternary quadratic with coefficients (0, 0, 0, 1, 0, 0, 0, 0, 0, 0), Binary quartic with coefficients (0, 0, 0, -1, 0), {aux...}) """ w, x, y, z = _check_polynomials_P3(quadratic1, quadratic2, variables) biquadratic = invariant_theory.quaternary_biquadratic(quadratic1, quadratic2, [w, x, y, z]) # construct auxiliary polynomial ring to work with the rhs of the syzygy R = biquadratic.ring() n = R.ngens() R_aux = PolynomialRing(R.base_ring(), n+2, 'aux') to_aux = dict() from_aux = dict() for var, var_aux in zip(R.gens(), R_aux.gens()[0:n]): to_aux[var] = var_aux from_aux[var_aux] = var T, T_prime = R_aux.gens()[n:] from_aux[T] = biquadratic.T_covariant() from_aux[T_prime] = biquadratic.T_prime_covariant() # Syzygy is J^2 = syz_rhs + (terms that vanish on the biquadratic) with # J = biquadratic.J_covariant() syz_rhs = T**4 * biquadratic.Delta_invariant().subs(to_aux) \ - T**3*T_prime * biquadratic.Theta_invariant().subs(to_aux) \ + T**2*T_prime**2 * biquadratic.Phi_invariant().subs(to_aux) \ - T*T_prime**3 * biquadratic.Theta_prime_invariant().subs(to_aux) \ + T_prime**4 * biquadratic.Delta_prime_invariant().subs(to_aux) quartic = invariant_theory.binary_quartic(syz_rhs, [T, T_prime]) return (biquadratic, quartic, from_aux)
def parametrization(self, point=None, morphism=True): r""" Return a parametrization `f` of ``self`` together with the inverse of `f`. If ``point`` is specified, then that point is used for the parametrization. Otherwise, use ``self.rational_point()`` to find a point. If ``morphism`` is True, then `f` is returned in the form of a Scheme morphism. Otherwise, it is a tuple of polynomials that gives the parametrization. ALGORITHM: Uses the PARI/GP function ``qfparam``. EXAMPLES :: sage: c = Conic([1,1,-1]) sage: c.parametrization() (Scheme morphism: From: Projective Space of dimension 1 over Rational Field To: Projective Conic Curve over Rational Field defined by x^2 + y^2 - z^2 Defn: Defined on coordinates by sending (x : y) to (2*x*y : x^2 - y^2 : x^2 + y^2), Scheme morphism: From: Projective Conic Curve over Rational Field defined by x^2 + y^2 - z^2 To: Projective Space of dimension 1 over Rational Field Defn: Defined on coordinates by sending (x : y : z) to (1/2*x : -1/2*y + 1/2*z)) An example with ``morphism = False`` :: sage: R.<x,y,z> = QQ[] sage: C = Curve(7*x^2 + 2*y*z + z^2) sage: (p, i) = C.parametrization(morphism = False); (p, i) ([-2*x*y, x^2 + 7*y^2, -2*x^2], [-1/2*x, 1/7*y + 1/14*z]) sage: C.defining_polynomial()(p) 0 sage: i[0](p) / i[1](p) x/y A ``ValueError`` is raised if ``self`` has no rational point :: sage: C = Conic(x^2 + 2*y^2 + z^2) sage: C.parametrization() Traceback (most recent call last): ... ValueError: Conic Projective Conic Curve over Rational Field defined by x^2 + 2*y^2 + z^2 has no rational points over Rational Field! A ``ValueError`` is raised if ``self`` is not smooth :: sage: C = Conic(x^2 + y^2) sage: C.parametrization() Traceback (most recent call last): ... ValueError: The conic self (=Projective Conic Curve over Rational Field defined by x^2 + y^2) is not smooth, hence does not have a parametrization. """ if (not self._parametrization is None) and not point: par = self._parametrization else: if not self.is_smooth(): raise ValueError("The conic self (=%s) is not smooth, hence does not have a parametrization." % self) if point is None: point = self.rational_point() point = Sequence(point) Q = PolynomialRing(QQ, 'x,y') [x, y] = Q.gens() gens = self.ambient_space().gens() M = self.symmetric_matrix() M *= lcm([ t.denominator() for t in M.list() ]) par1 = qfparam(M, point) B = Matrix([[par1[i][j] for j in range(3)] for i in range(3)]) # self is in the image of B and does not lie on a line, # hence B is invertible A = B.inverse() par2 = [sum([A[i,j]*gens[j] for j in range(3)]) for i in [1,0]] par = ([Q(pol(x/y)*y**2) for pol in par1], par2) if self._parametrization is None: self._parametrization = par if not morphism: return par P1 = ProjectiveSpace(self.base_ring(), 1, 'x,y') return P1.hom(par[0],self), self.Hom(P1)(par[1], check = False)
def _load_euler_table(self, n, force=False, verbose=False): r""" Load the euler table for self over the degree n extension of $\mathbb{F}_q$ to disk. If self is the versal j-curve, the table is pulled from SAGE_ROOT/data/jcurve_euler_tables . Otherwise, the table is pulled from the `user` table SAGE_ROOT/data/local_euler_tables . This should eventually be implemented using MongoDB. It currently doesn't check if the key exist. If the key doesn't exist, a RuntimeError is raised by sage.database.db. This RuntimeError should be sufficient, so key checking may not be necessary. INPUT: - n -- the degree of the extension of F_q - force -- boolean that overwrites self's euler table with one from database EXAMPLES:: sage: import psage sage: K.<t> = psage.FunctionField(GF(11)) sage: E = psage.ellff_EllipticCurve(K,[0,0,0,-27*t/(t-1728),54*t/(t-1728)]) sage: E._euler_table(1) Traceback (most recent call last): ... RuntimeError: table is empty sage: E._load_euler_table(1) sage: E._euler_table(1) [0, 0, 4, -6, 3, 5, 1, -2, 4, -2, 3, 1] """ import os SAGE_ROOT = os.environ['SAGE_ROOT'] K = self.K R = self.R t = K.gens()[0] p = self.p d = self.d q = self.q R2 = PolynomialRing(GF(q), 's') s = R2.gens()[0] a1n = R2(0) a1d = R2(1) a2n = R2(0) a2d = R2(1) a3n = R2(0) a3d = R2(1) a4n = R2(self.a4.numerator().coeffs()) a4d = R2(self.a4.denominator().coeffs()) a6n = R2(self.a6.numerator().coeffs()) a6d = R2(self.a6.denominator().coeffs()) ainvs = [0, 0, 0, self.a4, self.a6] ainvs_pairs = ((a1n, a1d), (a2n, a2d), (a3n, a3d), (a4n, a4d), (a6n, a6d)) # recognize if self is j-curve and use special repository if ainvs == [0,0,0,-27*t*(t-1728)**3,54*t*(t-1728)**5]: if verbose: print('j-curve recognized; saving euler table to database') if not os.path.exists(SAGE_ROOT + '/data/jcurve_euler_tables/jcurve_euler_tables'): print('Database does not exist; cannot load from it') else: euler_db = jCurveEulerTables() # check that keys exist? self._set_euler_table(n, euler_db[q][n], force) # work with user's repository of euler tables else: if not os.path.exists(SAGE_ROOT + '/data/local_euler_tables/local_euler_tables'): print('Database does not exist; cannot load from it') else: local_euler_db = LocalEulerTables() # check that keys exist? self._set_euler_table(n, local_euler_db[ainvs_pairs][q][n], force)
def _load_euler_table(self, n, force=False, verbose=False): r""" Load the euler table for self over the degree n extension of $\mathbb{F}_q$ to disk. If self is the versal j-curve, the table is pulled from SAGE_ROOT/data/jcurve_euler_tables . Otherwise, the table is pulled from the `user` table SAGE_ROOT/data/local_euler_tables . This should eventually be implemented using MongoDB. It currently doesn't check if the key exist. If the key doesn't exist, a RuntimeError is raised by sage.database.db. This RuntimeError should be sufficient, so key checking may not be necessary. INPUT: - n -- the degree of the extension of F_q - force -- boolean that overwrites self's euler table with one from database EXAMPLES:: sage: import psage sage: K.<t> = psage.FunctionField(GF(11)) sage: E = psage.ellff_EllipticCurve(K,[0,0,0,-27*t/(t-1728),54*t/(t-1728)]) sage: E._euler_table(1) Traceback (most recent call last): ... RuntimeError: table is empty sage: E._load_euler_table(1) sage: E._euler_table(1) [0, 0, 4, -6, 3, 5, 1, -2, 4, -2, 3, 1] """ import os SAGE_ROOT = os.environ['SAGE_ROOT'] K = self.K R = self.R t = K.gens()[0] p = self.p d = self.d q = self.q R2 = PolynomialRing(GF(q), 's') s = R2.gens()[0] a1n = R2(0) a1d = R2(1) a2n = R2(0) a2d = R2(1) a3n = R2(0) a3d = R2(1) a4n = R2(self.a4.numerator().coeffs()) a4d = R2(self.a4.denominator().coeffs()) a6n = R2(self.a6.numerator().coeffs()) a6d = R2(self.a6.denominator().coeffs()) ainvs = [0, 0, 0, self.a4, self.a6] ainvs_pairs = ((a1n, a1d), (a2n, a2d), (a3n, a3d), (a4n, a4d), (a6n, a6d)) # recognize if self is j-curve and use special repository if ainvs == [0,0,0,-27*t*(t-1728)**3,54*t*(t-1728)**5]: if verbose: print 'j-curve recognized; saving euler table to database' if not os.path.exists(SAGE_ROOT + '/data/jcurve_euler_tables/jcurve_euler_tables'): print 'Database does not exist; cannot load from it' else: euler_db = jCurveEulerTables() # check that keys exist? self._set_euler_table(n, euler_db[q][n], force) # work with user's repository of euler tables else: if not os.path.exists(SAGE_ROOT + '/data/local_euler_tables/local_euler_tables'): print 'Database does not exist; cannot load from it' else: local_euler_db = LocalEulerTables() # check that keys exist? self._set_euler_table(n, local_euler_db[ainvs_pairs][q][n], force)
def segre_embedding(self, PP=None, var='u'): r""" Return the Segre embedding of this space into the appropriate projective space. INPUT: - ``PP`` -- (default: ``None``) ambient image projective space; this is constructed if it is not given. - ``var`` -- string, variable name of the image projective space, default `u` (optional). OUTPUT: Hom -- from this space to the appropriate subscheme of projective space. .. TODO:: Cartesian products with more than two components. EXAMPLES:: sage: X.<y0,y1,y2,y3,y4,y5> = ProductProjectiveSpaces(ZZ, [2, 2]) sage: phi = X.segre_embedding(); phi Scheme morphism: From: Product of projective spaces P^2 x P^2 over Integer Ring To: Closed subscheme of Projective Space of dimension 8 over Integer Ring defined by: -u5*u7 + u4*u8, -u5*u6 + u3*u8, -u4*u6 + u3*u7, -u2*u7 + u1*u8, -u2*u4 + u1*u5, -u2*u6 + u0*u8, -u1*u6 + u0*u7, -u2*u3 + u0*u5, -u1*u3 + u0*u4 Defn: Defined by sending (y0 : y1 : y2 , y3 : y4 : y5) to (y0*y3 : y0*y4 : y0*y5 : y1*y3 : y1*y4 : y1*y5 : y2*y3 : y2*y4 : y2*y5). :: sage: T = ProductProjectiveSpaces([1, 2], CC, 'z') sage: T.segre_embedding() Scheme morphism: From: Product of projective spaces P^1 x P^2 over Complex Field with 53 bits of precision To: Closed subscheme of Projective Space of dimension 5 over Complex Field with 53 bits of precision defined by: -u2*u4 + u1*u5, -u2*u3 + u0*u5, -u1*u3 + u0*u4 Defn: Defined by sending (z0 : z1 , z2 : z3 : z4) to (z0*z2 : z0*z3 : z0*z4 : z1*z2 : z1*z3 : z1*z4). :: sage: T = ProductProjectiveSpaces([1, 2, 1], QQ, 'z') sage: T.segre_embedding() Scheme morphism: From: Product of projective spaces P^1 x P^2 x P^1 over Rational Field To: Closed subscheme of Projective Space of dimension 11 over Rational Field defined by: -u9*u10 + u8*u11, -u7*u10 + u6*u11, -u7*u8 + u6*u9, -u5*u10 + u4*u11, -u5*u8 + u4*u9, -u5*u6 + u4*u7, -u5*u9 + u3*u11, -u5*u8 + u3*u10, -u5*u8 + u2*u11, -u4*u8 + u2*u10, -u3*u8 + u2*u9, -u3*u6 + u2*u7, -u3*u4 + u2*u5, -u5*u7 + u1*u11, -u5*u6 + u1*u10, -u3*u7 + u1*u9, -u3*u6 + u1*u8, -u5*u6 + u0*u11, -u4*u6 + u0*u10, -u3*u6 + u0*u9, -u2*u6 + u0*u8, -u1*u6 + u0*u7, -u1*u4 + u0*u5, -u1*u2 + u0*u3 Defn: Defined by sending (z0 : z1 , z2 : z3 : z4 , z5 : z6) to (z0*z2*z5 : z0*z2*z6 : z0*z3*z5 : z0*z3*z6 : z0*z4*z5 : z0*z4*z6 : z1*z2*z5 : z1*z2*z6 : z1*z3*z5 : z1*z3*z6 : z1*z4*z5 : z1*z4*z6). """ N = self._dims M = prod([n+1 for n in N]) - 1 CR = self.coordinate_ring() vars = list(self.coordinate_ring().variable_names()) + [var + str(i) for i in range(M+1)] R = PolynomialRing(self.base_ring(), self.ngens()+M+1, vars, order='lex') #set-up the elimination for the segre embedding mapping = [] k = self.ngens() index = self.num_components()*[0] for count in range(M + 1): mapping.append(R.gen(k+count)-prod([CR(self[i].gen(index[i])) for i in range(len(index))])) for i in range(len(index)-1, -1, -1): if index[i] == N[i]: index[i] = 0 else: index[i] += 1 break #only increment once #change the defining ideal of the subscheme into the variables I = R.ideal(list(self.defining_polynomials()) + mapping) J = I.groebner_basis() s = set(R.gens()[:self.ngens()]) n = len(J)-1 L = [] while s.isdisjoint(J[n].variables()): L.append(J[n]) n = n-1 #create new subscheme if PP is None: PS = ProjectiveSpace(self.base_ring(), M, R.variable_names()[self.ngens():]) Y = PS.subscheme(L) else: if PP.dimension_relative() != M: raise ValueError("projective Space %s must be dimension %s")%(PP, M) S = PP.coordinate_ring() psi = R.hom([0]*k + list(S.gens()), S) L = [psi(l) for l in L] Y = PP.subscheme(L) #create embedding for points mapping = [] index = self.num_components()*[0] for count in range(M + 1): mapping.append(prod([CR(self[i].gen(index[i])) for i in range(len(index))])) for i in range(len(index)-1, -1, -1): if index[i] == N[i]: index[i] = 0 else: index[i] += 1 break #only increment once phi = self.hom(mapping, Y) return phi
def is_geom_trivial_when_field(C, bad_primes, B=200): r""" Determine if the geometric endomorphism ring is trivial assuming the geometric endomorphism algebra is a field. This is Algorithm 4.15 in [Lom2019]_. INPUT: - ``C`` -- the hyperelliptic curve. - ``bad_primes`` -- the list of odd primes of bad reduction. - ``B`` -- (default: 200) the bound which appears in the statement of the algorithm from [Lom2019]_ OUTPUT: Boolean indicating whether or not the geometric endomorphism algebra is the field of rational numbers. WARNING: There is a very small chance that this algorithm returns ``False`` when in fact it is ``True``. In this case, as explained in the discussion immediately preceding Algorithm 4.15 of [Lom2019]_, this can be established by increasing the optional `B` parameter. Mathematically, this algorithm gives the correct answer only in the limit as `B \to \infty`, although in practice `B = 200` was sufficient to correctly verify every single entry in the LMFDB. However, strictly speaking, a ``False`` returned by this function is not provably ``False``. EXAMPLES: This is LMFDB curve 461.a.461.2:: sage: from sage.schemes.hyperelliptic_curves.jacobian_endomorphism_utils import is_geom_trivial_when_field sage: R.<x> = QQ[] sage: f = 4*x^5 - 4*x^4 - 156*x^3 + 40*x^2 + 1088*x - 1223 sage: C = HyperellipticCurve(f) sage: is_geom_trivial_when_field(C,[461]) True This is LMFDB curve 4489.a.4489.1:: sage: f = x^6 + 4*x^5 + 2*x^4 + 2*x^3 + x^2 - 2*x + 1 sage: C = HyperellipticCurve(f) sage: is_geom_trivial_when_field(C,[67]) False """ running_gcd = 0 R = PolynomialRing(ZZ,2,"xv") x,v = R.gens() T = PolynomialRing(QQ,'v') g = v - x**4 for p in prime_range(3,B): if p not in bad_primes: Cp = C.change_ring(FiniteField(p)) fp = Cp.frobenius_polynomial() if satisfies_coefficient_condition(fp, p): # This defines the polynomial f_v**[4] from the paper fp4 = T(R(fp).resultant(g)) if fp4.is_irreducible(): running_gcd = gcd(running_gcd, NumberField(fp,'a').discriminant()) if running_gcd <= 24: return True return False
def Conic(base_field, F=None, names=None, unique=True): r""" Return the plane projective conic curve defined by ``F`` over ``base_field``. The input form ``Conic(F, names=None)`` is also accepted, in which case the fraction field of the base ring of ``F`` is used as base field. INPUT: - ``base_field`` -- The base field of the conic. - ``names`` -- a list, tuple, or comma separated string of three variable names specifying the names of the coordinate functions of the ambient space `\Bold{P}^3`. If not specified or read off from ``F``, then this defaults to ``'x,y,z'``. - ``F`` -- a polynomial, list, matrix, ternary quadratic form, or list or tuple of 5 points in the plane. If ``F`` is a polynomial or quadratic form, then the output is the curve in the projective plane defined by ``F = 0``. If ``F`` is a polynomial, then it must be a polynomial of degree at most 2 in 2 variables, or a homogeneous polynomial in of degree 2 in 3 variables. If ``F`` is a matrix, then the output is the zero locus of `(x,y,z) F (x,y,z)^t`. If ``F`` is a list of coefficients, then it has length 3 or 6 and gives the coefficients of the monomials `x^2, y^2, z^2` or all 6 monomials `x^2, xy, xz, y^2, yz, z^2` in lexicographic order. If ``F`` is a list of 5 points in the plane, then the output is a conic through those points. - ``unique`` -- Used only if ``F`` is a list of points in the plane. If the conic through the points is not unique, then raise ``ValueError`` if and only if ``unique`` is True OUTPUT: A plane projective conic curve defined by ``F`` over a field. EXAMPLES: Conic curves given by polynomials :: sage: X,Y,Z = QQ['X,Y,Z'].gens() sage: Conic(X^2 - X*Y + Y^2 - Z^2) Projective Conic Curve over Rational Field defined by X^2 - X*Y + Y^2 - Z^2 sage: x,y = GF(7)['x,y'].gens() sage: Conic(x^2 - x + 2*y^2 - 3, 'U,V,W') Projective Conic Curve over Finite Field of size 7 defined by U^2 + 2*V^2 - U*W - 3*W^2 Conic curves given by matrices :: sage: Conic(matrix(QQ, [[1, 2, 0], [4, 0, 0], [7, 0, 9]]), 'x,y,z') Projective Conic Curve over Rational Field defined by x^2 + 6*x*y + 7*x*z + 9*z^2 sage: x,y,z = GF(11)['x,y,z'].gens() sage: C = Conic(x^2+y^2-2*z^2); C Projective Conic Curve over Finite Field of size 11 defined by x^2 + y^2 - 2*z^2 sage: Conic(C.symmetric_matrix(), 'x,y,z') Projective Conic Curve over Finite Field of size 11 defined by x^2 + y^2 - 2*z^2 Conics given by coefficients :: sage: Conic(QQ, [1,2,3]) Projective Conic Curve over Rational Field defined by x^2 + 2*y^2 + 3*z^2 sage: Conic(GF(7), [1,2,3,4,5,6], 'X') Projective Conic Curve over Finite Field of size 7 defined by X0^2 + 2*X0*X1 - 3*X1^2 + 3*X0*X2 - 2*X1*X2 - X2^2 The conic through a set of points :: sage: C = Conic(QQ, [[10,2],[3,4],[-7,6],[7,8],[9,10]]); C Projective Conic Curve over Rational Field defined by x^2 + 13/4*x*y - 17/4*y^2 - 35/2*x*z + 91/4*y*z - 37/2*z^2 sage: C.rational_point() (10 : 2 : 1) sage: C.point([3,4]) (3 : 4 : 1) sage: a=AffineSpace(GF(13),2) sage: Conic([a([x,x^2]) for x in range(5)]) Projective Conic Curve over Finite Field of size 13 defined by x^2 - y*z """ if not (is_IntegralDomain(base_field) or base_field == None): if names is None: names = F F = base_field base_field = None if isinstance(F, (list,tuple)): if len(F) == 1: return Conic(base_field, F[0], names) if names == None: names = 'x,y,z' if len(F) == 5: L=[] for f in F: if isinstance(f, SchemeMorphism_point_affine): C = Sequence(f, universe = base_field) if len(C) != 2: raise TypeError, "points in F (=%s) must be planar"%F C.append(1) elif isinstance(f, SchemeMorphism_point_projective_field): C = Sequence(f, universe = base_field) elif isinstance(f, (list, tuple)): C = Sequence(f, universe = base_field) if len(C) == 2: C.append(1) else: raise TypeError, "F (=%s) must be a sequence of planar " \ "points" % F if len(C) != 3: raise TypeError, "points in F (=%s) must be planar" % F P = C.universe() if not is_IntegralDomain(P): raise TypeError, "coordinates of points in F (=%s) must " \ "be in an integral domain" % F L.append(Sequence([C[0]**2, C[0]*C[1], C[0]*C[2], C[1]**2, C[1]*C[2], C[2]**2], P.fraction_field())) M=Matrix(L) if unique and M.rank() != 5: raise ValueError, "points in F (=%s) do not define a unique " \ "conic" % F con = Conic(base_field, Sequence(M.right_kernel().gen()), names) con.point(F[0]) return con F = Sequence(F, universe = base_field) base_field = F.universe().fraction_field() temp_ring = PolynomialRing(base_field, 3, names) (x,y,z) = temp_ring.gens() if len(F) == 3: return Conic(F[0]*x**2 + F[1]*y**2 + F[2]*z**2) if len(F) == 6: return Conic(F[0]*x**2 + F[1]*x*y + F[2]*x*z + F[3]*y**2 + \ F[4]*y*z + F[5]*z**2) raise TypeError, "F (=%s) must be a sequence of 3 or 6" \ "coefficients" % F if is_QuadraticForm(F): F = F.matrix() if is_Matrix(F) and F.is_square() and F.ncols() == 3: if names == None: names = 'x,y,z' temp_ring = PolynomialRing(F.base_ring(), 3, names) F = vector(temp_ring.gens()) * F * vector(temp_ring.gens()) if not is_MPolynomial(F): raise TypeError, "F (=%s) must be a three-variable polynomial or " \ "a sequence of points or coefficients" % F if F.total_degree() != 2: raise TypeError, "F (=%s) must have degree 2" % F if base_field == None: base_field = F.base_ring() if not is_IntegralDomain(base_field): raise ValueError, "Base field (=%s) must be a field" % base_field base_field = base_field.fraction_field() if names == None: names = F.parent().variable_names() pol_ring = PolynomialRing(base_field, 3, names) if F.parent().ngens() == 2: (x,y,z) = pol_ring.gens() F = pol_ring(F(x/z,y/z)*z**2) if F == 0: raise ValueError, "F must be nonzero over base field %s" % base_field if F.total_degree() != 2: raise TypeError, "F (=%s) must have degree 2 over base field %s" % \ (F, base_field) if F.parent().ngens() == 3: P2 = ProjectiveSpace(2, base_field, names) if is_PrimeFiniteField(base_field): return ProjectiveConic_prime_finite_field(P2, F) if is_FiniteField(base_field): return ProjectiveConic_finite_field(P2, F) if is_RationalField(base_field): return ProjectiveConic_rational_field(P2, F) if is_NumberField(base_field): return ProjectiveConic_number_field(P2, F) return ProjectiveConic_field(P2, F) raise TypeError, "Number of variables of F (=%s) must be 2 or 3" % F
def parametrization(self, point=None, morphism=True): r""" Return a parametrization `f` of ``self`` together with the inverse of `f`. If ``point`` is specified, then that point is used for the parametrization. Otherwise, use ``self.rational_point()`` to find a point. If ``morphism`` is True, then `f` is returned in the form of a Scheme morphism. Otherwise, it is a tuple of polynomials that gives the parametrization. EXAMPLES: An example over a finite field :: sage: c = Conic(GF(2), [1,1,1,1,1,0]) sage: c.parametrization() (Scheme morphism: From: Projective Space of dimension 1 over Finite Field of size 2 To: Projective Conic Curve over Finite Field of size 2 defined by x^2 + x*y + y^2 + x*z + y*z Defn: Defined on coordinates by sending (x : y) to (x*y + y^2 : x^2 + x*y : x^2 + x*y + y^2), Scheme morphism: From: Projective Conic Curve over Finite Field of size 2 defined by x^2 + x*y + y^2 + x*z + y*z To: Projective Space of dimension 1 over Finite Field of size 2 Defn: Defined on coordinates by sending (x : y : z) to (y : x)) An example with ``morphism = False`` :: sage: R.<x,y,z> = QQ[] sage: C = Curve(7*x^2 + 2*y*z + z^2) sage: (p, i) = C.parametrization(morphism = False); (p, i) ([-2*x*y, 7*x^2 + y^2, -2*y^2], [-1/2*x, -1/2*z]) sage: C.defining_polynomial()(p) 0 sage: i[0](p) / i[1](p) x/y A ``ValueError`` is raised if ``self`` has no rational point :: sage: C = Conic(x^2 + y^2 + 7*z^2) sage: C.parametrization() Traceback (most recent call last): ... ValueError: Conic Projective Conic Curve over Rational Field defined by x^2 + y^2 + 7*z^2 has no rational points over Rational Field! A ``ValueError`` is raised if ``self`` is not smooth :: sage: C = Conic(x^2 + y^2) sage: C.parametrization() Traceback (most recent call last): ... ValueError: The conic self (=Projective Conic Curve over Rational Field defined by x^2 + y^2) is not smooth, hence does not have a parametrization. """ if (not self._parametrization is None) and not point: par = self._parametrization else: if not self.is_smooth(): raise ValueError, "The conic self (=%s) is not smooth, hence does not have a parametrization." % self if point == None: point = self.rational_point() point = Sequence(point) B = self.base_ring() Q = PolynomialRing(B, 'x,y') [x, y] = Q.gens() gens = self.ambient_space().gens() P = PolynomialRing(B, 4, ['X', 'Y', 'T0', 'T1']) [X, Y, T0, T1] = P.gens() c3 = [j for j in range(2,-1,-1) if point[j] != 0][0] c1 = [j for j in range(3) if j != c3][0] c2 = [j for j in range(3) if j != c3 and j != c1][0] L = [0,0,0] L[c1] = Y*T1*point[c1] + Y*T0 L[c2] = Y*T1*point[c2] + X*T0 L[c3] = Y*T1*point[c3] bezout = P(self.defining_polynomial()(L) / T0) t = [bezout([x,y,0,-1]),bezout([x,y,1,0])] par = (tuple([Q(p([x,y,t[0],t[1]])/y) for p in L]), tuple([gens[m]*point[c3]-gens[c3]*point[m] for m in [c2,c1]])) if self._parametrization is None: self._parametrization = par if not morphism: return par P1 = ProjectiveSpace(self.base_ring(), 1, 'x,y') return P1.hom(par[0],self), self.Hom(P1)(par[1], check = False)
t, = self.base_ring().base().gens() # t in F[t] supp = [] roots = [[], [], []] remove = None # loop through the coefficients and find a root of f_i (as in # [HC2006]) modulo each element in the coefficients' support for i in (0, 1, 2): supp.append(list(coeff[i].factor())) for p in supp[i]: if p[1] != 1: raise ValueError("Expected factor of exponent 1.") # Convert to monic factor x = p[0] / list(p[0])[-1] N = p[0].base_ring().extension(x, 'tbar') R = PolynomialRing(N, 'u') u, = R.gens() # If p[0] has degree 1, sage might forget the "defining # polynomial" of N, so we define our own modulo operation if p[0].degree() == 1: mod = t.parent().hom([-x[0]]) else: mod = N if i == 0: x = -mod(coeff[2]) / mod(coeff[1]) elif i == 1: x = -mod(coeff[0]) / mod(coeff[2]) else: x = -mod(coeff[1]) / mod(coeff[0]) if x.is_square(): root = N(x.sqrt()) else:
def rational_type(f, n=ZZ(3), base_ring=ZZ): r""" Return the basic analytic properties that can be determined directly from the specified rational function ``f`` which is interpreted as a representation of an element of a FormsRing for the Hecke Triangle group with parameter ``n`` and the specified ``base_ring``. In particular the following degree of the generators is assumed: `deg(1) := (0, 1)` `deg(x) := (4/(n-2), 1)` `deg(y) := (2n/(n-2), -1)` `deg(z) := (2, -1)` The meaning of homogeneous elements changes accordingly. INPUT: - ``f`` - A rational function in ``x,y,z,d`` over ``base_ring``. - ``n`` - An integer greater or equal to ``3`` corresponding to the ``HeckeTriangleGroup`` with that parameter (default: ``3``). - ``base_ring``` - The base ring of the corresponding forms ring, resp. polynomial ring (default: ``ZZ``). OUTPUT: A tuple ``(elem, h**o, k, ep, analytic_type)`` describing the basic analytic properties of ``f`` (with the interpretation indicated above). - ``elem`` - ``True`` if ``f`` has a homogeneous denominator. - ``h**o`` - ``True`` if ``f`` also has a homogeneous numerator. - ``k`` - ``None`` if ``f`` is not homogeneneous, otherwise the weight of ``f`` (which is the first component of its degree). - ``ep`` - ``None`` if ``f`` is not homogeneous, otherwise the multiplier of ``f`` (which is the second component of its degree) - ``analytic_type`` - The ``AnalyticType`` of ``f``. For the zero function the degree ``(0, 1)`` is choosen. This function is (heavily) used to determine the type of elements and to check if the element really is contained in its parent. EXAMPLES:: sage: (x,y,z,d) = var("x,y,z,d") sage: rational_type(0, n=4) (True, True, 0, 1, zero) sage: rational_type(1, n=12) (True, True, 0, 1, modular) sage: rational_type(x^3 - y^2) (True, True, 12, 1, cuspidal) sage: rational_type(x * z, n=7) (True, True, 14/5, -1, quasi modular) sage: rational_type(1/(x^3 - y^2) + z/d) (True, False, None, None, quasi weakly holomorphic modular) sage: rational_type(x^3/(x^3 - y^2)) (True, True, 0, 1, weakly holomorphic modular) sage: rational_type(1/(x + z)) (False, False, None, None, None) sage: rational_type(1/x + 1/z) (True, False, None, None, quasi meromorphic modular) sage: rational_type(d/x, n=10) (True, True, -1/2, 1, meromorphic modular) sage: rational_type(1.1 * z * (x^8-y^2), n=8, base_ring=CC) (True, True, 22/3, -1, quasi cuspidal) """ from analytic_type import AnalyticType AT = AnalyticType() # Determine whether f is zero if (f == 0): # elem, h**o, k, ep, analytic_type return (True, True, QQ(0), ZZ(1), AT([])) analytic_type = AT(["quasi", "mero"]) R = PolynomialRing(base_ring,'x,y,z,d') F = FractionField(R) (x,y,z,d) = R.gens() R2 = PolynomialRing(PolynomialRing(base_ring, 'd'), 'x,y,z') dhom = R.hom( R2.gens() + (R2.base().gen(),), R2) f = F(f) n = ZZ(n) num = R(f.numerator()) denom = R(f.denominator()) hom_num = R( num.subs(x=x**4, y=y**(2*n), z=z**(2*(n-2))) ) hom_denom = R( denom.subs(x=x**4, y=y**(2*n), z=z**(2*(n-2))) ) ep_num = set([ZZ(1) - 2*(( sum([g.exponents()[0][m] for m in [1,2]]) )%2) for g in dhom(num).monomials()]) ep_denom = set([ZZ(1) - 2*(( sum([g.exponents()[0][m] for m in [1,2]]) )%2) for g in dhom(denom).monomials()]) # Determine whether the denominator of f is homogeneous if (len(ep_denom) == 1 and dhom(hom_denom).is_homogeneous()): elem = True else: # elem, h**o, k, ep, analytic_type return (False, False, None, None, None) # Determine whether f is homogeneous if (len(ep_num) == 1 and dhom(hom_num).is_homogeneous()): h**o = True weight = (dhom(hom_num).degree() - dhom(hom_denom).degree()) / (n-2) ep = ep_num.pop() / ep_denom.pop() # TODO: decompose f (resp. its degrees) into homogeneous parts else: h**o = False weight = None ep = None # Note that we intentially leave out the d-factor! finf_pol = x**n-y**2 # Determine whether f is modular if not ( (num.degree(z) > 0) or (denom.degree(z) > 0) ): analytic_type = analytic_type.reduce_to("mero") # Determine whether f is holomorphic if (dhom(denom).is_constant()): analytic_type = analytic_type.reduce_to(["quasi", "holo"]) # Determine whether f is cuspidal in the sense that finf divides it... # Bug in singular: finf_pol.dividess(1.0) fails over RR if (not dhom(num).is_constant()) and finf_pol.divides(num): analytic_type = analytic_type.reduce_to(["quasi", "cusp"]) else: # -> because of a bug with singular in case some cases try: while (finf_pol.divides(denom)): # a simple "denom /= finf_pol" is strangely not enough for non-exact rings denom = denom.quo_rem(finf_pol)[0] denom = R(denom) except TypeError: pass # Determine whether f is weakly holomorphic in the sense that at most powers of finf occur in denom if (dhom(denom).is_constant()): analytic_type = analytic_type.reduce_to(["quasi", "weak"]) return (elem, h**o, weight, ep, analytic_type)
def get_is_geom_field(f, C, bad_primes, B=200): r""" Determine whether the geometric endomorphism algebra is a field. This is Algorithm 4.10 in [Lom2019]_. The computation done here may allow one to immediately conclude that the geometric endomorphism ring is trivial (i.e. the integer ring); this information is output in a second boolean to avoid unnecessary subsequent computation. An additional optimisation comes from Part (2) of Theorem 4.8 in [Lom2019]_, from which we can conclude that the endomorphism ring is geometrically trivial, and from Proposition 4.7 in loc. cit. from which we can rule out potential QM. INPUT: - ``f`` -- a polynomial defining the hyperelliptic curve. - ``C`` -- the hyperelliptic curve. - ``bad_primes`` -- the list of odd primes of bad reduction. - ``B`` -- (default: 200) the bound which appears in the statement of the algorithm from [Lom2019]_ OUTPUT: Pair of booleans (bool1, bool2). `bool1` indicates if the geometric endomorphism algebra is a field; `bool2` indicates if the geometric endomorphism algebra is the field of rational numbers. WARNING: There is a very small chance that this algorithm return ``False`` when in fact it is ``True``. In this case, as explained in the discussion immediately preceding Algorithm 4.15 of [Lom2019]_, this can be established by increasing the optional `B` parameter. Mathematically, this algorithm gives the correct answer only in the limit as `B \to \infty`, although in practice `B = 200` was sufficient to correctly verify every single entry in the LMFDB. However, strictly speaking, a ``False`` returned by this function is not provably ``False``. EXAMPLES: This is LMFDB curve 940693.a.960693.1:: sage: from sage.schemes.hyperelliptic_curves.jacobian_endomorphism_utils import get_is_geom_field sage: R.<x> = QQ[] sage: f = 4*x^6 - 12*x^5 + 20*x^3 - 8*x^2 - 4*x + 1 sage: C = HyperellipticCurve(f) sage: get_is_geom_field(f,C,[13,269]) (False, False) This is LMFDB curve 3125.a.3125.1:: sage: f = 4*x^5 + 1 sage: C = HyperellipticCurve(f) sage: get_is_geom_field(f,C,[5]) (True, False) This is LMFDB curve 277.a.277.2:: sage: f = 4*x^6 - 36*x^4 + 56*x^3 - 76*x^2 + 44*x - 23 sage: C = HyperellipticCurve(f) sage: get_is_geom_field(f,C,[277]) (True, True) """ if C.has_odd_degree_model(): C_odd = C.odd_degree_model() f_odd, h_odd = C_odd.hyperelliptic_polynomials() # if f was odd to begin with, then f_odd = f assert f_odd.degree() == 5 if (4*f_odd + h_odd**2).degree() == 5: f_new = 4*f_odd + h_odd**2 if f_new.is_irreducible(): # i.e. the Jacobian is geometrically simple f_disc_odd_prime_exponents = [v for _,v in f_new.discriminant().prime_to_S_part([ZZ(2)]).factor()] if 1 in f_disc_odd_prime_exponents: return (True, True) # Theorem 4.8 (2) # At this point we are in the situation of Algorithm 4.10 # Step 1, so either the geometric endomorphism algebra is a # field or it is a quaternion algebra. This latter case implies # that the Jacobian is the square of an elliptic curve modulo # a prime p where f is also irreducible (which exist by # Chebotarev density). This contradicts Prop 4.7, hence we can # conclude as follows. return (True, False) if f.is_irreducible(): assert f.degree() == 6 # else we should already have exited by now G = f.galois_group() if G.order() in [360, 720]: return (True, True) # Algorithm 4.10 Step 2 R = PolynomialRing(ZZ,2,"xv") x,v = R.gens() T = PolynomialRing(QQ,'v') g = v - x**12 for p in prime_range(3,B): if p not in bad_primes: fp = C.change_ring(FiniteField(p)).frobenius_polynomial() # This defines the polynomial f_v**[12] from the paper fp12 = T(R(fp).resultant(g)) if fp12.is_irreducible(): # i.e. the Jacobian is geometrically simple f_disc_odd_prime_exponents = [v for _,v in f.discriminant().prime_to_S_part([ZZ(2)]).factor()] if 1 in f_disc_odd_prime_exponents: return (True, True) # Theorem 4.8 (2) return (True, False) # Algorithm 4.10 Step 3 plus Prop 4.7 as above return (False, False)
class WeierstrassIsomorphism(EllipticCurveHom, baseWI): r""" Class representing a Weierstrass isomorphism between two elliptic curves. """ def __init__(self, E=None, urst=None, F=None): r""" Constructor for WeierstrassIsomorphism class, INPUT: - ``E`` -- an EllipticCurve, or None (see below). - ``urst`` -- a 4-tuple `(u,r,s,t)`, or None (see below). - ``F`` -- an EllipticCurve, or None (see below). Given two Elliptic Curves ``E`` and ``F`` (represented by Weierstrass models as usual), and a transformation ``urst`` from ``E`` to ``F``, construct an isomorphism from ``E`` to ``F``. An exception is raised if ``urst(E)!=F``. At most one of ``E``, ``F``, ``urst`` can be None. If ``F==None`` then ``F`` is constructed as ``urst(E)``. If ``E==None`` then ``E`` is constructed as ``urst^-1(F)``. If ``urst==None`` then an isomorphism from ``E`` to ``F`` is constructed if possible, and an exception is raised if they are not isomorphic. Otherwise ``urst`` can be a tuple of length 4 or a object of type ``baseWI``. Users will not usually need to use this class directly, but instead use methods such as ``isomorphism`` of elliptic curves. EXAMPLES:: sage: from sage.schemes.elliptic_curves.weierstrass_morphism import * sage: WeierstrassIsomorphism(EllipticCurve([0,1,2,3,4]),(-1,2,3,4)) Elliptic-curve morphism: From: Elliptic Curve defined by y^2 + 2*y = x^3 + x^2 + 3*x + 4 over Rational Field To: Elliptic Curve defined by y^2 - 6*x*y - 10*y = x^3 - 2*x^2 - 11*x - 2 over Rational Field Via: (u,r,s,t) = (-1, 2, 3, 4) sage: E = EllipticCurve([0,1,2,3,4]) sage: F = EllipticCurve(E.cremona_label()) sage: WeierstrassIsomorphism(E,None,F) Elliptic-curve morphism: From: Elliptic Curve defined by y^2 + 2*y = x^3 + x^2 + 3*x + 4 over Rational Field To: Elliptic Curve defined by y^2 = x^3 + x^2 + 3*x + 5 over Rational Field Via: (u,r,s,t) = (1, 0, 0, -1) sage: w = WeierstrassIsomorphism(None,(1,0,0,-1),F) sage: w._domain==E True """ from .ell_generic import is_EllipticCurve if E is not None: if not is_EllipticCurve(E): raise ValueError( "First argument must be an elliptic curve or None") if F is not None: if not is_EllipticCurve(F): raise ValueError( "Third argument must be an elliptic curve or None") if urst is not None: if len(urst) != 4: raise ValueError("Second argument must be [u,r,s,t] or None") if len([par for par in [E, urst, F] if par is not None]) < 2: raise ValueError("At most 1 argument can be None") if F is None: # easy case baseWI.__init__(self, *urst) F = EllipticCurve(baseWI.__call__(self, list(E.a_invariants()))) elif E is None: # easy case in reverse baseWI.__init__(self, *urst) inv_urst = baseWI.__invert__(self) E = EllipticCurve(baseWI.__call__(inv_urst, list(F.a_invariants()))) elif urst is None: # try to construct the morphism urst = isomorphisms(E, F, True) if urst is None: raise ValueError("Elliptic curves not isomorphic.") baseWI.__init__(self, *urst) else: # none of the parameters is None: baseWI.__init__(self, *urst) if F != EllipticCurve(baseWI.__call__(self, list( E.a_invariants()))): raise ValueError( "second argument is not an isomorphism from first argument to third argument" ) base_ring = get_coercion_model().common_parent(E.base_ring(), F.base_ring(), *urst) self._mpoly_ring = PolynomialRing(base_ring, ['x', 'y']) self._poly_ring = PolynomialRing(base_ring, ['x']) self._domain = E self._codomain = F EllipticCurveHom.__init__(self, self._domain, self._codomain) def _richcmp_(self, other, op): r""" Standard comparison function for the WeierstrassIsomorphism class. EXAMPLES:: sage: from sage.schemes.elliptic_curves.weierstrass_morphism import * sage: E = EllipticCurve('389a1') sage: F = E.change_weierstrass_model(1,2,3,4) sage: w1 = E.isomorphism_to(F) sage: w1 == w1 True sage: w2 = F.automorphisms()[0] *w1 sage: w1 == w2 False sage: E = EllipticCurve_from_j(GF(7)(0)) sage: F = E.change_weierstrass_model(2,3,4,5) sage: a = E.isomorphisms(F) sage: b = [w*a[0] for w in F.automorphisms()] sage: b.sort() sage: a == b True sage: c = [a[0]*w for w in E.automorphisms()] sage: c.sort() sage: a == c True """ if isinstance(other, WeierstrassIsomorphism): lx = self._domain rx = other._domain if lx != rx: return richcmp_not_equal(lx, rx, op) lx = self._codomain rx = other._codomain if lx != rx: return richcmp_not_equal(lx, rx, op) return baseWI.__richcmp__(self, other, op) return EllipticCurveHom._richcmp_(self, other, op) def _eval(self, P): r""" Less strict evaluation method for internal use. In particular, this can be used to evaluate ``self`` at a point defined over an extension field. INPUT: a sequence of 3 coordinates defining a point on ``self`` OUTPUT: the result of evaluating ``self'' at the given point EXAMPLES:: sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism sage: E = EllipticCurve([i,0]); E Elliptic Curve defined by y^2 = x^3 + I*x over Number Field in I with defining polynomial x^2 + 1 with I = 1*I sage: iso = WeierstrassIsomorphism(E, (i,1,2,3)) sage: P = E.change_ring(QQbar).lift_x(QQbar.random_element()) sage: Q = iso._eval(P) sage: Q.curve() Elliptic Curve defined by y^2 + (-4*I)*x*y + 6*I*y = x^3 + x^2 + (I-9)*x + (-I+8) over Algebraic Field sage: y = next(filter(bool, iter(QQbar.random_element, None))) # sample until nonzero sage: iso._eval((0, y, 0)) == 0 True """ if self._domain.defining_polynomial()(*P): raise ValueError(f'{P} not on {self._domain}') Q = baseWI.__call__(self, P) k = Sequence(tuple(P) + tuple(Q)).universe() return self._codomain.base_extend(k).point(Q) def __call__(self, P): r""" Call function for WeierstrassIsomorphism class. INPUT: - ``P`` (Point) -- a point on the domain curve. OUTPUT: (Point) the transformed point on the codomain curve. EXAMPLES:: sage: from sage.schemes.elliptic_curves.weierstrass_morphism import * sage: E = EllipticCurve('37a1') sage: w = WeierstrassIsomorphism(E,(2,3,4,5)) sage: P = E(0,-1) sage: w(P) (-3/4 : 3/4 : 1) sage: w(P).curve() == E.change_weierstrass_model((2,3,4,5)) True """ if P[2] == 0: return self._codomain(0) return self._codomain.point(baseWI.__call__(self, tuple(P._coords)), check=False) def __invert__(self): r""" Return the inverse of this WeierstrassIsomorphism. EXAMPLES:: sage: E = EllipticCurve('5077') sage: F = E.change_weierstrass_model([2,3,4,5]); F Elliptic Curve defined by y^2 + 4*x*y + 11/8*y = x^3 - 7/4*x^2 - 3/2*x - 9/32 over Rational Field sage: w = E.isomorphism_to(F) sage: P = E(-2,3,1) sage: w(P) (-5/4 : 9/4 : 1) sage: ~w Elliptic-curve morphism: From: Elliptic Curve defined by y^2 + 4*x*y + 11/8*y = x^3 - 7/4*x^2 - 3/2*x - 9/32 over Rational Field To: Elliptic Curve defined by y^2 + y = x^3 - 7*x + 6 over Rational Field Via: (u,r,s,t) = (1/2, -3/4, -2, 7/8) sage: Q = w(P); Q (-5/4 : 9/4 : 1) sage: (~w)(Q) (-2 : 3 : 1) """ winv = baseWI.__invert__(self).tuple() return WeierstrassIsomorphism(self._codomain, winv, self._domain) @staticmethod def _composition_impl(left, right): r""" Return the composition of a ``WeierstrassIsomorphism`` with another elliptic-curve morphism. Called by :meth:`EllipticCurveHom._composition_`. EXAMPLES:: sage: E1 = EllipticCurve('5077') sage: E2 = E1.change_weierstrass_model([2,3,4,5]) sage: w1 = E1.isomorphism_to(E2) sage: E3 = E2.change_weierstrass_model([6,7,8,9]) sage: w2 = E2.isomorphism_to(E3) sage: P = E1(-2,3,1) sage: (w2*w1)(P) == w2(w1(P)) True TESTS: We should return ``NotImplemented`` when passed a combination of elliptic-curve morphism types that we don't handle here:: sage: E = EllipticCurve([1,0]) sage: phi = E.isogeny(E(0,0)) sage: w1._composition_impl(phi.dual(), phi) NotImplemented """ if isinstance(left, WeierstrassIsomorphism) and isinstance( right, WeierstrassIsomorphism): if left._domain != right._codomain: raise ValueError( "Domain of first argument must equal codomain of second") w = baseWI.__mul__(left, right) return WeierstrassIsomorphism(right._domain, w.tuple(), left._codomain) return NotImplemented def __repr__(self): r""" Return the string representation of this WeierstrassIsomorphism. OUTPUT: (string) The underlying morphism, together with an extra line showing the `(u,r,s,t)` parameters. EXAMPLES:: sage: E1 = EllipticCurve('5077') sage: E2 = E1.change_weierstrass_model([2,3,4,5]) sage: E1.isomorphism_to(E2) Elliptic-curve morphism: From: Elliptic Curve defined by y^2 + y = x^3 - 7*x + 6 over Rational Field To: Elliptic Curve defined by y^2 + 4*x*y + 11/8*y = x^3 - 7/4*x^2 - 3/2*x - 9/32 over Rational Field Via: (u,r,s,t) = (2, 3, 4, 5) """ return EllipticCurveHom.__repr__( self) + "\n Via: (u,r,s,t) = " + baseWI.__repr__(self) # EllipticCurveHom methods def degree(self): """ Return the degree as a rational map of this isomorphism. Isomorphisms always have degree `1` by definition. EXAMPLES:: sage: E1 = EllipticCurve([1,2,3,4,5]) sage: E2 = EllipticCurve_from_j(E1.j_invariant()) sage: E1.isomorphism_to(E2).degree() 1 """ return 1 def rational_maps(self): """ Return the pair of rational maps defining this isomorphism. EXAMPLES:: sage: E1 = EllipticCurve([11,22,33,44,55]) sage: E2 = EllipticCurve_from_j(E1.j_invariant()) sage: iso = E1.isomorphism_to(E2); iso Elliptic-curve morphism: From: Elliptic Curve defined by y^2 + 11*x*y + 33*y = x^3 + 22*x^2 + 44*x + 55 over Rational Field To: Elliptic Curve defined by y^2 + x*y = x^3 + x^2 - 684*x + 6681 over Rational Field Via: (u,r,s,t) = (1, -17, -5, 77) sage: iso.rational_maps() (x + 17, 5*x + y + 8) sage: f = E2.defining_polynomial()(*iso.rational_maps(), 1) sage: I = E1.defining_ideal() sage: x,y,z = I.ring().gens() sage: f in I + Ideal(z-1) True :: sage: E = EllipticCurve(GF(65537), [1,1,1,1,1]) sage: w = E.isomorphism_to(E.short_weierstrass_model()) sage: f,g = w.rational_maps() sage: P = E.random_point() sage: w(P).xy() == (f(P.xy()), g(P.xy())) True TESTS:: sage: iso.rational_maps()[0].parent() Multivariate Polynomial Ring in x, y over Rational Field sage: iso.rational_maps()[1].parent() Multivariate Polynomial Ring in x, y over Rational Field """ return tuple(baseWI.__call__(self, self._mpoly_ring.gens())) def x_rational_map(self): """ Return the `x`-coordinate rational map of this isomorphism. EXAMPLES:: sage: E1 = EllipticCurve([11,22,33,44,55]) sage: E2 = EllipticCurve_from_j(E1.j_invariant()) sage: iso = E1.isomorphism_to(E2); iso Elliptic-curve morphism: From: Elliptic Curve defined by y^2 + 11*x*y + 33*y = x^3 + 22*x^2 + 44*x + 55 over Rational Field To: Elliptic Curve defined by y^2 + x*y = x^3 + x^2 - 684*x + 6681 over Rational Field Via: (u,r,s,t) = (1, -17, -5, 77) sage: iso.x_rational_map() x + 17 sage: iso.x_rational_map() == iso.rational_maps()[0] True TESTS:: sage: iso.x_rational_map().parent() Univariate Polynomial Ring in x over Rational Field """ x, = self._poly_ring.gens() return (x - self.r) / self.u**2 def kernel_polynomial(self): """ Return the kernel polynomial of this isomorphism. Isomorphisms have trivial kernel by definition, hence this method always returns `1`. EXAMPLES:: sage: E1 = EllipticCurve([11,22,33,44,55]) sage: E2 = EllipticCurve_from_j(E1.j_invariant()) sage: iso = E1.isomorphism_to(E2) sage: iso.kernel_polynomial() 1 sage: psi = E1.isogeny(iso.kernel_polynomial(), codomain=E2); psi Isogeny of degree 1 from Elliptic Curve defined by y^2 + 11*x*y + 33*y = x^3 + 22*x^2 + 44*x + 55 over Rational Field to Elliptic Curve defined by y^2 + x*y = x^3 + x^2 - 684*x + 6681 over Rational Field sage: psi in {iso, -iso} True TESTS:: sage: iso.kernel_polynomial().parent() Univariate Polynomial Ring in x over Rational Field """ return self._poly_ring(1) def dual(self): """ Return the dual isogeny of this isomorphism. For isomorphisms, the dual is just the inverse. EXAMPLES:: sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism sage: E = EllipticCurve(QuadraticField(-3), [0,1]) sage: w = WeierstrassIsomorphism(E, (CyclotomicField(3).gen(),0,0,0)) sage: (w.dual() * w).rational_maps() (x, y) :: sage: E1 = EllipticCurve([11,22,33,44,55]) sage: E2 = E1.short_weierstrass_model() sage: iso = E1.isomorphism_to(E2) sage: iso.dual() == ~iso True """ return ~self def __neg__(self): """ Return the negative of this isomorphism, i.e., its composition with the negation map `[-1]`. EXAMPLES:: sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism sage: E = EllipticCurve([11,22,33,44,55]) sage: w = WeierstrassIsomorphism(E, (66,77,88,99)) sage: -w Elliptic-curve morphism: From: Elliptic Curve defined by y^2 + 11*x*y + 33*y = x^3 + 22*x^2 + 44*x + 55 over Rational Field To: Elliptic Curve defined by y^2 + 17/6*x*y + 49/13068*y = x^3 - 769/396*x^2 - 3397/862488*x + 44863/7513995456 over Rational Field Via: (u,r,s,t) = (-66, 77, -99, -979) sage: -(-w) == w True :: sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism sage: E = EllipticCurve(QuadraticField(-3), [0,1]) sage: w = WeierstrassIsomorphism(E, (CyclotomicField(3).gen(),0,0,0)) sage: w.tuple() (zeta3, 0, 0, 0) sage: (-w).tuple() (-zeta3, 0, 0, 0) sage: (-w)^3 == -(w^3) True :: sage: from sage.schemes.elliptic_curves.weierstrass_morphism import WeierstrassIsomorphism sage: E = EllipticCurve(QuadraticField(-1), [1,0]) sage: t = WeierstrassIsomorphism(E, (i,0,0,0)) sage: -t^2 == WeierstrassIsomorphism(E, (1,0,0,0)) True """ a1, _, a3, _, _ = self._domain.a_invariants() w = baseWI(-1, 0, -a1, -a3) urst = baseWI.__mul__(self, w).tuple() return WeierstrassIsomorphism(self._domain, urst, self._codomain)
def _biquadratic_syzygy_quartic(quadratic1, quadratic2, variables=None): r""" Helper function for the Weierstrass form of a biquadratic in $`\mathbb{P}^3$ The invariants and covariants of a quaternary biquadratic satisfy the relation :meth:`sage.rings.invariant_theory.TwoQuaternaryQuadratics.syzygy`, which is (modulo the two quadratic equations) of the form $J^2 = p_4(T, T')$ where * $J$, $T$, $T'$ are the covariants of the biquadratic. * $p_4$ is some quartic polynomial whose coefficients are invariants of the biquadratic. INPUT: See :func:`WeierstrassForm_P3` OUTPUT: A triple consisting of - The quaternary biquadratic as an algebraic form :class:`~sage.rings.invariant_theory.TwoQuaternaryQuadratics` - The binary quartic $p_4$ as a :class:`~sage.rings.invariant_theory.BinaryQuartic` - The dictionary of variable substitutions from the variables of the quartic to the variables of the biquadratic. EXAMPLES:: sage: from sage.schemes.toric.weierstrass_higher import _biquadratic_syzygy_quartic sage: R.<w,x,y,z> = QQ[] sage: _biquadratic_syzygy_quartic(w^2+x^2+y^2, z^2) (Joint quaternary quadratic with coefficients (1, 1, 1, 0, 0, 0, 0, 0, 0, 0) and quaternary quadratic with coefficients (0, 0, 0, 1, 0, 0, 0, 0, 0, 0), Binary quartic with coefficients (0, 0, 0, -1, 0), {aux...}) """ w, x, y, z = _check_polynomials_P3(quadratic1, quadratic2, variables) biquadratic = invariant_theory.quaternary_biquadratic( quadratic1, quadratic2, [w, x, y, z]) # construct auxiliary polynomial ring to work with the rhs of the syzygy R = biquadratic.ring() n = R.ngens() R_aux = PolynomialRing(R.base_ring(), n + 2, 'aux') to_aux = dict() from_aux = dict() for var, var_aux in zip(R.gens(), R_aux.gens()[0:n]): to_aux[var] = var_aux from_aux[var_aux] = var T, T_prime = R_aux.gens()[n:] from_aux[T] = biquadratic.T_covariant() from_aux[T_prime] = biquadratic.T_prime_covariant() # Syzygy is J^2 = syz_rhs + (terms that vanish on the biquadratic) with # J = biquadratic.J_covariant() syz_rhs = T**4 * biquadratic.Delta_invariant().subs(to_aux) \ - T**3*T_prime * biquadratic.Theta_invariant().subs(to_aux) \ + T**2*T_prime**2 * biquadratic.Phi_invariant().subs(to_aux) \ - T*T_prime**3 * biquadratic.Theta_prime_invariant().subs(to_aux) \ + T_prime**4 * biquadratic.Delta_prime_invariant().subs(to_aux) quartic = invariant_theory.binary_quartic(syz_rhs, [T, T_prime]) return (biquadratic, quartic, from_aux)
def segre_embedding(self, PP=None, var='u'): r""" Return the Segre embedding of ``self`` into the appropriate projective space. INPUT: - ``PP`` -- (default: ``None``) ambient image projective space; this is constructed if it is not given. - ``var`` -- string, variable name of the image projective space, default `u` (optional) OUTPUT: Hom -- from ``self`` to the appropriate subscheme of projective space .. TODO:: Cartesian products with more than two components EXAMPLES:: sage: X.<y0,y1,y2,y3,y4,y5> = ProductProjectiveSpaces(ZZ,[2,2]) sage: phi = X.segre_embedding(); phi Scheme morphism: From: Product of projective spaces P^2 x P^2 over Integer Ring To: Closed subscheme of Projective Space of dimension 8 over Integer Ring defined by: -u5*u7 + u4*u8, -u5*u6 + u3*u8, -u4*u6 + u3*u7, -u2*u7 + u1*u8, -u2*u4 + u1*u5, -u2*u6 + u0*u8, -u1*u6 + u0*u7, -u2*u3 + u0*u5, -u1*u3 + u0*u4 Defn: Defined by sending (y0 : y1 : y2 , y3 : y4 : y5) to (y0*y3 : y0*y4 : y0*y5 : y1*y3 : y1*y4 : y1*y5 : y2*y3 : y2*y4 : y2*y5). :: sage: T = ProductProjectiveSpaces([1,2],CC,'z') sage: T.segre_embedding() Scheme morphism: From: Product of projective spaces P^1 x P^2 over Complex Field with 53 bits of precision To: Closed subscheme of Projective Space of dimension 5 over Complex Field with 53 bits of precision defined by: -u2*u4 + u1*u5, -u2*u3 + u0*u5, -u1*u3 + u0*u4 Defn: Defined by sending (z0 : z1 , z2 : z3 : z4) to (z0*z2 : z0*z3 : z0*z4 : z1*z2 : z1*z3 : z1*z4). """ N = self._dims if len(N) > 2: raise NotImplementedError("Cannot have more than two components.") M = (N[0]+1)*(N[1]+1)-1 vars = list(self.coordinate_ring().variable_names()) + [var + str(i) for i in range(M+1)] R = PolynomialRing(self.base_ring(),self.ngens()+M+1, vars, order='lex') #set-up the elimination for the segre embedding mapping = [] k = self.ngens() for i in range(N[0]+1): for j in range(N[0]+1,N[0]+N[1]+2): mapping.append(R.gen(k)-R(self.gen(i)*self.gen(j))) k+=1 #change the defining ideal of the subscheme into the variables I = R.ideal(list(self.defining_polynomials()) + mapping) J = I.groebner_basis() s = set(R.gens()[:self.ngens()]) n = len(J)-1 L = [] while s.isdisjoint(J[n].variables()): L.append(J[n]) n = n-1 #create new subscheme if PP is None: PS = ProjectiveSpace(self.base_ring(),M,R.gens()[self.ngens():]) Y = PS.subscheme(L) else: if PP.dimension_relative()!= M: raise ValueError("Projective Space %s must be dimension %s")%(PP, M) S = PP.coordinate_ring() psi = R.hom([0]*(N[0]+N[1]+2) + list(S.gens()),S) L = [psi(l) for l in L] Y = PP.subscheme(L) #create embedding for points mapping = [] for i in range(N[0]+1): for j in range(N[0]+1,N[0]+N[1]+2): mapping.append(self.gen(i)*self.gen(j)) phi = self.hom(mapping,Y) return phi
def rational_type(f, n=ZZ(3), base_ring=ZZ): r""" Return the basic analytic properties that can be determined directly from the specified rational function ``f`` which is interpreted as a representation of an element of a FormsRing for the Hecke Triangle group with parameter ``n`` and the specified ``base_ring``. In particular the following degree of the generators is assumed: `deg(1) := (0, 1)` `deg(x) := (4/(n-2), 1)` `deg(y) := (2n/(n-2), -1)` `deg(z) := (2, -1)` The meaning of homogeneous elements changes accordingly. INPUT: - ``f`` -- A rational function in ``x,y,z,d`` over ``base_ring``. - ``n`` -- An integer greater or equal to `3` corresponding to the ``HeckeTriangleGroup`` with that parameter (default: `3`). - ``base_ring`` -- The base ring of the corresponding forms ring, resp. polynomial ring (default: ``ZZ``). OUTPUT: A tuple ``(elem, h**o, k, ep, analytic_type)`` describing the basic analytic properties of `f` (with the interpretation indicated above). - ``elem`` -- ``True`` if `f` has a homogeneous denominator. - ``h**o`` -- ``True`` if `f` also has a homogeneous numerator. - ``k`` -- ``None`` if `f` is not homogeneous, otherwise the weight of `f` (which is the first component of its degree). - ``ep`` -- ``None`` if `f` is not homogeneous, otherwise the multiplier of `f` (which is the second component of its degree) - ``analytic_type`` -- The ``AnalyticType`` of `f`. For the zero function the degree `(0, 1)` is choosen. This function is (heavily) used to determine the type of elements and to check if the element really is contained in its parent. EXAMPLES:: sage: from sage.modular.modform_hecketriangle.constructor import rational_type sage: (x,y,z,d) = var("x,y,z,d") sage: rational_type(0, n=4) (True, True, 0, 1, zero) sage: rational_type(1, n=12) (True, True, 0, 1, modular) sage: rational_type(x^3 - y^2) (True, True, 12, 1, cuspidal) sage: rational_type(x * z, n=7) (True, True, 14/5, -1, quasi modular) sage: rational_type(1/(x^3 - y^2) + z/d) (True, False, None, None, quasi weakly holomorphic modular) sage: rational_type(x^3/(x^3 - y^2)) (True, True, 0, 1, weakly holomorphic modular) sage: rational_type(1/(x + z)) (False, False, None, None, None) sage: rational_type(1/x + 1/z) (True, False, None, None, quasi meromorphic modular) sage: rational_type(d/x, n=10) (True, True, -1/2, 1, meromorphic modular) sage: rational_type(1.1 * z * (x^8-y^2), n=8, base_ring=CC) (True, True, 22/3, -1, quasi cuspidal) sage: rational_type(x-y^2, n=infinity) (True, True, 4, 1, modular) sage: rational_type(x*(x-y^2), n=infinity) (True, True, 8, 1, cuspidal) sage: rational_type(1/x, n=infinity) (True, True, -4, 1, weakly holomorphic modular) """ from .analytic_type import AnalyticType AT = AnalyticType() # Determine whether f is zero if (f == 0): # elem, h**o, k, ep, analytic_type return (True, True, QQ(0), ZZ(1), AT([])) analytic_type = AT(["quasi", "mero"]) R = PolynomialRing(base_ring, 'x,y,z,d') F = FractionField(R) (x, y, z, d) = R.gens() R2 = PolynomialRing(PolynomialRing(base_ring, 'd'), 'x,y,z') dhom = R.hom(R2.gens() + (R2.base().gen(), ), R2) f = F(f) num = R(f.numerator()) denom = R(f.denominator()) ep_num = set([ ZZ(1) - 2 * ((sum([g.exponents()[0][m] for m in [1, 2]])) % 2) for g in dhom(num).monomials() ]) ep_denom = set([ ZZ(1) - 2 * ((sum([g.exponents()[0][m] for m in [1, 2]])) % 2) for g in dhom(denom).monomials() ]) if (n == infinity): hom_num = R(num.subs(x=x**4, y=y**2, z=z**2)) hom_denom = R(denom.subs(x=x**4, y=y**2, z=z**2)) else: n = ZZ(n) hom_num = R(num.subs(x=x**4, y=y**(2 * n), z=z**(2 * (n - 2)))) hom_denom = R(denom.subs(x=x**4, y=y**(2 * n), z=z**(2 * (n - 2)))) # Determine whether the denominator of f is homogeneous if (len(ep_denom) == 1 and dhom(hom_denom).is_homogeneous()): elem = True else: # elem, h**o, k, ep, analytic_type return (False, False, None, None, None) # Determine whether f is homogeneous if (len(ep_num) == 1 and dhom(hom_num).is_homogeneous()): h**o = True if (n == infinity): weight = (dhom(hom_num).degree() - dhom(hom_denom).degree()) else: weight = (dhom(hom_num).degree() - dhom(hom_denom).degree()) / (n - 2) ep = ep_num.pop() / ep_denom.pop() # TODO: decompose f (resp. its degrees) into homogeneous parts else: h**o = False weight = None ep = None # Note that we intentionally leave out the d-factor! if (n == infinity): finf_pol = (x - y**2) else: finf_pol = x**n - y**2 # Determine whether f is modular if not ((num.degree(z) > 0) or (denom.degree(z) > 0)): analytic_type = analytic_type.reduce_to("mero") # Determine whether f is holomorphic if (dhom(denom).is_constant()): analytic_type = analytic_type.reduce_to(["quasi", "holo"]) # Determine whether f is cuspidal in the sense that finf divides it... # Bug in singular: finf_pol.divides(1.0) fails over RR if (not dhom(num).is_constant() and finf_pol.divides(num)): if (n != infinity or x.divides(num)): analytic_type = analytic_type.reduce_to(["quasi", "cusp"]) else: # -> Because of a bug with singular in some cases try: while (finf_pol.divides(denom)): # a simple "denom /= finf_pol" is strangely not enough for non-exact rings # and dividing would/may result with an element of the quotient ring of the polynomial ring denom = denom.quo_rem(finf_pol)[0] denom = R(denom) if (n == infinity): while (x.divides(denom)): # a simple "denom /= x" is strangely not enough for non-exact rings # and dividing would/may result with an element of the quotient ring of the polynomial ring denom = denom.quo_rem(x)[0] denom = R(denom) except TypeError: pass # Determine whether f is weakly holomorphic in the sense that at most powers of finf occur in denom if (dhom(denom).is_constant()): analytic_type = analytic_type.reduce_to(["quasi", "weak"]) return (elem, h**o, weight, ep, analytic_type)
class FormsRing_abstract(Parent): r""" Abstract (Hecke) forms ring. This should never be called directly. Instead one should instantiate one of the derived classes of this class. """ from graded_ring_element import FormsRingElement Element = FormsRingElement from analytic_type import AnalyticType AT = AnalyticType() def __init__(self, group, base_ring, red_hom): r""" Abstract (Hecke) forms ring. INPUT: - ``group`` - The Hecke triangle group (default: ``HeckeTriangleGroup(3)``) - ``base_ring`` - The base_ring (default: ``ZZ``). - ``red_hom`` - If True then results of binary operations are considered homogeneous whenever it makes sense (default: False). This is mainly used by the (Hecke) forms. OUTPUT: The corresponding abstract (Hecke) forms ring. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: MR = ModularFormsRing(group=5, base_ring=ZZ, red_hom=True) sage: MR ModularFormsRing(n=5) over Integer Ring sage: MR.group() Hecke triangle group for n = 5 sage: MR.base_ring() Integer Ring sage: MR.has_reduce_hom() True sage: MR.is_homogeneous() False """ from graded_ring import canonical_parameters (group, base_ring, red_hom) = canonical_parameters(group, base_ring, red_hom) if (group == infinity): raise NotImplementedError #if (not group.is_arithmetic() and base_ring.characteristic()>0): # raise NotImplementedError #if (base_ring.characteristic().divides(2*group.n()*(group.n()-2))): # raise NotImplementedError if (base_ring.characteristic() > 0): raise NotImplementedError self._group = group self._red_hom = red_hom self._base_ring = base_ring self._coeff_ring = FractionField(PolynomialRing(base_ring,'d')) self._pol_ring = PolynomialRing(base_ring,'x,y,z,d') self._rat_field = FractionField(self._pol_ring) # default values self._weight = None self._ep = None self._analytic_type = self.AT(["quasi", "mero"]) self.default_prec(10) self.disp_prec(5) self.default_num_prec(53) #super(FormsRing_abstract, self).__init__(self.coeff_ring()) def _repr_(self): r""" Return the string representation of ``self``. EXAMPLES:: sage: from graded_ring import QModularFormsRing sage: QModularFormsRing(group=4) QuasiModularFormsRing(n=4) over Integer Ring """ return "{}FormsRing(n={}) over {}".format(self._analytic_type.analytic_space_name(), self._group.n(), self._base_ring) def _latex_(self): r""" Return the LaTeX representation of ``self``. EXAMPLES:: sage: from graded_ring import QWeakModularFormsRing sage: latex(QWeakModularFormsRing()) \mathcal{ QM^! }_{n=3}(\Bold{Z}) """ from sage.misc.latex import latex return "\\mathcal{{ {} }}_{{n={}}}({})".format(self._analytic_type.latex_space_name(), self._group.n(), latex(self._base_ring)) def _element_constructor_(self, x): r""" Return ``x`` coerced/converted into this forms ring. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: MR = ModularFormsRing() sage: (x,y,z,d) = MR.pol_ring().gens() sage: MR(x^3) f_rho^3 sage: el = MR.Delta().full_reduce() sage: MR(el) f_rho^3*d - f_i^2*d sage: el.parent() == MR False sage: MR(el).parent() == MR True """ from graded_ring_element import FormsRingElement if isinstance(x, FormsRingElement): x = self._rat_field(x._rat) else: x = self._rat_field(x) return self.element_class(self, x) def _coerce_map_from_(self, S): r""" Return whether or not there exists a coercion from ``S`` to ``self``. EXAMPLES:: sage: from graded_ring import QWeakModularFormsRing, ModularFormsRing, CuspFormsRing sage: MR1 = QWeakModularFormsRing(base_ring=CC) sage: MR2 = ModularFormsRing() sage: MR3 = CuspFormsRing() sage: MR3.has_coerce_map_from(MR2) False sage: MR1.has_coerce_map_from(MR2) True sage: MR2.has_coerce_map_from(MR3) True sage: MR3.has_coerce_map_from(ZZ) False sage: MR1.has_coerce_map_from(ZZ) True sage: from space import ModularForms, CuspForms sage: MF2 = ModularForms(k=6, ep=-1) sage: MF3 = CuspForms(k=12, ep=1) sage: MR1.has_coerce_map_from(MF2) True sage: MR2.has_coerce_map_from(MF3) True """ from space import FormsSpace_abstract if ( isinstance(S, FormsRing_abstract)\ and self._group == S._group\ and self._analytic_type >= S._analytic_type\ and self.base_ring().has_coerce_map_from(S.base_ring()) ): return True # TODO: This case never occurs: remove it? elif isinstance(S, FormsSpace_abstract): return self._coerce_map_from_(S.graded_ring()) elif (self.AT("holo") <= self._analytic_type) and (self.coeff_ring().has_coerce_map_from(S)): return True else: return False def _an_element_(self): r""" Return an element of ``self``. EXAMPLES:: sage: from graded_ring import CuspFormsRing sage: from space import WeakModularForms sage: CuspFormsRing().an_element() f_rho^3*d - f_i^2*d sage: CuspFormsRing().an_element() == CuspFormsRing().Delta() True sage: WeakModularForms().an_element() O(q^5) sage: WeakModularForms().an_element() == WeakModularForms().zero() True """ return self(self.Delta()) def default_prec(self, prec = None): r""" Set the default precision ``prec`` for the Fourier expansion. If ``prec=None`` (default) then the current default precision is returned instead. Note: This is also used as the default precision for the Fourier expansion when evaluating forms. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: from space import ModularForms sage: MR = ModularFormsRing() sage: MR.default_prec(3) sage: MR.default_prec() 3 sage: MR.Delta().q_expansion_fixed_d() q - 24*q^2 + O(q^3) sage: MF = ModularForms(k=4) sage: MF.default_prec(2) sage: MF.E4() 1 + 240*q + O(q^2) sage: MF.default_prec() 2 """ if (prec is not None): self._prec = ZZ(prec) else: return self._prec def disp_prec(self, prec = None): r""" Set the maximal display precision to ``prec``. If ``prec="max"`` the precision is set to the default precision. If ``prec=None`` (default) then the current display precision is returned instead. Note: This is used for displaying/representing (elements of) ``self`` as Fourier expansions. EXAMPLES:: sage: from space import ModularForms sage: MF = ModularForms(k=4) sage: MF.default_prec(5) sage: MF.disp_prec(3) sage: MF.disp_prec() 3 sage: MF.E4() 1 + 240*q + 2160*q^2 + O(q^3) sage: MF.disp_prec("max") sage: MF.E4() 1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + O(q^5) """ if (prec == "max"): self._disp_prec = self._prec; elif (prec is not None): self._disp_prec = ZZ(prec) else: return self._disp_prec def default_num_prec(self, prec = None): r""" Set the default numerical precision to ``prec`` (default: ``53``). If ``prec=None`` (default) the current default numerical precision is returned instead. EXAMPLES:: sage: from space import ModularForms sage: MF = ModularForms(k=6) sage: MF.default_prec(20) sage: MF.default_num_prec(10) sage: MF.default_num_prec() 10 sage: E6 = MF.E6() sage: E6(i) # rel tol 1e-4 -0.0020 sage: MF.default_num_prec(100) sage: E6(i) # rel tol 1e-25 0.00000000000000000000000000000 sage: MF = ModularForms(group=5, k=4/3) sage: F_rho = MF.F_rho() sage: F_rho.q_expansion(prec=2)[1] 7/(100*d) sage: MF.default_num_prec(10) sage: F_rho.q_expansion_fixed_d(prec=2)[1] # rel tol 1e-1 9.9 sage: MF.default_num_prec(100) sage: F_rho.q_expansion_fixed_d(prec=2)[1] # rel tol 1e-25 9.9259324351079591527601778294 """ if (prec is not None): self._num_prec = ZZ(prec) else: return self._num_prec def change_ring(self, new_base_ring): r""" Return the same space as ``self`` but over a new base ring ``new_base_ring``. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing().change_ring(CC) ModularFormsRing(n=3) over Complex Field with 53 bits of precision """ return self.__class__.__base__(self._group, new_base_ring, self._red_hom) def graded_ring(self): r""" Return the graded ring containing ``self``. EXAMPLES:: sage: from graded_ring import ModularFormsRing, CuspFormsRing sage: from space import CuspForms sage: MR = ModularFormsRing(group=5) sage: MR.graded_ring() == MR True sage: CF=CuspForms(k=12) sage: CF.graded_ring() == CuspFormsRing() False sage: CF.graded_ring() == CuspFormsRing(red_hom=True) True sage: CF.subspace([CF.Delta()]).graded_ring() == CuspFormsRing(red_hom=True) True """ return self.extend_type(ring=True) def extend_type(self, analytic_type=None, ring=False): r""" Return a new space which contains (elements of) ``self`` with the analytic type of ``self`` extended by ``analytic_type``, possibly extended to a graded ring in case ``ring`` is ``True``. INPUT: - ``analytic_type`` - An ``AnalyticType`` or something which coerces into it (default: ``None``). - ``ring`` - Whether to extend to a graded ring (default: ``False``). OUTPUT: The new extended space. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: from space import CuspForms sage: MR = ModularFormsRing(group=5) sage: MR.extend_type(["quasi", "weak"]) QuasiWeakModularFormsRing(n=5) over Integer Ring sage: CF=CuspForms(k=12) sage: CF.extend_type("holo") ModularForms(n=3, k=12, ep=1) over Integer Ring sage: CF.extend_type("quasi", ring=True) QuasiCuspFormsRing(n=3) over Integer Ring sage: CF.subspace([CF.Delta()]).extend_type() CuspForms(n=3, k=12, ep=1) over Integer Ring """ if analytic_type == None: analytic_type = self._analytic_type else: analytic_type = self._analytic_type.extend_by(analytic_type) if (ring or not self.is_homogeneous()): return FormsRing(analytic_type, group=self.group(), base_ring=self.base_ring(), red_hom=self.has_reduce_hom()) else: return FormsSpace(analytic_type, group=self.group(), base_ring=self.base_ring(), k=self.weight(), ep=self.ep()) def reduce_type(self, analytic_type=None, degree=None): r""" Return a new space with analytic properties shared by both ``self`` and ``analytic_type``, possibly reduced to its homogeneous space of the given ``degree`` (if ``degree`` is set). Elements of the new space are contained in ``self``. INPUT: - ``analytic_type`` - An ``AnalyticType`` or something which coerces into it (default: ``None``). - ``degree`` - ``None`` (default) or the degree of the homogeneous component to which ``self`` should be reduced. OUTPUT: The new reduced space. EXAMPLES:: sage: from graded_ring import QModularFormsRing sage: from space import QModularForms sage: MR = QModularFormsRing() sage: MR.reduce_type(["quasi", "cusp"]) QuasiCuspFormsRing(n=3) over Integer Ring sage: MR.reduce_type("cusp", degree=(12,1)) CuspForms(n=3, k=12, ep=1) over Integer Ring sage: MF=QModularForms(k=6) sage: MF.reduce_type("holo") ModularForms(n=3, k=6, ep=-1) over Integer Ring sage: MF.reduce_type([]) ZeroForms(n=3, k=6, ep=-1) over Integer Ring """ if analytic_type == None: analytic_type = self._analytic_type else: analytic_type = self._analytic_type.reduce_to(analytic_type) if (degree == None and not self.is_homogeneous()): return FormsRing(analytic_type, group=self.group(), base_ring=self.base_ring(), red_hom=self.has_reduce_hom()) elif (degree == None): return FormsSpace(analytic_type, group=self.group(), base_ring=self.base_ring(), k=self.weight(), ep=self.ep()) else: (weight, ep) = degree if (self.is_homogeneous() and (weight != self.weight() or ep!=self.ep())): analytic_type = self._analytic_type.reduce_to([]) return FormsSpace(analytic_type, group=self.group(), base_ring=self.base_ring(), k=weight, ep=ep) def construction(self): r""" Return a functor that constructs ``self`` (used by the coercion machinery). EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing().construction() (ModularFormsRingFunctor(n=3), BaseFacade(Integer Ring)) """ from functors import FormsRingFunctor, BaseFacade return FormsRingFunctor(self._analytic_type, self._group, self._red_hom), BaseFacade(self._base_ring) @cached_method def group(self): r""" Return the (Hecke triangle) group of ``self``. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: MR = ModularFormsRing(group=7) sage: MR.group() Hecke triangle group for n = 7 sage: from space import CuspForms sage: CF = CuspForms(group=7, k=4/5) sage: CF.group() Hecke triangle group for n = 7 """ return self._group @cached_method def hecke_n(self): r""" Return the parameter ``n`` of the (Hecke triangle) group of ``self``. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: MR = ModularFormsRing(group=7) sage: MR.hecke_n() 7 sage: from space import CuspForms sage: CF = CuspForms(group=7, k=4/5) sage: CF.hecke_n() 7 """ return self._group.n() @cached_method def base_ring(self): r""" Return base ring of ``self``. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing().base_ring() Integer Ring sage: from space import CuspForms sage: CuspForms(k=12, base_ring=AA).base_ring() Algebraic Real Field """ return self._base_ring @cached_method def coeff_ring(self): r""" Return coefficient ring of ``self``. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing().coeff_ring() Fraction Field of Univariate Polynomial Ring in d over Integer Ring sage: from space import CuspForms sage: CuspForms(k=12, base_ring=AA).coeff_ring() Fraction Field of Univariate Polynomial Ring in d over Algebraic Real Field """ return self._coeff_ring @cached_method def pol_ring(self): r""" Return the underlying polynomial ring used by ``self``. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing().pol_ring() Multivariate Polynomial Ring in x, y, z, d over Integer Ring sage: from space import CuspForms sage: CuspForms(k=12, base_ring=AA).pol_ring() Multivariate Polynomial Ring in x, y, z, d over Algebraic Real Field """ return self._pol_ring @cached_method def rat_field(self): r""" Return the underlying rational field used by ``self`` to construct/represent elements. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing().rat_field() Fraction Field of Multivariate Polynomial Ring in x, y, z, d over Integer Ring sage: from space import CuspForms sage: CuspForms(k=12, base_ring=AA).rat_field() Fraction Field of Multivariate Polynomial Ring in x, y, z, d over Algebraic Real Field """ return self._rat_field @cached_method def diff_alg(self): r""" Return the algebra of differential operators (over QQ) which is used on rational functions representing elements of ``self``. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing().diff_alg() Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dY*Y: Y*dY + 1, dZ*Z: Z*dZ + 1, dX*X: X*dX + 1} sage: from space import CuspForms sage: CuspForms(k=12, base_ring=AA).diff_alg() Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dY*Y: Y*dY + 1, dZ*Z: Z*dZ + 1, dX*X: X*dX + 1} """ # We only use two operators for now which do not involve 'd', so for performance # reason we choose FractionField(base_ring) instead of self.coeff_ring(). free_alg = FreeAlgebra(FractionField(ZZ),6,'X,Y,Z,dX,dY,dZ') (X,Y,Z,dX,dY,dZ) = free_alg.gens() diff_alg = free_alg.g_algebra({dX*X:1+X*dX,dY*Y:1+Y*dY,dZ*Z:1+Z*dZ}) return diff_alg @cached_method def _derivative_op(self): r""" Return the differential operator in ``self.diff_alg()`` corresponding to the derivative of forms. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing(group=7)._derivative_op() -1/2*X^6*dY - 5/28*X^5*dZ + 1/7*X*Z*dX + 1/2*Y*Z*dY + 5/28*Z^2*dZ - 1/7*Y*dX """ (X,Y,Z,dX,dY,dZ) = self.diff_alg().gens() return 1/self._group.n() * (X*Z-Y)*dX\ + ZZ(1)/ZZ(2) * (Y*Z-X**(self._group.n()-1))*dY\ + (self._group.n()-2) / (4*self._group.n()) * (Z**2-X**(self._group.n()-2))*dZ @cached_method def _serre_derivative_op(self): r""" Return the differential operator in ``self.diff_alg()`` corresponding to the serre derivative of forms. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing(group=8)._serre_derivative_op() -1/2*X^7*dY - 3/16*X^6*dZ - 3/16*Z^2*dZ - 1/8*Y*dX """ (X,Y,Z,dX,dY,dZ) = self.diff_alg().gens() return - 1/self._group.n() * Y*dX\ - ZZ(1)/ZZ(2) * X**(self._group.n()-1)*dY\ - (self._group.n()-2) / (4*self._group.n()) * (Z**2+X**(self._group.n()-2))*dZ @cached_method def has_reduce_hom(self): r""" Return whether the method ``reduce`` should reduce homogeneous elements to the corresponding homogeneous space. This is mainly used by binary operations on homogeneous spaces which temporarily produce an element of ``self`` but want to consider it as a homogeneous element (also see ``reduce``). EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing().has_reduce_hom() False sage: ModularFormsRing(red_hom=True).has_reduce_hom() True sage: from space import ModularForms sage: ModularForms(k=6).has_reduce_hom() True sage: ModularForms(k=6).graded_ring().has_reduce_hom() True """ return self._red_hom def is_homogeneous(self): r""" Return whether ``self`` is homogeneous component. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing().is_homogeneous() False sage: from space import ModularForms sage: ModularForms(k=6).is_homogeneous() True """ return self._weight != None def is_modular(self): r""" Return whether ``self`` only contains modular elements. EXAMPLES:: sage: from graded_ring import QWeakModularFormsRing, CuspFormsRing sage: QWeakModularFormsRing().is_modular() False sage: CuspFormsRing(group=7).is_modular() True sage: from space import QWeakModularForms, CuspForms sage: QWeakModularForms(k=10).is_modular() False sage: CuspForms(group=7, k=12, base_ring=AA).is_modular() True """ return not (self.AT("quasi") <= self._analytic_type) def is_weakly_holomorphic(self): r""" Return whether ``self`` only contains weakly holomorphic modular elements. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, QWeakModularFormsRing, CuspFormsRing sage: QMModularFormsRing().is_weakly_holomorphic() False sage: QWeakModularFormsRing().is_weakly_holomorphic() True sage: from space import MModularForms, CuspForms sage: MModularForms(k=10).is_weakly_holomorphic() False sage: CuspForms(group=7, k=12, base_ring=AA).is_weakly_holomorphic() True """ return (self.AT("weak", "quasi") >= self._analytic_type) def is_holomorphic(self): r""" Return whether ``self`` only contains holomorphic modular elements. EXAMPLES:: sage: from graded_ring import QWeakModularFormsRing, QModularFormsRing sage: QWeakModularFormsRing().is_holomorphic() False sage: QModularFormsRing().is_holomorphic() True sage: from space import WeakModularForms, CuspForms sage: WeakModularForms(k=10).is_holomorphic() False sage: CuspForms(group=7, k=12, base_ring=AA).is_holomorphic() True """ return (self.AT("holo", "quasi") >= self._analytic_type) def is_cuspidal(self): r""" Return whether ``self`` only contains cuspidal elements. EXAMPLES:: sage: from graded_ring import QModularFormsRing, QCuspFormsRing sage: QModularFormsRing().is_cuspidal() False sage: QCuspFormsRing().is_cuspidal() True sage: from space import ModularForms, QCuspForms sage: ModularForms(k=12).is_cuspidal() False sage: QCuspForms(k=12).is_cuspidal() True """ return (self.AT("cusp", "quasi") >= self._analytic_type) def is_zerospace(self): r""" Return whether ``self`` is the (0-dimensional) zero space. EXAMPLES:: sage: from graded_ring import ModularFormsRing sage: ModularFormsRing().is_zerospace() False sage: from space import ModularForms, CuspForms sage: ModularForms(k=12).is_zerospace() False sage: CuspForms(k=12).reduce_type([]).is_zerospace() True """ return (self.AT(["quasi"]) >= self._analytic_type) def analytic_type(self): r""" Return the analytic type of ``self``. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, QWeakModularFormsRing sage: QMModularFormsRing().analytic_type() quasi meromorphic modular sage: QWeakModularFormsRing().analytic_type() quasi weakly holomorphic modular sage: from space import MModularForms, CuspForms sage: MModularForms(k=10).analytic_type() meromorphic modular sage: CuspForms(group=7, k=12, base_ring=AA).analytic_type() cuspidal """ return self._analytic_type def homogeneous_space(self, k, ep): r""" Return the homogeneous component of degree (``k``, ``e``) of ``self``. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, QWeakModularFormsRing sage: QMModularFormsRing(group=7).homogeneous_space(k=2, ep=-1) QuasiMeromorphicModularForms(n=7, k=2, ep=-1) over Integer Ring """ return self.reduce_type(degree = (k,ep)) @cached_method def J_inv(self): r""" Return the J-invariant (Hauptmodul) of the group of ``self``. It is normalized such that ``J_inv(infinity) = infinity``, it has real Fourier coefficients starting with ``d > 0`` and ``J_inv(i) = 1`` It lies in a (weak) extension of the graded ring of ``self``. In case ``has_reduce_hom`` is ``True`` it is given as an element of the corresponding homogeneous space. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, WeakModularFormsRing, CuspFormsRing sage: MR = WeakModularFormsRing(group=7) sage: J_inv = MR.J_inv() sage: J_inv in MR True sage: CuspFormsRing(group=7).J_inv() == J_inv True sage: J_inv f_rho^7/(f_rho^7 - f_i^2) sage: QMModularFormsRing(group=7).J_inv() == QMModularFormsRing(group=7)(J_inv) True sage: from space import WeakModularForms, CuspForms sage: MF = WeakModularForms(group=5, k=0) sage: J_inv = MF.J_inv() sage: J_inv in MF True sage: WeakModularFormsRing(group=5, red_hom=True).J_inv() == J_inv True sage: CuspForms(group=5, k=12).J_inv() == J_inv True sage: MF.disp_prec(3) sage: J_inv d*q^-1 + 79/200 + 42877/(640000*d)*q + 12957/(2000000*d^2)*q^2 + O(q^3) sage: WeakModularForms().J_inv() 1/1728*q^-1 + 31/72 + 1823/16*q + 335840/27*q^2 + 16005555/32*q^3 + 11716352*q^4 + O(q^5) """ (x,y,z,d) = self._pol_ring.gens() return self.extend_type("weak", ring=True)(x**self._group.n()/(x**self._group.n()-y**2)).reduce() @cached_method def j_inv(self): r""" Return the j-invariant (Hauptmodul) of the group of ``self``. It is normalized such that ``j_inv(infinity) = infinity``, and such that it has real Fourier coefficients starting with ``1``. It lies in a (weak) extension of the graded ring of ``self``. In case ``has_reduce_hom`` is ``True`` it is given as an element of the corresponding homogeneous space. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, WeakModularFormsRing, CuspFormsRing sage: MR = WeakModularFormsRing(group=7) sage: j_inv = MR.j_inv() sage: j_inv in MR True sage: CuspFormsRing(group=7).j_inv() == j_inv True sage: j_inv f_rho^7/(f_rho^7*d - f_i^2*d) sage: QMModularFormsRing(group=7).j_inv() == QMModularFormsRing(group=7)(j_inv) True sage: from space import WeakModularForms, CuspForms sage: MF = WeakModularForms(group=5, k=0) sage: j_inv = MF.j_inv() sage: j_inv in MF True sage: WeakModularFormsRing(group=5, red_hom=True).j_inv() == j_inv True sage: CuspForms(group=5, k=12).j_inv() == j_inv True sage: MF.disp_prec(3) sage: j_inv q^-1 + 79/(200*d) + 42877/(640000*d^2)*q + 12957/(2000000*d^3)*q^2 + O(q^3) sage: WeakModularForms().j_inv() q^-1 + 744 + 196884*q + 21493760*q^2 + 864299970*q^3 + 20245856256*q^4 + O(q^5) """ (x,y,z,d) = self._pol_ring.gens() return self.extend_type("weak", ring=True)(1/d*x**self._group.n()/(x**self._group.n()-y**2)).reduce() @cached_method def F_rho(self): r""" Return the generator ``F_rho`` of the graded ring of ``self``. Up to the group action ``F_rho`` has exactly one simple zero at ``rho``. ``F_rho`` is normalized such that its first nontrivial Fourier coefficient is ``1``. The polynomial variable ``x`` exactly corresponds to ``F_rho``. It lies in a (cuspidal) extension of the graded ring of ``self``. In case ``has_reduce_hom`` is ``True`` it is given as an element of the corresponding homogeneous space. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, ModularFormsRing, CuspFormsRing sage: MR = ModularFormsRing(group=7) sage: F_rho = MR.F_rho() sage: F_rho in MR True sage: CuspFormsRing(group=7).F_rho() == F_rho True sage: F_rho f_rho sage: QMModularFormsRing(group=7).F_rho() == QMModularFormsRing(group=7)(F_rho) True sage: from space import ModularForms, CuspForms sage: MF = ModularForms(group=5, k=4/3) sage: F_rho = MF.F_rho() sage: F_rho in MF True sage: ModularFormsRing(group=5, red_hom=True).F_rho() == F_rho True sage: CuspForms(group=5, k=12).F_rho() == F_rho True sage: MF.disp_prec(3) sage: F_rho 1 + 7/(100*d)*q + 21/(160000*d^2)*q^2 + O(q^3) sage: ModularForms(k=4).F_rho() == ModularForms(k=4).E4() True sage: ModularForms(k=4).F_rho() 1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + O(q^5) """ (x,y,z,d) = self._pol_ring.gens() return self.extend_type("holo", ring=True)(x).reduce() @cached_method def F_i(self): r""" Return the generator ``F_i`` of the graded ring of ``self``. Up to the group action ``F_i`` has exactly one simple zero at ``i``. ``F_i`` is normalized such that its first nontrivial Fourier coefficient is ``1``. The polynomial variable ``y`` exactly corresponds to ``F_i``. It lies in a (holomorphic) extension of the graded ring of ``self``. In case ``has_reduce_hom`` is ``True`` it is given as an element of the corresponding homogeneous space. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, ModularFormsRing, CuspFormsRing sage: MR = ModularFormsRing(group=7) sage: F_i = MR.F_i() sage: F_i in MR True sage: CuspFormsRing(group=7).F_i() == F_i True sage: F_i f_i sage: QMModularFormsRing(group=7).F_i() == QMModularFormsRing(group=7)(F_i) True sage: from space import ModularForms, CuspForms sage: MF = ModularForms(group=5, k=10/3) sage: F_i = MF.F_i() sage: F_i in MF True sage: ModularFormsRing(group=5, red_hom=True).F_i() == F_i True sage: CuspForms(group=5, k=12).F_i() == F_i True sage: MF.disp_prec(3) sage: F_i 1 - 13/(40*d)*q - 351/(64000*d^2)*q^2 + O(q^3) sage: ModularForms(k=6).F_i() == ModularForms(k=4).E6() True sage: ModularForms(k=6).F_i() 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 + O(q^5) """ (x,y,z,d) = self._pol_ring.gens() return self.extend_type("holo", ring=True)(y).reduce() @cached_method def F_inf(self): r""" Return the first nontrivial cusp form ``F_inf`` of the graded ring of ``self``. Up to the group action ``F_inf`` has exactly one simple zero at ``infinity``. ``F_inf`` is normalized such that its first nontrivial Fourier coefficient is ``1``. It lies in a (holomorphic) extension of the graded ring of ``self``. In case ``has_reduce_hom`` is ``True`` it is given as an element of the corresponding homogeneous space. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, CuspFormsRing sage: MR = CuspFormsRing(group=7) sage: F_inf = MR.F_inf() sage: F_inf in MR True sage: F_inf f_rho^7*d - f_i^2*d sage: QMModularFormsRing(group=7).F_inf() == QMModularFormsRing(group=7)(F_inf) True sage: from space import CuspForms sage: MF = CuspForms(group=5, k=20/3) sage: F_inf = MF.F_inf() sage: F_inf in MF True sage: CuspFormsRing(group=5, red_hom=True).F_inf() == F_inf True sage: CuspForms(group=5, k=0).F_inf() == F_inf True sage: MF.disp_prec(3) sage: F_inf q - 9/(200*d)*q^2 + O(q^3) sage: CuspForms(k=12).F_inf() == CuspForms(k=12).Delta() True sage: CuspForms(k=12).F_inf() q - 24*q^2 + 252*q^3 - 1472*q^4 + O(q^5) """ (x,y,z,d) = self._pol_ring.gens() return self.extend_type("cusp", ring=True)(d*(x**self._group.n()-y**2)).reduce() @cached_method def G_inv(self): r""" If ``2`` divides ``n``: Return the G-invariant of the group of ``self``. The G-invariant is analogous to the G-invariant but has multiplier ``-1``. I.e. ``G_inv(-1/t) = -G_inv(t)``. It is a holomorphic square root of ``J_inv*(J_inv-1)`` with real Fourier coefficients. If ``2`` does not divide ``n`` the function doesn't exist and an exception is raised. It lies in a (weak) extension of the graded ring of ``self``. In case ``has_reduce_hom`` is ``True`` it is given as an element of the corresponding homogeneous space. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, WeakModularFormsRing, CuspFormsRing sage: MR = WeakModularFormsRing(group=8) sage: G_inv = MR.G_inv() sage: G_inv in MR True sage: CuspFormsRing(group=8).G_inv() == G_inv True sage: G_inv f_rho^4*f_i*d/(f_rho^8 - f_i^2) sage: QMModularFormsRing(group=8).G_inv() == QMModularFormsRing(group=8)(G_inv) True sage: from space import WeakModularForms, CuspForms sage: MF = WeakModularForms(group=8, k=0, ep=-1) sage: G_inv = MF.G_inv() sage: G_inv in MF True sage: WeakModularFormsRing(group=8, red_hom=True).G_inv() == G_inv True sage: CuspForms(group=8, k=12, ep=1).G_inv() == G_inv True sage: MF.disp_prec(3) sage: G_inv d^2*q^-1 - 15*d/128 - 15139/262144*q - 11575/(1572864*d)*q^2 + O(q^3) sage: WeakModularForms(group=4, k=0, ep=-1).G_inv() 1/65536*q^-1 - 3/8192 - 955/16384*q - 49/32*q^2 - 608799/32768*q^3 - 659/4*q^4 + O(q^5) """ if (ZZ(2).divides(self._group.n())): (x,y,z,d) = self._pol_ring.gens() return self.extend_type("weak", ring=True)(d*y*x**(self._group.n()/ZZ(2))/(x**self._group.n()-y**2)).reduce() else: raise Exception("G_inv doesn't exists for n={}.".format(self._group.n())) @cached_method def g_inv(self): r""" If ``2`` divides ``n``: Return the g-invariant of the group of ``self``. The g-invariant is analogous to the j-invariant but has multiplier ``-1``. I.e. ``g_inv(-1/t) = -g_inv(t)``. It is a (normalized) holomorphic square root of ``J_inv*(J_inv-1)``, normalized such that its first nontrivial Fourier coefficient is ``1``. If ``2`` does not divide ``n`` the function doesn't exist and an exception is raised. It lies in a (weak) extension of the graded ring of ``self``. In case ``has_reduce_hom`` is ``True`` it is given as an element of the corresponding homogeneous space. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, WeakModularFormsRing, CuspFormsRing sage: MR = WeakModularFormsRing(group=8) sage: g_inv = MR.g_inv() sage: g_inv in MR True sage: CuspFormsRing(group=8).g_inv() == g_inv True sage: g_inv f_rho^4*f_i/(f_rho^8*d - f_i^2*d) sage: QMModularFormsRing(group=8).g_inv() == QMModularFormsRing(group=8)(g_inv) True sage: from space import WeakModularForms, CuspForms sage: MF = WeakModularForms(group=8, k=0, ep=-1) sage: g_inv = MF.g_inv() sage: g_inv in MF True sage: WeakModularFormsRing(group=8, red_hom=True).g_inv() == g_inv True sage: CuspForms(group=8, k=12, ep=1).g_inv() == g_inv True sage: MF.disp_prec(3) sage: g_inv q^-1 - 15/(128*d) - 15139/(262144*d^2)*q - 11575/(1572864*d^3)*q^2 + O(q^3) sage: WeakModularForms(group=4, k=0, ep=-1).g_inv() q^-1 - 24 - 3820*q - 100352*q^2 - 1217598*q^3 - 10797056*q^4 + O(q^5) """ if (ZZ(2).divides(self._group.n())): (x,y,z,d) = self._pol_ring.gens() return self.extend_type("weak", ring=True)(1/d*y*x**(self._group.n()/ZZ(2))/(x**self._group.n()-y**2)).reduce() else: raise Exception("g_inv doesn't exists for n={}.".format(self._group.n())) @cached_method def E4(self): r""" Return the normalized Eisenstein series of weight ``4`` of the graded ring of ``self``. It is equal to ``F_rho^(n-2)``. It lies in a (holomorphic) extension of the graded ring of ``self``. In case ``has_reduce_hom`` is ``True`` it is given as an element of the corresponding homogeneous space. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, ModularFormsRing, CuspFormsRing sage: MR = ModularFormsRing(group=7) sage: E4 = MR.E4() sage: E4 in MR True sage: CuspFormsRing(group=7).E4() == E4 True sage: E4 f_rho^5 sage: QMModularFormsRing(group=7).E4() == QMModularFormsRing(group=7)(E4) True sage: from space import ModularForms, CuspForms sage: MF = ModularForms(group=5, k=4) sage: E4 = MF.E4() sage: E4 in MF True sage: ModularFormsRing(group=5, red_hom=True).E4() == E4 True sage: CuspForms(group=5, k=12).E4() == E4 True sage: MF.disp_prec(3) sage: E4 1 + 21/(100*d)*q + 483/(32000*d^2)*q^2 + O(q^3) sage: ModularForms(k=4).F_rho() == ModularForms(k=4).E4() True sage: ModularForms(k=4).E4() 1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + O(q^5) """ (x,y,z,d) = self._pol_ring.gens() return self.extend_type("holo", ring=True)(x**(self._group.n()-2)).reduce() @cached_method def E6(self): r""" Return the normalized Eisenstein series of weight ``6`` of the graded ring of ``self``, It is equal to ``F_rho^(n-3) * F_i``. It lies in a (holomorphic) extension of the graded ring of ``self``. In case ``has_reduce_hom`` is ``True`` it is given as an element of the corresponding homogeneous space. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, ModularFormsRing, CuspFormsRing sage: MR = ModularFormsRing(group=7) sage: E6 = MR.E6() sage: E6 in MR True sage: CuspFormsRing(group=7).E6() == E6 True sage: E6 f_rho^4*f_i sage: QMModularFormsRing(group=7).E6() == QMModularFormsRing(group=7)(E6) True sage: from space import ModularForms, CuspForms sage: MF = ModularForms(group=5, k=6) sage: E6 = MF.E6() sage: E6 in MF True sage: ModularFormsRing(group=5, red_hom=True).E6() == E6 True sage: CuspForms(group=5, k=12).E6() == E6 True sage: MF.disp_prec(3) sage: E6 1 - 37/(200*d)*q - 14663/(320000*d^2)*q^2 + O(q^3) sage: ModularForms(k=6).F_i() == ModularForms(k=6).E6() True sage: ModularForms(k=6).E6() 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 + O(q^5) """ (x,y,z,d) = self._pol_ring.gens() return self.extend_type("holo", ring=True)(x**(self._group.n()-3)*y).reduce() @cached_method def Delta(self): r""" Return an analog of the Delta-function of the graded ring of ``self``. It is a cusp form of weight ``12`` and is equal to ``d*(E4^3 - E6^2)`` or (in terms of the generators) ``d*x^(2*n-6)*(x^n - y^2)``. It lies in a (cuspidal) extension of the graded ring of ``self``. In case ``has_reduce_hom`` is ``True`` it is given as an element of the corresponding homogeneous space. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, CuspFormsRing sage: MR = CuspFormsRing(group=7) sage: Delta = MR.Delta() sage: Delta in MR True sage: Delta f_rho^15*d - f_rho^8*f_i^2*d sage: QMModularFormsRing(group=7).Delta() == QMModularFormsRing(group=7)(Delta) True sage: from space import CuspForms, ModularForms sage: MF = CuspForms(group=5, k=12) sage: Delta = MF.Delta() sage: Delta in MF True sage: CuspFormsRing(group=5, red_hom=True).Delta() == Delta True sage: CuspForms(group=5, k=0).Delta() == Delta True sage: MF.disp_prec(3) sage: Delta q + 47/(200*d)*q^2 + O(q^3) sage: d = ModularForms(group=5).coeff_ring().gen() sage: Delta == (d*(ModularForms(group=5).E4()^3-ModularForms(group=5).E6()^2)) True sage: CuspForms(k=12).F_inf() == CuspForms(k=12).Delta() True sage: CuspForms(k=12).Delta() q - 24*q^2 + 252*q^3 - 1472*q^4 + O(q^5) """ (x,y,z,d) = self._pol_ring.gens() return self.extend_type("cusp", ring=True)(d*x**(2*self._group.n()-6)*(x**self._group.n()-y**2)).reduce() @cached_method def E2(self): r""" Return the normalized quasi holomorphic Eisenstein series of weight ``2`` of the graded ring of ``self``. It is also a generator of the graded ring of ``self`` and the polynomial variable ``z`` exactly corresponds to ``E2``. It lies in a (quasi holomorphic) extension of the graded ring of ``self``. In case ``has_reduce_hom`` is ``True`` it is given as an element of the corresponding homogeneous space. EXAMPLES:: sage: from graded_ring import QMModularFormsRing, QModularFormsRing, CuspFormsRing sage: MR = QModularFormsRing(group=7) sage: E2 = MR.E2() sage: E2 in MR True sage: CuspFormsRing(group=7).E2() == E2 True sage: E2 E2 sage: QMModularFormsRing(group=7).E2() == QMModularFormsRing(group=7)(E2) True sage: from space import QModularForms, CuspForms sage: MF = QModularForms(group=5, k=2) sage: E2 = MF.E2() sage: E2 in MF True sage: QModularFormsRing(group=5, red_hom=True).E2() == E2 True sage: CuspForms(group=5, k=12, ep=1).E2() == E2 True sage: MF.disp_prec(3) sage: E2 1 - 9/(200*d)*q - 369/(320000*d^2)*q^2 + O(q^3) sage: F_inf = MF.F_inf() sage: E2 == F_inf.derivative() / F_inf True sage: QModularForms(k=2).E2() 1 - 24*q - 72*q^2 - 96*q^3 - 168*q^4 + O(q^5) """ (x,y,z,d) = self._pol_ring.gens() return self.extend_type(["holo", "quasi"], ring=True)(z).reduce()