# Some Sage 8 code to do calculations with definite quaternion algebras over # Q and compute (trivial weight) quaternionic modular forms and prime level N # # Author: Kimball Martin # Updated: May 2020 # # The following code is not intended to optimize efficiency. # The main functions are eigenbasis(N) and cuspbasis(N) which will compute # quaternionic modular eigenforms phi as vectors of values # (phi(x_1), ..., phi(x_h)) # for a "nice" ordering of x_1, ..., x_h, with respect to the permumation # sigma_N described in my in my congruence mod 2 paper (Can J 2018) or my # paper on zeroes of # quaternionic modular forms with Jordan Wiebe (JNT 2020). # Namely, x_1, ..., x_t will be fixed points of sigma_N, and subsequent # elements will appear in pairs (x_m, x_{m+1}) which are transposed # # The point of this ordering means that all eigenforms with root number -1 # will have zeroes in their first t entries, and for subsequent pairs # (x_m, x_{m+1}) transposed by sigma_N, we will have # phi(x_{m+1}) = w_phi phi(x_m), where w_phi is the root number of phi # # Note: imposing this ordering is rather slow (it takes more than # a couple of seconds for N > 300), so if you want to compute examples for # larger N, you should modify this code to not force the ordering # (my Magma code allows you the option to impose or not impose such an # ordering, but I didn't include this option in the following Sage functions) # compute the class number of definite quaternion algebra over Q # of discrimant D def quatclassno(D): pd = prime_divisors(D) x, y, z = 1, 3, 4 for p in pd: x = x*(p-1) y = y*(1-legendre_symbol(-4,p)) z = z*(1-legendre_symbol(-3,p)) return (x+y+z)/12 # return True/False according to whether Q(sqrt(d)) embeds in the definite # rational algebra of discriminant N # ASSUMES: d is the discriminant of Q(sqrt(d)) def hasembedding(N,d): if d >= 0: return False for p in prime_divisors(N): if kronecker_symbol(d,p) == 1: return False return True # given a vector of algebraic integers, scale so all elements are integral def intmult(v): m = 1 for x in v: m = lcm(m,x.denominator()) return [m*x for x in v] # given an integer matrix T, and a factor m of the charpoly occuring with # multiplicity one, return the # eivenvector associated to a root of m def eigenvec(T,m): K. = NumberField(m) A = Matrix(K,T) - identity_matrix(T.ncols())*a kr = A.transpose().kernel() return kr.basis()[0] # given an n-by-n permutation matrix of order 2, return a vector of length n # consisting of +1's, 0's, and -1's such that the fixed points of W are # 0's and W interchanges +1 and -1's def Wrep(W): n = W.ncols() v = [] for i in xrange(n): j = 0 while W[i][j] != 1: j += 1 if j == i: v.append(0) if j > i: v.append(1) if j < i: v.append(-1) return v # given an n-by-n permutation matrix of order 2, return the associated # permutation as a list, so w[i]=j means W sends i to j def Wperm(W): n = W.ncols() w = [] for i in xrange(n): j = 0 while W[i][j] != 1: j += 1 w.append(j) return w # given a Hecke operator T = Tp permutation of order 2 W, return T with # with respect to a permuted basis e_0, ..., e_{n-1} such that # (i) W fixes e_0,..., e_r and permutes e_{r+2i}, e_{r+2i-1} # (ii) any columns with column sum < p+1 occur at the beginning def changebasis(T,W): n = T.nrows() k = sum(T.row(0)) w = Wperm(W) neword = [] # specify prelim order of new basis for i in xrange(n): if w[i] == i: neword.append(i) for i in xrange(n): if w[i] > i: neword.append(i) neword.append(w[i]) U = matrix(n,{}) for i in xrange(n): U.add_to_entry(neword[i],i,1) T = U.transpose()*T*U front = [] # impose property (ii) for i in xrange(n): if sum(T.column(i)) < k: front.append(i) for i in xrange(len(front)): T.swap_rows(i,front[i]) T.swap_columns(i,front[i]) return T # return a basis of Hecke eigenvectors for quaternionic modular forms # of weight 2 and prime level N # QMFs are given as tuples # (eigenvector in Q(a), minpoly of a, level N, root number, # fixed points of W) # here we order the entries in the eigenvectors so that the fixed points of # W come first (this is slow for large N) def eigenbasis(N): B = BrandtModule(N) qmfs = [] for p in primes(100): T = B.hecke_matrix(p) f = T.charpoly() fact = list(f.factor()) norepfact = True for x in fact: if x[1] > 1: norepfact = False if norepfact: W = B.hecke_matrix(N) r = Wrep(W).count(0) T = changebasis(T,W) W = changebasis(W,W) for x in fact: ev = intmult(eigenvec(T,x[0])) phi = column_matrix(ev) aN = -1 if W*phi == phi: aN = 1 qmfs.append((ev, x[0], N, aN,r)) return qmfs return "No T_p has distinct eigenvalues for p < 100" # return a basis of Hecke eigenvectors for quaternionic cusp forms # of weight 2 and prime level N def cuspbasis(N): qmfs = eigenbasis(N) return qmfs[1:] # return the degree d quaternionic eigencuspforms of weight 2 and prime level N def degdforms(N, d): B = BrandtModule(N) qmfs = [] for p in primes(100): T = B.hecke_matrix(p) f = T.charpoly() fact = list(f.factor()) norepfact = True for x in fact: if x[1] > 1: norepfact = False if norepfact: count = [x[0].degree() for x in fact].count(d) if count > 0: if d > 1 or count > 1: W = B.hecke_matrix(N) w = Wrep(W) for x in fact: if x[0].degree() == d: ev = intmult(eigenvec(T,x[0])) if d == 1: if len(ev) == ev.count(1): continue phi = column_matrix(ev) aN = -1 if W*phi == phi: aN = 1 qmfs.append((ev, x[0],N, aN,w)) return qmfs return "No T_p has distinct eigenvalues for p < 100" # return the integral Hecke cusp eigenforms (attached to ECs) for prime # levels up to X def ecforms(X): ec = dict() for N in primes(11,X+1): S = degdforms(N,1) if len(S) > 0: ec[N] = S return ec