Skip to content

Commit

Permalink
Trac #32357: Implement derivative for modular forms and quasimodular …
Browse files Browse the repository at this point in the history
…forms

In #32343 the Serre derivative for {{{ModularFormElement}}} was
implemented. This ticket implements similar feature but for the classes
{{{GradedModularFormElement}}} and {{{QuasiModularFormsElement}}}.
Moreover, this ticket aims to implement the derivative ''q * df/dq'' of
a graded modular form and a quasiform using the Serre derivative.

This ticket is part of #31560

URL: https://trac.sagemath.org/32357
Reported by: gh-DavidAyotte
Ticket author(s): David Ayotte
Reviewer(s): Frédéric Chapoton
  • Loading branch information
Release Manager committed Oct 29, 2021
2 parents 906387d + 4ed58cf commit c855f39
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 7 deletions.
69 changes: 67 additions & 2 deletions src/sage/modular/modform/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -3590,7 +3590,8 @@ def weight(self):

def weights_list(self):
r"""
Return the list of the weights of all the graded components of the given graded modular form.
Return the list of the weights of all the homogeneous components of the
given graded modular form.
EXAMPLES::
Expand Down Expand Up @@ -3628,7 +3629,8 @@ def is_homogeneous(self):

def _homogeneous_to_polynomial(self, names, gens):
r"""
If ``self`` is a homogeneous form, return a polynomial `P(x_0,..., x_n)` corresponding to ``self``.
Return a polynomial `P(x_0,..., x_n)` corresponding to the given homogeneous graded form.
Each variable `x_i` of the returned polynomial correspond to a generator `g_i` of the
list ``gens`` (following the order of the list)
Expand Down Expand Up @@ -3744,3 +3746,66 @@ def to_polynomial(self, names='x', gens=None):

# sum the polynomial of each homogeneous part
return sum(M(self[k])._homogeneous_to_polynomial(names, gens) for k in self.weights_list())

def serre_derivative(self):
r"""
Return the Serre derivative of the given graded modular form.
If ``self`` is a modular form of weight `k`, then the returned modular
form will be of weight `k + 2`. If the form is not homogeneous, then
this method sums the Serre derivative of each homogeneous component.
EXAMPLES::
sage: M = ModularFormsRing(1)
sage: E4 = M.0
sage: E6 = M.1
sage: DE4 = E4.serre_derivative(); DE4
-1/3 + 168*q + 5544*q^2 + 40992*q^3 + 177576*q^4 + 525168*q^5 + O(q^6)
sage: DE4 == (-1/3) * E6
True
sage: DE6 = E6.serre_derivative(); DE6
-1/2 - 240*q - 30960*q^2 - 525120*q^3 - 3963120*q^4 - 18750240*q^5 + O(q^6)
sage: DE6 == (-1/2) * E4^2
True
sage: f = E4 + E6
sage: Df = f.serre_derivative(); Df
-5/6 - 72*q - 25416*q^2 - 484128*q^3 - 3785544*q^4 - 18225072*q^5 + O(q^6)
sage: Df == (-1/3) * E6 + (-1/2) * E4^2
True
sage: M(1/2).serre_derivative()
0
"""
M = self.parent()
return M(sum(M(f.serre_derivative()) for k, f in self._forms_dictionary.items() if k != 0))

def derivative(self, name='E2'):
r"""
Return the derivative `q \frac{d}{dq}` of the given graded form.
Note that this method returns an element of a new parent, that is a
quasimodular form. If the form is not homogeneous, then this method sums
the derivative of each homogeneous component.
INPUT:
- ``name`` (str, default: 'E2') - the name of the weight 2 Eisenstein
series generating the graded algebra of quasimodular forms over the
ring of modular forms.
OUTPUT: a :class:`sage.modular.quasimodform.element.QuasiModularFormsElement`
EXAMPLES::
sage: M = ModularFormsRing(1)
sage: E4 = M.0; E6 = M.1
sage: dE4 = E4.derivative(); dE4
240*q + 4320*q^2 + 20160*q^3 + 70080*q^4 + 151200*q^5 + O(q^6)
sage: dE4.parent()
Ring of Quasimodular Forms for Modular Group SL(2,Z) over Rational Field
sage: dE4.is_modular_form()
False
"""
from sage.modular.quasimodform.ring import QuasiModularForms
F = QuasiModularForms(self.group(), self.base_ring(), name)(self)
return F.derivative()
107 changes: 102 additions & 5 deletions src/sage/modular/quasimodform/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ def is_one(self):

