def quadratic_twist(self, D=None): """ Return the quadratic twist of this curve by ``D``. INPUT: - ``D`` (default None) the twisting parameter (see below). In characteristics other than 2, `D` must be nonzero, and the twist is isomorphic to self after adjoining `\sqrt(D)` to the base. In characteristic 2, `D` is arbitrary, and the twist is isomorphic to self after adjoining a root of `x^2+x+D` to the base. In characteristic 2 when `j=0`, this is not implemented. If the base field `F` is finite, `D` need not be specified, and the curve returned is the unique curve (up to isomorphism) defined over `F` isomorphic to the original curve over the quadratic extension of `F` but not over `F` itself. Over infinite fields, an error is raised if `D` is not given. EXAMPLES:: sage: E = EllipticCurve([GF(1103)(1), 0, 0, 107, 340]); E Elliptic Curve defined by y^2 + x*y = x^3 + 107*x + 340 over Finite Field of size 1103 sage: F=E.quadratic_twist(-1); F Elliptic Curve defined by y^2 = x^3 + 1102*x^2 + 609*x + 300 over Finite Field of size 1103 sage: E.is_isomorphic(F) False sage: E.is_isomorphic(F,GF(1103^2,'a')) True A characteristic 2 example:: sage: E=EllipticCurve(GF(2),[1,0,1,1,1]) sage: E1=E.quadratic_twist(1) sage: E.is_isomorphic(E1) False sage: E.is_isomorphic(E1,GF(4,'a')) True Over finite fields, the twisting parameter may be omitted:: sage: k.<a> = GF(2^10) sage: E = EllipticCurve(k,[a^2,a,1,a+1,1]) sage: Et = E.quadratic_twist() sage: Et # random (only determined up to isomorphism) Elliptic Curve defined by y^2 + x*y = x^3 + (a^7+a^4+a^3+a^2+a+1)*x^2 + (a^8+a^6+a^4+1) over Finite Field in a of size 2^10 sage: E.is_isomorphic(Et) False sage: E.j_invariant()==Et.j_invariant() True sage: p=next_prime(10^10) sage: k = GF(p) sage: E = EllipticCurve(k,[1,2,3,4,5]) sage: Et = E.quadratic_twist() sage: Et # random (only determined up to isomorphism) Elliptic Curve defined by y^2 = x^3 + 7860088097*x^2 + 9495240877*x + 3048660957 over Finite Field of size 10000000019 sage: E.is_isomorphic(Et) False sage: k2 = GF(p^2,'a') sage: E.change_ring(k2).is_isomorphic(Et.change_ring(k2)) True """ K=self.base_ring() char=K.characteristic() if D is None: if K.is_finite(): x = rings.polygen(K) if char==2: # We find D such that x^2+x+D is irreducible. If the # degree is odd we can take D=1; otherwise it suffices to # consider odd powers of a generator. D = K(1) if K.degree()%2==0: D = K.gen() a = D**2 while len((x**2+x+D).roots())>0: D *= a else: # We could take a multiplicative generator but # that might be expensive to compute; otherwise # half the elements will do D = K.random_element() while len((x**2-D).roots())>0: D = K.random_element() else: raise ValueError("twisting parameter D must be specified over infinite fields.") else: try: D=K(D) except ValueError: raise ValueError("twisting parameter D must be in the base field.") if char!=2 and D.is_zero(): raise ValueError("twisting parameter D must be nonzero when characteristic is not 2") if char!=2: b2,b4,b6,b8=self.b_invariants() # E is isomorphic to [0,b2,0,8*b4,16*b6] return EllipticCurve(K,[0,b2*D,0,8*b4*D**2,16*b6*D**3]) # now char==2 if self.j_invariant() !=0: # iff a1!=0 a1,a2,a3,a4,a6=self.ainvs() E0=self.change_weierstrass_model(a1,a3/a1,0,(a1**2*a4+a3**2)/a1**3) # which has the form = [1,A2,0,0,A6] assert E0.a1()==K(1) assert E0.a3()==K(0) assert E0.a4()==K(0) return EllipticCurve(K,[1,E0.a2()+D,0,0,E0.a6()]) else: raise ValueError("Quadratic twist not implemented in char 2 when j=0")
def characters(self): r""" Return the two conjugate characters of `K^\times`, where `K` is some quadratic extension of `\QQ_p`, defining this representation. This is fully implemented only in the case where the power of `p` dividing the level of the form is even, in which case `K` is the unique unramified quadratic extension of `\QQ_p`. EXAMPLES: The first example from _[LW11]:: sage: f = Newform('50a') sage: Pi = LocalComponent(f, 5) sage: chars = Pi.characters(); chars [ Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> d, 5 |--> 1, Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -d - 1, 5 |--> 1 ] sage: chars[0].base_ring() Number Field in d with defining polynomial x^2 + x + 1 These characters are interchanged by the Frobenius automorphism of `\mathbb{F}_{25}`:: sage: chars[0] == chars[1]**5 True A more complicated example (higher weight and nontrivial central character):: sage: f = Newforms(GammaH(25, [6]), 3, names='j')[0]; f q + j0*q^2 + 1/3*j0^3*q^3 - 1/3*j0^2*q^4 + O(q^6) sage: Pi = LocalComponent(f, 5) sage: Pi.characters() [ Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> d, 5 |--> 5, Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -d - 1/3*j0^3, 5 |--> 5 ] sage: Pi.characters()[0].base_ring() Number Field in d with defining polynomial x^2 + 1/3*j0^3*x - 1/3*j0^2 over its base field .. warning:: The above output isn't actually the same as in Example 2 of _[LW11], due to an error in the published paper (correction pending) -- the published paper has the inverses of the above characters. A higher level example:: sage: f = Newform('81a', names='j'); f q + j0*q^2 + q^4 - j0*q^5 + O(q^6) sage: LocalComponent(f, 3).characters() # long time (12s on sage.math, 2012) [ Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> -2*d - j0, 4 |--> 1, 3*s + 1 |--> -j0*d - 2, 3 |--> 1, Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> 2*d + j0, 4 |--> 1, 3*s + 1 |--> j0*d + 1, 3 |--> 1 ] In the ramified case, it's not fully implemented, and just returns a string indicating which ramified extension is being considered:: sage: Pi = LocalComponent(Newform('27a'), 3) sage: Pi.characters() 'Character of Q_3(sqrt(-3))' sage: Pi = LocalComponent(Newform('54a'), 3) sage: Pi.characters() 'Character of Q_3(sqrt(3))' """ T = self.type_space() if self.conductor() % 2 == 0: G = SmoothCharacterGroupUnramifiedQuadratic( self.prime(), self.coefficient_field()) n = self.conductor() // 2 g = G.quotient_gen(n) m = g.matrix().change_ring(ZZ).list() tr = (~T.rho(m)).trace() # The inverse is needed here because T is the *homological* type space, # which is dual to the cohomological one that defines the local component. X = polygen(self.coefficient_field()) theta_poly = X**2 - (-1)**n * tr * X + self.central_character()( g.norm()) if theta_poly.is_irreducible(): F = self.coefficient_field().extension(theta_poly, "d") G = G.base_extend(F) chi1, chi2 = [ G.extend_character(n, self.central_character(), x[0]) for x in theta_poly.roots(G.base_ring()) ] # Consistency checks assert chi1.restrict_to_Qp() == chi2.restrict_to_Qp( ) == self.central_character() assert chi1 * chi2 == chi1.parent().compose_with_norm( self.central_character()) return Sequence([chi1, chi2], check=False, cr=True) else: # The ramified case. p = self.prime() if p == 2: # The ramified 2-adic representations aren't classified by admissible pairs. Die. raise NotImplementedError( "Computation with ramified 2-adic representations not implemented" ) if p % 4 == 3: a = ZZ(-1) else: a = ZZ(Zmod(self.prime()).quadratic_nonresidue()) tr1 = (~T.rho([0, 1, a * p, 0])).trace() tr2 = (~T.rho([0, 1, p, 0])).trace() if tr1 == tr2 == 0: # This *can* happen. E.g. if the central character satisfies # chi(-1) = -1, then we have theta(pi) + theta(-pi) = theta(pi) # * (1 + -1) = 0. In this case, one can presumably identify # the character and the extension by some more subtle argument # but I don't know of a good way to automate the process. raise NotImplementedError( "Can't identify ramified quadratic extension -- both traces zero" ) elif tr1 == 0: return "Character of Q_%s(sqrt(%s))" % (p, p) elif tr2 == 0: return "Character of Q_%s(sqrt(%s))" % (p, a * p) else: # At least one of the traces is *always* 0, since the type # space has to be isomorphic to its twist by the (ramified # quadratic) character corresponding to the quadratic # extension. raise RuntimeError("Can't get here!")
def is_quadratic_twist(self, other): r""" Determine whether this curve is a quadratic twist of another. INPUT: - ``other`` -- an elliptic curves with the same base field as self. OUTPUT: Either 0, if the curves are not quadratic twists, or `D` if ``other`` is ``self.quadratic_twist(D)`` (up to isomorphism). If ``self`` and ``other`` are isomorphic, returns 1. If the curves are defined over `\mathbb{Q}`, the output `D` is a squarefree integer. .. note:: Not fully implemented in characteristic 2, or in characteristic 3 when both `j`-invariants are 0. EXAMPLES:: sage: E = EllipticCurve('11a1') sage: Et = E.quadratic_twist(-24) sage: E.is_quadratic_twist(Et) -6 sage: E1=EllipticCurve([0,0,1,0,0]) sage: E1.j_invariant() 0 sage: E2=EllipticCurve([0,0,0,0,2]) sage: E1.is_quadratic_twist(E2) 2 sage: E1.is_quadratic_twist(E1) 1 sage: type(E1.is_quadratic_twist(E1)) == type(E1.is_quadratic_twist(E2)) #trac 6574 True :: sage: E1=EllipticCurve([0,0,0,1,0]) sage: E1.j_invariant() 1728 sage: E2=EllipticCurve([0,0,0,2,0]) sage: E1.is_quadratic_twist(E2) 0 sage: E2=EllipticCurve([0,0,0,25,0]) sage: E1.is_quadratic_twist(E2) 5 :: sage: F = GF(101) sage: E1 = EllipticCurve(F,[4,7]) sage: E2 = E1.quadratic_twist() sage: D = E1.is_quadratic_twist(E2); D!=0 True sage: F = GF(101) sage: E1 = EllipticCurve(F,[4,7]) sage: E2 = E1.quadratic_twist() sage: D = E1.is_quadratic_twist(E2) sage: E1.quadratic_twist(D).is_isomorphic(E2) True sage: E1.is_isomorphic(E2) False sage: F2 = GF(101^2,'a') sage: E1.change_ring(F2).is_isomorphic(E2.change_ring(F2)) True A characteristic 3 example:: sage: F = GF(3^5,'a') sage: E1 = EllipticCurve_from_j(F(1)) sage: E2 = E1.quadratic_twist(-1) sage: D = E1.is_quadratic_twist(E2); D!=0 True sage: E1.quadratic_twist(D).is_isomorphic(E2) True :: sage: E1 = EllipticCurve_from_j(F(0)) sage: E2 = E1.quadratic_twist() sage: D = E1.is_quadratic_twist(E2); D 1 sage: E1.is_isomorphic(E2) True """ from sage.schemes.elliptic_curves.ell_generic import is_EllipticCurve E = self F = other if not is_EllipticCurve(E) or not is_EllipticCurve(F): raise ValueError("arguments are not elliptic curves") K = E.base_ring() zero = K.zero_element() if not K == F.base_ring(): return zero j=E.j_invariant() if j != F.j_invariant(): return zero if E.is_isomorphic(F): if K is rings.QQ: return rings.ZZ(1) return K.one_element() char=K.characteristic() if char==2: raise NotImplementedError("not implemented in characteristic 2") elif char==3: if j==0: raise NotImplementedError("not implemented in characteristic 3 for curves of j-invariant 0") D = E.b2()/F.b2() else: # now char!=2,3: c4E,c6E = E.c_invariants() c4F,c6F = F.c_invariants() if j==0: um = c6E/c6F x=rings.polygen(K) ulist=(x**3-um).roots(multiplicities=False) if len(ulist)==0: D = zero else: D = ulist[0] elif j==1728: um=c4E/c4F x=rings.polygen(K) ulist=(x**2-um).roots(multiplicities=False) if len(ulist)==0: D = zero else: D = ulist[0] else: D = (c6E*c4F)/(c6F*c4E) # Normalization of output: if D.is_zero(): return D if K is rings.QQ: D = D.squarefree_part() assert E.quadratic_twist(D).is_isomorphic(F) return D
def quadratic_twist(self, D=None): """ Return the quadratic twist of this curve by ``D``. INPUT: - ``D`` (default None) the twisting parameter (see below). In characteristics other than 2, `D` must be nonzero, and the twist is isomorphic to self after adjoining `\sqrt(D)` to the base. In characteristic 2, `D` is arbitrary, and the twist is isomorphic to self after adjoining a root of `x^2+x+D` to the base. In characteristic 2 when `j=0`, this is not implemented. If the base field `F` is finite, `D` need not be specified, and the curve returned is the unique curve (up to isomorphism) defined over `F` isomorphic to the original curve over the quadratic extension of `F` but not over `F` itself. Over infinite fields, an error is raised if `D` is not given. EXAMPLES:: sage: E = EllipticCurve([GF(1103)(1), 0, 0, 107, 340]); E Elliptic Curve defined by y^2 + x*y = x^3 + 107*x + 340 over Finite Field of size 1103 sage: F=E.quadratic_twist(-1); F Elliptic Curve defined by y^2 = x^3 + 1102*x^2 + 609*x + 300 over Finite Field of size 1103 sage: E.is_isomorphic(F) False sage: E.is_isomorphic(F,GF(1103^2,'a')) True A characteristic 2 example:: sage: E=EllipticCurve(GF(2),[1,0,1,1,1]) sage: E1=E.quadratic_twist(1) sage: E.is_isomorphic(E1) False sage: E.is_isomorphic(E1,GF(4,'a')) True Over finite fields, the twisting parameter may be omitted:: sage: k.<a> = GF(2^10) sage: E = EllipticCurve(k,[a^2,a,1,a+1,1]) sage: Et = E.quadratic_twist() sage: Et # random (only determined up to isomorphism) Elliptic Curve defined by y^2 + x*y = x^3 + (a^7+a^4+a^3+a^2+a+1)*x^2 + (a^8+a^6+a^4+1) over Finite Field in a of size 2^10 sage: E.is_isomorphic(Et) False sage: E.j_invariant()==Et.j_invariant() True sage: p=next_prime(10^10) sage: k = GF(p) sage: E = EllipticCurve(k,[1,2,3,4,5]) sage: Et = E.quadratic_twist() sage: Et # random (only determined up to isomorphism) Elliptic Curve defined by y^2 = x^3 + 7860088097*x^2 + 9495240877*x + 3048660957 over Finite Field of size 10000000019 sage: E.is_isomorphic(Et) False sage: k2 = GF(p^2,'a') sage: E.change_ring(k2).is_isomorphic(Et.change_ring(k2)) True """ K = self.base_ring() char = K.characteristic() if D is None: if K.is_finite(): x = rings.polygen(K) if char == 2: # We find D such that x^2+x+D is irreducible. If the # degree is odd we can take D=1; otherwise it suffices to # consider odd powers of a generator. D = K(1) if K.degree() % 2 == 0: D = K.gen() a = D**2 while len((x**2 + x + D).roots()) > 0: D *= a else: # We could take a multiplicative generator but # that might be expensive to compute; otherwise # half the elements will do D = K.random_element() while len((x**2 - D).roots()) > 0: D = K.random_element() else: raise ValueError( "twisting parameter D must be specified over infinite fields." ) else: try: D = K(D) except ValueError: raise ValueError( "twisting parameter D must be in the base field.") if char != 2 and D.is_zero(): raise ValueError( "twisting parameter D must be nonzero when characteristic is not 2" ) if char != 2: b2, b4, b6, b8 = self.b_invariants() # E is isomorphic to [0,b2,0,8*b4,16*b6] return EllipticCurve(K, [0, b2 * D, 0, 8 * b4 * D**2, 16 * b6 * D**3]) # now char==2 if self.j_invariant() != 0: # iff a1!=0 a1, a2, a3, a4, a6 = self.ainvs() E0 = self.change_weierstrass_model(a1, a3 / a1, 0, (a1**2 * a4 + a3**2) / a1**3) # which has the form = [1,A2,0,0,A6] assert E0.a1() == K(1) assert E0.a3() == K(0) assert E0.a4() == K(0) return EllipticCurve(K, [1, E0.a2() + D, 0, 0, E0.a6()]) else: raise ValueError( "Quadratic twist not implemented in char 2 when j=0")
def characters(self): r""" Return the two conjugate characters of `K^\times`, where `K` is some quadratic extension of `\QQ_p`, defining this representation. This is fully implemented only in the case where the power of `p` dividing the level of the form is even, in which case `K` is the unique unramified quadratic extension of `\QQ_p`. EXAMPLES: The first example from _[LW11]:: sage: f = Newform('50a') sage: Pi = LocalComponent(f, 5) sage: chars = Pi.characters(); chars [ Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> d, 5 |--> 1, Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -d - 1, 5 |--> 1 ] sage: chars[0].base_ring() Number Field in d with defining polynomial x^2 + x + 1 These characters are interchanged by the Frobenius automorphism of `\mathbb{F}_{25}`:: sage: chars[0] == chars[1]**5 True A more complicated example (higher weight and nontrivial central character):: sage: f = Newforms(GammaH(25, [6]), 3, names='j')[0]; f q + j0*q^2 + 1/3*j0^3*q^3 - 1/3*j0^2*q^4 + O(q^6) sage: Pi = LocalComponent(f, 5) sage: Pi.characters() [ Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> d, 5 |--> 5, Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -d - 1/3*j0^3, 5 |--> 5 ] sage: Pi.characters()[0].base_ring() Number Field in d with defining polynomial x^2 + 1/3*j0^3*x - 1/3*j0^2 over its base field .. warning:: The above output isn't actually the same as in Example 2 of _[LW11], due to an error in the published paper (correction pending) -- the published paper has the inverses of the above characters. A higher level example:: sage: f = Newform('81a', names='j'); f q + j0*q^2 + q^4 - j0*q^5 + O(q^6) sage: LocalComponent(f, 3).characters() [ Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> -2*d - j0, 4 |--> 1, 3*s + 1 |--> -j0*d - 2, 3 |--> 1, Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> 2*d + j0, 4 |--> 1, 3*s + 1 |--> j0*d + 1, 3 |--> 1 ] In the ramified case, it's not fully implemented, and just returns a string indicating which ramified extension is being considered:: sage: Pi = LocalComponent(Newform('27a'), 3) sage: Pi.characters() 'Character of Q_3(sqrt(-3))' sage: Pi = LocalComponent(Newform('54a'), 3) sage: Pi.characters() 'Character of Q_3(sqrt(3))' """ T = self.type_space() if self.conductor() % 2 == 0: G = SmoothCharacterGroupUnramifiedQuadratic(self.prime(), self.coefficient_field()) n = self.conductor() // 2 g = G.quotient_gen(n) m = g.matrix().change_ring(ZZ).list() tr = (~T.rho(m)).trace() # The inverse is needed here because T is the *homological* type space, # which is dual to the cohomological one that defines the local component. X = polygen(self.coefficient_field()) theta_poly = X**2 - (-1)**n*tr*X + self.central_character()(g.norm()) if theta_poly.is_irreducible(): F = self.coefficient_field().extension(theta_poly, "d") G = G.base_extend(F) chi1, chi2 = [G.extend_character(n, self.central_character(), x[0]) for x in theta_poly.roots(G.base_ring())] # Consistency checks assert chi1.restrict_to_Qp() == chi2.restrict_to_Qp() == self.central_character() assert chi1*chi2 == chi1.parent().compose_with_norm(self.central_character()) return Sequence([chi1, chi2], check=False, cr=True) else: # The ramified case. p = self.prime() if p == 2: # The ramified 2-adic representations aren't classified by admissible pairs. Die. raise NotImplementedError( "Computation with ramified 2-adic representations not implemented" ) if p % 4 == 3: a = ZZ(-1) else: a = ZZ(Zmod(self.prime()).quadratic_nonresidue()) tr1 = (~T.rho([0,1,a*p, 0])).trace() tr2 = (~T.rho([0,1,p,0])).trace() if tr1 == tr2 == 0: # This *can* happen. E.g. if the central character satisfies # chi(-1) = -1, then we have theta(pi) + theta(-pi) = theta(pi) # * (1 + -1) = 0. In this case, one can presumably identify # the character and the extension by some more subtle argument # but I don't know of a good way to automate the process. raise NotImplementedError( "Can't identify ramified quadratic extension -- both traces zero" ) elif tr1 == 0: return "Character of Q_%s(sqrt(%s))" % (p, p) elif tr2 == 0: return "Character of Q_%s(sqrt(%s))" % (p, a*p) else: # At least one of the traces is *always* 0, since the type # space has to be isomorphic to its twist by the (ramified # quadratic) character corresponding to the quadratic # extension. raise RuntimeError( "Can't get here!" )
def is_quadratic_twist(self, other): r""" Determine whether this curve is a quadratic twist of another. INPUT: - ``other`` -- an elliptic curves with the same base field as self. OUTPUT: Either 0, if the curves are not quadratic twists, or `D` if ``other`` is ``self.quadratic_twist(D)`` (up to isomorphism). If ``self`` and ``other`` are isomorphic, returns 1. If the curves are defined over `\mathbb{Q}`, the output `D` is a squarefree integer. .. note:: Not fully implemented in characteristic 2, or in characteristic 3 when both `j`-invariants are 0. EXAMPLES:: sage: E = EllipticCurve('11a1') sage: Et = E.quadratic_twist(-24) sage: E.is_quadratic_twist(Et) -6 sage: E1=EllipticCurve([0,0,1,0,0]) sage: E1.j_invariant() 0 sage: E2=EllipticCurve([0,0,0,0,2]) sage: E1.is_quadratic_twist(E2) 2 sage: E1.is_quadratic_twist(E1) 1 sage: type(E1.is_quadratic_twist(E1)) == type(E1.is_quadratic_twist(E2)) #trac 6574 True :: sage: E1=EllipticCurve([0,0,0,1,0]) sage: E1.j_invariant() 1728 sage: E2=EllipticCurve([0,0,0,2,0]) sage: E1.is_quadratic_twist(E2) 0 sage: E2=EllipticCurve([0,0,0,25,0]) sage: E1.is_quadratic_twist(E2) 5 :: sage: F = GF(101) sage: E1 = EllipticCurve(F,[4,7]) sage: E2 = E1.quadratic_twist() sage: D = E1.is_quadratic_twist(E2); D!=0 True sage: F = GF(101) sage: E1 = EllipticCurve(F,[4,7]) sage: E2 = E1.quadratic_twist() sage: D = E1.is_quadratic_twist(E2) sage: E1.quadratic_twist(D).is_isomorphic(E2) True sage: E1.is_isomorphic(E2) False sage: F2 = GF(101^2,'a') sage: E1.change_ring(F2).is_isomorphic(E2.change_ring(F2)) True A characteristic 3 example:: sage: F = GF(3^5,'a') sage: E1 = EllipticCurve_from_j(F(1)) sage: E2 = E1.quadratic_twist(-1) sage: D = E1.is_quadratic_twist(E2); D!=0 True sage: E1.quadratic_twist(D).is_isomorphic(E2) True :: sage: E1 = EllipticCurve_from_j(F(0)) sage: E2 = E1.quadratic_twist() sage: D = E1.is_quadratic_twist(E2); D 1 sage: E1.is_isomorphic(E2) True """ from sage.schemes.elliptic_curves.ell_generic import is_EllipticCurve E = self F = other if not is_EllipticCurve(E) or not is_EllipticCurve(F): raise ValueError("arguments are not elliptic curves") K = E.base_ring() zero = K.zero() if not K == F.base_ring(): return zero j = E.j_invariant() if j != F.j_invariant(): return zero if E.is_isomorphic(F): if K is rings.QQ: return rings.ZZ(1) return K.one() char = K.characteristic() if char == 2: raise NotImplementedError("not implemented in characteristic 2") elif char == 3: if j == 0: raise NotImplementedError( "not implemented in characteristic 3 for curves of j-invariant 0" ) D = E.b2() / F.b2() else: # now char!=2,3: c4E, c6E = E.c_invariants() c4F, c6F = F.c_invariants() if j == 0: um = c6E / c6F x = rings.polygen(K) ulist = (x**3 - um).roots(multiplicities=False) if len(ulist) == 0: D = zero else: D = ulist[0] elif j == 1728: um = c4E / c4F x = rings.polygen(K) ulist = (x**2 - um).roots(multiplicities=False) if len(ulist) == 0: D = zero else: D = ulist[0] else: D = (c6E * c4F) / (c6F * c4E) # Normalization of output: if D.is_zero(): return D if K is rings.QQ: D = D.squarefree_part() assert E.quadratic_twist(D).is_isomorphic(F) return D
def characters(self): r""" Return the two conjugate characters of `K^\times`, where `K` is some quadratic extension of `\QQ_p`, defining this representation. An error will be raised in some 2-adic cases, since not all 2-adic supercuspidal representations arise in this way. EXAMPLES: The first example from [LW2012]_:: sage: f = Newform('50a') sage: Pi = LocalComponent(f, 5) sage: chars = Pi.characters(); chars [ Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -d - 1, 5 |--> 1, Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> d, 5 |--> 1 ] sage: chars[0].base_ring() Number Field in d with defining polynomial x^2 + x + 1 These characters are interchanged by the Frobenius automorphism of `\GF{25}`:: sage: chars[0] == chars[1]**5 True A more complicated example (higher weight and nontrivial central character):: sage: f = Newforms(GammaH(25, [6]), 3, names='j')[0]; f q + j0*q^2 + 1/3*j0^3*q^3 - 1/3*j0^2*q^4 + O(q^6) sage: Pi = LocalComponent(f, 5) sage: Pi.characters() [ Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> 1/3*j0^2*d - 1/3*j0^3, 5 |--> 5, Character of unramified extension Q_5(s)* (s^2 + 4*s + 2 = 0), of level 1, mapping s |--> -1/3*j0^2*d, 5 |--> 5 ] sage: Pi.characters()[0].base_ring() Number Field in d with defining polynomial x^2 - j0*x + 1/3*j0^2 over its base field .. warning:: The above output isn't actually the same as in Example 2 of [LW2012]_, due to an error in the published paper (correction pending) -- the published paper has the inverses of the above characters. A higher level example:: sage: f = Newform('81a', names='j'); f q + j0*q^2 + q^4 - j0*q^5 + O(q^6) sage: LocalComponent(f, 3).characters() # long time (12s on sage.math, 2012) [ Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> -2*d + j0, 4 |--> 1, 3*s + 1 |--> -j0*d + 1, 3 |--> 1, Character of unramified extension Q_3(s)* (s^2 + 2*s + 2 = 0), of level 2, mapping -2*s |--> 2*d - j0, 4 |--> 1, 3*s + 1 |--> j0*d - 2, 3 |--> 1 ] Some ramified examples:: sage: Newform('27a').local_component(3).characters() [ Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> -d, s |--> -1, Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> d - 1, s |--> -1 ] sage: LocalComponent(Newform('54a'), 3, twist_factor=4).characters() [ Character of ramified extension Q_3(s)* (s^2 - 3 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> -1/9*d, s |--> -9, Character of ramified extension Q_3(s)* (s^2 - 3 = 0), of level 2, mapping 2 |--> 1, s + 1 |--> 1/9*d - 1, s |--> -9 ] A 2-adic non-example:: sage: Newform('24a').local_component(2).characters() Traceback (most recent call last): ... ValueError: Totally ramified 2-adic representations are not classified by characters Examples where `K^\times / \QQ_p^\times` is not topologically cyclic (which complicates the computations greatly):: sage: Newforms(DirichletGroup(64, QQ).1, 2, names='a')[0].local_component(2).characters() # long time, random [ Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> 1/2*a0, 4*s + 1 |--> 1, -1 |--> 1, 2 |--> 1, Character of unramified extension Q_2(s)* (s^2 + s + 1 = 0), of level 3, mapping s |--> 1, 2*s + 1 |--> 1/2*a0, 4*s + 1 |--> -1, -1 |--> 1, 2 |--> 1 ] sage: Newform('243a',names='a').local_component(3).characters() # long time [ Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 4, mapping -2*s - 1 |--> -d - 1, 4 |--> 1, 3*s + 1 |--> -d - 1, s |--> 1, Character of ramified extension Q_3(s)* (s^2 - 6 = 0), of level 4, mapping -2*s - 1 |--> d, 4 |--> 1, 3*s + 1 |--> d, s |--> 1 ] """ T = self.type_space() p = self.prime() if self.conductor() % 2 == 0: G = SmoothCharacterGroupUnramifiedQuadratic( self.prime(), self.coefficient_field()) n = self.conductor() // 2 gs = G.quotient_gens(n) g = gs[-1] assert g.valuation(G.ideal(1)) == 0 m = g.matrix().change_ring(ZZ).list() tr = (~T.rho(m)).trace() # The inverse is needed here because T is the *homological* type space, # which is dual to the cohomological one that defines the local component. X = polygen(self.coefficient_field()) theta_poly = X**2 - (-1)**n * tr * X + self.central_character()( g.norm()) verbose("theta_poly for %s is %s" % (g, theta_poly), level=1) if theta_poly.is_irreducible(): F = self.coefficient_field().extension(theta_poly, "d") G = G.base_extend(F) # roots with repetitions allowed gvals = flatten([[y[0]] * y[1] for y in theta_poly.roots(G.base_ring())]) if len(gs) == 1: # This is always the case if p != 2 chi1, chi2 = [ G.extend_character(n, self.central_character(), [x]) for x in gvals ] else: # 2-adic cases, conductor >= 64. Here life is complicated # because the quotient (O_K* / p^n)^* / (image of Z_2^*) is not # cyclic. g0 = gs[0] try: G._reduce_Qp(1, g0) raise ArithmeticError("Bad generators returned") except ValueError: pass tr = (~T.rho(g0.matrix().list())).trace() X = polygen(G.base_ring()) theta0_poly = X**2 - ( -1)**n * tr * X + self.central_character()(g0.norm()) verbose("theta_poly for %s is %s" % (g0, theta_poly), level=1) if theta0_poly.is_irreducible(): F = theta0_poly.base_ring().extension(theta_poly, "e") G = G.base_extend(F) g0vals = flatten([[y[0]] * y[1] for y in theta0_poly.roots(G.base_ring())]) pairA = [[g0vals[0], gvals[0]], [g0vals[1], gvals[1]]] pairB = [[g0vals[0], gvals[1]], [g0vals[1], gvals[0]]] A_fail = 0 B_fail = 0 try: chisA = [ G.extend_character(n, self.central_character(), [y, x]) for (y, x) in pairA ] except ValueError: A_fail = 1 try: chisB = [ G.extend_character(n, self.central_character(), [y, x]) for (y, x) in pairB ] except ValueError: B_fail = 1 if chisA == chisB or chisA == reversed(chisB): # repeated roots -- break symmetry arbitrarily B_fail = 1 # check the character relation from LW12 if (not A_fail and not B_fail): for x in G.ideal(n).invertible_residues(): try: # test if G mod p is in Fp flag = G._reduce_Qp(1, x) except ValueError: flag = None if flag is not None: verbose("skipping x=%s as congruent to %s mod p" % (x, flag)) continue verbose("testing x = %s" % x, level=1) ti = (-1)**n * (~T.rho(x.matrix().list())).trace() verbose(" trace of matrix is %s" % ti, level=1) if ti != chisA[0](x) + chisA[1](x): verbose(" chisA FAILED", level=1) A_fail = 1 break if ti != chisB[0](x) + chisB[1](x): verbose(" chisB FAILED", level=1) B_fail = 1 break else: verbose(" Trace identity check works for both", level=1) if B_fail and not A_fail: chi1, chi2 = chisA elif A_fail and not B_fail: chi1, chi2 = chisB else: raise ValueError( "Something went wrong: can't identify the characters") # Consistency checks assert chi1.restrict_to_Qp() == chi2.restrict_to_Qp( ) == self.central_character() assert chi1 * chi2 == chi1.parent().compose_with_norm( self.central_character()) return Sequence([chi1, chi2], check=False, cr=True) else: # The ramified case. n = self.conductor() - 1 if p == 2: # The ramified 2-adic representations aren't classified by admissible pairs. Die. raise ValueError( "Totally ramified 2-adic representations are not classified by characters" ) G0 = SmoothCharacterGroupRamifiedQuadratic( p, 0, self.coefficient_field()) G1 = SmoothCharacterGroupRamifiedQuadratic( p, 1, self.coefficient_field()) q0 = G0.quotient_gens(n) assert all(x.valuation(G0.ideal(1)) == 1 for x in q0) q1 = G1.quotient_gens(n) assert all(x.valuation(G1.ideal(1)) == 1 for x in q1) t0 = [(~T.rho(q.matrix().list())).trace() for q in q0] t1 = [(~T.rho(q.matrix().list())).trace() for q in q1] if all(x == 0 for x in t0 + t1): # Can't happen? raise NotImplementedError( "Can't identify ramified quadratic extension -- all traces zero" ) elif all(x == 0 for x in t1): G, qs, ts = G0, q0, t0 elif all(x == 0 for x in t0): G, qs, ts = G1, q1, t1 else: # At least one of the traces is *always* 0, since the type # space has to be isomorphic to its twist by the (ramified # quadratic) character corresponding to the quadratic # extension. raise RuntimeError("Can't get here!") q = qs[0] t = ts[0] k = self.newform().weight() t *= p**ZZ((k - 2 + self.twist_factor()) / 2) X = polygen(self.coefficient_field()) theta_poly = X**2 - X * t + self.central_character()(q.norm()) verbose("theta_poly is %s" % theta_poly, level=1) if theta_poly.is_irreducible(): F = self.coefficient_field().extension(theta_poly, "d") G = G.base_extend(F) c1q, c2q = flatten([[x] * e for x, e in theta_poly.roots(G.base_ring())]) if len(qs) == 1: chi1, chi2 = [ G.extend_character(n, self.central_character(), [x]) for x in [c1q, c2q] ] else: assert p == 3 q = qs[1] t = ts[1] t *= p**ZZ((k - 2 + self.twist_factor()) / 2) X = polygen(G.base_ring()) theta_poly = X**2 - X * t + self.central_character()(q.norm()) verbose("theta_poly is %s" % theta_poly, level=1) if theta_poly.is_irreducible(): F = G.base_ring().extension(theta_poly, "e") G = G.base_extend(F) c1q2, c2q2 = flatten( [[x] * e for x, e in theta_poly.roots(G.base_ring())]) pairA = [[c1q, c1q2], [c2q, c2q2]] pairB = [[c1q, c2q2], [c2q, c1q2]] A_fail = 0 B_fail = 0 try: chisA = [ G.extend_character(n, self.central_character(), [x, y]) for (x, y) in pairA ] except ValueError: verbose('A failed to create', level=1) A_fail = 1 try: chisB = [ G.extend_character(n, self.central_character(), [x, y]) for (x, y) in pairB ] except ValueError: verbose('A failed to create', level=1) B_fail = 1 if c1q == c2q or c1q2 == c2q2: B_fail = 1 for u in G.ideal(n).invertible_residues(): if A_fail or B_fail: break x = q * u verbose("testing x = %s" % x, level=1) ti = (~T.rho(x.matrix().list())).trace() * p**ZZ( (k - 2 + self.twist_factor()) / 2) verbose("trace of matrix is %s" % ti, level=1) if chisA[0](x) + chisA[1](x) != ti: A_fail = 1 if chisB[0](x) + chisB[1](x) != ti: B_fail = 1 if B_fail and not A_fail: chi1, chi2 = chisA elif A_fail and not B_fail: chi1, chi2 = chisB else: raise ValueError( "Something went wrong: can't identify the characters") # Consistency checks assert chi1.restrict_to_Qp() == chi2.restrict_to_Qp( ) == self.central_character() assert chi1 * chi2 == chi1.parent().compose_with_norm( self.central_character()) return Sequence([chi1, chi2], check=False, cr=True)
def mcmullen_genus2_prototype(w, h, t, e, rel=0): r""" McMullen prototypes in the stratum H(2). These prototype appear at least in McMullen "Teichmüller curves in genus two: Discriminant and spin" (2004). The notation from that paper are quadruple ``(a, b, c, e)`` which translates in our notation as ``w = b``, ``h = c``, ``t = a`` (and ``e = e``). The associated discriminant is `D = e^2 + 4 wh`. If ``rel`` is a positive parameter (less than w-lambda) the surface belongs to the eigenform locus in H(1,1). EXAMPLES:: sage: from flatsurf import translation_surfaces sage: from surface_dynamics import AbelianStratum sage: prototypes = { ....: 5: [(1,1,0,-1)], ....: 8: [(1,1,0,-2), (2,1,0,0)], ....: 9: [(2,1,0,-1)], ....: 12: [(1,2,0,-2), (2,1,0,-2), (3,1,0,0)], ....: 13: [(1,1,0,-3), (3,1,0,-1), (3,1,0,1)], ....: 16: [(3,1,0,-2), (4,1,0,0)], ....: 17: [(1,2,0,-3), (2,1,0,-3), (2,2,0,-1), (2,2,1,-1), (4,1,0,-1), (4,1,0,1)], ....: 20: [(1,1,0,-4), (2,2,1,-2), (4,1,0,-2), (4,1,0,2)], ....: 21: [(1,3,0,-3), (3,1,0,-3)], ....: 24: [(1,2,0,-4), (2,1,0,-4), (3,2,0,0)], ....: 25: [(2,2,0,-3), (2,2,1,-3), (3,2,0,-1), (4,1,0,-3)]} sage: for D in sorted(prototypes): ....: for w,h,t,e in prototypes[D]: ....: T = translation_surfaces.mcmullen_genus2_prototype(w,h,t,e) ....: assert T.stratum() == AbelianStratum(2) ....: assert (D.is_square() and T.base_ring() is QQ) or (T.base_ring().polynomial().discriminant() == D) An example with some relative homology:: sage: U8 = translation_surfaces.mcmullen_genus2_prototype(2,1,0,0,1/4) # discriminant 8 sage: U12 = translation_surfaces.mcmullen_genus2_prototype(3,1,0,0,3/10) # discriminant 12 sage: U8.stratum() H_2(1^2) sage: U8.base_ring().polynomial().discriminant() 8 sage: U8.j_invariant() ( [4 0] (0), (0), [0 2] ) sage: U12.stratum() H_2(1^2) sage: U12.base_ring().polynomial().discriminant() 12 sage: U12.j_invariant() ( [6 0] (0), (0), [0 2] ) """ w = ZZ(w) h = ZZ(h) t = ZZ(t) e = ZZ(e) g = w.gcd(h) gg = g.gcd(t).gcd(e) if w <= 0 or h <= 0 or t < 0 or t >= g or not g.gcd(t).gcd( e).is_one() or e + h >= w: raise ValueError("invalid parameters") x = polygen(QQ) poly = x**2 - e * x - w * h if poly.is_irreducible(): emb = AA.polynomial_root(poly, RIF(0, w)) K = NumberField(poly, 'l', embedding=emb) l = K.gen() else: K = QQ D = e**2 + 4 * w * h d = D.sqrt() l = (e + d) / 2 rel = K(rel) # (lambda,lambda) square on top # twisted (w,0), (t,h) s = Surface_list(base_ring=K) if rel: if rel < 0 or rel > w - l: raise ValueError("invalid rel argument") s.add_polygon( polygons(vertices=[(0, 0), (l, 0), (l + rel, l), (rel, l)], ring=K)) s.add_polygon( polygons(vertices=[(0, 0), (rel, 0), (rel + l, 0), (w, 0), (w + t, h), (l + rel + t, h), (t + l, h), (t, h)], ring=K)) s.set_edge_pairing(0, 1, 0, 3) s.set_edge_pairing(0, 0, 1, 6) s.set_edge_pairing(0, 2, 1, 1) s.set_edge_pairing(1, 2, 1, 4) s.set_edge_pairing(1, 3, 1, 7) s.set_edge_pairing(1, 0, 1, 5) else: s.add_polygon( polygons(vertices=[(0, 0), (l, 0), (l, l), (0, l)], ring=K)) s.add_polygon( polygons(vertices=[(0, 0), (l, 0), (w, 0), (w + t, h), (l + t, h), (t, h)], ring=K)) s.set_edge_pairing(0, 1, 0, 3) s.set_edge_pairing(0, 0, 1, 4) s.set_edge_pairing(0, 2, 1, 0) s.set_edge_pairing(1, 1, 1, 3) s.set_edge_pairing(1, 2, 1, 5) s.set_immutable() return TranslationSurface(s)
def arnoux_yoccoz(genus): r""" Construct the Arnoux-Yoccoz surface of genus 3 or greater. This presentation of the surface follows Section 2.3 of Joshua P. Bowman's paper "The Complete Family of Arnoux-Yoccoz Surfaces." EXAMPLES:: sage: from flatsurf import * sage: s = translation_surfaces.arnoux_yoccoz(4) sage: TestSuite(s).run() sage: s.is_delaunay_decomposed() True sage: s = s.canonicalize() sage: field=s.base_ring() sage: a = field.gen() sage: from sage.matrix.constructor import Matrix sage: m = Matrix([[a,0],[0,~a]]) sage: ss = m*s sage: ss = ss.canonicalize() sage: s.cmp(ss) == 0 True The Arnoux-Yoccoz pseudo-Anosov are known to have (minimal) invariant foliations with SAF=0:: sage: S3 = translation_surfaces.arnoux_yoccoz(3) sage: Jxx, Jyy, Jxy = S3.j_invariant() sage: Jxx.is_zero() and Jyy.is_zero() True sage: Jxy [ 0 2 0] [ 2 -2 0] [ 0 0 2] sage: S4 = translation_surfaces.arnoux_yoccoz(4) sage: Jxx, Jyy, Jxy = S4.j_invariant() sage: Jxx.is_zero() and Jyy.is_zero() True sage: Jxy [ 0 2 0 0] [ 2 -2 0 0] [ 0 0 2 2] [ 0 0 2 0] """ g = ZZ(genus) assert g >= 3 x = polygen(AA) p = sum([x**i for i in range(1, g + 1)]) - 1 cp = AA.common_polynomial(p) alpha_AA = AA.polynomial_root(cp, RIF(1 / 2, 1)) field = NumberField(alpha_AA.minpoly(), 'alpha', embedding=alpha_AA) a = field.gen() V = VectorSpace(field, 2) p = [None for i in range(g + 1)] q = [None for i in range(g + 1)] p[0] = V(((1 - a**g) / 2, a**2 / (1 - a))) q[0] = V((-a**g / 2, a)) p[1] = V((-(a**(g - 1) + a**g) / 2, (a - a**2 + a**3) / (1 - a))) p[g] = V((1 + (a - a**g) / 2, (3 * a - 1 - a**2) / (1 - a))) for i in range(2, g): p[i] = V(((a - a**i) / (1 - a), a / (1 - a))) for i in range(1, g + 1): q[i] = V(((2 * a - a**i - a**(i + 1)) / (2 * (1 - a)), (a - a**(g - i + 2)) / (1 - a))) P = ConvexPolygons(field) s = Surface_list(field) T = [None] * (2 * g + 1) Tp = [None] * (2 * g + 1) from sage.matrix.constructor import Matrix m = Matrix([[1, 0], [0, -1]]) for i in range(1, g + 1): # T_i is (P_0,Q_i,Q_{i-1}) T[i] = s.add_polygon( P(edges=[q[i] - p[0], q[i - 1] - q[i], p[0] - q[i - 1]])) # T_{g+i} is (P_i,Q_{i-1},Q_{i}) T[g + i] = s.add_polygon( P(edges=[q[i - 1] - p[i], q[i] - q[i - 1], p[i] - q[i]])) # T'_i is (P'_0,Q'_{i-1},Q'_i) Tp[i] = s.add_polygon(m * s.polygon(T[i])) # T'_{g+i} is (P'_i,Q'_i, Q'_{i-1}) Tp[g + i] = s.add_polygon(m * s.polygon(T[g + i])) for i in range(1, g): s.change_edge_gluing(T[i], 0, T[i + 1], 2) s.change_edge_gluing(Tp[i], 2, Tp[i + 1], 0) for i in range(1, g + 1): s.change_edge_gluing(T[i], 1, T[g + i], 1) s.change_edge_gluing(Tp[i], 1, Tp[g + i], 1) #P 0 Q 0 is paired with P' 0 Q' 0, ... s.change_edge_gluing(T[1], 2, Tp[g], 2) s.change_edge_gluing(Tp[1], 0, T[g], 0) # P1Q1 is paired with P'_g Q_{g-1} s.change_edge_gluing(T[g + 1], 2, Tp[2 * g], 2) s.change_edge_gluing(Tp[g + 1], 0, T[2 * g], 0) # P1Q0 is paired with P_{g-1} Q_{g-1} s.change_edge_gluing(T[g + 1], 0, T[2 * g - 1], 2) s.change_edge_gluing(Tp[g + 1], 2, Tp[2 * g - 1], 0) # PgQg is paired with Q1P2 s.change_edge_gluing(T[2 * g], 2, T[g + 2], 0) s.change_edge_gluing(Tp[2 * g], 0, Tp[g + 2], 2) for i in range(2, g - 1): # PiQi is paired with Q'_i P'_{i+1} s.change_edge_gluing(T[g + i], 2, Tp[g + i + 1], 2) s.change_edge_gluing(Tp[g + i], 0, T[g + i + 1], 0) s.set_immutable() return TranslationSurface(s)
def cos_minpoly(n, var='x'): r""" Return the minimal polynomial of 2 cos pi/n Done via [KoRoTr2015]_ Algorithm 3. EXAMPLES:: sage: from flatsurf.geometry.subfield import cos_minpoly sage: cos_minpoly(1) x + 2 sage: cos_minpoly(2) x sage: cos_minpoly(3) x - 1 sage: cos_minpoly(4) x^2 - 2 sage: cos_minpoly(5) x^2 - x - 1 sage: cos_minpoly(6) x^2 - 3 sage: cos_minpoly(8) x^4 - 4*x^2 + 2 sage: cos_minpoly(9) x^3 - 3*x - 1 sage: cos_minpoly(10) x^4 - 5*x^2 + 5 sage: cos_minpoly(90) x^24 - 24*x^22 + 252*x^20 - 1519*x^18 + 5796*x^16 - 14553*x^14 + 24206*x^12 - 26169*x^10 + 17523*x^8 - 6623*x^6 + 1182*x^4 - 72*x^2 + 1 sage: cos_minpoly(148) x^72 - 72*x^70 + 2483*x^68 - 54604*x^66 + 860081*x^64 ... - 3511656*x^6 + 77691*x^4 - 684*x^2 + 1 """ n = ZZ(n) facs = list(n.factor()) if isinstance(var, str): var = polygen(ZZ, var) if not facs: # 2 cos (pi) = -2 return var + 2 if facs[0][0] == 2: # n is even k = facs[0][1] facs.pop(0) else: k = 0 if not facs: # 0. n = 2^k # ([KoRoTr2015] Lemma 12) return chebyshev_T(2**(k-1), var) # 1. Compute M_{n0} = M_{p1 ... ps} # ([KoRoTr2015] Lemma 14 and Lemma 15) M = cos_minpoly_odd_prime(facs[0][0], var) for i in range(1, len(facs)): p = facs[i][0] M, r = M(chebyshev_T(p, var)).quo_rem(M) assert r.is_zero() # 2. Compute M_{2^k p1^{a1} ... ps^{as}} # ([KoRoTr2015] Lemma 12) nn = 2**k * prod(p**(a-1) for p,a in facs) if nn != 1: M = M(chebyshev_T(nn, var)) return M