Overview of Hecke triangle groups and modular forms for Hecke triangle groups¶
AUTHORS:
Jonas Jermann (2013): initial version
Hecke triangle groups and elements:¶
Hecke triangle group: The Von Dyck group corresponding to the triangle group with angles
(pi/2, pi/n, 0)
forn=3, 4, 5, ...
, generated by the conformal circle inversionS
and by the translationT
bylambda=2*cos(pi/n)
. I.e. the subgroup of orientation preserving elements of the triangle group generated by reflections along the boundaries of the above hyperbolic triangle. The group is arithmetic iffn=3, 4, 6, infinity
.The group elements correspond to matrices over ZZ[lambda], namely the corresponding order in the number field defined by the minimal polynomial of lambda (which embeds into
AlgebraicReal
accordingly).An exact symbolic expression of the corresponding transfinite diameter
d
(which is used as a formal parameter for Fourier expansion of modular forms) can be obtained. For arithmetic groups the (correct) rational number is returned instead.Basic matrices like
S, T, U, V(j)
are available.EXAMPLES:
sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(12) sage: G Hecke triangle group for n = 12 sage: G.is_arithmetic() False sage: G.dvalue() e^(2*euler_gamma - 4*pi/(sqrt(6) + sqrt(2)) + psi(19/24) + psi(17/24)) sage: AA(G.lam()) 1.9318516525781...? sage: G = HeckeTriangleGroup(6) sage: G Hecke triangle group for n = 6 sage: G.is_arithmetic() True sage: G.dvalue() 1/108 sage: AA(G.lam()) == AA(sqrt(3)) True sage: G.gens() ( [ 0 -1] [ 1 lam] [ 1 0], [ 0 1] ) sage: G.U()^3 [ lam -2] [ 2 -lam] sage: G.U().parent() Hecke triangle group for n = 6 sage: G.U().matrix().parent() Full MatrixSpace of 2 by 2 dense matrices over Maximal Order generated by lam in Number Field in lam with defining polynomial x^2 - 3 with lam = 1.732050807568878?
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup >>> G = HeckeTriangleGroup(Integer(12)) >>> G Hecke triangle group for n = 12 >>> G.is_arithmetic() False >>> G.dvalue() e^(2*euler_gamma - 4*pi/(sqrt(6) + sqrt(2)) + psi(19/24) + psi(17/24)) >>> AA(G.lam()) 1.9318516525781...? >>> G = HeckeTriangleGroup(Integer(6)) >>> G Hecke triangle group for n = 6 >>> G.is_arithmetic() True >>> G.dvalue() 1/108 >>> AA(G.lam()) == AA(sqrt(Integer(3))) True >>> G.gens() ( [ 0 -1] [ 1 lam] [ 1 0], [ 0 1] ) >>> G.U()**Integer(3) [ lam -2] [ 2 -lam] >>> G.U().parent() Hecke triangle group for n = 6 >>> G.U().matrix().parent() Full MatrixSpace of 2 by 2 dense matrices over Maximal Order generated by lam in Number Field in lam with defining polynomial x^2 - 3 with lam = 1.732050807568878?
from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup G = HeckeTriangleGroup(12) G G.is_arithmetic() G.dvalue() AA(G.lam()) G = HeckeTriangleGroup(6) G G.is_arithmetic() G.dvalue() AA(G.lam()) == AA(sqrt(3)) G.gens() G.U()^3 G.U().parent() G.U().matrix().parent()
Decomposition into product of generators: It is possible to decompose any group element into products of generators the
S
andT
. In particular this allows to check whether a given matrix indeed is a group element.It also allows one to calculate the automorphy factor of a modular form for the Hecke triangle group for arbitrary arguments.
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(6) sage: G.element_repr_method("basic") sage: A = G.V(2)*G.V(3)^(-2) sage: (L, sgn) = A.word_S_T() sage: L (S, T^(-2), S, T^(-1), S, T^(-1)) sage: sgn -1 sage: sgn.parent() Hecke triangle group for n = 6 sage: G(matrix([[-1, 1+G.lam()],[0, -1]])) Traceback (most recent call last): ... TypeError: The matrix is not an element of Hecke triangle group for n = 6, up to equivalence it identifies two nonequivalent points. sage: G(matrix([[-1, G.lam()],[0, -1]])) -T^(-1) sage: G.element_repr_method("basic") sage: from sage.modular.modform_hecketriangle.space import ModularForms sage: MF = ModularForms(G, k=4, ep=1) sage: z = AlgebraicField()(1+i/2) sage: MF.aut_factor(A, z) 37.62113890008...? + 12.18405525839...?*I
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup >>> G = HeckeTriangleGroup(Integer(6)) >>> G.element_repr_method("basic") >>> A = G.V(Integer(2))*G.V(Integer(3))**(-Integer(2)) >>> (L, sgn) = A.word_S_T() >>> L (S, T^(-2), S, T^(-1), S, T^(-1)) >>> sgn -1 >>> sgn.parent() Hecke triangle group for n = 6 >>> G(matrix([[-Integer(1), Integer(1)+G.lam()],[Integer(0), -Integer(1)]])) Traceback (most recent call last): ... TypeError: The matrix is not an element of Hecke triangle group for n = 6, up to equivalence it identifies two nonequivalent points. >>> G(matrix([[-Integer(1), G.lam()],[Integer(0), -Integer(1)]])) -T^(-1) >>> G.element_repr_method("basic") >>> from sage.modular.modform_hecketriangle.space import ModularForms >>> MF = ModularForms(G, k=Integer(4), ep=Integer(1)) >>> z = AlgebraicField()(Integer(1)+i/Integer(2)) >>> MF.aut_factor(A, z) 37.62113890008...? + 12.18405525839...?*I
from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup G = HeckeTriangleGroup(6) G.element_repr_method("basic") A = G.V(2)*G.V(3)^(-2) (L, sgn) = A.word_S_T() L sgn sgn.parent() G(matrix([[-1, 1+G.lam()],[0, -1]])) G(matrix([[-1, G.lam()],[0, -1]])) G.element_repr_method("basic") from sage.modular.modform_hecketriangle.space import ModularForms MF = ModularForms(G, k=4, ep=1) z = AlgebraicField()(1+i/2) MF.aut_factor(A, z)
Representation of elements: An element can be represented in several ways:
As a matrix over the base ring (default)
As a product of the generators
S
andT
As a product of basic blocks conjugated by some element
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=5) sage: el = G.S()*G.T(3)*G.S()*G.T(-2) sage: G.element_repr_method("default") sage: el [ -1 2*lam] [ 3*lam -6*lam - 7] sage: G.element_repr_method("basic") sage: el S*T^3*S*T^(-2) sage: G.element_repr_method("block") sage: el -(S*T^3) * (V(4)^2*V(1)^3) * (S*T^3)^(-1) sage: G.element_repr_method("conj") sage: el [-V(4)^2*V(1)^3] sage: G.element_repr_method("default")
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup >>> G = HeckeTriangleGroup(n=Integer(5)) >>> el = G.S()*G.T(Integer(3))*G.S()*G.T(-Integer(2)) >>> G.element_repr_method("default") >>> el [ -1 2*lam] [ 3*lam -6*lam - 7] >>> G.element_repr_method("basic") >>> el S*T^3*S*T^(-2) >>> G.element_repr_method("block") >>> el -(S*T^3) * (V(4)^2*V(1)^3) * (S*T^3)^(-1) >>> G.element_repr_method("conj") >>> el [-V(4)^2*V(1)^3] >>> G.element_repr_method("default")
from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup G = HeckeTriangleGroup(n=5) el = G.S()*G.T(3)*G.S()*G.T(-2) G.element_repr_method("default") el G.element_repr_method("basic") el G.element_repr_method("block") el G.element_repr_method("conj") el G.element_repr_method("default")
Group action on the (extended) upper half plane: The group action of Hecke triangle groups on the (extended) upper half plane (by linear fractional transformations) is implemented. The implementation is not based on a specific upper half plane model but is defined formally (for arbitrary arguments) instead.
It is possible to determine the group translate of an element in the classic (strict) fundamental domain for the group, together with the corresponding mapping group element.
The corresponding action of the group on itself by conjugation is supported as well.
The usual \(slash\)-operator for even integer weights is also available. It acts on rational functions (resp. polynomials). For modular forms an evaluation argument is required.
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=7) sage: G.element_repr_method("basic") sage: G.S().acton(i + exp(-2)) -1/(e^(-2) + I) sage: A = G.V(2)*G.V(3)^(-2) sage: A -S*T^(-2)*S*T^(-1)*S*T^(-1) sage: A.acton(CC(i + exp(-2))) 0.344549645079... + 0.0163901095115...*I sage: G.S().acton(A) -T^(-2)*S*T^(-1)*S*T^(-1)*S sage: z = AlgebraicField()(4 + 1/7*i) sage: G.in_FD(z) False sage: (A, w) = G.get_FD(z) sage: A T^2*S*T^(-1)*S sage: w 0.516937798396...? + 0.964078044600...?*I sage: A.acton(w) == z True sage: G.in_FD(w) True sage: z = PolynomialRing(G.base_ring(), 'z').gen() sage: rat = z^2 + 1/(z-G.lam()) sage: G.S().slash(rat) (z^6 - lam*z^4 - z^3)/(-lam*z^4 - z^3) sage: G.element_repr_method("default")
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup >>> G = HeckeTriangleGroup(n=Integer(7)) >>> G.element_repr_method("basic") >>> G.S().acton(i + exp(-Integer(2))) -1/(e^(-2) + I) >>> A = G.V(Integer(2))*G.V(Integer(3))**(-Integer(2)) >>> A -S*T^(-2)*S*T^(-1)*S*T^(-1) >>> A.acton(CC(i + exp(-Integer(2)))) 0.344549645079... + 0.0163901095115...*I >>> G.S().acton(A) -T^(-2)*S*T^(-1)*S*T^(-1)*S >>> z = AlgebraicField()(Integer(4) + Integer(1)/Integer(7)*i) >>> G.in_FD(z) False >>> (A, w) = G.get_FD(z) >>> A T^2*S*T^(-1)*S >>> w 0.516937798396...? + 0.964078044600...?*I >>> A.acton(w) == z True >>> G.in_FD(w) True >>> z = PolynomialRing(G.base_ring(), 'z').gen() >>> rat = z**Integer(2) + Integer(1)/(z-G.lam()) >>> G.S().slash(rat) (z^6 - lam*z^4 - z^3)/(-lam*z^4 - z^3) >>> G.element_repr_method("default")
from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup G = HeckeTriangleGroup(n=7) G.element_repr_method("basic") G.S().acton(i + exp(-2)) A = G.V(2)*G.V(3)^(-2) A A.acton(CC(i + exp(-2))) G.S().acton(A) z = AlgebraicField()(4 + 1/7*i) G.in_FD(z) (A, w) = G.get_FD(z) A w A.acton(w) == z G.in_FD(w) z = PolynomialRing(G.base_ring(), 'z').gen() rat = z^2 + 1/(z-G.lam()) G.S().slash(rat) G.element_repr_method("default")
Basic properties of group elements: The trace, sign (based on the trace), discriminant and elliptic/parabolic/hyperbolic type are available.
Group elements can be displayed/represented in several ways:
As matrices over the base ring.
As a word in (powers of) the generators
S
andT
.As a word in (powers of) basic block matrices
V(j)
(resp.U, S
in the elliptic case) together with the conjugation matrix that maps the element to this form (also see below).
For the case
n=infinity
the last method is not properly implemented.EXAMPLES:
sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=7) sage: A = -G.V(2)*G.V(3)^(-2) sage: print(A.string_repr("default")) [ lam -lam^2 + 1] [ 2*lam^2 - 1 -2*lam^2 - lam + 2] sage: print(A.string_repr("basic")) S*T^(-2)*S*T^(-1)*S*T^(-1) sage: print(A.string_repr("block")) -(-S*T^(-1)*S) * (V(3)) * (-S*T^(-1)*S)^(-1) sage: print(A.string_repr("conj")) [-V(3)] sage: A.trace() -2*lam^2 + 2 sage: A.sign() [-1 0] [ 0 -1] sage: A.discriminant() 4*lam^2 + 4*lam - 4 sage: A.is_elliptic() False sage: A.is_hyperbolic() True
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup >>> G = HeckeTriangleGroup(n=Integer(7)) >>> A = -G.V(Integer(2))*G.V(Integer(3))**(-Integer(2)) >>> print(A.string_repr("default")) [ lam -lam^2 + 1] [ 2*lam^2 - 1 -2*lam^2 - lam + 2] >>> print(A.string_repr("basic")) S*T^(-2)*S*T^(-1)*S*T^(-1) >>> print(A.string_repr("block")) -(-S*T^(-1)*S) * (V(3)) * (-S*T^(-1)*S)^(-1) >>> print(A.string_repr("conj")) [-V(3)] >>> A.trace() -2*lam^2 + 2 >>> A.sign() [-1 0] [ 0 -1] >>> A.discriminant() 4*lam^2 + 4*lam - 4 >>> A.is_elliptic() False >>> A.is_hyperbolic() True
from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup G = HeckeTriangleGroup(n=7) A = -G.V(2)*G.V(3)^(-2) print(A.string_repr("default")) print(A.string_repr("basic")) print(A.string_repr("block")) print(A.string_repr("conj")) A.trace() A.sign() A.discriminant() A.is_elliptic() A.is_hyperbolic()
Fixed points: Elliptic, parabolic or hyperbolic fixed points of group can be obtained. They are implemented as a (relative) quadratic extension (given by the square root of the discriminant) of the base ring. It is possible to query the correct embedding into a given field.
Note that for hyperbolic (and parabolic) fixed points there is a 1-1 correspondence with primitive hyperbolic/parabolic group elements (at least if
n < infinity
). The group action on fixed points resp. on matrices is compatible with this correspondence.EXAMPLES:
sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=7) sage: A = G.S() sage: A.fixed_points() (1/2*e, -1/2*e) sage: A.fixed_points(embedded=True) (I, -I) sage: A = G.U() sage: A.fixed_points() (1/2*e + 1/2*lam, -1/2*e + 1/2*lam) sage: A.fixed_points(embedded=True) (0.9009688679024...? + 0.4338837391175...?*I, 0.9009688679024...? - 0.4338837391175...?*I) sage: A = -G.V(2)*G.V(3)^(-2) sage: A.fixed_points() ((-3/7*lam^2 + 2/7*lam + 11/14)*e - 1/7*lam^2 + 3/7*lam + 3/7, (3/7*lam^2 - 2/7*lam - 11/14)*e - 1/7*lam^2 + 3/7*lam + 3/7) sage: A.fixed_points(embedded=True) (0.3707208390178...?, 1.103231619181...?) sage: el = A.fixed_points()[0] sage: F = A.root_extension_field() sage: F == el.parent() True sage: A.root_extension_embedding(CC) Relative number field morphism: From: Number Field in e with defining polynomial x^2 - 4*lam^2 - 4*lam + 4 over its base field To: Complex Field with 53 bits of precision Defn: e |--> 4.02438434522465 lam |--> 1.80193773580484 sage: G.V(2).acton(A).fixed_points()[0] == G.V(2).acton(el) True
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup >>> G = HeckeTriangleGroup(n=Integer(7)) >>> A = G.S() >>> A.fixed_points() (1/2*e, -1/2*e) >>> A.fixed_points(embedded=True) (I, -I) >>> A = G.U() >>> A.fixed_points() (1/2*e + 1/2*lam, -1/2*e + 1/2*lam) >>> A.fixed_points(embedded=True) (0.9009688679024...? + 0.4338837391175...?*I, 0.9009688679024...? - 0.4338837391175...?*I) >>> A = -G.V(Integer(2))*G.V(Integer(3))**(-Integer(2)) >>> A.fixed_points() ((-3/7*lam^2 + 2/7*lam + 11/14)*e - 1/7*lam^2 + 3/7*lam + 3/7, (3/7*lam^2 - 2/7*lam - 11/14)*e - 1/7*lam^2 + 3/7*lam + 3/7) >>> A.fixed_points(embedded=True) (0.3707208390178...?, 1.103231619181...?) >>> el = A.fixed_points()[Integer(0)] >>> F = A.root_extension_field() >>> F == el.parent() True >>> A.root_extension_embedding(CC) Relative number field morphism: From: Number Field in e with defining polynomial x^2 - 4*lam^2 - 4*lam + 4 over its base field To: Complex Field with 53 bits of precision Defn: e |--> 4.02438434522465 lam |--> 1.80193773580484 >>> G.V(Integer(2)).acton(A).fixed_points()[Integer(0)] == G.V(Integer(2)).acton(el) True
from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup G = HeckeTriangleGroup(n=7) A = G.S() A.fixed_points() A.fixed_points(embedded=True) A = G.U() A.fixed_points() A.fixed_points(embedded=True) A = -G.V(2)*G.V(3)^(-2) A.fixed_points() A.fixed_points(embedded=True) el = A.fixed_points()[0] F = A.root_extension_field() F == el.parent() A.root_extension_embedding(CC) G.V(2).acton(A).fixed_points()[0] == G.V(2).acton(el)
Lambda-continued fractions: For parabolic or hyperbolic elements (resp. their corresponding fixed point) the (negative) lambda-continued fraction expansion is eventually periodic. The lambda-CF (i.e. the preperiod and period) is calculated exactly.
In particular this allows to determine primitive and reduced generators of group elements and the corresponding primitive power of the element.
The case
n=infinity
is not properly implemented.EXAMPLES:
sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=7) sage: G.element_repr_method("block") sage: G.V(6).continued_fraction() ((1,), (1, 1, 1, 1, 2)) sage: (-G.V(2)).continued_fraction() ((1,), (2,)) sage: A = -(G.V(2)*G.V(3)^(-2))^2 sage: A.is_primitive() False sage: A.primitive_power() 2 sage: A.is_reduced() False sage: A.continued_fraction() ((1, 1, 1, 1), (1, 2)) sage: B = A.primitive_part() sage: B (-S*T^(-1)*S) * (V(3)) * (-S*T^(-1)*S)^(-1) sage: B.is_primitive() True sage: B.is_reduced() False sage: B.continued_fraction() ((1, 1, 1, 1), (1, 2)) sage: A == A.sign() * B^A.primitive_power() True sage: B = A.reduce() sage: B (T*S*T) * (V(3)) * (T*S*T)^(-1) sage: B.is_primitive() True sage: B.is_reduced() True sage: B.continued_fraction() ((), (1, 2)) sage: G.element_repr_method("default")
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup >>> G = HeckeTriangleGroup(n=Integer(7)) >>> G.element_repr_method("block") >>> G.V(Integer(6)).continued_fraction() ((1,), (1, 1, 1, 1, 2)) >>> (-G.V(Integer(2))).continued_fraction() ((1,), (2,)) >>> A = -(G.V(Integer(2))*G.V(Integer(3))**(-Integer(2)))**Integer(2) >>> A.is_primitive() False >>> A.primitive_power() 2 >>> A.is_reduced() False >>> A.continued_fraction() ((1, 1, 1, 1), (1, 2)) >>> B = A.primitive_part() >>> B (-S*T^(-1)*S) * (V(3)) * (-S*T^(-1)*S)^(-1) >>> B.is_primitive() True >>> B.is_reduced() False >>> B.continued_fraction() ((1, 1, 1, 1), (1, 2)) >>> A == A.sign() * B**A.primitive_power() True >>> B = A.reduce() >>> B (T*S*T) * (V(3)) * (T*S*T)^(-1) >>> B.is_primitive() True >>> B.is_reduced() True >>> B.continued_fraction() ((), (1, 2)) >>> G.element_repr_method("default")
from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup G = HeckeTriangleGroup(n=7) G.element_repr_method("block") G.V(6).continued_fraction() (-G.V(2)).continued_fraction() A = -(G.V(2)*G.V(3)^(-2))^2 A.is_primitive() A.primitive_power() A.is_reduced() A.continued_fraction() B = A.primitive_part() B B.is_primitive() B.is_reduced() B.continued_fraction() A == A.sign() * B^A.primitive_power() B = A.reduce() B B.is_primitive() B.is_reduced() B.continued_fraction() G.element_repr_method("default")
Reduced and simple elements, Hecke-symmetric elements: For primitive conjugacy classes of hyperbolic elements the cycle of reduced elements can be obtain as well as all simple elements. It is also possible to determine whether a class is Hecke-symmetric.
The case
n=infinity
is not properly implemented.EXAMPLES:
sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=5) sage: el = G.V(1)^2*G.V(2)*G.V(4) sage: R = el.reduced_elements() sage: [v.continued_fraction() for v in R] [((), (2, 1, 1, 4)), ((), (1, 1, 4, 2)), ((), (1, 4, 2, 1)), ((), (4, 2, 1, 1))] sage: el = G.V(1)^2*G.V(2)*G.V(4) sage: R = el.simple_elements() sage: [v.is_simple() for v in R] [True, True, True, True] sage: (fp1, fp2) = R[2].fixed_points(embedded=True) sage: fp2 < 0 < fp1 True sage: el = G.V(2) sage: el.is_hecke_symmetric() False sage: (el.simple_fixed_point_set(), el.inverse().simple_fixed_point_set()) ({1/2*e, (-1/2*lam + 1/2)*e}, {-1/2*e, (1/2*lam - 1/2)*e}) sage: el = G.V(2)*G.V(3) sage: el.is_hecke_symmetric() True sage: el.simple_fixed_point_set() == el.inverse().simple_fixed_point_set() True
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup >>> G = HeckeTriangleGroup(n=Integer(5)) >>> el = G.V(Integer(1))**Integer(2)*G.V(Integer(2))*G.V(Integer(4)) >>> R = el.reduced_elements() >>> [v.continued_fraction() for v in R] [((), (2, 1, 1, 4)), ((), (1, 1, 4, 2)), ((), (1, 4, 2, 1)), ((), (4, 2, 1, 1))] >>> el = G.V(Integer(1))**Integer(2)*G.V(Integer(2))*G.V(Integer(4)) >>> R = el.simple_elements() >>> [v.is_simple() for v in R] [True, True, True, True] >>> (fp1, fp2) = R[Integer(2)].fixed_points(embedded=True) >>> fp2 < Integer(0) < fp1 True >>> el = G.V(Integer(2)) >>> el.is_hecke_symmetric() False >>> (el.simple_fixed_point_set(), el.inverse().simple_fixed_point_set()) ({1/2*e, (-1/2*lam + 1/2)*e}, {-1/2*e, (1/2*lam - 1/2)*e}) >>> el = G.V(Integer(2))*G.V(Integer(3)) >>> el.is_hecke_symmetric() True >>> el.simple_fixed_point_set() == el.inverse().simple_fixed_point_set() True
from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup G = HeckeTriangleGroup(n=5) el = G.V(1)^2*G.V(2)*G.V(4) R = el.reduced_elements() [v.continued_fraction() for v in R] el = G.V(1)^2*G.V(2)*G.V(4) R = el.simple_elements() [v.is_simple() for v in R] (fp1, fp2) = R[2].fixed_points(embedded=True) fp2 < 0 < fp1 el = G.V(2) el.is_hecke_symmetric() (el.simple_fixed_point_set(), el.inverse().simple_fixed_point_set()) el = G.V(2)*G.V(3) el.is_hecke_symmetric() el.simple_fixed_point_set() == el.inverse().simple_fixed_point_set()
Rational period functions: For each primitive (hyperbolic) conjugacy classes and each even weight
k
we can associate a corresponding rational period function. I.e. a rational functionq
of weightk
which satisfies:q | S == 0
andq + q|U + ... + q|U^(n-1) == 0
, whereS
,U
are the corresponding group elements and|
is the usual \(slash-operator\) of weightk
.The set of all rational period function is expected to be generated by such functions.
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=5) sage: S = G.S() sage: U = G.U() sage: def is_rpf(f, k=None): ....: if not f + S.slash(f, k=k) == 0: ....: return False ....: if not sum([(U^m).slash(f, k=k) for m in range(G.n())]) == 0: ....: return False ....: return True sage: z = PolynomialRing(G.base_ring(), 'z').gen() sage: [is_rpf(1 - z^(-k), k=k) for k in range(-6, 6, 2)] # long time [True, True, True, True, True, True] sage: [is_rpf(1/z, k=k) for k in range(-6, 6, 2)] [False, False, False, False, True, False] sage: el = G.V(2) sage: el.is_hecke_symmetric() False sage: rpf = el.rational_period_function(-4) sage: is_rpf(rpf) True sage: rpf -lam*z^4 + lam sage: rpf = el.rational_period_function(-2) sage: is_rpf(rpf) True sage: rpf (lam + 1)*z^2 - lam - 1 sage: el.rational_period_function(0) == 0 True sage: rpf = el.rational_period_function(2) sage: is_rpf(rpf) True sage: rpf ((lam + 1)*z^2 - lam - 1)/(lam*z^4 + (-lam - 2)*z^2 + lam) sage: el = G.V(2)*G.V(3) sage: el.is_hecke_symmetric() True sage: el.rational_period_function(-4) == 0 True sage: rpf = el.rational_period_function(-2) sage: rpf (8*lam + 4)*z^2 - 8*lam - 4 sage: rpf = el.rational_period_function(2) sage: is_rpf(rpf) True sage: rpf.denominator() (144*lam + 89)*z^8 + (-618*lam - 382)*z^6 + (951*lam + 588)*z^4 + (-618*lam - 382)*z^2 + 144*lam + 89 sage: el.rational_period_function(4) == 0 True sage: G = HeckeTriangleGroup(n=4) sage: G.rational_period_functions(k=4, D=12) [(z^4 - 1)/z^4] sage: G.rational_period_functions(k=2, D=14) [(z^2 - 1)/z^2, 1/z, (24*z^6 - 120*z^4 + 120*z^2 - 24)/(9*z^8 - 80*z^6 + 146*z^4 - 80*z^2 + 9), (24*z^6 - 120*z^4 + 120*z^2 - 24)/(9*z^8 - 80*z^6 + 146*z^4 - 80*z^2 + 9)]
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup >>> G = HeckeTriangleGroup(n=Integer(5)) >>> S = G.S() >>> U = G.U() >>> def is_rpf(f, k=None): ... if not f + S.slash(f, k=k) == Integer(0): ... return False ... if not sum([(U**m).slash(f, k=k) for m in range(G.n())]) == Integer(0): ... return False ... return True >>> z = PolynomialRing(G.base_ring(), 'z').gen() >>> [is_rpf(Integer(1) - z**(-k), k=k) for k in range(-Integer(6), Integer(6), Integer(2))] # long time [True, True, True, True, True, True] >>> [is_rpf(Integer(1)/z, k=k) for k in range(-Integer(6), Integer(6), Integer(2))] [False, False, False, False, True, False] >>> el = G.V(Integer(2)) >>> el.is_hecke_symmetric() False >>> rpf = el.rational_period_function(-Integer(4)) >>> is_rpf(rpf) True >>> rpf -lam*z^4 + lam >>> rpf = el.rational_period_function(-Integer(2)) >>> is_rpf(rpf) True >>> rpf (lam + 1)*z^2 - lam - 1 >>> el.rational_period_function(Integer(0)) == Integer(0) True >>> rpf = el.rational_period_function(Integer(2)) >>> is_rpf(rpf) True >>> rpf ((lam + 1)*z^2 - lam - 1)/(lam*z^4 + (-lam - 2)*z^2 + lam) >>> el = G.V(Integer(2))*G.V(Integer(3)) >>> el.is_hecke_symmetric() True >>> el.rational_period_function(-Integer(4)) == Integer(0) True >>> rpf = el.rational_period_function(-Integer(2)) >>> rpf (8*lam + 4)*z^2 - 8*lam - 4 >>> rpf = el.rational_period_function(Integer(2)) >>> is_rpf(rpf) True >>> rpf.denominator() (144*lam + 89)*z^8 + (-618*lam - 382)*z^6 + (951*lam + 588)*z^4 + (-618*lam - 382)*z^2 + 144*lam + 89 >>> el.rational_period_function(Integer(4)) == Integer(0) True >>> G = HeckeTriangleGroup(n=Integer(4)) >>> G.rational_period_functions(k=Integer(4), D=Integer(12)) [(z^4 - 1)/z^4] >>> G.rational_period_functions(k=Integer(2), D=Integer(14)) [(z^2 - 1)/z^2, 1/z, (24*z^6 - 120*z^4 + 120*z^2 - 24)/(9*z^8 - 80*z^6 + 146*z^4 - 80*z^2 + 9), (24*z^6 - 120*z^4 + 120*z^2 - 24)/(9*z^8 - 80*z^6 + 146*z^4 - 80*z^2 + 9)]
from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup G = HeckeTriangleGroup(n=5) S = G.S() U = G.U() def is_rpf(f, k=None): if not f + S.slash(f, k=k) == 0: return False if not sum([(U^m).slash(f, k=k) for m in range(G.n())]) == 0: return False return True z = PolynomialRing(G.base_ring(), 'z').gen() [is_rpf(1 - z^(-k), k=k) for k in range(-6, 6, 2)] # long time [is_rpf(1/z, k=k) for k in range(-6, 6, 2)] el = G.V(2) el.is_hecke_symmetric() rpf = el.rational_period_function(-4) is_rpf(rpf) rpf rpf = el.rational_period_function(-2) is_rpf(rpf) rpf el.rational_period_function(0) == 0 rpf = el.rational_period_function(2) is_rpf(rpf) rpf el = G.V(2)*G.V(3) el.is_hecke_symmetric() el.rational_period_function(-4) == 0 rpf = el.rational_period_function(-2) rpf rpf = el.rational_period_function(2) is_rpf(rpf) rpf.denominator() el.rational_period_function(4) == 0 G = HeckeTriangleGroup(n=4) G.rational_period_functions(k=4, D=12) G.rational_period_functions(k=2, D=14)
Block decomposition of elements: For each group element a very specific conjugacy representative can be obtained. For hyperbolic and parabolic elements the representative is a product
V(j)
-matrices. They all have nonnegative trace and the number of factors is called the block length of the element (which is implemented).Note: For this decomposition special care is given to the sign (of the trace) of the matrices.
The case
n=infinity
for everything above is not properly implemented.EXAMPLES:
sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=7) sage: G.element_repr_method("block") sage: A = -G.V(2)*G.V(6)^3*G.V(3) sage: A -(T*S*T) * (V(6)^3*V(3)*V(2)) * (T*S*T)^(-1) sage: A.sign() -1 sage: (L, R, sgn) = A.block_decomposition() sage: L ((-S*T^(-1)*S) * (V(6)^3) * (-S*T^(-1)*S)^(-1), (T*S*T*S*T) * (V(3)) * (T*S*T*S*T)^(-1), (T*S*T) * (V(2)) * (T*S*T)^(-1)) sage: prod(L).sign() 1 sage: A == sgn * (R.acton(prod(L))) True sage: t = A.block_length() sage: t 5 sage: AA(A.discriminant()) >= AA(t^2 * G.lam() - 4) True
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup >>> G = HeckeTriangleGroup(n=Integer(7)) >>> G.element_repr_method("block") >>> A = -G.V(Integer(2))*G.V(Integer(6))**Integer(3)*G.V(Integer(3)) >>> A -(T*S*T) * (V(6)^3*V(3)*V(2)) * (T*S*T)^(-1) >>> A.sign() -1 >>> (L, R, sgn) = A.block_decomposition() >>> L ((-S*T^(-1)*S) * (V(6)^3) * (-S*T^(-1)*S)^(-1), (T*S*T*S*T) * (V(3)) * (T*S*T*S*T)^(-1), (T*S*T) * (V(2)) * (T*S*T)^(-1)) >>> prod(L).sign() 1 >>> A == sgn * (R.acton(prod(L))) True >>> t = A.block_length() >>> t 5 >>> AA(A.discriminant()) >= AA(t**Integer(2) * G.lam() - Integer(4)) True
from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup G = HeckeTriangleGroup(n=7) G.element_repr_method("block") A = -G.V(2)*G.V(6)^3*G.V(3) A A.sign() (L, R, sgn) = A.block_decomposition() L prod(L).sign() A == sgn * (R.acton(prod(L))) t = A.block_length() t AA(A.discriminant()) >= AA(t^2 * G.lam() - 4)
Class number and class representatives: The block length provides a lower bound for the discriminant. This allows to enlist all (representatives of) matrices of (or up to) a given discriminant.
Using the 1-1 correspondence with hyperbolic fixed points (and certain hyperbolic binary quadratic forms) this makes it possible to calculate the corresponding class number (number of conjugacy classes for a given discriminant).
It also allows to list all occurring discriminants up to some bound. Or to enlist all reduced/simple elements resp. their corresponding hyperbolic fixed points for the given discriminant.
Warning: The currently used algorithm is very slow!
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: G = HeckeTriangleGroup(n=4) sage: G.element_repr_method("basic") sage: G.is_discriminant(68) True sage: G.class_number(14) 2 sage: G.list_discriminants(D=68) [4, 12, 14, 28, 32, 46, 60, 68] sage: G.list_discriminants(D=0, hyperbolic=False, primitive=False) [-4, -2, 0] sage: G.class_number(68) 4 sage: sorted(G.class_representatives(68)) [S*T^(-5)*S*T^(-1)*S, S*T^(-2)*S*T^(-1)*S*T, T*S*T^5, -S*T^(-1)*S*T^2*S*T] sage: R = G.reduced_elements(68) sage: all(v.is_reduced() for v in R) # long time True sage: R = G.simple_elements(68) sage: all(v.is_simple() for v in R) # long time True sage: G.element_repr_method("default") sage: G = HeckeTriangleGroup(n=5) sage: G.element_repr_method("basic") sage: G.list_discriminants(9*G.lam() + 5) [4*lam, 7*lam + 6, 9*lam + 5] sage: G.list_discriminants(D=0, hyperbolic=False, primitive=False) [-4, -lam - 2, lam - 3, 0] sage: G.class_number(9*G.lam() + 5) 2 sage: sorted(G.class_representatives(9*G.lam() + 5)) [S*T^(-2)*S*T^(-1)*S, T*S*T^2] sage: R = G.reduced_elements(9*G.lam() + 5) sage: all(v.is_reduced() for v in R) # long time True sage: R = G.simple_elements(7*G.lam() + 6) sage: for v in R: print(v.string_repr("default")) [lam + 2 lam] [ lam 1] [ 1 lam] [ lam lam + 2] sage: G.element_repr_method("default")
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup >>> G = HeckeTriangleGroup(n=Integer(4)) >>> G.element_repr_method("basic") >>> G.is_discriminant(Integer(68)) True >>> G.class_number(Integer(14)) 2 >>> G.list_discriminants(D=Integer(68)) [4, 12, 14, 28, 32, 46, 60, 68] >>> G.list_discriminants(D=Integer(0), hyperbolic=False, primitive=False) [-4, -2, 0] >>> G.class_number(Integer(68)) 4 >>> sorted(G.class_representatives(Integer(68))) [S*T^(-5)*S*T^(-1)*S, S*T^(-2)*S*T^(-1)*S*T, T*S*T^5, -S*T^(-1)*S*T^2*S*T] >>> R = G.reduced_elements(Integer(68)) >>> all(v.is_reduced() for v in R) # long time True >>> R = G.simple_elements(Integer(68)) >>> all(v.is_simple() for v in R) # long time True >>> G.element_repr_method("default") >>> G = HeckeTriangleGroup(n=Integer(5)) >>> G.element_repr_method("basic") >>> G.list_discriminants(Integer(9)*G.lam() + Integer(5)) [4*lam, 7*lam + 6, 9*lam + 5] >>> G.list_discriminants(D=Integer(0), hyperbolic=False, primitive=False) [-4, -lam - 2, lam - 3, 0] >>> G.class_number(Integer(9)*G.lam() + Integer(5)) 2 >>> sorted(G.class_representatives(Integer(9)*G.lam() + Integer(5))) [S*T^(-2)*S*T^(-1)*S, T*S*T^2] >>> R = G.reduced_elements(Integer(9)*G.lam() + Integer(5)) >>> all(v.is_reduced() for v in R) # long time True >>> R = G.simple_elements(Integer(7)*G.lam() + Integer(6)) >>> for v in R: print(v.string_repr("default")) [lam + 2 lam] [ lam 1] [ 1 lam] [ lam lam + 2] >>> G.element_repr_method("default")
from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup G = HeckeTriangleGroup(n=4) G.element_repr_method("basic") G.is_discriminant(68) G.class_number(14) G.list_discriminants(D=68) G.list_discriminants(D=0, hyperbolic=False, primitive=False) G.class_number(68) sorted(G.class_representatives(68)) R = G.reduced_elements(68) all(v.is_reduced() for v in R) # long time R = G.simple_elements(68) all(v.is_simple() for v in R) # long time G.element_repr_method("default") G = HeckeTriangleGroup(n=5) G.element_repr_method("basic") G.list_discriminants(9*G.lam() + 5) G.list_discriminants(D=0, hyperbolic=False, primitive=False) G.class_number(9*G.lam() + 5) sorted(G.class_representatives(9*G.lam() + 5)) R = G.reduced_elements(9*G.lam() + 5) all(v.is_reduced() for v in R) # long time R = G.simple_elements(7*G.lam() + 6) for v in R: print(v.string_repr("default")) G.element_repr_method("default")
Modular forms ring and spaces for Hecke triangle groups:¶
Analytic type: The analytic type of forms, including the behavior at infinity:
Meromorphic (and meromorphic at infinity)
Weakly holomorphic (holomorphic and meromorphic at infinity)
Holomorphic (and holomorphic at infinity)
Cuspidal (holomorphic and zero at infinity)
Additionally the type specifies whether the form is modular or only quasi modular.
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.analytic_type import AnalyticType sage: AnalyticType()(["quasi", "cusp"]) quasi cuspidal
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.analytic_type import AnalyticType >>> AnalyticType()(["quasi", "cusp"]) quasi cuspidal
from sage.modular.modform_hecketriangle.analytic_type import AnalyticType AnalyticType()(["quasi", "cusp"])
Modular form (for Hecke triangle groups): A function of some analytic type which transforms like a modular form for the given group, weight
k
and multiplierepsilon
:f(z+lambda) = f(lambda)
f(-1/z) = epsilon * (z/i)^k * f(z)
The multiplier is either
1
or-1
. The weight is a rational number of the form4*(n*l+l')/(n-2) + (1-epsilon)*n/(n-2)
. Ifn
is odd, then the multiplier is unique and given by(-1)^(k*(n-2)/2)
. The space of modular forms for a given group, weight and multiplier forms a module over the base ring. It is finite dimensional if the analytic type isholomorphic
.Modular forms can be constructed in several ways:
Using some already available construction function for modular forms (those function are available for all spaces/rings and in general do not return elements of the same parent)
Specifying the form as a rational function in the basic generators (see below)
For weakly holomorphic modular forms it is possible to exactly determine the form by specifying (sufficiently many) initial coefficients of its Fourier expansion.
There is even hope (no guarantee) to determine a (exact) form from the initial numerical coefficients (see below).
By specifying the coefficients with respect to a basis of the space (if the corresponding space supports coordinate vectors)
Arithmetic combination of forms or differential operators applied to forms
The implementation is based on the implementation of the graded ring (see below). All calculations are exact (no precision argument is required). The analytic type of forms is checked during construction. The analytic type of parent spaces after arithmetic/differential operations with elements is changed (extended/reduced) accordingly.
In particular it is possible to multiply arbitrary modular forms (and end up with an element of a modular forms space). If two forms of different weight/multiplier are added then an element of the corresponding modular forms ring is returned instead.
Elements of modular forms spaces are represented by their Fourier expansion.
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.space import CuspForms, ModularForms, MeromorphicModularForms sage: MeromorphicModularForms(n=4, k=8, ep=1) MeromorphicModularForms(n=4, k=8, ep=1) over Integer Ring sage: CF = CuspForms(n=7, k=12, ep=1) sage: CF CuspForms(n=7, k=12, ep=1) over Integer Ring sage: MF = ModularForms(k=12, ep=1) sage: (x,y,z,d) = MF.pol_ring().gens()
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.space import CuspForms, ModularForms, MeromorphicModularForms >>> MeromorphicModularForms(n=Integer(4), k=Integer(8), ep=Integer(1)) MeromorphicModularForms(n=4, k=8, ep=1) over Integer Ring >>> CF = CuspForms(n=Integer(7), k=Integer(12), ep=Integer(1)) >>> CF CuspForms(n=7, k=12, ep=1) over Integer Ring >>> MF = ModularForms(k=Integer(12), ep=Integer(1)) >>> (x,y,z,d) = MF.pol_ring().gens()
from sage.modular.modform_hecketriangle.space import CuspForms, ModularForms, MeromorphicModularForms MeromorphicModularForms(n=4, k=8, ep=1) CF = CuspForms(n=7, k=12, ep=1) CF MF = ModularForms(k=12, ep=1) (x,y,z,d) = MF.pol_ring().gens()
Using existing functions:
sage: CF.Delta() q + 17/(56*d)*q^2 + 88887/(2458624*d^2)*q^3 + 941331/(481890304*d^3)*q^4 + O(q^5)
>>> from sage.all import * >>> CF.Delta() q + 17/(56*d)*q^2 + 88887/(2458624*d^2)*q^3 + 941331/(481890304*d^3)*q^4 + O(q^5)
CF.Delta()
Using rational function in the basic generators:
sage: MF(x^3) 1 + 720*q + 179280*q^2 + 16954560*q^3 + 396974160*q^4 + O(q^5)
>>> from sage.all import * >>> MF(x**Integer(3)) 1 + 720*q + 179280*q^2 + 16954560*q^3 + 396974160*q^4 + O(q^5)
MF(x^3)
Using Fourier expansions:
sage: qexp = CF.Delta().q_expansion(prec=2) sage: qexp q + O(q^2) sage: qexp.parent() Power Series Ring in q over Fraction Field of Univariate Polynomial Ring in d over Integer Ring sage: MF(qexp) q - 24*q^2 + 252*q^3 - 1472*q^4 + O(q^5)
>>> from sage.all import * >>> qexp = CF.Delta().q_expansion(prec=Integer(2)) >>> qexp q + O(q^2) >>> qexp.parent() Power Series Ring in q over Fraction Field of Univariate Polynomial Ring in d over Integer Ring >>> MF(qexp) q - 24*q^2 + 252*q^3 - 1472*q^4 + O(q^5)
qexp = CF.Delta().q_expansion(prec=2) qexp qexp.parent() MF(qexp)
Using coordinate vectors:
sage: MF([0,1]) == MF.f_inf() True
>>> from sage.all import * >>> MF([Integer(0),Integer(1)]) == MF.f_inf() True
MF([0,1]) == MF.f_inf()
Using arithmetic expressions:
sage: d = CF.get_d() sage: CF.f_rho()^7 / (d*CF.f_rho()^7 - d*CF.f_i()^2) == CF.j_inv() True sage: MF.E4().serre_derivative() == -1/3 * MF.E6() True
>>> from sage.all import * >>> d = CF.get_d() >>> CF.f_rho()**Integer(7) / (d*CF.f_rho()**Integer(7) - d*CF.f_i()**Integer(2)) == CF.j_inv() True >>> MF.E4().serre_derivative() == -Integer(1)/Integer(3) * MF.E6() True
d = CF.get_d() CF.f_rho()^7 / (d*CF.f_rho()^7 - d*CF.f_i()^2) == CF.j_inv() MF.E4().serre_derivative() == -1/3 * MF.E6()
Hauptmodul: The
j-function
for Hecke triangle groups is given by the unique Riemann map from the hyperbolic triangle with vertices atrho
,i
andinfinity
to the upper half plane, normalized such that its Fourier coefficients are real and such that the first nontrivial Fourier coefficient is 1. The function extends to a completely invariant weakly holomorphic function from the upper half plane to the complex numbers. Another used normalization (in capital letters) isJ(i)=1
. The coefficients ofj
are rational numbers up to a power ofd=1/j(i)
which is only rational in the arithmetic casesn=3, 4, 6, infinity
.All Fourier coefficients of modular forms are based on the coefficients of
j
. The coefficients ofj
are calculated by inverting the Fourier series of its inverse (the series inversion is also by far the most expensive operation of all).EXAMPLES:
sage: from sage.modular.modform_hecketriangle.graded_ring import WeakModularFormsRing sage: from sage.modular.modform_hecketriangle.space import WeakModularForms sage: WeakModularForms(n=3, k=0, ep=1).j_inv() q^-1 + 744 + 196884*q + 21493760*q^2 + 864299970*q^3 + 20245856256*q^4 + O(q^5) sage: WeakModularFormsRing(n=7).j_inv() f_rho^7/(f_rho^7*d - f_i^2*d) sage: WeakModularFormsRing(n=7, red_hom=True).j_inv() q^-1 + 151/(392*d) + 165229/(2458624*d^2)*q + 107365/(15059072*d^3)*q^2 + 25493858865/(48358655787008*d^4)*q^3 + 2771867459/(92561489592320*d^5)*q^4 + O(q^5)
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.graded_ring import WeakModularFormsRing >>> from sage.modular.modform_hecketriangle.space import WeakModularForms >>> WeakModularForms(n=Integer(3), k=Integer(0), ep=Integer(1)).j_inv() q^-1 + 744 + 196884*q + 21493760*q^2 + 864299970*q^3 + 20245856256*q^4 + O(q^5) >>> WeakModularFormsRing(n=Integer(7)).j_inv() f_rho^7/(f_rho^7*d - f_i^2*d) >>> WeakModularFormsRing(n=Integer(7), red_hom=True).j_inv() q^-1 + 151/(392*d) + 165229/(2458624*d^2)*q + 107365/(15059072*d^3)*q^2 + 25493858865/(48358655787008*d^4)*q^3 + 2771867459/(92561489592320*d^5)*q^4 + O(q^5)
from sage.modular.modform_hecketriangle.graded_ring import WeakModularFormsRing from sage.modular.modform_hecketriangle.space import WeakModularForms WeakModularForms(n=3, k=0, ep=1).j_inv() WeakModularFormsRing(n=7).j_inv() WeakModularFormsRing(n=7, red_hom=True).j_inv()
Basic generators: There exist unique modular forms
f_rho
,f_i
andf_inf
such that each has a simple zero atrho=exp(pi/n)
,i
andinfinity
resp. and no other zeros. The forms are normalized such that their first Fourier coefficient is1
. They have the weight and multiplier(4/(n-2), 1)
,(2*n/(n-2), -1)
,(4*n/(n-2), 1)
resp. and can be defined in terms of the Hauptmodulj
.EXAMPLES:
sage: from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing sage: ModularFormsRing(n=5, red_hom=True).f_rho() 1 + 7/(100*d)*q + 21/(160000*d^2)*q^2 + 1043/(192000000*d^3)*q^3 + 45479/(1228800000000*d^4)*q^4 + O(q^5) sage: ModularFormsRing(n=5, red_hom=True).f_i() 1 - 13/(40*d)*q - 351/(64000*d^2)*q^2 - 13819/(76800000*d^3)*q^3 - 1163669/(491520000000*d^4)*q^4 + O(q^5) sage: ModularFormsRing(n=5, red_hom=True).f_inf() q - 9/(200*d)*q^2 + 279/(640000*d^2)*q^3 + 961/(192000000*d^3)*q^4 + O(q^5) sage: ModularFormsRing(n=5).f_inf() f_rho^5*d - f_i^2*d
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing >>> ModularFormsRing(n=Integer(5), red_hom=True).f_rho() 1 + 7/(100*d)*q + 21/(160000*d^2)*q^2 + 1043/(192000000*d^3)*q^3 + 45479/(1228800000000*d^4)*q^4 + O(q^5) >>> ModularFormsRing(n=Integer(5), red_hom=True).f_i() 1 - 13/(40*d)*q - 351/(64000*d^2)*q^2 - 13819/(76800000*d^3)*q^3 - 1163669/(491520000000*d^4)*q^4 + O(q^5) >>> ModularFormsRing(n=Integer(5), red_hom=True).f_inf() q - 9/(200*d)*q^2 + 279/(640000*d^2)*q^3 + 961/(192000000*d^3)*q^4 + O(q^5) >>> ModularFormsRing(n=Integer(5)).f_inf() f_rho^5*d - f_i^2*d
from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing ModularFormsRing(n=5, red_hom=True).f_rho() ModularFormsRing(n=5, red_hom=True).f_i() ModularFormsRing(n=5, red_hom=True).f_inf() ModularFormsRing(n=5).f_inf()
Eisenstein series and Delta: The Eisenstein series of weight
2
,4
and6
exist for alln
and are all implemented . Note that except forn=3
the seriesE4
andE6
do not coincide withf_rho
andf_i
.Similarly there always exists a (generalization of)
Delta
. Except forn=3
it also does not coincide withf_inf
.In general Eisenstein series of all even weights exist for all
n
. In the non-arithmetic cases they are however very hard to determine (it’s an open problem(?) and consequently not yet implemented, except for trivial one-dimensional cases).The Eisenstein series in the arithmetic cases
n = 3, 4, 6
are fully implemented though. Note that this requires a lot more work/effort fork != 2, 4, 6
resp. for multidimensional spaces.The case
n=infinity
is a special case (since there are two cusps) and is not implemented yet.EXAMPLES:
sage: from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing sage: from sage.modular.modform_hecketriangle.space import ModularForms sage: ModularFormsRing(n=5).E4() f_rho^3 sage: ModularFormsRing(n=5).E6() f_rho^2*f_i sage: ModularFormsRing(n=5).Delta() f_rho^9*d - f_rho^4*f_i^2*d sage: ModularFormsRing(n=5).Delta() == ModularFormsRing(n=5).f_inf()*ModularFormsRing(n=5).f_rho()^4 True
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing >>> from sage.modular.modform_hecketriangle.space import ModularForms >>> ModularFormsRing(n=Integer(5)).E4() f_rho^3 >>> ModularFormsRing(n=Integer(5)).E6() f_rho^2*f_i >>> ModularFormsRing(n=Integer(5)).Delta() f_rho^9*d - f_rho^4*f_i^2*d >>> ModularFormsRing(n=Integer(5)).Delta() == ModularFormsRing(n=Integer(5)).f_inf()*ModularFormsRing(n=Integer(5)).f_rho()**Integer(4) True
from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing from sage.modular.modform_hecketriangle.space import ModularForms ModularFormsRing(n=5).E4() ModularFormsRing(n=5).E6() ModularFormsRing(n=5).Delta() ModularFormsRing(n=5).Delta() == ModularFormsRing(n=5).f_inf()*ModularFormsRing(n=5).f_rho()^4
The basic generators in some arithmetic cases:
sage: ModularForms(n=3, k=6).E6() 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 + O(q^5) sage: ModularForms(n=4, k=6).E6() 1 - 56*q - 2296*q^2 - 13664*q^3 - 73976*q^4 + O(q^5) sage: ModularForms(n=infinity, k=4).E4() 1 + 16*q + 112*q^2 + 448*q^3 + 1136*q^4 + O(q^5)
>>> from sage.all import * >>> ModularForms(n=Integer(3), k=Integer(6)).E6() 1 - 504*q - 16632*q^2 - 122976*q^3 - 532728*q^4 + O(q^5) >>> ModularForms(n=Integer(4), k=Integer(6)).E6() 1 - 56*q - 2296*q^2 - 13664*q^3 - 73976*q^4 + O(q^5) >>> ModularForms(n=infinity, k=Integer(4)).E4() 1 + 16*q + 112*q^2 + 448*q^3 + 1136*q^4 + O(q^5)
ModularForms(n=3, k=6).E6() ModularForms(n=4, k=6).E6() ModularForms(n=infinity, k=4).E4()
General Eisenstein series in some arithmetic cases:
sage: ModularFormsRing(n=4).EisensteinSeries(k=8) * 34 25*f_rho^4 + 9*f_i^2 sage: ModularForms(n=3, k=12).EisensteinSeries() 1 + 65520/691*q + 134250480/691*q^2 + 11606736960/691*q^3 + 274945048560/691*q^4 + O(q^5) sage: ModularForms(n=6, k=12).EisensteinSeries() 1 + 6552/50443*q + 13425048/50443*q^2 + 1165450104/50443*q^3 + 27494504856/50443*q^4 + O(q^5) sage: ModularForms(n=4, k=22, ep=-1).EisensteinSeries() 1 - 184/53057489*q - 386252984/53057489*q^2 - 1924704989536/53057489*q^3 - 810031218278584/53057489*q^4 + O(q^5)
>>> from sage.all import * >>> ModularFormsRing(n=Integer(4)).EisensteinSeries(k=Integer(8)) * Integer(34) 25*f_rho^4 + 9*f_i^2 >>> ModularForms(n=Integer(3), k=Integer(12)).EisensteinSeries() 1 + 65520/691*q + 134250480/691*q^2 + 11606736960/691*q^3 + 274945048560/691*q^4 + O(q^5) >>> ModularForms(n=Integer(6), k=Integer(12)).EisensteinSeries() 1 + 6552/50443*q + 13425048/50443*q^2 + 1165450104/50443*q^3 + 27494504856/50443*q^4 + O(q^5) >>> ModularForms(n=Integer(4), k=Integer(22), ep=-Integer(1)).EisensteinSeries() 1 - 184/53057489*q - 386252984/53057489*q^2 - 1924704989536/53057489*q^3 - 810031218278584/53057489*q^4 + O(q^5)
ModularFormsRing(n=4).EisensteinSeries(k=8) * 34 ModularForms(n=3, k=12).EisensteinSeries() ModularForms(n=6, k=12).EisensteinSeries() ModularForms(n=4, k=22, ep=-1).EisensteinSeries()
Generator for ``k=0``, ``ep=-1``: If
n
is even then the space of weakly holomorphic modular forms of weight0
and multiplier-1
is not empty and generated by one element, denoted byg_inv
.EXAMPLES:
sage: from sage.modular.modform_hecketriangle.space import WeakModularForms sage: WeakModularForms(n=4, k=0, ep=-1).g_inv() q^-1 - 24 - 3820*q - 100352*q^2 - 1217598*q^3 - 10797056*q^4 + O(q^5) sage: WeakModularFormsRing(n=8).g_inv() f_rho^4*f_i/(f_rho^8*d - f_i^2*d)
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.space import WeakModularForms >>> WeakModularForms(n=Integer(4), k=Integer(0), ep=-Integer(1)).g_inv() q^-1 - 24 - 3820*q - 100352*q^2 - 1217598*q^3 - 10797056*q^4 + O(q^5) >>> WeakModularFormsRing(n=Integer(8)).g_inv() f_rho^4*f_i/(f_rho^8*d - f_i^2*d)
from sage.modular.modform_hecketriangle.space import WeakModularForms WeakModularForms(n=4, k=0, ep=-1).g_inv() WeakModularFormsRing(n=8).g_inv()
Quasi modular form (for Hecke triangle groups):
E2
no longer transforms like a modular form but like a quasi modular form. More generally quasi modular forms are given in terms of modular forms and powers ofE2
. E.g. a holomorphic quasi modular form is a sum of holomorphic modular forms multiplied with a power ofE2
such that the weights and multipliers match up. The space of quasi modular forms for a given group, weight and multiplier forms a module over the base ring. It is finite dimensional if the analytic type isholomorphic
.The implementation and construction are analogous to modular forms (see above). In particular construction of quasi weakly holomorphic forms by their initial Laurent coefficients is supported as well!
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing sage: from sage.modular.modform_hecketriangle.space import QuasiCuspForms, QuasiModularForms, QuasiWeakModularForms sage: QuasiCuspForms(n=7, k=12, ep=1) QuasiCuspForms(n=7, k=12, ep=1) over Integer Ring sage: QuasiModularForms(n=4, k=8, ep=-1) QuasiModularForms(n=4, k=8, ep=-1) over Integer Ring sage: QuasiModularForms(n=4, k=2, ep=-1).E2() 1 - 8*q - 40*q^2 - 32*q^3 - 104*q^4 + O(q^5)
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing >>> from sage.modular.modform_hecketriangle.space import QuasiCuspForms, QuasiModularForms, QuasiWeakModularForms >>> QuasiCuspForms(n=Integer(7), k=Integer(12), ep=Integer(1)) QuasiCuspForms(n=7, k=12, ep=1) over Integer Ring >>> QuasiModularForms(n=Integer(4), k=Integer(8), ep=-Integer(1)) QuasiModularForms(n=4, k=8, ep=-1) over Integer Ring >>> QuasiModularForms(n=Integer(4), k=Integer(2), ep=-Integer(1)).E2() 1 - 8*q - 40*q^2 - 32*q^3 - 104*q^4 + O(q^5)
from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing from sage.modular.modform_hecketriangle.space import QuasiCuspForms, QuasiModularForms, QuasiWeakModularForms QuasiCuspForms(n=7, k=12, ep=1) QuasiModularForms(n=4, k=8, ep=-1) QuasiModularForms(n=4, k=2, ep=-1).E2()
A quasi weak form can be constructed by using its initial Laurent expansion:
sage: QF = QuasiWeakModularForms(n=8, k=10/3, ep=-1) sage: qexp = (QF.quasi_part_gens(min_exp=-1)[4]).q_expansion(prec=5) sage: qexp q^-1 - 19/(64*d) - 7497/(262144*d^2)*q + 15889/(8388608*d^3)*q^2 + 543834047/(1649267441664*d^4)*q^3 + 711869853/(43980465111040*d^5)*q^4 + O(q^5) sage: qexp.parent() Laurent Series Ring in q over Fraction Field of Univariate Polynomial Ring in d over Integer Ring sage: QF(qexp).as_ring_element() f_rho^3*f_i*E2^2/(f_rho^8*d - f_i^2*d) sage: QF(qexp).reduced_parent() QuasiWeakModularForms(n=8, k=10/3, ep=-1) over Integer Ring
>>> from sage.all import * >>> QF = QuasiWeakModularForms(n=Integer(8), k=Integer(10)/Integer(3), ep=-Integer(1)) >>> qexp = (QF.quasi_part_gens(min_exp=-Integer(1))[Integer(4)]).q_expansion(prec=Integer(5)) >>> qexp q^-1 - 19/(64*d) - 7497/(262144*d^2)*q + 15889/(8388608*d^3)*q^2 + 543834047/(1649267441664*d^4)*q^3 + 711869853/(43980465111040*d^5)*q^4 + O(q^5) >>> qexp.parent() Laurent Series Ring in q over Fraction Field of Univariate Polynomial Ring in d over Integer Ring >>> QF(qexp).as_ring_element() f_rho^3*f_i*E2^2/(f_rho^8*d - f_i^2*d) >>> QF(qexp).reduced_parent() QuasiWeakModularForms(n=8, k=10/3, ep=-1) over Integer Ring
QF = QuasiWeakModularForms(n=8, k=10/3, ep=-1) qexp = (QF.quasi_part_gens(min_exp=-1)[4]).q_expansion(prec=5) qexp qexp.parent() QF(qexp).as_ring_element() QF(qexp).reduced_parent()
Derivatives of (quasi weak) modular forms are again quasi (weak) modular forms:
sage: CF.f_inf().derivative() == CF.f_inf()*CF.E2() True
>>> from sage.all import * >>> CF.f_inf().derivative() == CF.f_inf()*CF.E2() True
CF.f_inf().derivative() == CF.f_inf()*CF.E2()
Ring of (quasi) modular forms: The ring of (quasi) modular forms for a given analytic type and Hecke triangle group. In fact it is a graded algebra over the base ring where the grading is over
1/(n-2)*Z x Z/(2Z)
corresponding to the weight and multiplier. A ring element is thus a finite linear combination of (quasi) modular forms of (possibly) varying weights and multipliers.Each ring element is represented as a rational function in the generators
f_rho
,f_i
andE2
. The representations and arithmetic operations are exact (no precision argument is required).Elements of the ring are represented by the rational function in the generators.
If the parameter
red_hom
is set toTrue
(default:False
) then operations with homogeneous elements try to return an element of the corresponding vector space (if the element is homogeneous) instead of the forms ring. It is also easier to use the forms ring withred_hom=True
to construct known forms (since then it is not required to specify the weight and multiplier).EXAMPLES:
sage: from sage.modular.modform_hecketriangle.graded_ring import QuasiModularFormsRing, ModularFormsRing sage: QuasiModularFormsRing(n=5, red_hom=True) QuasiModularFormsRing(n=5) over Integer Ring sage: ModularFormsRing() ModularFormsRing(n=3) over Integer Ring sage: (x,y,z,d) = ModularFormsRing().pol_ring().gens() sage: ModularFormsRing()(x+y) f_rho + f_i sage: QuasiModularFormsRing(n=5, red_hom=True)(x^5-y^2).reduce() 1/d*q - 9/(200*d^2)*q^2 + 279/(640000*d^3)*q^3 + 961/(192000000*d^4)*q^4 + O(q^5)
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.graded_ring import QuasiModularFormsRing, ModularFormsRing >>> QuasiModularFormsRing(n=Integer(5), red_hom=True) QuasiModularFormsRing(n=5) over Integer Ring >>> ModularFormsRing() ModularFormsRing(n=3) over Integer Ring >>> (x,y,z,d) = ModularFormsRing().pol_ring().gens() >>> ModularFormsRing()(x+y) f_rho + f_i >>> QuasiModularFormsRing(n=Integer(5), red_hom=True)(x**Integer(5)-y**Integer(2)).reduce() 1/d*q - 9/(200*d^2)*q^2 + 279/(640000*d^3)*q^3 + 961/(192000000*d^4)*q^4 + O(q^5)
from sage.modular.modform_hecketriangle.graded_ring import QuasiModularFormsRing, ModularFormsRing QuasiModularFormsRing(n=5, red_hom=True) ModularFormsRing() (x,y,z,d) = ModularFormsRing().pol_ring().gens() ModularFormsRing()(x+y) QuasiModularFormsRing(n=5, red_hom=True)(x^5-y^2).reduce()
Construction of modular forms spaces and rings: There are functorial constructions behind all forms spaces and rings which assure that arithmetic operations between those spaces and rings work and fit into the coercion framework. In particular ring elements are interpreted as constant modular forms in this context and base extensions are done if necessary.
Fourier expansion of (quasi) modular forms (for Hecke triangle groups): Each (quasi) modular form (in fact each ring element) possesses a Fourier expansion of the form
sum_{n>=n_0} a_n q^n
, wheren_0
is an integer,q=exp(2*pi*i*z/lambda)
and the coefficientsa_n
are rational numbers (or more generally an extension of rational numbers) up to a power ofd
, whered
is the (possibly) transcendental parameter described above. I.e. the coefficient ring is given byFrac(R)(d)
.The coefficients are calculated exactly in terms of the (formal) parameter
d
. The expansion is calculated exactly up to the specified precision. It is also possible to get a Fourier expansion whered
is evaluated to its numerical approximation.EXAMPLES:
sage: from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing, QuasiModularFormsRing sage: ModularFormsRing(n=4).j_inv().q_expansion(prec=3) q^-1 + 13/(32*d) + 1093/(16384*d^2)*q + 47/(8192*d^3)*q^2 + O(q^3) sage: QuasiModularFormsRing(n=5).E2().q_expansion(prec=3) 1 - 9/(200*d)*q - 369/(320000*d^2)*q^2 + O(q^3) sage: QuasiModularFormsRing(n=5).E2().q_expansion_fixed_d(prec=3) 1.000000000000... - 6.380956565426...*q - 23.18584547617...*q^2 + O(q^3)
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing, QuasiModularFormsRing >>> ModularFormsRing(n=Integer(4)).j_inv().q_expansion(prec=Integer(3)) q^-1 + 13/(32*d) + 1093/(16384*d^2)*q + 47/(8192*d^3)*q^2 + O(q^3) >>> QuasiModularFormsRing(n=Integer(5)).E2().q_expansion(prec=Integer(3)) 1 - 9/(200*d)*q - 369/(320000*d^2)*q^2 + O(q^3) >>> QuasiModularFormsRing(n=Integer(5)).E2().q_expansion_fixed_d(prec=Integer(3)) 1.000000000000... - 6.380956565426...*q - 23.18584547617...*q^2 + O(q^3)
from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing, QuasiModularFormsRing ModularFormsRing(n=4).j_inv().q_expansion(prec=3) QuasiModularFormsRing(n=5).E2().q_expansion(prec=3) QuasiModularFormsRing(n=5).E2().q_expansion_fixed_d(prec=3)
Evaluation of forms: (Quasi) modular forms (and also ring elements) can be viewed as functions from the upper half plane and can be numerically evaluated by using the Fourier expansion.
The evaluation uses the (quasi) modularity properties (if possible) for a faster and more precise evaluation. The precision of the result depends both on the numerical precision and on the default precision used for the Fourier expansion.
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing sage: f_i = ModularFormsRing(n=4).f_i() sage: f_i(i) 0 sage: f_i(infinity) 1 sage: f_i(1/7 + 0.01*i) 32189.02016723... + 21226.62951394...*I
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing >>> f_i = ModularFormsRing(n=Integer(4)).f_i() >>> f_i(i) 0 >>> f_i(infinity) 1 >>> f_i(Integer(1)/Integer(7) + RealNumber('0.01')*i) 32189.02016723... + 21226.62951394...*I
from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing f_i = ModularFormsRing(n=4).f_i() f_i(i) f_i(infinity) f_i(1/7 + 0.01*i)
L-functions of forms: Using the (pari based) function
Dokchitser
L-functions of non-constant holomorphic modular forms are supported for all values ofn
.Note: For non-arithmetic groups this involves an irrational conductor. The conductor for the arithmetic groups
n = 3, 4, 6, infinity
is1, 2, 3, 4
respectively.EXAMPLES:
sage: from sage.modular.modform.eis_series import eisenstein_series_lseries sage: from sage.modular.modform_hecketriangle.space import ModularForms sage: f = ModularForms(n=3, k=4).E4()/240 sage: L = f.lseries() sage: L.conductor 1 sage: L.check_functional_equation() < 2^(-50) True sage: L(1) -0.0304484570583... sage: abs(L(1) - eisenstein_series_lseries(4)(1)) < 2^(-53) True sage: L.taylor_series(1, 3) -0.0304484570583... - 0.0504570844798...*z - 0.0350657360354...*z^2 + O(z^3) sage: coeffs = f.q_expansion_vector(min_exp=0, max_exp=20, fix_d=True) sage: abs(L(10) - sum([coeffs[k] * ZZ(k)^(-10) for k in range(1,len(coeffs))]).n(53)) < 10^(-7) True sage: L = ModularForms(n=6, k=6, ep=-1).E6().lseries(num_prec=200) sage: L.conductor 3 sage: L.check_functional_equation() < 2^(-180) True sage: L.eps -1 sage: abs(L(3)) < 2^(-180) True sage: L = ModularForms(n=17, k=12).Delta().lseries() sage: L.conductor 3.86494445880... sage: L.check_functional_equation() < 2^(-50) True sage: L.taylor_series(6, 3) 2.15697985314... - 1.17385918996...*z + 0.605865993050...*z^2 + O(z^3) sage: L = ModularForms(n=infinity, k=2, ep=-1).f_i().lseries() sage: L.conductor 4 sage: L.check_functional_equation() < 2^(-50) True sage: L.taylor_series(1, 3) 0.000000000000... + 5.76543616701...*z + 9.92776715593...*z^2 + O(z^3)
>>> from sage.all import * >>> from sage.modular.modform.eis_series import eisenstein_series_lseries >>> from sage.modular.modform_hecketriangle.space import ModularForms >>> f = ModularForms(n=Integer(3), k=Integer(4)).E4()/Integer(240) >>> L = f.lseries() >>> L.conductor 1 >>> L.check_functional_equation() < Integer(2)**(-Integer(50)) True >>> L(Integer(1)) -0.0304484570583... >>> abs(L(Integer(1)) - eisenstein_series_lseries(Integer(4))(Integer(1))) < Integer(2)**(-Integer(53)) True >>> L.taylor_series(Integer(1), Integer(3)) -0.0304484570583... - 0.0504570844798...*z - 0.0350657360354...*z^2 + O(z^3) >>> coeffs = f.q_expansion_vector(min_exp=Integer(0), max_exp=Integer(20), fix_d=True) >>> abs(L(Integer(10)) - sum([coeffs[k] * ZZ(k)**(-Integer(10)) for k in range(Integer(1),len(coeffs))]).n(Integer(53))) < Integer(10)**(-Integer(7)) True >>> L = ModularForms(n=Integer(6), k=Integer(6), ep=-Integer(1)).E6().lseries(num_prec=Integer(200)) >>> L.conductor 3 >>> L.check_functional_equation() < Integer(2)**(-Integer(180)) True >>> L.eps -1 >>> abs(L(Integer(3))) < Integer(2)**(-Integer(180)) True >>> L = ModularForms(n=Integer(17), k=Integer(12)).Delta().lseries() >>> L.conductor 3.86494445880... >>> L.check_functional_equation() < Integer(2)**(-Integer(50)) True >>> L.taylor_series(Integer(6), Integer(3)) 2.15697985314... - 1.17385918996...*z + 0.605865993050...*z^2 + O(z^3) >>> L = ModularForms(n=infinity, k=Integer(2), ep=-Integer(1)).f_i().lseries() >>> L.conductor 4 >>> L.check_functional_equation() < Integer(2)**(-Integer(50)) True >>> L.taylor_series(Integer(1), Integer(3)) 0.000000000000... + 5.76543616701...*z + 9.92776715593...*z^2 + O(z^3)
from sage.modular.modform.eis_series import eisenstein_series_lseries from sage.modular.modform_hecketriangle.space import ModularForms f = ModularForms(n=3, k=4).E4()/240 L = f.lseries() L.conductor L.check_functional_equation() < 2^(-50) L(1) abs(L(1) - eisenstein_series_lseries(4)(1)) < 2^(-53) L.taylor_series(1, 3) coeffs = f.q_expansion_vector(min_exp=0, max_exp=20, fix_d=True) abs(L(10) - sum([coeffs[k] * ZZ(k)^(-10) for k in range(1,len(coeffs))]).n(53)) < 10^(-7) L = ModularForms(n=6, k=6, ep=-1).E6().lseries(num_prec=200) L.conductor L.check_functional_equation() < 2^(-180) L.eps abs(L(3)) < 2^(-180) L = ModularForms(n=17, k=12).Delta().lseries() L.conductor L.check_functional_equation() < 2^(-50) L.taylor_series(6, 3) L = ModularForms(n=infinity, k=2, ep=-1).f_i().lseries() L.conductor L.check_functional_equation() < 2^(-50) L.taylor_series(1, 3)
(Serre) derivatives: Derivatives and Serre derivatives of forms can be calculated. The analytic type is extended accordingly.
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing sage: from sage.modular.modform_hecketriangle.space import QuasiModularForms sage: f_inf = ModularFormsRing(n=4, red_hom=True).f_inf() sage: f_inf.derivative()/f_inf == QuasiModularForms(n=4, k=2, ep=-1).E2() True sage: ModularFormsRing().E4().serre_derivative() == -1/3 * ModularFormsRing().E6() True
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing >>> from sage.modular.modform_hecketriangle.space import QuasiModularForms >>> f_inf = ModularFormsRing(n=Integer(4), red_hom=True).f_inf() >>> f_inf.derivative()/f_inf == QuasiModularForms(n=Integer(4), k=Integer(2), ep=-Integer(1)).E2() True >>> ModularFormsRing().E4().serre_derivative() == -Integer(1)/Integer(3) * ModularFormsRing().E6() True
from sage.modular.modform_hecketriangle.graded_ring import ModularFormsRing from sage.modular.modform_hecketriangle.space import QuasiModularForms f_inf = ModularFormsRing(n=4, red_hom=True).f_inf() f_inf.derivative()/f_inf == QuasiModularForms(n=4, k=2, ep=-1).E2() ModularFormsRing().E4().serre_derivative() == -1/3 * ModularFormsRing().E6()
Basis for weakly holomorphic modular forms and Faber polynomials: (Natural) generators of weakly holomorphic modular forms can be obtained using the corresponding generalized Faber polynomials.
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.space import WeakModularForms, CuspForms sage: MF = WeakModularForms(n=5, k=62/3, ep=-1) sage: MF.disp_prec(MF._l1+2) sage: MF.F_basis(2) q^2 - 41/(200*d)*q^3 + O(q^4) sage: MF.F_basis(1) q - 13071/(640000*d^2)*q^3 + O(q^4) sage: MF.F_basis(-0) 1 - 277043/(192000000*d^3)*q^3 + O(q^4) sage: MF.F_basis(-2) q^-2 - 162727620113/(40960000000000000*d^5)*q^3 + O(q^4)
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.space import WeakModularForms, CuspForms >>> MF = WeakModularForms(n=Integer(5), k=Integer(62)/Integer(3), ep=-Integer(1)) >>> MF.disp_prec(MF._l1+Integer(2)) >>> MF.F_basis(Integer(2)) q^2 - 41/(200*d)*q^3 + O(q^4) >>> MF.F_basis(Integer(1)) q - 13071/(640000*d^2)*q^3 + O(q^4) >>> MF.F_basis(-Integer(0)) 1 - 277043/(192000000*d^3)*q^3 + O(q^4) >>> MF.F_basis(-Integer(2)) q^-2 - 162727620113/(40960000000000000*d^5)*q^3 + O(q^4)
from sage.modular.modform_hecketriangle.space import WeakModularForms, CuspForms MF = WeakModularForms(n=5, k=62/3, ep=-1) MF.disp_prec(MF._l1+2) MF.F_basis(2) MF.F_basis(1) MF.F_basis(-0) MF.F_basis(-2)
Basis for quasi weakly holomorphic modular forms: (Natural) generators of quasi weakly holomorphic modular forms can also be obtained. In most cases it is even possible to find a basis consisting of elements with only one non-trivial Laurent coefficient (up to some coefficient).
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.space import QuasiWeakModularForms sage: QF = QuasiWeakModularForms(n=8, k=10/3, ep=-1) sage: QF.default_prec(1) sage: QF.quasi_part_gens(min_exp=-1) [q^-1 + O(q), 1 + O(q), q^-1 - 9/(128*d) + O(q), 1 + O(q), q^-1 - 19/(64*d) + O(q), q^-1 + 1/(64*d) + O(q)] sage: QF.default_prec(QF.required_laurent_prec(min_exp=-1)) sage: QF.q_basis(min_exp=-1) # long time [q^-1 + O(q^5), 1 + O(q^5), q + O(q^5), q^2 + O(q^5), q^3 + O(q^5), q^4 + O(q^5)]
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.space import QuasiWeakModularForms >>> QF = QuasiWeakModularForms(n=Integer(8), k=Integer(10)/Integer(3), ep=-Integer(1)) >>> QF.default_prec(Integer(1)) >>> QF.quasi_part_gens(min_exp=-Integer(1)) [q^-1 + O(q), 1 + O(q), q^-1 - 9/(128*d) + O(q), 1 + O(q), q^-1 - 19/(64*d) + O(q), q^-1 + 1/(64*d) + O(q)] >>> QF.default_prec(QF.required_laurent_prec(min_exp=-Integer(1))) >>> QF.q_basis(min_exp=-Integer(1)) # long time [q^-1 + O(q^5), 1 + O(q^5), q + O(q^5), q^2 + O(q^5), q^3 + O(q^5), q^4 + O(q^5)]
from sage.modular.modform_hecketriangle.space import QuasiWeakModularForms QF = QuasiWeakModularForms(n=8, k=10/3, ep=-1) QF.default_prec(1) QF.quasi_part_gens(min_exp=-1) QF.default_prec(QF.required_laurent_prec(min_exp=-1)) QF.q_basis(min_exp=-1) # long time
Dimension and basis for holomorphic or cuspidal (quasi) modular forms: For finite dimensional spaces the dimension and a basis can be obtained.
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.space import QuasiModularForms sage: MF = QuasiModularForms(n=5, k=6, ep=-1) sage: MF.dimension() 3 sage: MF.default_prec(2) sage: MF.gens() [1 - 37/(200*d)*q + O(q^2), 1 + 33/(200*d)*q + O(q^2), 1 - 27/(200*d)*q + O(q^2)]
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.space import QuasiModularForms >>> MF = QuasiModularForms(n=Integer(5), k=Integer(6), ep=-Integer(1)) >>> MF.dimension() 3 >>> MF.default_prec(Integer(2)) >>> MF.gens() [1 - 37/(200*d)*q + O(q^2), 1 + 33/(200*d)*q + O(q^2), 1 - 27/(200*d)*q + O(q^2)]
from sage.modular.modform_hecketriangle.space import QuasiModularForms MF = QuasiModularForms(n=5, k=6, ep=-1) MF.dimension() MF.default_prec(2) MF.gens()
Coordinate vectors for (quasi) holomorphic modular forms and (quasi) cusp forms: For (quasi) holomorphic modular forms and (quasi) cusp forms it is possible to determine the coordinate vectors of elements with respect to the basis.
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.space import ModularForms sage: ModularForms(n=7, k=12, ep=1).dimension() 3 sage: ModularForms(n=7, k=12, ep=1).Delta().coordinate_vector() (0, 1, 17/(56*d)) sage: from sage.modular.modform_hecketriangle.space import QuasiCuspForms sage: MF = QuasiCuspForms(n=7, k=20, ep=1) sage: MF.dimension() 13 sage: el = MF(MF.Delta()*MF.E2()^4 + MF.Delta()*MF.E2()*MF.E6()) sage: el.coordinate_vector() # long time (0, 0, 0, 1, 29/(196*d), 0, 0, 0, 0, 1, 17/(56*d), 0, 0)
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.space import ModularForms >>> ModularForms(n=Integer(7), k=Integer(12), ep=Integer(1)).dimension() 3 >>> ModularForms(n=Integer(7), k=Integer(12), ep=Integer(1)).Delta().coordinate_vector() (0, 1, 17/(56*d)) >>> from sage.modular.modform_hecketriangle.space import QuasiCuspForms >>> MF = QuasiCuspForms(n=Integer(7), k=Integer(20), ep=Integer(1)) >>> MF.dimension() 13 >>> el = MF(MF.Delta()*MF.E2()**Integer(4) + MF.Delta()*MF.E2()*MF.E6()) >>> el.coordinate_vector() # long time (0, 0, 0, 1, 29/(196*d), 0, 0, 0, 0, 1, 17/(56*d), 0, 0)
from sage.modular.modform_hecketriangle.space import ModularForms ModularForms(n=7, k=12, ep=1).dimension() ModularForms(n=7, k=12, ep=1).Delta().coordinate_vector() from sage.modular.modform_hecketriangle.space import QuasiCuspForms MF = QuasiCuspForms(n=7, k=20, ep=1) MF.dimension() el = MF(MF.Delta()*MF.E2()^4 + MF.Delta()*MF.E2()*MF.E6()) el.coordinate_vector() # long time
Subspaces: It is possible to construct subspaces of (quasi) holomorphic modular forms or (quasi) cusp forms spaces with respect to a specified basis of the corresponding ambient space. The subspaces also support coordinate vectors with respect to its basis.
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.space import ModularForms sage: MF = ModularForms(n=7, k=12, ep=1) sage: subspace = MF.subspace([MF.E4()^3, MF.Delta()]) sage: subspace Subspace of dimension 2 of ModularForms(n=7, k=12, ep=1) over Integer Ring sage: el = subspace(MF.E6()^2) sage: el.coordinate_vector() (1, -61/(196*d)) sage: el.ambient_coordinate_vector() (1, -61/(196*d), -51187/(614656*d^2)) sage: from sage.modular.modform_hecketriangle.space import QuasiCuspForms sage: MF = QuasiCuspForms(n=7, k=20, ep=1) sage: subspace = MF.subspace([MF.Delta()*MF.E2()^2*MF.E4(), MF.Delta()*MF.E2()^4]) # long time sage: subspace # long time Subspace of dimension 2 of QuasiCuspForms(n=7, k=20, ep=1) over Integer Ring sage: el = subspace(MF.Delta()*MF.E2()^4) # long time sage: el.coordinate_vector() # long time (0, 1) sage: el.ambient_coordinate_vector() # long time (0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 17/(56*d), 0, 0)
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.space import ModularForms >>> MF = ModularForms(n=Integer(7), k=Integer(12), ep=Integer(1)) >>> subspace = MF.subspace([MF.E4()**Integer(3), MF.Delta()]) >>> subspace Subspace of dimension 2 of ModularForms(n=7, k=12, ep=1) over Integer Ring >>> el = subspace(MF.E6()**Integer(2)) >>> el.coordinate_vector() (1, -61/(196*d)) >>> el.ambient_coordinate_vector() (1, -61/(196*d), -51187/(614656*d^2)) >>> from sage.modular.modform_hecketriangle.space import QuasiCuspForms >>> MF = QuasiCuspForms(n=Integer(7), k=Integer(20), ep=Integer(1)) >>> subspace = MF.subspace([MF.Delta()*MF.E2()**Integer(2)*MF.E4(), MF.Delta()*MF.E2()**Integer(4)]) # long time >>> subspace # long time Subspace of dimension 2 of QuasiCuspForms(n=7, k=20, ep=1) over Integer Ring >>> el = subspace(MF.Delta()*MF.E2()**Integer(4)) # long time >>> el.coordinate_vector() # long time (0, 1) >>> el.ambient_coordinate_vector() # long time (0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 17/(56*d), 0, 0)
from sage.modular.modform_hecketriangle.space import ModularForms MF = ModularForms(n=7, k=12, ep=1) subspace = MF.subspace([MF.E4()^3, MF.Delta()]) subspace el = subspace(MF.E6()^2) el.coordinate_vector() el.ambient_coordinate_vector() from sage.modular.modform_hecketriangle.space import QuasiCuspForms MF = QuasiCuspForms(n=7, k=20, ep=1) subspace = MF.subspace([MF.Delta()*MF.E2()^2*MF.E4(), MF.Delta()*MF.E2()^4]) # long time subspace # long time el = subspace(MF.Delta()*MF.E2()^4) # long time el.coordinate_vector() # long time el.ambient_coordinate_vector() # long time
Theta subgroup: The Hecke triangle group corresponding to
n=infinity
is also completely supported. In particular the (special) behavior around the cusp-1
is considered and can be specified.EXAMPLES:
sage: from sage.modular.modform_hecketriangle.graded_ring import QuasiMeromorphicModularFormsRing sage: MR = QuasiMeromorphicModularFormsRing(n=infinity, red_hom=True) sage: MR QuasiMeromorphicModularFormsRing(n=+Infinity) over Integer Ring sage: j_inv = MR.j_inv().full_reduce() sage: f_i = MR.f_i().full_reduce() sage: E4 = MR.E4().full_reduce() sage: E2 = MR.E2().full_reduce() sage: j_inv q^-1 + 24 + 276*q + 2048*q^2 + 11202*q^3 + 49152*q^4 + O(q^5) sage: MR.f_rho() == MR(1) True sage: E4 1 + 16*q + 112*q^2 + 448*q^3 + 1136*q^4 + O(q^5) sage: f_i 1 - 24*q + 24*q^2 - 96*q^3 + 24*q^4 + O(q^5) sage: E2 1 - 8*q - 8*q^2 - 32*q^3 - 40*q^4 + O(q^5) sage: E4.derivative() == E4 * (E2 - f_i) True sage: f_i.serre_derivative() == -1/2 * E4 True sage: MF = f_i.serre_derivative().parent() sage: MF ModularForms(n=+Infinity, k=4, ep=1) over Integer Ring sage: MF.dimension() 2 sage: MF.gens() [1 + 240*q^2 + 2160*q^4 + O(q^5), q - 8*q^2 + 28*q^3 - 64*q^4 + O(q^5)] sage: E4(i) 1.941017189... sage: E4.order_at(-1) 1 sage: MF = (E2/E4).reduced_parent() sage: MF.quasi_part_gens(order_1=-1) [1 - 40*q + 552*q^2 - 4896*q^3 + 33320*q^4 + O(q^5), 1 - 24*q + 264*q^2 - 2016*q^3 + 12264*q^4 + O(q^5)] sage: prec = MF.required_laurent_prec(order_1=-1) sage: qexp = (E2/E4).q_expansion(prec=prec) sage: qexp 1 - 3/(8*d)*q + O(q^2) sage: MF.construct_quasi_form(qexp, order_1=-1) == E2/E4 True sage: MF.disp_prec(6) sage: MF.q_basis(m=-1, order_1=-1, min_exp=-1) q^-1 - 203528/7*q^5 + O(q^6)
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.graded_ring import QuasiMeromorphicModularFormsRing >>> MR = QuasiMeromorphicModularFormsRing(n=infinity, red_hom=True) >>> MR QuasiMeromorphicModularFormsRing(n=+Infinity) over Integer Ring >>> j_inv = MR.j_inv().full_reduce() >>> f_i = MR.f_i().full_reduce() >>> E4 = MR.E4().full_reduce() >>> E2 = MR.E2().full_reduce() >>> j_inv q^-1 + 24 + 276*q + 2048*q^2 + 11202*q^3 + 49152*q^4 + O(q^5) >>> MR.f_rho() == MR(Integer(1)) True >>> E4 1 + 16*q + 112*q^2 + 448*q^3 + 1136*q^4 + O(q^5) >>> f_i 1 - 24*q + 24*q^2 - 96*q^3 + 24*q^4 + O(q^5) >>> E2 1 - 8*q - 8*q^2 - 32*q^3 - 40*q^4 + O(q^5) >>> E4.derivative() == E4 * (E2 - f_i) True >>> f_i.serre_derivative() == -Integer(1)/Integer(2) * E4 True >>> MF = f_i.serre_derivative().parent() >>> MF ModularForms(n=+Infinity, k=4, ep=1) over Integer Ring >>> MF.dimension() 2 >>> MF.gens() [1 + 240*q^2 + 2160*q^4 + O(q^5), q - 8*q^2 + 28*q^3 - 64*q^4 + O(q^5)] >>> E4(i) 1.941017189... >>> E4.order_at(-Integer(1)) 1 >>> MF = (E2/E4).reduced_parent() >>> MF.quasi_part_gens(order_1=-Integer(1)) [1 - 40*q + 552*q^2 - 4896*q^3 + 33320*q^4 + O(q^5), 1 - 24*q + 264*q^2 - 2016*q^3 + 12264*q^4 + O(q^5)] >>> prec = MF.required_laurent_prec(order_1=-Integer(1)) >>> qexp = (E2/E4).q_expansion(prec=prec) >>> qexp 1 - 3/(8*d)*q + O(q^2) >>> MF.construct_quasi_form(qexp, order_1=-Integer(1)) == E2/E4 True >>> MF.disp_prec(Integer(6)) >>> MF.q_basis(m=-Integer(1), order_1=-Integer(1), min_exp=-Integer(1)) q^-1 - 203528/7*q^5 + O(q^6)
from sage.modular.modform_hecketriangle.graded_ring import QuasiMeromorphicModularFormsRing MR = QuasiMeromorphicModularFormsRing(n=infinity, red_hom=True) MR j_inv = MR.j_inv().full_reduce() f_i = MR.f_i().full_reduce() E4 = MR.E4().full_reduce() E2 = MR.E2().full_reduce() j_inv MR.f_rho() == MR(1) E4 f_i E2 E4.derivative() == E4 * (E2 - f_i) f_i.serre_derivative() == -1/2 * E4 MF = f_i.serre_derivative().parent() MF MF.dimension() MF.gens() E4(i) E4.order_at(-1) MF = (E2/E4).reduced_parent() MF.quasi_part_gens(order_1=-1) prec = MF.required_laurent_prec(order_1=-1) qexp = (E2/E4).q_expansion(prec=prec) qexp MF.construct_quasi_form(qexp, order_1=-1) == E2/E4 MF.disp_prec(6) MF.q_basis(m=-1, order_1=-1, min_exp=-1)
Elements with respect to the full group are automatically coerced to elements of the Theta subgroup if necessary:
sage: el = QuasiMeromorphicModularFormsRing(n=3).Delta().full_reduce() + E2 sage: el (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3 + 4096*E2)/4096 sage: el.parent() QuasiModularFormsRing(n=+Infinity) over Integer Ring
>>> from sage.all import * >>> el = QuasiMeromorphicModularFormsRing(n=Integer(3)).Delta().full_reduce() + E2 >>> el (E4*f_i^4 - 2*E4^2*f_i^2 + E4^3 + 4096*E2)/4096 >>> el.parent() QuasiModularFormsRing(n=+Infinity) over Integer Ring
el = QuasiMeromorphicModularFormsRing(n=3).Delta().full_reduce() + E2 el el.parent()
Determine exact coefficients from numerical ones: There is some experimental support for replacing numerical coefficients with corresponding exact coefficients. There is however NO guarantee that the procedure will work (and most probably there are cases where it won’t).
EXAMPLES:
sage: from sage.modular.modform_hecketriangle.space import WeakModularForms, QuasiCuspForms sage: WF = WeakModularForms(n=14) sage: qexp = WF.J_inv().q_expansion_fixed_d(d_num_prec=1000) sage: qexp.parent() Laurent Series Ring in q over Real Field with 1000 bits of precision sage: qexp_int = WF.rationalize_series(qexp) doctest:...: UserWarning: Using an experimental rationalization of coefficients, please check the result for correctness! sage: qexp_int.parent() Laurent Series Ring in q over Fraction Field of Univariate Polynomial Ring in d over Integer Ring sage: qexp_int == WF.J_inv().q_expansion() True sage: WF(qexp_int) == WF.J_inv() True sage: QF = QuasiCuspForms(n=8, k=22/3, ep=-1) sage: el = QF(QF.f_inf()*QF.E2()) sage: qexp = el.q_expansion_fixed_d(d_num_prec=1000) sage: qexp_int = QF.rationalize_series(qexp) sage: qexp_int == el.q_expansion() True sage: QF(qexp_int) == el True
>>> from sage.all import * >>> from sage.modular.modform_hecketriangle.space import WeakModularForms, QuasiCuspForms >>> WF = WeakModularForms(n=Integer(14)) >>> qexp = WF.J_inv().q_expansion_fixed_d(d_num_prec=Integer(1000)) >>> qexp.parent() Laurent Series Ring in q over Real Field with 1000 bits of precision >>> qexp_int = WF.rationalize_series(qexp) doctest:...: UserWarning: Using an experimental rationalization of coefficients, please check the result for correctness! >>> qexp_int.parent() Laurent Series Ring in q over Fraction Field of Univariate Polynomial Ring in d over Integer Ring >>> qexp_int == WF.J_inv().q_expansion() True >>> WF(qexp_int) == WF.J_inv() True >>> QF = QuasiCuspForms(n=Integer(8), k=Integer(22)/Integer(3), ep=-Integer(1)) >>> el = QF(QF.f_inf()*QF.E2()) >>> qexp = el.q_expansion_fixed_d(d_num_prec=Integer(1000)) >>> qexp_int = QF.rationalize_series(qexp) >>> qexp_int == el.q_expansion() True >>> QF(qexp_int) == el True
from sage.modular.modform_hecketriangle.space import WeakModularForms, QuasiCuspForms WF = WeakModularForms(n=14) qexp = WF.J_inv().q_expansion_fixed_d(d_num_prec=1000) qexp.parent() qexp_int = WF.rationalize_series(qexp) qexp_int.parent() qexp_int == WF.J_inv().q_expansion() WF(qexp_int) == WF.J_inv() QF = QuasiCuspForms(n=8, k=22/3, ep=-1) el = QF(QF.f_inf()*QF.E2()) qexp = el.q_expansion_fixed_d(d_num_prec=1000) qexp_int = QF.rationalize_series(qexp) qexp_int == el.q_expansion() QF(qexp_int) == el
Future ideas:¶
Complete support for the case
n=infinity
(e.g. lambda-CF)Properly implemented lambda-CF
Binary quadratic forms for Hecke triangle groups
Cycle integrals
Maybe: Proper spaces (with coordinates) for (quasi) weakly holomorphic forms with bounds on the initial Fourier exponent
Support for general triangle groups (hard)
Support for “congruence” subgroups (hard)