def is_graded_modular_form(self):
r"""
Return ``True`` if the given quasiform is a graded modular forms element
Return ``True`` if the given quasimodular form is a graded modular forms element
and ``False`` otherwise.
EXAMPLES::
Expand Down Expand Up @@ -325,8 +325,8 @@ def is_graded_modular_form(self):

def is_modular_form(self):
r"""
Return ``True`` if the given quasiform is a modular form and ``False``
otherwise.
Return ``True`` if the given quasimodular form is a modular form and
``False`` otherwise.
EXAMPLES::
Expand Down Expand Up @@ -415,8 +415,9 @@ def is_homogeneous(self):

def weight(self):
r"""
Return the weight of the given quasiform. Note that the given form must
be homogeneous.
Return the weight of the given quasimodular form.
Note that the given form must be homogeneous.
EXAMPLES::
Expand Down Expand Up @@ -458,3 +459,99 @@ def homogeneous_components(self):
poly_self = self.to_polynomial()
pol_hom_comp = poly_self.homogeneous_components()
return { k : QM.from_polynomial(pol) for k, pol in pol_hom_comp.items()}

def serre_derivative(self):
r"""
Return the Serre derivative of the given quasimodular form.
If the form is not homogeneous, then this method sums the serre
derivative of each homogeneous component.
EXAMPLES::
sage: QM = QuasiModularForms(1)
sage: E2, E4, E6 = QM.gens()
sage: DE2 = E2.serre_derivative(); DE2
-1/6 - 16*q - 216*q^2 - 832*q^3 - 2248*q^4 - 4320*q^5 + O(q^6)
sage: DE2 == (-E2^2 - E4)/12
True
sage: DE4 = E4.serre_derivative(); DE4
-1/3 + 168*q + 5544*q^2 + 40992*q^3 + 177576*q^4 + 525168*q^5 + O(q^6)
sage: DE4 == (-1/3) * E6
True
sage: DE6 = E6.serre_derivative(); DE6
-1/2 - 240*q - 30960*q^2 - 525120*q^3 - 3963120*q^4 - 18750240*q^5 + O(q^6)
sage: DE6 == (-1/2) * E4^2
True
The Serre derivative raises the weight of homogeneous elements by 2::
sage: F = E6 + E4 * E2
sage: F.weight()
6
sage: F.serre_derivative().weight()
8
"""
# initial variables:
QM = self.parent()
R = QM.base_ring()
E2 = QM.gen(0)
E4 = QM.gen(1)

# compute the derivative of E2: q*dE2/dq
E2deriv = R(12).inverse_of_unit() * (E2 ** 2 - E4)

# sum the Serre derivative of each monomial of the form: f * E2^n
# they are equal to:
# [E2^n * serre_deriv(f)] + [n * f * E2^(n-1) * D(E2)] - [n/6 * f * E2^(n+1)]
# = A + B - C
der = QM.zero()
u6 = R(6).inverse_of_unit()
for n, f in enumerate(self._polynomial.coefficients(sparse=False)):
if n == 0:
der += QM(f.serre_derivative())
else:
A = (E2 ** n) * f.serre_derivative()
B = R(n) * f * E2 ** (n - 1) * E2deriv
C = R(n) * u6 * E2 ** (n + 1) * f
der += QM(A + B - C)
return der

def derivative(self):
r"""
Return the derivative `q \frac{d}{dq}` of the given quasimodular form.
If the form is not homogeneous, then this method sums the derivative of
each homogeneous component.
EXAMPLES::
sage: QM = QuasiModularForms(1)
sage: E2, E4, E6 = QM.gens()
sage: dE2 = E2.derivative(); dE2
-24*q - 144*q^2 - 288*q^3 - 672*q^4 - 720*q^5 + O(q^6)
sage: dE2 == (E2^2 - E4)/12 # Ramanujan identity
True
sage: dE4 = E4.derivative(); dE4
240*q + 4320*q^2 + 20160*q^3 + 70080*q^4 + 151200*q^5 + O(q^6)
sage: dE4 == (E2 * E4 - E6)/3 # Ramanujan identity
True
sage: dE6 = E6.derivative(); dE6
-504*q - 33264*q^2 - 368928*q^3 - 2130912*q^4 - 7877520*q^5 + O(q^6)
sage: dE6 == (E2 * E6 - E4^2)/2 # Ramanujan identity
True
Note that the derivative of a modular form is not necessarily a modular form::
sage: dE4.is_modular_form()
False
sage: dE4.weight()
6
"""
QM = self.parent()
E2 = QM.gen(0)
R = self.base_ring()
u = R(12).inverse_of_unit()
hom_comp = self.homogeneous_components()

return sum(f.serre_derivative() + R(k) * u * f * E2 for k, f in hom_comp.items())

0 comments on commit c855f39

Please sign in to comment.