예제 #1
0
    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
예제 #2
0
    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()
예제 #3
0
파일: special.py 프로젝트: sagemath/sage
    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()
예제 #4
0
    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))
예제 #5
0
    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))
예제 #6
0
    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))
예제 #7
0
    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
예제 #8
0
파일: error.py 프로젝트: mcognetta/sage
    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)
예제 #9
0
파일: error.py 프로젝트: swewers/mein_sage
    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)
예제 #10
0
파일: error.py 프로젝트: mcognetta/sage
    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
예제 #11
0
파일: error.py 프로젝트: swewers/mein_sage
    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
예제 #12
0
파일: error.py 프로젝트: swewers/mein_sage
    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)
예제 #13
0
파일: error.py 프로젝트: mcognetta/sage
    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)
예제 #14
0
    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))
예제 #15
0
파일: special.py 프로젝트: manguluka/sage
    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')
예제 #16
0
파일: error.py 프로젝트: swewers/mein_sage
    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)
예제 #17
0
파일: error.py 프로젝트: mcognetta/sage
    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)
예제 #18
0
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)
예제 #19
0
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)