Skip to content

Commit 63a510b

Browse files
author
Release Manager
committed
gh-39028: Numerical evaluation of modular form & implement conversion of more modular form spaces to Pari As in the title. Currently modular form can be evaluated at symbolic `q` that is a generator of a power series, I think we can also allow evaluating it at a numerical `q`. ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. (not aware of one) - [x] I have created tests covering the changes. - [ ] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> URL: #39028 Reported by: user202729 Reviewer(s): Travis Scrimshaw, user202729
2 parents 9ff4ae5 + 6804989 commit 63a510b

File tree

4 files changed

+220
-1
lines changed

4 files changed

+220
-1
lines changed

src/sage/modular/modform/ambient_eps.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,3 +288,18 @@ def hecke_module_of_level(self, N):
288288
return constructor.ModularForms(self.character().restrict(N), self.weight(), self.base_ring(), prec=self.prec())
289289
else:
290290
raise ValueError("N (=%s) must be a divisor or a multiple of the level of self (=%s)" % (N, self.level()))
291+
292+
def _pari_init_(self):
293+
"""
294+
Conversion to Pari.
295+
296+
EXAMPLES::
297+
298+
sage: m = ModularForms(DirichletGroup(17).0^2, 2)
299+
sage: pari.mfdim(m)
300+
3
301+
sage: pari.mfparams(m)
302+
[17, 2, Mod(9, 17), 4, t^4 + 1]
303+
"""
304+
from sage.libs.pari import pari
305+
return pari.mfinit([self.level(), self.weight(), self.character()], 4)

src/sage/modular/modform/cuspidal_submodule.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,21 @@ def _compute_q_expansion_basis(self, prec=None):
377377
return [weight1.modular_ratio_to_prec(chi, f, prec) for f in
378378
weight1.hecke_stable_subspace(chi)]
379379

380+
def _pari_init_(self):
381+
"""
382+
Conversion to Pari.
383+
384+
EXAMPLES::
385+
386+
sage: A = CuspForms(DirichletGroup(23, QQ).0, 1)
387+
sage: pari.mfparams(A)
388+
[23, 1, -23, 1, t + 1]
389+
sage: pari.mfdim(A)
390+
1
391+
"""
392+
from sage.libs.pari import pari
393+
return pari.mfinit([self.level(), self.weight(), self.character()], 1)
394+
380395

381396
class CuspidalSubmodule_wt1_gH(CuspidalSubmodule):
382397
r"""

src/sage/modular/modform/eisenstein_submodule.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,22 @@ class EisensteinSubmodule_eps(EisensteinSubmodule_params):
586586
q^5 + (zeta3 + 1)*q^8 + O(q^10)
587587
]
588588
"""
589+
def _pari_init_(self):
590+
"""
591+
Conversion to Pari.
592+
593+
EXAMPLES::
594+
595+
sage: e = DirichletGroup(27,CyclotomicField(3)).0**2
596+
sage: M = ModularForms(e,2,prec=10).eisenstein_subspace()
597+
sage: pari.mfdim(M)
598+
6
599+
sage: pari.mfparams(M)
600+
[27, 2, Mod(10, 27), 3, t^2 + t + 1]
601+
"""
602+
from sage.libs.pari import pari
603+
return pari.mfinit([self.level(), self.weight(), self.character()], 3)
604+
589605
# TODO
590606
# def _compute_q_expansion_basis(self, prec):
591607
# B = EisensteinSubmodule_params._compute_q_expansion_basis(self, prec)

