# Jacobi theta q series

I'd like to express the classical Jacobi theta series in Sage:

• $\Theta_2(z) = \displaystyle{\sum_{n\in{\mathbb{Z} + \frac{1}{2}}} q^{\frac{1}{2}n^2}}$
• $\Theta_3(z) = \displaystyle{\sum_{n\in{\mathbb{Z}}} q^{\frac{1}{2}n^2}}$
• $\Theta_4(z) = \displaystyle{\sum_{n\in{\mathbb{Z}}} (-1)^nq^{\frac{1}{2}n^2}}$

where $q=e^{2 \pi i z}$. Preferably, these functions would play nicely with other q series such as

E4 = eisenstein_series_qexp(4,5, normalization = 'constant')

type(E4)

<class 'sage.rings.power_series_poly.PowerSeries_poly'>

I'd like to be able to take rational combinations of these theta functions and the Eisenstein series and analyze/compare Fourier coefficients in the q-series. However, it appears that working with non-integer powers of q may be problematic. For instance, if we start with the theta_qexp function (which is $\Theta_3(2z)$ from above), we see that

theta_qexp(10,'q',ZZ)

1 + 2*q + 2*q^4 + 2*q^9 + O(q^10)

But if we let f = theta_qexp(10,'q',ZZ) then f*q^(-1/2) throws an error, presumably because of the non-integer power.

edit retag close merge delete

@Daniel L : I tried to reformat the LaTeX code of your question, but the third definition notation is inconsistent with the first two. Could you clarify ?

( 2022-05-06 10:52:15 +0200 )edit

There is a $\Theta_4$ now in there.

( 2022-05-06 17:40:06 +0200 )edit

Sort by ยป oldest newest most voted

If i am correctly understanding the question, the programming issue is related to finding a common ring to work in for the following objects:

• power series in $t$, where $t$ is a nome for $\exp(\pi i\tau)$, and
• power series in $q$, where $q$ is a nome for $\exp(2\pi i\tau)$, and use already implemented modular functions already implemented in sage for this nome.

Obviously, we should have the bridge $q=t^2$. Since $q$ can be algebraically obtained from $t$, but not conversely, we will work with $t$. So we work in the power series ring over $\Bbb Q$ in the variable $t$.

Here is a way to initialize the above objects, and work with them in a common world.

q_PREC = 8
t_PREC = 2*q_PREC

R.<t> = PowerSeriesRing(QQ, default_prec=t_PREC)
E4 = eisenstein_series_qexp(4, prec=q_PREC, var='q', normalization = 'constant')
RQ = E4.parent()
RQ.inject_variables()    # this is defining the variable q used by default in E4
print(f'E4 = E4(q) is {E4}')
print(f'E4(t^2)    is {E4(t^2)}')


Results:

Defining q
E4 = E4(q) is 1 + 240*q + 2160*q^2 + 6720*q^3 + 17520*q^4 + 30240*q^5 + 60480*q^6 + 82560*q^7 + O(q^8)
E4(t^2)    is 1 + 240*t^2 + 2160*t^4 + 6720*t^6 + 17520*t^8 + 30240*t^10 + 60480*t^12 + 82560*t^14 + O(t^16)


I hope the way the programming strategy would work is now clear. Let us define in $\Bbb Q[[t]]$ (modulo $t^{2\cdot 8}$) the following objects:

E4t = E4(t^2) + O(t^t_PREC)
Theta3 = theta_qexp(q_PREC)(t^2) + O(t^t_PREC)

print(f'E4t * Theta3 is:\n{E4t * Theta3}')
g = E4t - Theta3
gdic = g.dict()
print(f'E4t - Theta3 is:\n{g}')
print(f'The coefficients of E4t - Theta3 are as in the dictionary:\n{gdic}')
print('The first coefficients are:')
for k in [0..10]:
print(f'Coefficient in degree {k} is {gdic.get(k, 0)}')


The results are:

E4t * Theta3 is:
1 + 242*t^2 + 2640*t^4 + 11040*t^6 + 30962*t^8 + 65760*t^10 + 125280*t^12 + 216960*t^14 + O(t^16)
E4t - Theta3 is:
238*t^2 + 2160*t^4 + 6720*t^6 + 17518*t^8 + 30240*t^10 + 60480*t^12 + 82560*t^14 + O(t^16)
The coefficients of E4t - Theta3 are as in the dictionary:
{2: 238, 4: 2160, 6: 6720, 8: 17518, 10: 30240, 12: 60480, 14: 82560}
The first coefficients are:
Coefficient in degree 0 is 0
Coefficient in degree 1 is 0
Coefficient in degree 2 is 238
Coefficient in degree 3 is 0
Coefficient in degree 4 is 2160
Coefficient in degree 5 is 0
Coefficient in degree 6 is 6720
Coefficient in degree 7 is 0
Coefficient in degree 8 is 17518
Coefficient in degree 9 is 0
Coefficient in degree 10 is 30240


The coefficients of a power series are not really obtained in a natural way. The method g.coefficients() shows them, but you have to guess the degrees. You can try [coeff for coeff in g] to have the coefficients quickly in a list.

sage: [coeff for coeff in g]
[0, 0, 238, 0, 2160, 0, 6720, 0, 17518, 0, 30240, 0, 60480, 0, 82560]


An other way is to associate the dictionary gdic as above, since for sparse power series it may be useful. For instance:

sage: theta2_qexp(300).dict()
{1: 1, 9: 1, 25: 1, 49: 1, 81: 1, 121: 1, 169: 1, 225: 1, 289: 1}
sage: theta2_qexp(300)(t^2).dict()
{2: 1, 18: 1, 50: 1, 98: 1, 162: 1, 242: 1, 338: 1, 450: 1, 578: 1}


Note that for missing degrees, we have to provide the default value, zero. For instance:

sage: th2_dic = theta2_qexp(300)(t^2).dict()

sage: th2_dic.get(18)
1
sage: th2_dic.get(19)
sage: type(th2_dic.get(19))
<class 'NoneType'>

sage: th2_dic.get(19, 0)
0
sage: type(th2_dic.get(19, 0))
<class 'sage.rings.integer.Integer'>


I hope the above way to proceed works for the purpose, good luck in your research projects!

more