def element_of_norm(M, O, bound=100): """Finds an element of B with norm M. This corresponds to Step 3 of the algorithm in the notes. Args: M: A sage integer. O: A maximal order in a quaternion algebra. bound: The values 0 <= y, z <= bound will be tried. If no solution is found then the function returns None. Returns: gamma in B such that gamma.reduced_norm() == M or None if there is no solution in the box [0, bound]**2. """ B = O.quaternion_algebra() a, b = B.invariants() i, j, k = B.gens() q, p = -Integer(a), -Integer(b) for y in range(bound + 1): for z in range(bound + 1): r = M - p * (y**2 + q * z**2) if not is_prime(r): # Can replace with easily factorizable. continue # The norm equation is N(x + iy) = x^2 + qy^2. sol = solve_norm_equation(q, r) if sol is not None: t, x = sol gamma = t + x * i + y * j + z * k assert Integer((gamma).reduced_norm()) == M return gamma return None
def solve_ideal_equation(gamma, I, D, N, O): """Find mu_0 in Rj such that (O* gamma / NO)[mu_0] = I / NO. Args: gamma: An element of O. I: A left O-ideal. D: The index [O : R + Rj]. N: The norm of I. Must be prime. O: An order in a rational quaternion algebra containing 1, i, j, k. Returns: mu_0 in Rj such that 0 != gamma * mu_0 in I. """ assert N == I.norm() assert is_prime(N) # d = D*c + N*_ d, c, _ = xgcd(D, N) assert d == 1 a, b = [Integer(x) for x in O.quaternion_algebra().invariants()] F = GF(N) # The suffix _ff means that this element is over a finite field, not the # rationals. B_ff = QuaternionAlgebra(F, a, b) i_ff, j_ff, k_ff = B_ff.gens() # phi is homomorphism O -> (a, b | F) with kernel NO. def phi(alpha): # alpha + NO = alpha_prime + NO. alpha_prime = D * c * alpha t, x, y, z = [ Integer(coeff) for coeff in alpha_prime.coefficient_tuple() ] return t + x * i_ff + y * j_ff + z * k_ff gamma_ff = phi(gamma) gamma_ff_mat = gamma_ff.matrix(action="left") I_basis_ff = [phi(alpha).coefficient_tuple() for alpha in I.basis()] # Only use 2nd and 3rd rows of gamma_ff_mat so solution is in Rj. lin_system = matrix(F, [gamma_ff_mat[2], gamma_ff_mat[3]] + I_basis_ff) sol = lin_system.left_kernel().basis()[0] y, z = sol[0], sol[1] mu_ff = y * j_ff + z * k_ff assert vector(F, (gamma_ff * mu_ff).coefficient_tuple()) in span( I_basis_ff, F) B = O.quaternion_algebra() i, j, k = B.gens() mu_0 = sum( Integer(coeff) * elem for coeff, elem in zip(mu_ff.coefficient_tuple(), [1, i, j, k])) assert 0 != mu_0 assert gamma * mu_0 in I return mu_0
def ell_power_equiv(J, O, ell, print_progress=False): """Solve ell isogeny problem. This function only works in quaternion algebras with prime discriminant p = 3 mod 4. Args: J: A left O-ideal. O: An order in a quaternion algebra. ell: A prime. print_progress: True if you want to print progress. Returns: A pair (J, delta) where J = I*delta, delta is in the quaternion algebra and J is a nonfractional ideal in the same class as I that has ell power norm. """ B = O.quaternion_algebra() if not is_prime(B.discriminant()) or not mod(B.discriminant(), 4) == 3: raise NotImplementedError("The quaternion algebra must have prime" " discrimint p = 3 mod 4.") # When B has discriminant p = 3 mod 4 the call B.maximal_order() always # returns the first maximal order in the cases environment in Lemma 2 of # the paper. I probably shouldn't rely on this. O_special = B.maximal_order() I = connecting_ideal(O_special, O) K = I * J I_1, gamma_1 = special_ell_power_equiv(I, O_special, ell) I_2, gamma_2 = special_ell_power_equiv(K, O_special, ell) gamma = gamma_1.conjugate() * gamma_2 * I.norm() J_2 = J.scale(gamma) assert J_2.left_order() == O assert Integer(J_2.norm()).prime_factors() == [ell] assert [x in O for x in J_2.basis()] return J_2, gamma
def strong_approximation(mu_0, N, O, ell): """Find mu in O with nrd(mu) = ell^e and mu = lambda * mu_0 mod NO Args: mu_0: An element of Rj. N: A prime. O: An order contaning 1, i, j, k. ell: A prime. Returns: mu in O with nrd(mu) = ell^e and mu = lambda * mu_0 mod NO. """ ell = Integer(ell) N = Integer(N) B = O.quaternion_algebra() p = B.discriminant() i, j, k = B.gens() t_0, x_0, y_0, z_0 = mu_0.coefficient_tuple() assert t_0 == x_0 == 0 beta_0 = y_0 + z_0 * i # TODO: gracefully handle the case where # ~mod(p * Integer(beta_0.reduced_norm()), N) does not exist. e = 2 * ceil(log(N**4 * (p + 1) / 2, ell)) + (0 if ( ~mod(p * Integer(beta_0.reduced_norm()), N)).is_square() else 1) e_max = e + 2 * (2 * ceil(log(p, ell)) + 2) # First we solve for lambda. lamb = Integer( (ell**e * ~mod(p * Integer(beta_0.reduced_norm()), N)).sqrt()) assert mod(ell**e, N) == mod(lamb**2 * Integer(mu_0.reduced_norm()), N) mu = None count = 0 count_max = 5 * e while e < e_max: # Then we solve for beta_1. lhs = Integer( (ell**e - p * lamb**2 * Integer(beta_0.reduced_norm())) / N) y_1, z_1 = solve_linear_congruence(2 * Integer(y_0) * p * lamb, p * lamb * 2 * z_0, lhs, N) y_1 = center_around(y_1, -2 * lamb * y_0, N) z_1 = center_around(z_1, -2 * lamb * y_0, N) beta_1 = Integer(y_1) + Integer(z_1) * i assert mod(lhs, N) == mod( p * lamb * (beta_0 * beta_1.conjugate()).reduced_trace(), N) # Now we calculate r. r = Integer( (ell**e - p * (lamb * beta_0 + N * beta_1).reduced_norm()) / N**2) # In the paper they say that r can be the product of a prime and a # smooth square. For simplicity I will just wait for r prime. if not is_prime(r): count += 1 if count > count_max: print("Increasing", e, e_max) e += 2 count_max = 0 continue sol = solve_norm_equation(1, r) if sol is not None: t_1, x_1 = sol alpha_1 = t_1 + x_1 * i mu_1 = alpha_1 + beta_1 * j mu = lamb * mu_0 + N * mu_1 assert mu - lamb * mu_0 in O.left_ideal(O.basis()).scale(N) assert mu.reduced_norm() == ell**e return mu # This is sort of a random heuristic. Can do better. count += 1 if count > count_max: e += 2 count_max = 0 return None
def prime_norm_representative(I, O, D, ell): """ Given an order O and a left O-ideal I return another left O-ideal J in the same class, but with prime norm. This corresponds to Step 1 in the notes. So given an ideal I it returns an ideal in the same class but with reduced norm N where N != ell is a large prime coprime to both D and p, and ell is a quadratic nonresidue module N. Args: I: A left O-ideal. O: An order in a quaternion algebra. D: An integer. ell: A prime. Returns: A pair (J, gamma) where J = I * gamma is a left O-ideal in the same class with prime norm N. N will be coprime to both D and p, and ell will be a nonquadratic residue module N. """ # TODO: Change so O is not an argument. if not is_minkowski_basis(I.basis()): print("Warning: The ideal I does not have a minkowski basis" " precomputed and Sage can not do it for you.") nrd_I = I.norm() B = I.quaternion_algebra() p = B.discriminant() alpha = B(0) normalized_norm = Integer(alpha.reduced_norm() / nrd_I) # Choose random elements in I until one is found with norm N*nrd(I) where N # is prime. m_power = 3 m = Integer(2)**m_power # TODO: Change this to a proper bound. count = 0 while (not is_prime(normalized_norm) or normalized_norm.divides(D) or normalized_norm == ell or normalized_norm == p or mod(ell, normalized_norm).is_square()): # Make a new random element. alpha = random_combination(I.basis(), bound=m) normalized_norm = Integer(alpha.reduced_norm() / nrd_I) # Increase the box we search in if we've been trying for too long. Note # this was just a random heuristic I came up with, it's not in the # paper. count += 1 if count > 4 * m_power: m_power += 1 m = Integer(2)**m_power count = 0 # We now have an element alpha with norm N*nrd(I) where N is prime. The # ideal J = I*gamma has prime norm where gamma = conjugate(alpha) / nrd(I). gamma = alpha.conjugate() / nrd_I J = I.scale(gamma) assert is_prime(Integer(J.norm())) assert not mod(ell, Integer(J.norm())).is_square() assert gcd(Integer(J.norm()), D) == 1 return J, gamma
def hilbert_symbol_negative_at_S(self, S, b, check=True): r""" Returns an integer that has a negative Hilbert symbol with respect to a given rational number and a given set of primes (or places). The function is algorithm 3.4.1 in [Kir2016]_. It finds an integer `a` that has negative Hilbert symbol with respect to a given rational number exactly at a given set of primes (or places). INPUT: - ``S`` -- a list of rational primes, the infinite place as real embedding of `\QQ` or as -1 - ``b`` -- a non-zero rational number which is a non-square locally at every prime in ``S``. - ``check`` -- ``bool`` (default:``True``) perform additional checks on input and confirm the output. OUTPUT: - An integer `a` that has negative Hilbert symbol `(a,b)_p` for every place `p` in `S` and no other place. EXAMPLES:: sage: QQ.hilbert_symbol_negative_at_S([-1,5,3,2,7,11,13,23], -10/7) -9867 sage: QQ.hilbert_symbol_negative_at_S([3, 5, QQ.places()[0], 11], -15) -33 sage: QQ.hilbert_symbol_negative_at_S([3, 5], 2) 15 TESTS:: sage: QQ.hilbert_symbol_negative_at_S(5/2, -2) Traceback (most recent call last): ... TypeError: first argument must be a list or integer :: sage: QQ.hilbert_symbol_negative_at_S([1, 3], 0) Traceback (most recent call last): ... ValueError: second argument must be nonzero :: sage: QQ.hilbert_symbol_negative_at_S([-1, 3, 5], 2) Traceback (most recent call last): ... ValueError: list should be of even cardinality :: sage: QQ.hilbert_symbol_negative_at_S([1, 3], 2) Traceback (most recent call last): ... ValueError: all entries in list must be prime or -1 for infinite place :: sage: QQ.hilbert_symbol_negative_at_S([5, 7], 2) Traceback (most recent call last): ... ValueError: second argument must be a nonsquare with respect to every finite prime in the list :: sage: QQ.hilbert_symbol_negative_at_S([1, 3], sqrt(2)) Traceback (most recent call last): ... TypeError: second argument must be a rational number :: sage: QQ.hilbert_symbol_negative_at_S([-1, 3], 2) Traceback (most recent call last): ... ValueError: if the infinite place is in the list, the second argument must be negative AUTHORS: - Simon Brandhorst, Juanita Duque, Anna Haensch, Manami Roy, Sandi Rudzinski (10-24-2017) """ from sage.rings.finite_rings.finite_field_constructor import FiniteField as GF from sage.rings.padics.factory import Qp from sage.modules.free_module import VectorSpace from sage.matrix.constructor import matrix from sage.sets.primes import Primes from sage.arith.misc import hilbert_symbol, is_prime # input checks if not type(S) is list: raise TypeError("first argument must be a list or integer") # -1 is used for the infinite place infty = -1 for i in range(len(S)): if S[i] == self.places()[0]: S[i] = -1 if not b in self: raise TypeError("second argument must be a rational number") b = self(b) if b == 0: raise ValueError("second argument must be nonzero") if len(S) % 2: raise ValueError("list should be of even cardinality") for p in S: if p != infty: if check and not is_prime(p): raise ValueError("all entries in list must be prime" " or -1 for infinite place") R = Qp(p) if R(b).is_square(): raise ValueError( "second argument must be a nonsquare with" " respect to every finite prime in the list") elif b > 0: raise ValueError("if the infinite place is in the list, " "the second argument must be negative") # L is the list of primes that we need to consider, b must have # nonzero valuation for each prime in L, this is the set S' # in Kirschmer's algorithm L = [] L = [p[0] for p in b.factor() if p[0] not in S] # We must also consider 2 to be in L if 2 not in L and 2 not in S: L.append(2) # This adds the infinite place to L if b < 0 and infty not in S: L.append(infty) P = S + L # This constructs the vector v in the algorithm. This is the vector # that we are searching for. It represents the case when the Hilbert # symbol is negative for all primes in S and positive # at all primes in S' V = VectorSpace(GF(2), len(P)) v = V([1] * len(S) + [0] * len(L)) # Compute the map phi of Hilbert symbols at all the primes # in S and S' # For technical reasons, a Hilbert symbol of -1 is # respresented as 1 and a Hilbert symbol of 1 # is represented as 0 def phi(x): v = [(1 - hilbert_symbol(x, b, p)) // 2 for p in P] return V(v) M = matrix(GF(2), [phi(p) for p in P + [-1]]) # We search through all the primes for q in Primes(): # Only look at this prime if it is not in our list if q in P: continue # The algorithm terminates when the vector v is in the # subspace of V generated by the image of the phi map # on the set of generators w = phi(q) W = M.stack(matrix(w)) if v in W.row_space(): break Pq = P + [-1] + [q] l = W.solve_left(v) a = self.prod([Pq[i]**ZZ(l[i]) for i in range(l.degree())]) if check: assert phi(a) == v, "oops" return a
def euler_factor(self, t, p, cache_p=False): """ Return the Euler factor of the motive `H_t` at prime `p`. INPUT: - `t` -- rational number, not 0 or 1 - `p` -- prime number of good reduction OUTPUT: a polynomial See [Benasque2009]_ for explicit examples of Euler factors. For odd weight, the sign of the functional equation is +1. For even weight, the sign is computed by a recipe found in 11.1 of [Watkins]_. EXAMPLES:: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: H = Hyp(alpha_beta=([1/2]*4,[0]*4)) sage: H.euler_factor(-1, 5) 15625*T^4 + 500*T^3 - 130*T^2 + 4*T + 1 sage: H = Hyp(gamma_list=[-6,-1,4,3]) sage: H.weight(), H.degree() (1, 2) sage: t = 189/125 sage: [H.euler_factor(1/t,p) for p in [11,13,17,19,23,29]] [11*T^2 + 4*T + 1, 13*T^2 + 1, 17*T^2 + 1, 19*T^2 + 1, 23*T^2 + 8*T + 1, 29*T^2 + 2*T + 1] sage: H = Hyp(cyclotomic=([6,2],[1,1,1])) sage: H.weight(), H.degree() (2, 3) sage: [H.euler_factor(1/4,p) for p in [5,7,11,13,17,19]] [125*T^3 + 20*T^2 + 4*T + 1, 343*T^3 - 42*T^2 - 6*T + 1, -1331*T^3 - 22*T^2 + 2*T + 1, -2197*T^3 - 156*T^2 + 12*T + 1, 4913*T^3 + 323*T^2 + 19*T + 1, 6859*T^3 - 57*T^2 - 3*T + 1] sage: H = Hyp(alpha_beta=([1/12,5/12,7/12,11/12],[0,1/2,1/2,1/2])) sage: H.weight(), H.degree() (2, 4) sage: t = -5 sage: [H.euler_factor(1/t,p) for p in [11,13,17,19,23,29]] [-14641*T^4 - 1210*T^3 + 10*T + 1, -28561*T^4 - 2704*T^3 + 16*T + 1, -83521*T^4 - 4046*T^3 + 14*T + 1, 130321*T^4 + 14440*T^3 + 969*T^2 + 40*T + 1, 279841*T^4 - 25392*T^3 + 1242*T^2 - 48*T + 1, 707281*T^4 - 7569*T^3 + 696*T^2 - 9*T + 1] This is an example of higher degree:: sage: H = Hyp(cyclotomic=([11], [7, 12])) sage: H.euler_factor(2, 13) 371293*T^10 - 85683*T^9 + 26364*T^8 + 1352*T^7 - 65*T^6 + 394*T^5 - 5*T^4 + 8*T^3 + 12*T^2 - 3*T + 1 sage: H.euler_factor(2, 19) # long time 2476099*T^10 - 651605*T^9 + 233206*T^8 - 77254*T^7 + 20349*T^6 - 4611*T^5 + 1071*T^4 - 214*T^3 + 34*T^2 - 5*T + 1 TESTS:: sage: H1 = Hyp(alpha_beta=([1,1,1],[1/2,1/2,1/2])) sage: H2 = H1.swap_alpha_beta() sage: H1.euler_factor(-1, 3) 27*T^3 + 3*T^2 + T + 1 sage: H2.euler_factor(-1, 3) 27*T^3 + 3*T^2 + T + 1 sage: H = Hyp(alpha_beta=([0,0,0,1/3,2/3],[1/2,1/5,2/5,3/5,4/5])) sage: H.euler_factor(5,7) 16807*T^5 - 686*T^4 - 105*T^3 - 15*T^2 - 2*T + 1 Check for precision downsampling:: sage: H = Hyp(cyclotomic=[[3],[4]]) sage: H.euler_factor(2, 11, cache_p=True) 11*T^2 - 3*T + 1 sage: H = Hyp(cyclotomic=[[12],[1,2,6]]) sage: H.euler_factor(2, 11, cache_p=True) -T^4 + T^3 - T + 1 REFERENCES: - [Roberts2015]_ - [Watkins]_ """ if t not in QQ or t in [0, 1]: raise ValueError('wrong t') alpha = self._alpha if 0 in alpha: return self._swap.euler_factor(~t, p) if not is_prime(p): raise ValueError('p not prime') if not all(x.denominator() % p for x in self._alpha + self._beta): raise NotImplementedError('p is wild') if (t.valuation(p) or (t - 1).valuation(p) > 0): raise NotImplementedError('p is tame') # now p is good d = self.degree() bound = d // 2 traces = [self.padic_H_value(p, i + 1, t, cache_p=cache_p) for i in range(bound)] w = self.weight() sign = self.sign(t, p) return characteristic_polynomial_from_traces(traces, d, p, w, sign)
def euler_factor(self, t, p, degree=0): """ Return the Euler factor of the motive `H_t` at prime `p`. INPUT: - `t` -- rational number, not 0 or 1 - `p` -- prime number of good reduction - ``degree`` -- optional integer (default 0) OUTPUT: a polynomial See [Benasque2009]_ for explicit examples of Euler factors. For odd weight, the sign of the functional equation is +1. For even weight, the sign is computed by a recipe found in 11.1 of [Watkins]_. EXAMPLES:: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: H = Hyp(alpha_beta=([1/2]*4,[0]*4)) sage: H.euler_factor(-1, 5) 15625*T^4 + 500*T^3 - 130*T^2 + 4*T + 1 sage: H = Hyp(gamma_list=[-6,-1,4,3]) sage: H.weight(), H.degree() (1, 2) sage: t = 189/125 sage: [H.euler_factor(1/t,p) for p in [11,13,17,19,23,29]] [11*T^2 + 4*T + 1, 13*T^2 + 1, 17*T^2 + 1, 19*T^2 + 1, 23*T^2 + 8*T + 1, 29*T^2 + 2*T + 1] sage: H = Hyp(cyclotomic=([6,2],[1,1,1])) sage: H.weight(), H.degree() (2, 3) sage: [H.euler_factor(1/4,p) for p in [5,7,11,13,17,19]] [125*T^3 + 20*T^2 + 4*T + 1, 343*T^3 - 42*T^2 - 6*T + 1, -1331*T^3 - 22*T^2 + 2*T + 1, -2197*T^3 - 156*T^2 + 12*T + 1, 4913*T^3 + 323*T^2 + 19*T + 1, 6859*T^3 - 57*T^2 - 3*T + 1] sage: H = Hyp(alpha_beta=([1/12,5/12,7/12,11/12],[0,1/2,1/2,1/2])) sage: H.weight(), H.degree() (2, 4) sage: t = -5 sage: [H.euler_factor(1/t,p) for p in [11,13,17,19,23,29]] [-14641*T^4 - 1210*T^3 + 10*T + 1, -28561*T^4 - 2704*T^3 + 16*T + 1, -83521*T^4 - 4046*T^3 + 14*T + 1, 130321*T^4 + 14440*T^3 + 969*T^2 + 40*T + 1, 279841*T^4 - 25392*T^3 + 1242*T^2 - 48*T + 1, 707281*T^4 - 7569*T^3 + 696*T^2 - 9*T + 1] TESTS:: sage: H1 = Hyp(alpha_beta=([1,1,1],[1/2,1/2,1/2])) sage: H2 = H1.swap_alpha_beta() sage: H1.euler_factor(-1, 3) 27*T^3 + 3*T^2 + T + 1 sage: H2.euler_factor(-1, 3) 27*T^3 + 3*T^2 + T + 1 sage: H = Hyp(alpha_beta=([0,0,0,1/3,2/3],[1/2,1/5,2/5,3/5,4/5])) sage: H.euler_factor(5,7) 16807*T^5 - 686*T^4 - 105*T^3 - 15*T^2 - 2*T + 1 REFERENCES: - [Roberts2015]_ - [Watkins]_ """ alpha = self._alpha if 0 in alpha: H = self.swap_alpha_beta() return(H.euler_factor(~t, p, degree)) if t not in QQ or t in [0, 1]: raise ValueError('wrong t') if not is_prime(p): raise ValueError('p not prime') if not all(x.denominator() % p for x in self._alpha + self._beta): raise NotImplementedError('p is wild') if (t.valuation(p) or (t - 1).valuation(p) > 0): raise NotImplementedError('p is tame') # now p is good if degree == 0: d = self.degree() bound = d // 2 traces = [self.padic_H_value(p, i + 1, t) for i in range(bound)] w = self.weight() if w % 2: # sign is always +1 for odd weight sign = 1 elif d % 2: sign = -kronecker_symbol((1 - t) * self._sign_param, p) else: sign = kronecker_symbol(t * (t - 1) * self._sign_param, p) return characteristic_polynomial_from_traces(traces, d, p, w, sign)
def euler_factor(self, t, p, degree=0): """ Return the Euler factor of the motive `H_t` at prime `p`. INPUT: - `t` -- rational number, not 0 or 1 - `p` -- prime number of good reduction - ``degree`` -- optional integer (default 0) OUTPUT: a polynomial See [Benasque2009]_ for explicit examples of Euler factors. For odd weight, the sign of the functional equation is +1. For even weight, the sign is computed by a recipe found in 11.1 of [Watkins]_. EXAMPLES:: sage: from sage.modular.hypergeometric_motive import HypergeometricData as Hyp sage: H = Hyp(alpha_beta=([1/2]*4,[0]*4)) sage: H.euler_factor(-1, 5) 15625*T^4 + 500*T^3 - 130*T^2 + 4*T + 1 sage: H = Hyp(gamma_list=[-6,-1,4,3]) sage: H.weight(), H.degree() (1, 2) sage: t = 189/125 sage: [H.euler_factor(1/t,p) for p in [11,13,17,19,23,29]] [11*T^2 + 4*T + 1, 13*T^2 + 1, 17*T^2 + 1, 19*T^2 + 1, 23*T^2 + 8*T + 1, 29*T^2 + 2*T + 1] sage: H = Hyp(cyclotomic=([6,2],[1,1,1])) sage: H.weight(), H.degree() (2, 3) sage: [H.euler_factor(1/4,p) for p in [5,7,11,13,17,19]] [125*T^3 + 20*T^2 + 4*T + 1, 343*T^3 - 42*T^2 - 6*T + 1, -1331*T^3 - 22*T^2 + 2*T + 1, -2197*T^3 - 156*T^2 + 12*T + 1, 4913*T^3 + 323*T^2 + 19*T + 1, 6859*T^3 - 57*T^2 - 3*T + 1] sage: H = Hyp(alpha_beta=([1/12,5/12,7/12,11/12],[0,1/2,1/2,1/2])) sage: H.weight(), H.degree() (2, 4) sage: t = -5 sage: [H.euler_factor(1/t,p) for p in [11,13,17,19,23,29]] [-14641*T^4 - 1210*T^3 + 10*T + 1, -28561*T^4 - 2704*T^3 + 16*T + 1, -83521*T^4 - 4046*T^3 + 14*T + 1, 130321*T^4 + 14440*T^3 + 969*T^2 + 40*T + 1, 279841*T^4 - 25392*T^3 + 1242*T^2 - 48*T + 1, 707281*T^4 - 7569*T^3 + 696*T^2 - 9*T + 1] TESTS:: sage: H1 = Hyp(alpha_beta=([1,1,1],[1/2,1/2,1/2])) sage: H2 = H1.swap_alpha_beta() sage: H1.euler_factor(-1, 3) 27*T^3 + 3*T^2 + T + 1 sage: H2.euler_factor(-1, 3) 27*T^3 + 3*T^2 + T + 1 sage: H = Hyp(alpha_beta=([0,0,0,1/3,2/3],[1/2,1/5,2/5,3/5,4/5])) sage: H.euler_factor(5,7) 16807*T^5 - 686*T^4 - 105*T^3 - 15*T^2 - 2*T + 1 REFERENCES: - [Roberts2015]_ - [Watkins]_ """ alpha = self._alpha if 0 in alpha: H = self.swap_alpha_beta() return (H.euler_factor(~t, p, degree)) if t not in QQ or t in [0, 1]: raise ValueError('wrong t') if not is_prime(p): raise ValueError('p not prime') if not all(x.denominator() % p for x in self._alpha + self._beta): raise NotImplementedError('p is wild') if (t.valuation(p) or (t - 1).valuation(p) > 0): raise NotImplementedError('p is tame') # now p is good if degree == 0: d = self.degree() bound = d // 2 traces = [self.padic_H_value(p, i + 1, t) for i in range(bound)] w = self.weight() if w % 2: # sign is always +1 for odd weight sign = 1 elif d % 2: sign = -kronecker_symbol((1 - t) * self._sign_param, p) else: sign = kronecker_symbol(t * (t - 1) * self._sign_param, p) return characteristic_polynomial_from_traces(traces, d, p, w, sign)