src/sage/modular/modform/element.py

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,38 @@ def _repr_(self):
219219
"""
220220
return str(self.q_expansion())
221221

222+
def _pari_init_(self):
223+
"""
224+
Conversion to Pari.
225+
226+
TESTS::
227+
228+
sage: M = EisensteinForms(96, 2)
229+
sage: M.6
230+
O(q^6)
231+
sage: M.7
232+
O(q^6)
233+
sage: pari(M.6) == pari(M.7)
234+
False
235+
sage: pari(M.6).mfcoefs(10)
236+
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0]
237+
238+
sage: M = ModularForms(DirichletGroup(17).0^2, 2)
239+
sage: pari(M.0).mfcoefs(5)
240+
[0, 1, Mod(-t^3 + t^2 - 1, t^4 + 1), Mod(t^3 - t^2 - t - 1, t^4 + 1), Mod(2*t^3 - t^2 + 2*t, t^4 + 1), Mod(-t^3 - t^2, t^4 + 1)]
241+
sage: M.0.qexp(5)
242+
q + (-zeta8^3 + zeta8^2 - 1)*q^2 + (zeta8^3 - zeta8^2 - zeta8 - 1)*q^3 + (2*zeta8^3 - zeta8^2 + 2*zeta8)*q^4 + O(q^5)
243+
"""
244+
from sage.libs.pari import pari
245+
from sage.rings.number_field.number_field_element import NumberFieldElement
246+
M = pari(self.parent())
247+
f = self.qexp(self.parent().sturm_bound())
248+
coefficients = [
249+
x.__pari__('t') if isinstance(x, NumberFieldElement) else x
250+
for x in f]
251+
# we cannot compute pari(f) directly because we need to set the variable name as t
252+
return M.mflinear(M.mftobasis(coefficients + [0] * (f.prec() - len(coefficients))))
253+
222254
def __call__(self, x, prec=None):
223255
"""
224256
Evaluate the `q`-expansion of this modular form at x.
@@ -233,9 +265,150 @@ def __call__(self, x, prec=None):
233265
234266
sage: f(0)
235267
0
236-
"""
268+
269+
Evaluate numerically::
270+
271+
sage: f = ModularForms(1, 12).0
272+
sage: f(0.3) # rel tol 1e-12
273+
2.34524576548591e-6
274+
sage: f = EisensteinForms(1, 4).0
275+
sage: f(0.9) # rel tol 1e-12
276+
1.26475942209241e7
277+
278+
TESTS::
279+
280+
sage: f = ModularForms(96, 2).0
281+
sage: f(0.3) # rel tol 1e-12
282+
0.299999997396191
283+
sage: f(0.0+0.0*I)
284+
0
285+
286+
For simplicity, ``float`` or ``complex`` input are converted to ``CC``, except for
287+
input ``0`` where exact result is returned::
288+
289+
sage: result = f(0.3r); result # rel tol 1e-12
290+
0.299999997396191
291+
sage: result.parent()
292+
Complex Field with 53 bits of precision
293+
sage: result = f(0.3r + 0.3jr); result # rel tol 1e-12
294+
0.299999359878484 + 0.299999359878484*I
295+
sage: result.parent()
296+
Complex Field with 53 bits of precision
297+
298+
Symbolic numerical values use precision of ``CC`` by default::
299+
300+
sage: f(sqrt(1/2)) # rel tol 1e-12
301+
0.700041406692037
302+
sage: f(sqrt(1/2)*QQbar.zeta(8)) # rel tol 1e-12
303+
0.496956554651376 + 0.496956554651376*I
304+
305+
Higher precision::
306+
307+
sage: f(ComplexField(128)(0.3)) # rel tol 1e-36
308+
0.29999999739619131029285166058750164058
309+
sage: f(ComplexField(128)(1+2*I)/3) # rel tol 1e-36
310+
0.32165384572356882556790532669389900691 + 0.67061244638367586302820790711257777390*I
311+
312+
Confirm numerical evaluation matches the q-expansion::
313+
314+
sage: f = EisensteinForms(1, 4).0
315+
sage: f(0.3) # rel tol 1e-12
316+
741.741819297986
317+
sage: f.qexp(50).polynomial()(0.3) # rel tol 1e-12
318+
741.741819297986
319+
320+
With a nontrivial character::
321+
322+
sage: M = ModularForms(DirichletGroup(17).0^2, 2)
323+
sage: M.0(0.5) # rel tol 1e-12
324+
0.166916655031616 + 0.0111529051752428*I
325+
sage: M.0.qexp(60).polynomial()(0.5) # rel tol 1e-12
326+
0.166916655031616 + 0.0111529051752428*I
327+
328+
Higher precision::
329+
330+
sage: f(ComplexField(128)(1+2*I)/3) # rel tol 1e-36
331+
429.19994832206294278688085399056359632 - 786.15736284188243351153830824852974995*I
332+
sage: f.qexp(400).polynomial()(ComplexField(128)(1+2*I)/3) # rel tol 1e-36
333+
429.19994832206294278688085399056359631 - 786.15736284188243351153830824852974999*I
334+
335+
Check ``SR`` does not make the result lose precision::
336+
337+
sage: f(ComplexField(128)(1+2*I)/3 + x - x) # rel tol 1e-36
338+
429.19994832206294278688085399056359632 - 786.15736284188243351153830824852974995*I
339+
"""
340+
from sage.rings.integer import Integer
341+
from sage.misc.functional import log
342+
from sage.structure.element import parent
343+
from sage.rings.complex_mpfr import ComplexNumber
344+
from sage.rings.cc import CC
345+
from sage.rings.real_mpfr import RealNumber
346+
from sage.symbolic.constants import pi
347+
from sage.rings.imaginary_unit import I # import from here instead of sage.symbolic.constants to avoid cast to SR
348+
from sage.symbolic.expression import Expression
349+
if isinstance(x, Expression):
350+
try:
351+
x = x.pyobject()
352+
except TypeError:
353+
pass
354+
if x in CC:
355+
if x == 0:
356+
return self.qexp(1)[0]
357+
if not isinstance(x, (RealNumber, ComplexNumber)):
358+
x = CC(x) # might lose precision if this is done unconditionally (TODO what about interval and ball types?)
359+
if isinstance(x, (RealNumber, ComplexNumber)):
360+
return self.eval_at_tau(log(x)/(2*parent(x)(pi)*I)) # cast to parent(x) to force numerical evaluation of pi
237361
return self.q_expansion(prec)(x)
238362

