Path: blob/master/sage/quadratic_forms/quadratic_form__theta.py
4097 views
"""1Theta Series of Quadratic Forms23AUTHORS:45- Jonathan Hanke: initial code, theta series of degree 167- Gonzalo Tornaria (2009-02-22): fixes and doctests89- Gonzalo Tornaria (2010-03-23): theta series of degree 21011"""1213from copy import deepcopy1415from sage.rings.real_mpfr import RealField16from sage.rings.power_series_ring import PowerSeriesRing17from sage.rings.integer_ring import ZZ18from sage.functions.all import sqrt, floor, ceil19202122from sage.misc.misc import cputime, verbose232425def theta_series(self, Max=10, var_str='q', safe_flag=True):26"""27Compute the theta series as a power series in the variable given28in var_str (which defaults to '`q`'), up to the specified precision29`O(q^max)`.3031This uses the PARI/GP function qfrep, wrapped by the32theta_by_pari() method. This caches the result for future33computations.3435The safe_flag allows us to select whether we want a copy of the36output, or the original output. It is only meaningful when a37vector is returned, otherwise a copy is automatically made in38creating the power series. By default safe_flag = True, so we39return a copy of the cached information. If this is set to False,40then the routine is much faster but the return values are41vulnerable to being corrupted by the user.4243TO DO: Allow the option Max='mod_form' to give enough coefficients44to ensure we determine the theta series as a modular form. This45is related to the Sturm bound, but we'll need to be careful about46this (particularly for half-integral weights!).4748EXAMPLES::4950sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])51sage: Q.theta_series()521 + 2*q + 2*q^3 + 6*q^4 + 2*q^5 + 4*q^6 + 6*q^7 + 8*q^8 + 14*q^9 + O(q^10)5354sage: Q.theta_series(25)551 + 2*q + 2*q^3 + 6*q^4 + 2*q^5 + 4*q^6 + 6*q^7 + 8*q^8 + 14*q^9 + 4*q^10 + 12*q^11 + 18*q^12 + 12*q^13 + 12*q^14 + 8*q^15 + 34*q^16 + 12*q^17 + 8*q^18 + 32*q^19 + 10*q^20 + 28*q^21 + 16*q^23 + 44*q^24 + O(q^25)5657"""58## Sanity Check: Max is an integer or an allowed string:59try:60M = ZZ(Max)61except:62M = -16364if (Max not in ['mod_form']) and (not M >= 0):65print Max66raise TypeError, "Oops! Max is not an integer >= 0 or an allowed string."6768if Max == 'mod_form':69raise NotImplementedError, "Oops! We have to figure out the correct number of Fourier coefficients to use..."70#return self.theta_by_pari(sturm_bound(self.level(), self.dim() / ZZ(2)) + 1, var_str, safe_flag)71else:72return self.theta_by_pari(M, var_str, safe_flag)73747576## ------------- Compute the theta function by using the PARI/GP routine qfrep ------------7778def theta_by_pari(self, Max, var_str='q', safe_flag=True):79"""80Use PARI/GP to compute the theta function as a power series (or81vector) up to the precision `O(q^Max)`. This also caches the result82for future computations.8384If var_str = '', then we return a vector `v` where `v[i]` counts the85number of vectors of length `i`.8687The safe_flag allows us to select whether we want a copy of the88output, or the original output. It is only meaningful when a89vector is returned, otherwise a copy is automatically made in90creating the power series. By default safe_flag = True, so we91return a copy of the cached information. If this is set to False,92then the routine is much faster but the return values are93vulnerable to being corrupted by the user.949596INPUT:97Max -- an integer >=098var_str -- a string99100OUTPUT:101a power series or a vector102103EXAMPLES::104105sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1])106sage: Prec = 100107sage: compute = Q.theta_by_pari(Prec, '')108sage: exact = [1] + [8 * sum([d for d in divisors(i) if d % 4 != 0]) for i in range(1, Prec)]109sage: compute == exact110True111112"""113## Try to use the cached result if it's enough precision114if hasattr(self, '__theta_vec') and len(self.__theta_vec) >= Max:115theta_vec = self.__theta_vec[:Max]116else:117theta_vec = self.representation_number_list(Max)118## Cache the theta vector119self.__theta_vec = theta_vec120121## Return the answer122if var_str == '':123if safe_flag:124return deepcopy(theta_vec) ## We must make a copy here to insure the integrity of the cached version!125else:126return theta_vec127else:128return PowerSeriesRing(ZZ, var_str)(theta_vec, Max)129130131132## ------------- Compute the theta function by using an explicit Cholesky decomposition ------------133134135##########################################################################136## Routines to compute the Fourier expansion of the theta function of Q ##137## (to a given precision) via a Cholesky decomposition over RR. ##138## ##139## The Cholesky code was taken from: ##140## ~/Documents/290_Project/C/Ver13.2__3-5-2007/Matrix_mpz/Matrix_mpz.cc ##141##########################################################################142143144145def theta_by_cholesky(self, q_prec):146r"""147Uses the real Cholesky decomposition to compute (the `q`-expansion of) the148theta function of the quadratic form as a power series in `q` with terms149correct up to the power `q^{\text{q\_prec}}`. (So its error is `O(q^150{\text{q\_prec} + 1})`.)151152REFERENCE:153From Cohen's "A Course in Computational Algebraic Number Theory" book,154p 102.155156EXAMPLES::157158## Check the sum of 4 squares form against Jacobi's formula159sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1])160sage: Theta = Q.theta_by_cholesky(10)161sage: Theta1621 + 8*q + 24*q^2 + 32*q^3 + 24*q^4 + 48*q^5 + 96*q^6 + 64*q^7 + 24*q^8 + 104*q^9 + 144*q^10163sage: Expected = [1] + [8*sum([d for d in divisors(n) if d%4 != 0]) for n in range(1,11)]164sage: Expected165[1, 8, 24, 32, 24, 48, 96, 64, 24, 104, 144]166sage: Theta.list() == Expected167True168169::170171## Check the form x^2 + 3y^2 + 5z^2 + 7w^2 represents everything except 2 and 22.172sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])173sage: Theta = Q.theta_by_cholesky(50)174sage: Theta_list = Theta.list()175sage: [m for m in range(len(Theta_list)) if Theta_list[m] == 0]176[2, 22]177178"""179## RAISE AN ERROR -- This routine is deprecated!180#raise NotImplementedError, "This routine is deprecated. Try theta_series(), which uses theta_by_pari()."181182183n = self.dim()184theta = [0 for i in range(q_prec+1)]185PS = PowerSeriesRing(ZZ, 'q')186187bit_prec = 53 ## TO DO: Set this precision to reflect the appropriate roundoff188Cholesky = self.cholesky_decomposition(bit_prec) ## error estimate, to be confident through our desired q-precision.189Q = Cholesky ## <---- REDUNDANT!!!190R = RealField(bit_prec)191half = R(0.5)192193194195## 1. Initialize196i = n - 1197T = [R(0) for j in range(n)]198U = [R(0) for j in range(n)]199T[i] = R(q_prec)200U[i] = 0201L = [0 for j in range (n)]202x = [0 for j in range (n)]203204205## 2. Compute bounds206#Z = sqrt(T[i] / Q[i,i]) ## IMPORTANT NOTE: sqrt preserves the precision of the real number it's given... which is not so good... =|207#L[i] = floor(Z - U[i]) ## Note: This is a Sage Integer208#x[i] = ceil(-Z - U[i]) - 1 ## Note: This is a Sage Integer too209210211done_flag = False212from_step4_flag = False213from_step3_flag = True ## We start by pretending this, since then we get to run through 2 and 3a once. =)214215#double Q_val_double;216#unsigned long Q_val; // WARNING: Still need a good way of checking overflow for this value...217218219220## Big loop which runs through all vectors221while (done_flag == False):222223## Loop through until we get to i=1 (so we defined a vector x)224while from_step3_flag or from_step4_flag: ## IMPORTANT WARNING: This replaces a do...while loop, so it may have to be adjusted!225226## Go to directly to step 3 if we're coming from step 4, otherwise perform step 2.227if from_step4_flag:228from_step4_flag = False229else:230## 2. Compute bounds231from_step3_flag = False232Z = sqrt(T[i] / Q[i,i])233L[i] = floor(Z - U[i])234x[i] = ceil(-Z - U[i]) - 1235236237238## 3a. Main loop239240## DIAGNOSTIC241242#print " L = ", L243#print " x = ", x244245x[i] += 1246while (x[i] > L[i]):247248## DIAGNOSTIC249#print " x = ", x250251i += 1252x[i] += 1253254255## 3b. Main loop256if (i > 0):257from_step3_flag = True258259## DIAGNOSTIC260#print " i = " + str(i)261#print " T[i] = " + str(T[i])262#print " Q[i,i] = " + str(Q[i,i])263#print " x[i] = " + str(x[i])264#print " U[i] = " + str(U[i])265#print " x[i] + U[i] = " + str(x[i] + U[i])266#print " T[i-1] = " + str(T[i-1])267268T[i-1] = T[i] - Q[i,i] * (x[i] + U[i]) * (x[i] + U[i])269270# DIAGNOSTIC271#print " T[i-1] = " + str(T[i-1])272273274i += - 1275U[i] = 0276for j in range(i+1, n):277U[i] += Q[i,j] * x[j]278279280281## 4. Solution found (This happens when i=0)282from_step4_flag = True283Q_val_double = q_prec - T[0] + Q[0,0] * (x[0] + U[0]) * (x[0] + U[0])284Q_val = floor(Q_val_double + half) ## Note: This rounds the value up, since the "round" function returns a float, but floor returns integer.285286287288## DIAGNOSTIC289#print " Q_val_double = ", Q_val_double290#print " Q_val = ", Q_val291#raise RuntimeError292293294## OPTIONAL SAFETY CHECK:295eps = 0.000000001296if (abs(Q_val_double - Q_val) > eps):297raise RuntimeError, "Oh No! We have a problem with the floating point precision... \n" \298+ " Q_val_double = " + str(Q_val_double) + "\n" \299+ " Q_val = " + str(Q_val) + "\n" \300+ " x = " + str(x) + "\n"301302303## DIAGNOSTIC304#print " The float value is " + str(Q_val_double)305#print " The associated long value is " + str(Q_val)306307308if (Q_val <= q_prec):309theta[Q_val] += 2310311## 5. Check if x = 0, for exit condition. =)312done_flag = True313for j in range(n):314if (x[j] != 0):315done_flag = False316317318## Set the value: theta[0] = 1319theta[0] = 1320321## DIAGNOSTIC322#print "Leaving ComputeTheta \n"323324325## Return the series, truncated to the desired q-precision326return PS(theta)327328329def theta_series_degree_2(Q, prec):330r"""331Compute the theta series of degree 2 for the quadratic form Q.332333INPUT:334335- ``prec`` -- an integer.336337OUTPUT:338339dictionary, where:340341- keys are `{\rm GL}_2(\ZZ)`-reduced binary quadratic forms (given as triples of342coefficients)343- values are coefficients344345EXAMPLES::346347sage: Q2 = QuadraticForm(ZZ, 4, [1,1,1,1, 1,0,0, 1,0, 1])348sage: S = Q2.theta_series_degree_2(10)349sage: S[(0,0,2)]35024351sage: S[(1,0,1)]352144353sage: S[(1,1,1)]354192355356AUTHORS:357358- Gonzalo Tornaria (2010-03-23)359360REFERENCE:361362- Raum, Ryan, Skoruppa, Tornaria, 'On Formal Siegel Modular Forms'363(preprint)364"""365if Q.base_ring() != ZZ:366raise TypeError, "The quadratic form must be integral"367if not Q.is_positive_definite():368raise ValueError, "The quadratic form must be positive definite"369try:370X = ZZ(prec-1) # maximum discriminant371except:372raise TypeError, "prec is not an integer"373374if X < -1:375raise ValueError, "prec must be >= 0"376377if X == -1:378return {}379380V = ZZ ** Q.dim()381H = Q.Hessian_matrix()382383t = cputime()384max = int(floor((X+1)/4))385v_list = (Q.vectors_by_length(max)) # assume a>0386v_list = map(lambda(vs):map(V,vs), v_list) # coerce vectors into V387verbose("Computed vectors_by_length" , t)388389# Deal with the singular part390coeffs = {(0,0,0):ZZ(1)}391for i in range(1,max+1):392coeffs[(0,0,i)] = ZZ(2) * len(v_list[i])393394# Now deal with the non-singular part395a_max = int(floor(sqrt(X/3)))396for a in range(1, a_max + 1):397t = cputime()398c_max = int(floor((a*a + X)/(4*a)))399for c in range(a, c_max + 1):400for v1 in v_list[a]:401v1_H = v1 * H402def B_v1(v):403return v1_H * v2404for v2 in v_list[c]:405b = abs(B_v1(v2))406if b <= a and 4*a*c-b*b <= X:407qf = (a,b,c)408count = ZZ(4) if b == 0 else ZZ(2)409coeffs[qf] = coeffs.get(qf, ZZ(0)) + count410verbose("done a = %d" % a, t)411412return coeffs413414415416417