Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/quadratic_forms/quadratic_form__theta.py
4097 views
1
"""
2
Theta Series of Quadratic Forms
3
4
AUTHORS:
5
6
- Jonathan Hanke: initial code, theta series of degree 1
7
8
- Gonzalo Tornaria (2009-02-22): fixes and doctests
9
10
- Gonzalo Tornaria (2010-03-23): theta series of degree 2
11
12
"""
13
14
from copy import deepcopy
15
16
from sage.rings.real_mpfr import RealField
17
from sage.rings.power_series_ring import PowerSeriesRing
18
from sage.rings.integer_ring import ZZ
19
from sage.functions.all import sqrt, floor, ceil
20
21
22
23
from sage.misc.misc import cputime, verbose
24
25
26
def theta_series(self, Max=10, var_str='q', safe_flag=True):
27
"""
28
Compute the theta series as a power series in the variable given
29
in var_str (which defaults to '`q`'), up to the specified precision
30
`O(q^max)`.
31
32
This uses the PARI/GP function qfrep, wrapped by the
33
theta_by_pari() method. This caches the result for future
34
computations.
35
36
The safe_flag allows us to select whether we want a copy of the
37
output, or the original output. It is only meaningful when a
38
vector is returned, otherwise a copy is automatically made in
39
creating the power series. By default safe_flag = True, so we
40
return a copy of the cached information. If this is set to False,
41
then the routine is much faster but the return values are
42
vulnerable to being corrupted by the user.
43
44
TO DO: Allow the option Max='mod_form' to give enough coefficients
45
to ensure we determine the theta series as a modular form. This
46
is related to the Sturm bound, but we'll need to be careful about
47
this (particularly for half-integral weights!).
48
49
EXAMPLES::
50
51
sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])
52
sage: Q.theta_series()
53
1 + 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)
54
55
sage: Q.theta_series(25)
56
1 + 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)
57
58
"""
59
## Sanity Check: Max is an integer or an allowed string:
60
try:
61
M = ZZ(Max)
62
except:
63
M = -1
64
65
if (Max not in ['mod_form']) and (not M >= 0):
66
print Max
67
raise TypeError, "Oops! Max is not an integer >= 0 or an allowed string."
68
69
if Max == 'mod_form':
70
raise NotImplementedError, "Oops! We have to figure out the correct number of Fourier coefficients to use..."
71
#return self.theta_by_pari(sturm_bound(self.level(), self.dim() / ZZ(2)) + 1, var_str, safe_flag)
72
else:
73
return self.theta_by_pari(M, var_str, safe_flag)
74
75
76
77
## ------------- Compute the theta function by using the PARI/GP routine qfrep ------------
78
79
def theta_by_pari(self, Max, var_str='q', safe_flag=True):
80
"""
81
Use PARI/GP to compute the theta function as a power series (or
82
vector) up to the precision `O(q^Max)`. This also caches the result
83
for future computations.
84
85
If var_str = '', then we return a vector `v` where `v[i]` counts the
86
number of vectors of length `i`.
87
88
The safe_flag allows us to select whether we want a copy of the
89
output, or the original output. It is only meaningful when a
90
vector is returned, otherwise a copy is automatically made in
91
creating the power series. By default safe_flag = True, so we
92
return a copy of the cached information. If this is set to False,
93
then the routine is much faster but the return values are
94
vulnerable to being corrupted by the user.
95
96
97
INPUT:
98
Max -- an integer >=0
99
var_str -- a string
100
101
OUTPUT:
102
a power series or a vector
103
104
EXAMPLES::
105
106
sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1])
107
sage: Prec = 100
108
sage: compute = Q.theta_by_pari(Prec, '')
109
sage: exact = [1] + [8 * sum([d for d in divisors(i) if d % 4 != 0]) for i in range(1, Prec)]
110
sage: compute == exact
111
True
112
113
"""
114
## Try to use the cached result if it's enough precision
115
if hasattr(self, '__theta_vec') and len(self.__theta_vec) >= Max:
116
theta_vec = self.__theta_vec[:Max]
117
else:
118
theta_vec = self.representation_number_list(Max)
119
## Cache the theta vector
120
self.__theta_vec = theta_vec
121
122
## Return the answer
123
if var_str == '':
124
if safe_flag:
125
return deepcopy(theta_vec) ## We must make a copy here to insure the integrity of the cached version!
126
else:
127
return theta_vec
128
else:
129
return PowerSeriesRing(ZZ, var_str)(theta_vec, Max)
130
131
132
133
## ------------- Compute the theta function by using an explicit Cholesky decomposition ------------
134
135
136
##########################################################################
137
## Routines to compute the Fourier expansion of the theta function of Q ##
138
## (to a given precision) via a Cholesky decomposition over RR. ##
139
## ##
140
## The Cholesky code was taken from: ##
141
## ~/Documents/290_Project/C/Ver13.2__3-5-2007/Matrix_mpz/Matrix_mpz.cc ##
142
##########################################################################
143
144
145
146
def theta_by_cholesky(self, q_prec):
147
r"""
148
Uses the real Cholesky decomposition to compute (the `q`-expansion of) the
149
theta function of the quadratic form as a power series in `q` with terms
150
correct up to the power `q^{\text{q\_prec}}`. (So its error is `O(q^
151
{\text{q\_prec} + 1})`.)
152
153
REFERENCE:
154
From Cohen's "A Course in Computational Algebraic Number Theory" book,
155
p 102.
156
157
EXAMPLES::
158
159
## Check the sum of 4 squares form against Jacobi's formula
160
sage: Q = DiagonalQuadraticForm(ZZ, [1,1,1,1])
161
sage: Theta = Q.theta_by_cholesky(10)
162
sage: Theta
163
1 + 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^10
164
sage: Expected = [1] + [8*sum([d for d in divisors(n) if d%4 != 0]) for n in range(1,11)]
165
sage: Expected
166
[1, 8, 24, 32, 24, 48, 96, 64, 24, 104, 144]
167
sage: Theta.list() == Expected
168
True
169
170
::
171
172
## Check the form x^2 + 3y^2 + 5z^2 + 7w^2 represents everything except 2 and 22.
173
sage: Q = DiagonalQuadraticForm(ZZ, [1,3,5,7])
174
sage: Theta = Q.theta_by_cholesky(50)
175
sage: Theta_list = Theta.list()
176
sage: [m for m in range(len(Theta_list)) if Theta_list[m] == 0]
177
[2, 22]
178
179
"""
180
## RAISE AN ERROR -- This routine is deprecated!
181
#raise NotImplementedError, "This routine is deprecated. Try theta_series(), which uses theta_by_pari()."
182
183
184
n = self.dim()
185
theta = [0 for i in range(q_prec+1)]
186
PS = PowerSeriesRing(ZZ, 'q')
187
188
bit_prec = 53 ## TO DO: Set this precision to reflect the appropriate roundoff
189
Cholesky = self.cholesky_decomposition(bit_prec) ## error estimate, to be confident through our desired q-precision.
190
Q = Cholesky ## <---- REDUNDANT!!!
191
R = RealField(bit_prec)
192
half = R(0.5)
193
194
195
196
## 1. Initialize
197
i = n - 1
198
T = [R(0) for j in range(n)]
199
U = [R(0) for j in range(n)]
200
T[i] = R(q_prec)
201
U[i] = 0
202
L = [0 for j in range (n)]
203
x = [0 for j in range (n)]
204
205
206
## 2. Compute bounds
207
#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... =|
208
#L[i] = floor(Z - U[i]) ## Note: This is a Sage Integer
209
#x[i] = ceil(-Z - U[i]) - 1 ## Note: This is a Sage Integer too
210
211
212
done_flag = False
213
from_step4_flag = False
214
from_step3_flag = True ## We start by pretending this, since then we get to run through 2 and 3a once. =)
215
216
#double Q_val_double;
217
#unsigned long Q_val; // WARNING: Still need a good way of checking overflow for this value...
218
219
220
221
## Big loop which runs through all vectors
222
while (done_flag == False):
223
224
## Loop through until we get to i=1 (so we defined a vector x)
225
while from_step3_flag or from_step4_flag: ## IMPORTANT WARNING: This replaces a do...while loop, so it may have to be adjusted!
226
227
## Go to directly to step 3 if we're coming from step 4, otherwise perform step 2.
228
if from_step4_flag:
229
from_step4_flag = False
230
else:
231
## 2. Compute bounds
232
from_step3_flag = False
233
Z = sqrt(T[i] / Q[i,i])
234
L[i] = floor(Z - U[i])
235
x[i] = ceil(-Z - U[i]) - 1
236
237
238
239
## 3a. Main loop
240
241
## DIAGNOSTIC
242
#print
243
#print " L = ", L
244
#print " x = ", x
245
246
x[i] += 1
247
while (x[i] > L[i]):
248
249
## DIAGNOSTIC
250
#print " x = ", x
251
252
i += 1
253
x[i] += 1
254
255
256
## 3b. Main loop
257
if (i > 0):
258
from_step3_flag = True
259
260
## DIAGNOSTIC
261
#print " i = " + str(i)
262
#print " T[i] = " + str(T[i])
263
#print " Q[i,i] = " + str(Q[i,i])
264
#print " x[i] = " + str(x[i])
265
#print " U[i] = " + str(U[i])
266
#print " x[i] + U[i] = " + str(x[i] + U[i])
267
#print " T[i-1] = " + str(T[i-1])
268
269
T[i-1] = T[i] - Q[i,i] * (x[i] + U[i]) * (x[i] + U[i])
270
271
# DIAGNOSTIC
272
#print " T[i-1] = " + str(T[i-1])
273
#print
274
275
i += - 1
276
U[i] = 0
277
for j in range(i+1, n):
278
U[i] += Q[i,j] * x[j]
279
280
281
282
## 4. Solution found (This happens when i=0)
283
from_step4_flag = True
284
Q_val_double = q_prec - T[0] + Q[0,0] * (x[0] + U[0]) * (x[0] + U[0])
285
Q_val = floor(Q_val_double + half) ## Note: This rounds the value up, since the "round" function returns a float, but floor returns integer.
286
287
288
289
## DIAGNOSTIC
290
#print " Q_val_double = ", Q_val_double
291
#print " Q_val = ", Q_val
292
#raise RuntimeError
293
294
295
## OPTIONAL SAFETY CHECK:
296
eps = 0.000000001
297
if (abs(Q_val_double - Q_val) > eps):
298
raise RuntimeError, "Oh No! We have a problem with the floating point precision... \n" \
299
+ " Q_val_double = " + str(Q_val_double) + "\n" \
300
+ " Q_val = " + str(Q_val) + "\n" \
301
+ " x = " + str(x) + "\n"
302
303
304
## DIAGNOSTIC
305
#print " The float value is " + str(Q_val_double)
306
#print " The associated long value is " + str(Q_val)
307
#print
308
309
if (Q_val <= q_prec):
310
theta[Q_val] += 2
311
312
## 5. Check if x = 0, for exit condition. =)
313
done_flag = True
314
for j in range(n):
315
if (x[j] != 0):
316
done_flag = False
317
318
319
## Set the value: theta[0] = 1
320
theta[0] = 1
321
322
## DIAGNOSTIC
323
#print "Leaving ComputeTheta \n"
324
325
326
## Return the series, truncated to the desired q-precision
327
return PS(theta)
328
329
330
def theta_series_degree_2(Q, prec):
331
r"""
332
Compute the theta series of degree 2 for the quadratic form Q.
333
334
INPUT:
335
336
- ``prec`` -- an integer.
337
338
OUTPUT:
339
340
dictionary, where:
341
342
- keys are `{\rm GL}_2(\ZZ)`-reduced binary quadratic forms (given as triples of
343
coefficients)
344
- values are coefficients
345
346
EXAMPLES::
347
348
sage: Q2 = QuadraticForm(ZZ, 4, [1,1,1,1, 1,0,0, 1,0, 1])
349
sage: S = Q2.theta_series_degree_2(10)
350
sage: S[(0,0,2)]
351
24
352
sage: S[(1,0,1)]
353
144
354
sage: S[(1,1,1)]
355
192
356
357
AUTHORS:
358
359
- Gonzalo Tornaria (2010-03-23)
360
361
REFERENCE:
362
363
- Raum, Ryan, Skoruppa, Tornaria, 'On Formal Siegel Modular Forms'
364
(preprint)
365
"""
366
if Q.base_ring() != ZZ:
367
raise TypeError, "The quadratic form must be integral"
368
if not Q.is_positive_definite():
369
raise ValueError, "The quadratic form must be positive definite"
370
try:
371
X = ZZ(prec-1) # maximum discriminant
372
except:
373
raise TypeError, "prec is not an integer"
374
375
if X < -1:
376
raise ValueError, "prec must be >= 0"
377
378
if X == -1:
379
return {}
380
381
V = ZZ ** Q.dim()
382
H = Q.Hessian_matrix()
383
384
t = cputime()
385
max = int(floor((X+1)/4))
386
v_list = (Q.vectors_by_length(max)) # assume a>0
387
v_list = map(lambda(vs):map(V,vs), v_list) # coerce vectors into V
388
verbose("Computed vectors_by_length" , t)
389
390
# Deal with the singular part
391
coeffs = {(0,0,0):ZZ(1)}
392
for i in range(1,max+1):
393
coeffs[(0,0,i)] = ZZ(2) * len(v_list[i])
394
395
# Now deal with the non-singular part
396
a_max = int(floor(sqrt(X/3)))
397
for a in range(1, a_max + 1):
398
t = cputime()
399
c_max = int(floor((a*a + X)/(4*a)))
400
for c in range(a, c_max + 1):
401
for v1 in v_list[a]:
402
v1_H = v1 * H
403
def B_v1(v):
404
return v1_H * v2
405
for v2 in v_list[c]:
406
b = abs(B_v1(v2))
407
if b <= a and 4*a*c-b*b <= X:
408
qf = (a,b,c)
409
count = ZZ(4) if b == 0 else ZZ(2)
410
coeffs[qf] = coeffs.get(qf, ZZ(0)) + count
411
verbose("done a = %d" % a, t)
412
413
return coeffs
414
415
416
417