def rho(self): r""" Return the vertex ``rho`` of the basic hyperbolic triangle which describes ``self``. ``rho`` has absolute value 1 and angle ``pi/n``. EXAMPLES:: sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: HeckeTriangleGroup(3).rho() == QQbar(1/2 + sqrt(3)/2*i) True sage: HeckeTriangleGroup(4).rho() == QQbar(sqrt(2)/2*(1 + i)) True sage: HeckeTriangleGroup(6).rho() == QQbar(sqrt(3)/2 + 1/2*i) True sage: HeckeTriangleGroup(10).rho() 0.95105651629515...? + 0.30901699437494...?*I sage: HeckeTriangleGroup(infinity).rho() 1 """ # TODO: maybe rho should be replaced by -rhobar # Also we could use NumberFields... if (self._n == infinity): return coerce_AA(1) else: rho = AlgebraicField()(exp(pi / self._n * i)) rho.simplify() return rho
def _eval_(self, n, m, theta, phi, **kwargs): r""" TESTS:: sage: x, y = var('x y') sage: spherical_harmonic(1, 2, x, y) 0 sage: spherical_harmonic(1, -2, x, y) 0 sage: spherical_harmonic(1/2, 2, x, y) spherical_harmonic(1/2, 2, x, y) sage: spherical_harmonic(3, 2, x, y) 1/8*sqrt(30)*sqrt(7)*cos(x)*e^(2*I*y)*sin(x)^2/sqrt(pi) sage: spherical_harmonic(3, 2, 1, 2) 1/8*sqrt(30)*sqrt(7)*cos(1)*e^(4*I)*sin(1)^2/sqrt(pi) sage: spherical_harmonic(3 + I, 2., 1, 2) -0.351154337307488 - 0.415562233975369*I Check that :trac:`20939` is fixed:: sage: ex = spherical_harmonic(3,2,1,2*pi/3) sage: QQbar(ex * sqrt(pi)/cos(1)/sin(1)^2).minpoly() x^4 + 105/32*x^2 + 11025/1024 """ if n in ZZ and m in ZZ and n > -1: if abs(m) > n: return ZZ(0) if m == 0 and theta.is_zero(): return sqrt((2*n+1)/4/pi) from sage.arith.misc import factorial from sage.functions.trig import cos from sage.functions.orthogonal_polys import gen_legendre_P return (sqrt(factorial(n-m) * (2*n+1) / (4*pi * factorial(n+m))) * exp(I*m*phi) * gen_legendre_P(n, m, cos(theta)) * (-1)**m).simplify_trig()
def dvalue(self): r""" Return a symbolic expression (or an exact value in case n=3, 4, 6) for the transfinite diameter (or capacity) of ``self``. I.e. the first nontrivial Fourier coefficient of the Hauptmodul for the Hecke triangle group in case it is normalized to ``J_inv(i)=1``. EXAMPLES:: sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: HeckeTriangleGroup(3).dvalue() 1/1728 sage: HeckeTriangleGroup(4).dvalue() 1/256 sage: HeckeTriangleGroup(5).dvalue() e^(2*euler_gamma - 4*pi/(sqrt(5) + 1) + psi(17/20) + psi(13/20)) sage: HeckeTriangleGroup(6).dvalue() 1/108 sage: HeckeTriangleGroup(10).dvalue() e^(2*euler_gamma - 2*pi/sqrt(1/2*sqrt(5) + 5/2) + psi(4/5) + psi(7/10)) sage: HeckeTriangleGroup(infinity).dvalue() 1/64 """ n = self._n if (n==3): return ZZ(1)/ZZ(2**6*3**3) elif (n==4): return ZZ(1)/ZZ(2**8) elif (n==6): return ZZ(1)/ZZ(2**2*3**3) elif (n==infinity): return ZZ(1)/ZZ(2**6) else: return exp(-ZZ(2)*psi1(ZZ(1)) + psi1(ZZ(1)-self.alpha())+psi1(ZZ(1)-self.beta()) - pi*sec(pi/self._n))
def dvalue(self): r""" Return a symbolic expression (or an exact value in case n=3, 4, 6) for the transfinite diameter (or capacity) of ``self``. This is the first nontrivial Fourier coefficient of the Hauptmodul for the Hecke triangle group in case it is normalized to ``J_inv(i)=1``. EXAMPLES:: sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: HeckeTriangleGroup(3).dvalue() 1/1728 sage: HeckeTriangleGroup(4).dvalue() 1/256 sage: HeckeTriangleGroup(5).dvalue() e^(2*euler_gamma - 4*pi/(sqrt(5) + 1) + psi(17/20) + psi(13/20)) sage: HeckeTriangleGroup(6).dvalue() 1/108 sage: HeckeTriangleGroup(10).dvalue() e^(2*euler_gamma - 4*pi/sqrt(2*sqrt(5) + 10) + psi(4/5) + psi(7/10)) sage: HeckeTriangleGroup(infinity).dvalue() 1/64 """ n = self._n if n == 3: return ZZ(1)/ZZ(2**6*3**3) elif n == 4: return ZZ(1)/ZZ(2**8) elif n == 6: return ZZ(1)/ZZ(2**2*3**3) elif n == infinity: return ZZ(1)/ZZ(2**6) else: return exp(-ZZ(2)*psi1(ZZ(1)) + psi1(ZZ(1)-self.alpha())+psi1(ZZ(1)-self.beta()) - pi*sec(pi/self._n))
def dvalue(self): r""" Return a symbolic expression (or an exact value in case n=3, 4, 6) for the transfinite diameter (or capacity) of ``self``. EXAMPLES: sage: HeckeTriangleGroup(3).dvalue() 1/1728 sage: HeckeTriangleGroup(4).dvalue() 1/256 sage: HeckeTriangleGroup(5).dvalue() e^(2*euler_gamma - 4*pi/(sqrt(5) + 1) + psi(17/20) + psi(13/20)) sage: HeckeTriangleGroup(6).dvalue() 1/108 sage: HeckeTriangleGroup(10).dvalue() e^(2*euler_gamma - pi*sec(1/10*pi) + psi(4/5) + psi(7/10)) sage: HeckeTriangleGroup(infinity).dvalue() 1/64 """ n=self._n if (n==3): return ZZ(1)/ZZ(2**6*3**3) elif (n==4): return ZZ(1)/ZZ(2**8) elif (n==6): return ZZ(1)/ZZ(2**2*3**3) elif (n==infinity): return ZZ(1)/ZZ(2**6) else: return exp(-ZZ(2)*psi1(ZZ(1)) + psi1(ZZ(1)-self.alpha())+psi1(ZZ(1)-self.beta()) - pi*sec(pi/self._n))
def rho(self): r""" Return the vertex ``rho`` of the basic hyperbolic triangle which describes ``self``. ``rho`` has absolute value 1 and angle ``pi/n``. EXAMPLES:: sage: from sage.modular.modform_hecketriangle.hecke_triangle_groups import HeckeTriangleGroup sage: HeckeTriangleGroup(3).rho() == 1/2 + sqrt(3)/2*i True sage: HeckeTriangleGroup(4).rho() == sqrt(2)/2*(1 + i) True sage: HeckeTriangleGroup(6).rho() == sqrt(3)/2 + 1/2*i True sage: HeckeTriangleGroup(10).rho() 0.95105651629515...? + 0.30901699437494...?*I sage: HeckeTriangleGroup(infinity).rho() 1 """ # TODO: maybe rho should be replaced by -rhobar # Also we could use NumberFields... if (self._n == infinity): return coerce_AA(1) else: rho = AlgebraicField()(exp(pi/self._n*i)) rho.simplify() return rho
def _derivative_(self, x, diff_param=None): """ Derivative of erfc function. EXAMPLES:: sage: erfc(x).diff(x) -2*e^(-x^2)/sqrt(pi) """ return -2*exp(-x**2)/sqrt(pi)
def _derivative_(self, x, diff_param=None): """ Derivative of erfc function. EXAMPLES:: sage: erfc(x).diff(x) -2*e^(-x^2)/sqrt(pi) """ return -2 * exp(-x**2) / sqrt(pi)
def _derivative_(self, x, diff_param=None): """ Derivative of inverse erf function. EXAMPLES:: sage: erfinv(x).diff(x) 1/2*sqrt(pi)*e^(erfinv(x)^2) """ return sqrt(pi)*exp(erfinv(x)**2)/2
def _derivative_(self, x, diff_param=None): """ Derivative of inverse erf function. EXAMPLES:: sage: erfinv(x).diff(x) 1/2*sqrt(pi)*e^(erfinv(x)^2) """ return sqrt(pi) * exp(erfinv(x)**2) / 2
def _derivative_(self, x, diff_param=None): """ Derivative of erfi function. EXAMPLES:: sage: erfi(x).diff(x) 2*e^(x^2)/sqrt(pi) """ return 2 * exp(x**2) / sqrt(pi)
def _derivative_(self, x, diff_param=None): """ Derivative of erfi function. EXAMPLES:: sage: erfi(x).diff(x) 2*e^(x^2)/sqrt(pi) """ return 2*exp(x**2)/sqrt(pi)
def rho(self): r""" Return the vertex ``rho`` of the basic hyperbolic triangle which describes ``self``. ``rho`` has absolute value 1 and angle ``pi/n``. EXAMPLES:: sage: HeckeTriangleGroup(3).rho() == 1/2 + sqrt(3)/2*i True sage: HeckeTriangleGroup(4).rho() == sqrt(2)/2*(1 + i) True sage: HeckeTriangleGroup(6).rho() == sqrt(3)/2 + 1/2*i True sage: HeckeTriangleGroup(10).rho() 0.9510565162951536? + 0.3090169943749474?*I """ return AlgebraicField()(exp(pi/self._n*i))
def _derivative_(self, n, m, theta, phi, diff_param): r""" TESTS:: sage: n, m, theta, phi = var('n m theta phi') sage: spherical_harmonic(n, m, theta, phi).diff(theta) m*cot(theta)*spherical_harmonic(n, m, theta, phi) + sqrt(-(m + n + 1)*(m - n))*e^(-I*phi)*spherical_harmonic(n, m + 1, theta, phi) sage: spherical_harmonic(n, m, theta, phi).diff(phi) I*m*spherical_harmonic(n, m, theta, phi) """ if diff_param == 2: return (m * cot(theta) * spherical_harmonic(n, m, theta, phi) + sqrt((n - m) * (n + m + 1)) * exp(-I * phi) * spherical_harmonic(n, m + 1, theta, phi)) if diff_param == 3: return I * m * spherical_harmonic(n, m, theta, phi) raise ValueError('only derivative with respect to theta or phi' ' supported')
def _derivative_(self, x, diff_param=None): """ Derivative of erf function. EXAMPLES:: sage: erf(x).diff(x) 2*e^(-x^2)/sqrt(pi) TESTS: Check if :trac:`8568` is fixed:: sage: var('c,x') (c, x) sage: derivative(erf(c*x),x) 2*c*e^(-c^2*x^2)/sqrt(pi) sage: erf(c*x).diff(x)._maxima_init_() '((%pi)^(-1/2))*(_SAGE_VAR_c)*(exp(((_SAGE_VAR_c)^(2))*((_SAGE_VAR_x)^(2))*(-1)))*(2)' """ return 2 * exp(-x**2) / sqrt(pi)
def _derivative_(self, x, diff_param=None): """ Derivative of erf function. EXAMPLES:: sage: erf(x).diff(x) 2*e^(-x^2)/sqrt(pi) TESTS: Check if :trac:`8568` is fixed:: sage: var('c,x') (c, x) sage: derivative(erf(c*x),x) 2*c*e^(-c^2*x^2)/sqrt(pi) sage: erf(c*x).diff(x)._maxima_init_() '((%pi)^(-1/2))*(_SAGE_VAR_c)*(exp(((_SAGE_VAR_c)^(2))*((_SAGE_VAR_x)^(2))*(-1)))*(2)' """ return 2*exp(-x**2)/sqrt(pi)
def dimension__vector_valued(k, L, conjugate = False) : r""" Compute the dimension of the space of weight `k` vector valued modular forms for the Weil representation (or its conjugate) attached to the lattice `L`. See [Borcherds, Borcherds - Reflection groups of Lorentzian lattices] for a proof of the formula that we use here. INPUT: - `k` -- A half-integer. - ``L`` -- An quadratic form. - ``conjugate`` -- A boolean; If ``True``, then compute the dimension for the conjugated Weil representation. OUTPUT: An integer. TESTS:: sage: dimension__vector_valued(3, QuadraticForm(-matrix(2, [2, 1, 1, 2]))) 1 sage: dimension__vector_valued(3, QuadraticForm(-matrix(2, [2, 0, 0, 2]))) 1 sage: dimension__vector_valued(3, QuadraticForm(-matrix(2, [2, 0, 0, 4]))) 1 """ if 2 * k not in ZZ : raise ValueError( "Weight must be half-integral" ) if k <= 0 : return 0 if k < 2 : raise NotImplementedError( "Weight <2 is not implemented." ) if L.matrix().rank() != L.matrix().nrows() : raise ValueError( "The lattice (={0}) must be non-degenerate.".format(L) ) L_dimension = L.matrix().nrows() if L_dimension % 2 != ZZ(2 * k) % 2 : return 0 plus_basis = ZZ(L_dimension + 2 * k) % 4 == 0 ## The bilinear and the quadratic form attached to L quadratic = lambda x: L(x) // 2 bilinear = lambda x,y: L(x + y) - L(x) - L(y) ## A dual basis for L (elementary_divisors, dual_basis_pre, _) = L.matrix().smith_form() elementary_divisors = elementary_divisors.diagonal() dual_basis = map(operator.div, list(dual_basis_pre), elementary_divisors) L_level = ZZ(lcm([ b.denominator() for b in dual_basis ])) (elementary_divisors, _, discriminant_basis_pre) = (L_level * matrix(dual_basis)).change_ring(ZZ).smith_form() elementary_divisors = filter( lambda d: d not in ZZ, (elementary_divisors / L_level).diagonal() ) elementary_divisors_inv = map(ZZ, [ed**-1 for ed in elementary_divisors]) discriminant_basis = matrix(map( operator.mul, discriminant_basis_pre.inverse().rows()[:len(elementary_divisors)], elementary_divisors )).transpose() ## This is a form over QQ, so that we cannot use an instance of QuadraticForm discriminant_form = discriminant_basis.transpose() * L.matrix() * discriminant_basis if conjugate : discriminant_form = - discriminant_form if prod(elementary_divisors_inv) > 100 : disc_den = discriminant_form.denominator() disc_bilinear_pre = \ cython_lambda( ', '.join( ['int a{0}'.format(i) for i in range(discriminant_form.nrows())] + ['int b{0}'.format(i) for i in range(discriminant_form.nrows())] ), ' + '.join('{0} * a{1} * b{2}'.format(disc_den * discriminant_form[i,j], i, j) for i in range(discriminant_form.nrows()) for j in range(discriminant_form.nrows())) ) disc_bilinear = lambda *a: disc_bilinear_pre(*a) / disc_den else : disc_bilinear = lambda *xy: vector(ZZ, xy[:discriminant_form.nrows()]) * discriminant_form * vector(ZZ, xy[discriminant_form.nrows():]) disc_quadratic = lambda *a: disc_bilinear(*(2 * a)) / 2 ## red gives a normal form for elements in the discriminant group red = lambda x : map(operator.mod, x, elementary_divisors_inv) def is_singl(x) : y = red(map(operator.neg, x)) for (e, f) in zip(x, y) : if e < f : return -1 elif e > f : return 1 return 0 ## singls and pairs are elements of the discriminant group that are, respectively, ## fixed and not fixed by negation. singls = list() pairs = list() for x in mrange(elementary_divisors_inv) : si = is_singl(x) if si == 0 : singls.append(x) elif si == 1 : pairs.append(x) if plus_basis : subspace_dimension = len(singls + pairs) else : subspace_dimension = len(pairs) ## 200 bits are, by far, sufficient to distinguish 12-th roots of unity ## by increasing the precision by 4 for each additional dimension, we ## compensate, by far, the errors introduced by the QR decomposition, ## which are of the size of (absolute error) * dimension CC = ComplexIntervalField(200 + subspace_dimension * 4) zeta_order = ZZ(lcm([8, 12] + map(lambda ed: 2 * ed, elementary_divisors_inv))) zeta = CC(exp(2 * pi * I / zeta_order)) sqrt2 = CC(sqrt(2)) drt = CC(sqrt(L.det())) Tmat = diagonal_matrix(CC, [zeta**(zeta_order*disc_quadratic(*a)) for a in (singls + pairs if plus_basis else pairs)]) if plus_basis : Smat = zeta**(zeta_order / 8 * L_dimension) / drt \ * matrix( CC, [ [zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) for delta in singls] + [sqrt2 * zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) for delta in pairs] for gamma in singls] \ + [ [sqrt2 * zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) for delta in singls] + [zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) + zeta**(-zeta_order * disc_bilinear(*(gamma + map(operator.neg, delta)))) for delta in pairs] for gamma in pairs] ) else : Smat = zeta**(zeta_order / 8 * L_dimension) / drt \ * matrix( CC, [ [zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) - zeta**(-zeta_order * disc_bilinear(*(gamma + map(operator.neg,delta)))) for delta in pairs] for gamma in pairs ] ) STmat = Smat * Tmat ## This function overestimates the number of eigenvalues, if it is not correct def eigenvalue_multiplicity(mat, ev) : mat = matrix(CC, mat - ev * identity_matrix(subspace_dimension)) return len(filter( lambda row: all( e.contains_zero() for e in row), _qr(mat).rows() )) rti = CC(exp(2 * pi * I / 8)) S_ev_multiplicity = [eigenvalue_multiplicity(Smat, rti**n) for n in range(8)] ## Together with the fact that eigenvalue_multiplicity overestimates the multiplicities ## this asserts that the computed multiplicities are correct assert sum(S_ev_multiplicity) == subspace_dimension rho = CC(exp(2 * pi * I / 12)) ST_ev_multiplicity = [eigenvalue_multiplicity(STmat, rho**n) for n in range(12)] ## Together with the fact that eigenvalue_multiplicity overestimates the multiplicities ## this asserts that the computed multiplicities are correct assert sum(ST_ev_multiplicity) == subspace_dimension T_evs = [ ZZ((zeta_order * disc_quadratic(*a)) % zeta_order) / zeta_order for a in (singls + pairs if plus_basis else pairs) ] return subspace_dimension * (1 + QQ(k) / 12) \ - ZZ(sum( (ST_ev_multiplicity[n] * ((-2 * k - n) % 12)) for n in range(12) )) / 12 \ - ZZ(sum( (S_ev_multiplicity[n] * ((2 * k + n) % 8)) for n in range(8) )) / 8 \ - sum(T_evs)
def dimension__vector_valued(k, L, conjugate=False): r""" Compute the dimension of the space of weight `k` vector valued modular forms for the Weil representation (or its conjugate) attached to the lattice `L`. See [Borcherds, Borcherds - Reflection groups of Lorentzian lattices] for a proof of the formula that we use here. INPUT: - `k` -- A half-integer. - ``L`` -- An quadratic form. - ``conjugate`` -- A boolean; If ``True``, then compute the dimension for the conjugated Weil representation. OUTPUT: An integer. TESTS:: sage: dimension__vector_valued(3, QuadraticForm(-matrix(2, [2, 1, 1, 2]))) 1 sage: dimension__vector_valued(3, QuadraticForm(-matrix(2, [2, 0, 0, 2]))) 1 sage: dimension__vector_valued(3, QuadraticForm(-matrix(2, [2, 0, 0, 4]))) 1 """ if 2 * k not in ZZ: raise ValueError("Weight must be half-integral") if k <= 0: return 0 if k < 2: raise NotImplementedError("Weight <2 is not implemented.") if L.matrix().rank() != L.matrix().nrows(): raise ValueError( "The lattice (={0}) must be non-degenerate.".format(L)) L_dimension = L.matrix().nrows() if L_dimension % 2 != ZZ(2 * k) % 2: return 0 plus_basis = ZZ(L_dimension + 2 * k) % 4 == 0 ## The bilinear and the quadratic form attached to L quadratic = lambda x: L(x) // 2 bilinear = lambda x, y: L(x + y) - L(x) - L(y) ## A dual basis for L (elementary_divisors, dual_basis_pre, _) = L.matrix().smith_form() elementary_divisors = elementary_divisors.diagonal() dual_basis = map(operator.div, list(dual_basis_pre), elementary_divisors) L_level = ZZ(lcm([b.denominator() for b in dual_basis])) (elementary_divisors, _, discriminant_basis_pre) = ( L_level * matrix(dual_basis)).change_ring(ZZ).smith_form() elementary_divisors = filter(lambda d: d not in ZZ, (elementary_divisors / L_level).diagonal()) elementary_divisors_inv = map(ZZ, [ed**-1 for ed in elementary_divisors]) discriminant_basis = matrix( map(operator.mul, discriminant_basis_pre.inverse().rows()[:len(elementary_divisors)], elementary_divisors)).transpose() ## This is a form over QQ, so that we cannot use an instance of QuadraticForm discriminant_form = discriminant_basis.transpose() * L.matrix( ) * discriminant_basis if conjugate: discriminant_form = -discriminant_form if prod(elementary_divisors_inv) > 100: disc_den = discriminant_form.denominator() disc_bilinear_pre = \ cython_lambda( ', '.join( ['int a{0}'.format(i) for i in range(discriminant_form.nrows())] + ['int b{0}'.format(i) for i in range(discriminant_form.nrows())] ), ' + '.join('{0} * a{1} * b{2}'.format(disc_den * discriminant_form[i,j], i, j) for i in range(discriminant_form.nrows()) for j in range(discriminant_form.nrows())) ) disc_bilinear = lambda *a: disc_bilinear_pre(*a) / disc_den else: disc_bilinear = lambda *xy: vector(ZZ, xy[:discriminant_form.nrows( )]) * discriminant_form * vector(ZZ, xy[discriminant_form.nrows():]) disc_quadratic = lambda *a: disc_bilinear(*(2 * a)) / 2 ## red gives a normal form for elements in the discriminant group red = lambda x: map(operator.mod, x, elementary_divisors_inv) def is_singl(x): y = red(map(operator.neg, x)) for (e, f) in zip(x, y): if e < f: return -1 elif e > f: return 1 return 0 ## singls and pairs are elements of the discriminant group that are, respectively, ## fixed and not fixed by negation. singls = list() pairs = list() for x in mrange(elementary_divisors_inv): si = is_singl(x) if si == 0: singls.append(x) elif si == 1: pairs.append(x) if plus_basis: subspace_dimension = len(singls + pairs) else: subspace_dimension = len(pairs) ## 200 bits are, by far, sufficient to distinguish 12-th roots of unity ## by increasing the precision by 4 for each additional dimension, we ## compensate, by far, the errors introduced by the QR decomposition, ## which are of the size of (absolute error) * dimension CC = ComplexIntervalField(200 + subspace_dimension * 4) zeta_order = ZZ( lcm([8, 12] + map(lambda ed: 2 * ed, elementary_divisors_inv))) zeta = CC(exp(2 * pi * I / zeta_order)) sqrt2 = CC(sqrt(2)) drt = CC(sqrt(L.det())) Tmat = diagonal_matrix(CC, [ zeta**(zeta_order * disc_quadratic(*a)) for a in (singls + pairs if plus_basis else pairs) ]) if plus_basis: Smat = zeta**(zeta_order / 8 * L_dimension) / drt \ * matrix( CC, [ [zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) for delta in singls] + [sqrt2 * zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) for delta in pairs] for gamma in singls] \ + [ [sqrt2 * zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) for delta in singls] + [zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) + zeta**(-zeta_order * disc_bilinear(*(gamma + map(operator.neg, delta)))) for delta in pairs] for gamma in pairs] ) else: Smat = zeta**(zeta_order / 8 * L_dimension) / drt \ * matrix( CC, [ [zeta**(-zeta_order * disc_bilinear(*(gamma + delta))) - zeta**(-zeta_order * disc_bilinear(*(gamma + map(operator.neg,delta)))) for delta in pairs] for gamma in pairs ] ) STmat = Smat * Tmat ## This function overestimates the number of eigenvalues, if it is not correct def eigenvalue_multiplicity(mat, ev): mat = matrix(CC, mat - ev * identity_matrix(subspace_dimension)) return len( filter(lambda row: all(e.contains_zero() for e in row), _qr(mat).rows())) rti = CC(exp(2 * pi * I / 8)) S_ev_multiplicity = [ eigenvalue_multiplicity(Smat, rti**n) for n in range(8) ] ## Together with the fact that eigenvalue_multiplicity overestimates the multiplicities ## this asserts that the computed multiplicities are correct assert sum(S_ev_multiplicity) == subspace_dimension rho = CC(exp(2 * pi * I / 12)) ST_ev_multiplicity = [ eigenvalue_multiplicity(STmat, rho**n) for n in range(12) ] ## Together with the fact that eigenvalue_multiplicity overestimates the multiplicities ## this asserts that the computed multiplicities are correct assert sum(ST_ev_multiplicity) == subspace_dimension T_evs = [ ZZ((zeta_order * disc_quadratic(*a)) % zeta_order) / zeta_order for a in (singls + pairs if plus_basis else pairs) ] return subspace_dimension * (1 + QQ(k) / 12) \ - ZZ(sum( (ST_ev_multiplicity[n] * ((-2 * k - n) % 12)) for n in range(12) )) / 12 \ - ZZ(sum( (S_ev_multiplicity[n] * ((2 * k + n) % 8)) for n in range(8) )) / 8 \ - sum(T_evs)