363+
def eval_at_tau(self, tau):
364+
r"""
365+
Evaluate this modular form at the half-period ratio `\tau`.
366+
This is related to `q` by `q = e^{2\pi i \tau}`.
367+
368+
EXAMPLES::
369+
370+
sage: f = ModularForms(1, 12).0
371+
sage: f.eval_at_tau(0.3 * I) # rel tol 1e-12
372+
0.00150904633897550
373+
374+
TESTS:
375+
376+
Symbolic numerical values use precision of ``CC`` by default::
377+
378+
sage: f.eval_at_tau(sqrt(1/5)*I) # rel tol 1e-12
379+
0.0123633234207127
380+
sage: f.eval_at_tau(sqrt(1/2)*QQbar.zeta(8)) # rel tol 1e-12
381+
-0.114263670441098
382+
383+
For simplicity, ``complex`` input are converted to ``CC``::
384+
385+
sage: result = f.eval_at_tau(0.3jr); result # rel tol 1e-12
386+
0.00150904633897550
387+
sage: result.parent()
388+
Complex Field with 53 bits of precision
389+
390+
Check ``SR`` does not make the result lose precision::
391+
392+
sage: f = EisensteinForms(1, 4).0
393+
sage: f.eval_at_tau(ComplexField(128)(1+2*I)/3 + x - x) # rel tol 1e-36
394+
-1.0451570582202060056197878314286036966 + 2.7225112098519803098203933583286590274*I
395+
"""
396+
from sage.libs.pari.convert_sage import gen_to_sage
397+
from sage.libs.pari import pari
398+
from sage.rings.cc import CC
399+
from sage.rings.complex_mpfr import ComplexNumber, ComplexField
400+
from sage.rings.real_mpfr import RealNumber
401+
from sage.symbolic.expression import Expression
402+
if isinstance(tau, Expression):
403+
try:
404+
tau = tau.pyobject()
405+
except TypeError:
406+
pass
407+
if not isinstance(tau, (RealNumber, ComplexNumber)):
408+
tau = CC(tau)
409+
precision = tau.prec()
410+
return ComplexField(precision)(pari.mfeval(self.parent(), self, tau, precision=precision))
411+
239412
@cached_method
240413
def valuation(self):
241414
"""

0 commit comments

Comments
 (0)