def matrix(self): V = FreeModule(ZZ, len(self._start)) columns = [copy(v) for v in V.basis()] for p, winner, side in self: winner_letter = p._labels[winner][side] loser_letter = p._labels[1 - winner][side] columns[loser_letter] += columns[winner_letter] m = MatrixSpace(ZZ, len(p))(columns).transpose() perm_on_list_inplace(self._relabelling, m, swap=sage.matrix.matrix0.Matrix.swap_columns) return m
def matrix(self): r""" Return the Rauzy matrix of this path. TESTS:: sage: from surface_dynamics import iet sage: p = iet.Permutation('a b', 'b a') sage: iet.FlipSequence(p, '').matrix() [1 0] [0 1] """ V = FreeModule(ZZ, len(self._start)) columns = [copy(v) for v in V.basis()] for p, winner, side in self: winner_letter = p._labels[winner][side] loser_letter = p._labels[1-winner][side] columns[loser_letter] += columns[winner_letter] m = MatrixSpace(ZZ, len(self._start))(columns).transpose() perm_on_list_inplace(self._relabelling, m, swap=sage.matrix.matrix0.Matrix.swap_columns) return m
class JordanAlgebraSymmetricBilinear(JordanAlgebra): r""" A Jordan algebra given by a symmetric bilinear form `m`. """ def __init__(self, R, form, names=None): """ Initialize ``self``. TESTS:: sage: m = matrix([[-2,3],[3,4]]) sage: J = JordanAlgebra(m) sage: TestSuite(J).run() """ self._form = form self._M = FreeModule(R, form.ncols()) cat = MagmaticAlgebras( R).Commutative().Unital().FiniteDimensional().WithBasis() self._no_generic_basering_coercion = True # Remove once 16492 is fixed Parent.__init__(self, base=R, names=names, category=cat) def _repr_(self): """ Return a string representation of ``self``. EXAMPLES:: sage: m = matrix([[-2,3],[3,4]]) sage: JordanAlgebra(m) Jordan algebra over Integer Ring given by the symmetric bilinear form: [-2 3] [ 3 4] """ return "Jordan algebra over {} given by the symmetric bilinear" \ " form:\n{}".format(self.base_ring(), self._form) def _element_constructor_(self, *args): """ Construct an element of ``self`` from ``s``. Here ``s`` can be a pair of an element of `R` and an element of `M`, or an element of `R`, or an element of `M`, or an element of a(nother) Jordan algebra given by a symmetric bilinear form. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J(2) 2 + (0, 0) sage: J((-4, (2, 5))) -4 + (2, 5) sage: J((-4, (ZZ^2)((2, 5)))) -4 + (2, 5) sage: J(2, (-2, 3)) 2 + (-2, 3) sage: J(2, (ZZ^2)((-2, 3))) 2 + (-2, 3) sage: J(-1, 1, 0) -1 + (1, 0) sage: J((ZZ^2)((1, 3))) 0 + (1, 3) sage: m = matrix([[2]]) sage: J = JordanAlgebra(m) sage: J(2) 2 + (0) sage: J((-4, (2,))) -4 + (2) sage: J(2, (-2,)) 2 + (-2) sage: J(-1, 1) -1 + (1) sage: J((ZZ^1)((3,))) 0 + (3) sage: m = Matrix(QQ, []) sage: J = JordanAlgebra(m) sage: J(2) 2 + () sage: J((-4, ())) -4 + () sage: J(2, ()) 2 + () sage: J(-1) -1 + () sage: J((ZZ^0)(())) 0 + () """ R = self.base_ring() if len(args) == 1: s = args[0] if isinstance(s, JordanAlgebraSymmetricBilinear.Element): if s.parent() is self: return s return self.element_class(self, R(s._s), self._M(s._v)) if isinstance(s, (list, tuple)): if len(s) != 2: raise ValueError("must be length 2") return self.element_class(self, R(s[0]), self._M(s[1])) if s in self._M: return self.element_class(self, R.zero(), self._M(s)) return self.element_class(self, R(s), self._M.zero()) if len(args) == 2 and (isinstance(args[1], (list, tuple)) or args[1] in self._M): return self.element_class(self, R(args[0]), self._M(args[1])) if len(args) == self._form.ncols() + 1: return self.element_class(self, R(args[0]), self._M(args[1:])) raise ValueError("unable to construct an element from the given data") @cached_method def basis(self): """ Return a basis of ``self``. The basis returned begins with the unity of `R` and continues with the standard basis of `M`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.basis() Family (1 + (0, 0), 0 + (1, 0), 0 + (0, 1)) """ R = self.base_ring() ret = (self.element_class(self, R.one(), self._M.zero()), ) ret += tuple( self.element_class(self, R.zero(), x) for x in self._M.basis()) return Family(ret) algebra_generators = basis def gens(self): """ Return the generators of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.basis() Family (1 + (0, 0), 0 + (1, 0), 0 + (0, 1)) """ return tuple(self.algebra_generators()) @cached_method def zero(self): """ Return the element 0. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.zero() 0 + (0, 0) """ return self.element_class(self, self.base_ring().zero(), self._M.zero()) @cached_method def one(self): """ Return the element 1 if it exists. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.one() 1 + (0, 0) """ return self.element_class(self, self.base_ring().one(), self._M.zero()) class Element(AlgebraElement): """ An element of a Jordan algebra defined by a symmetric bilinear form. """ def __init__(self, parent, s, v): """ Initialize ``self``. TESTS:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: TestSuite(a + 2*b - c).run() """ self._s = s self._v = v AlgebraElement.__init__(self, parent) def _repr_(self): """ Return a string representation of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a + 2*b - c 1 + (2, -1) """ return "{} + {}".format(self._s, self._v) def _latex_(self): r""" Return a latex representation of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: latex(a + 2*b - c) 1 + \left(2,\,-1\right) """ from sage.misc.latex import latex return "{} + {}".format(latex(self._s), latex(self._v)) def __bool__(self): """ Return if ``self`` is non-zero. TESTS:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: bool(1) True sage: bool(b) True sage: bool(a + 2*b - c) True """ return bool(self._s) or bool(self._v) __nonzero__ = __bool__ def __eq__(self, other): """ Check equality. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x == J((4, (-1, 3))) True sage: a == x False sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 4*a - b + 3*c == x False """ if not isinstance(other, JordanAlgebraSymmetricBilinear.Element): return False if other.parent() != self.parent(): return False return self._s == other._s and self._v == other._v def __ne__(self, other): """ Check inequality. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x != J((4, (-1, 3))) False sage: a != x True sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 4*a - b + 3*c != x True """ return not self == other def _add_(self, other): """ Add ``self`` and ``other``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a + b 1 + (1, 0) sage: b + c 0 + (1, 1) """ return self.__class__(self.parent(), self._s + other._s, self._v + other._v) def _neg_(self): """ Negate ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: -(a + b - 2*c) -1 + (-1, 2) """ return self.__class__(self.parent(), -self._s, -self._v) def _sub_(self, other): """ Subtract ``other`` from ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a - b 1 + (-1, 0) sage: b - c 0 + (1, -1) """ return self.__class__(self.parent(), self._s - other._s, self._v - other._v) def _mul_(self, other): """ Multiply ``self`` and ``other``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (4*a - b + 3*c)*(2*a + 2*b - c) 12 + (6, 2) sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (4*a - b + 3*c)*(2*a + 2*b - c) 21 + (6, 2) """ P = self.parent() return self.__class__( P, self._s * other._s + (self._v * P._form * other._v.column())[0], other._s * self._v + self._s * other._v) def _lmul_(self, other): """ Multiply ``self`` by the scalar ``other`` on the left. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (a + b - c) * 2 2 + (2, -2) """ return self.__class__(self.parent(), self._s * other, self._v * other) def _rmul_(self, other): """ Multiply ``self`` with the scalar ``other`` by the right action. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 2 * (a + b - c) 2 + (2, -2) """ return self.__class__(self.parent(), other * self._s, other * self._v) def monomial_coefficients(self, copy=True): """ Return a dictionary whose keys are indices of basis elements in the support of ``self`` and whose values are the corresponding coefficients. INPUT: - ``copy`` -- ignored EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: elt = a + 2*b - c sage: elt.monomial_coefficients() {0: 1, 1: 2, 2: -1} """ d = {0: self._s} for i, c in enumerate(self._v): d[i + 1] = c return d def trace(self): r""" Return the trace of ``self``. The trace of an element `\alpha + x \in M^*` is given by `t(\alpha + x) = 2 \alpha`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x.trace() 8 """ return 2 * self._s def norm(self): r""" Return the norm of ``self``. The norm of an element `\alpha + x \in M^*` is given by `n(\alpha + x) = \alpha^2 - (x, x)`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c; x 4 + (-1, 3) sage: x.norm() 13 """ return self._s * self._s - (self._v * self.parent()._form * self._v.column())[0] def bar(self): r""" Return the result of the bar involution of ``self``. The bar involution `\bar{\cdot}` is the `R`-linear endomorphism of `M^*` defined by `\bar{1} = 1` and `\bar{x} = -x` for `x \in M`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x.bar() 4 + (1, -3) We check that it is an algebra morphism:: sage: y = 2*a + 2*b - c sage: x.bar() * y.bar() == (x*y).bar() True """ return self.__class__(self.parent(), self._s, -self._v)
class JordanAlgebraSymmetricBilinear(JordanAlgebra): r""" A Jordan algebra given by a symmetric bilinear form `m`. """ def __init__(self, R, form, names=None): """ Initialize ``self``. TESTS:: sage: m = matrix([[-2,3],[3,4]]) sage: J = JordanAlgebra(m) sage: TestSuite(J).run() """ self._form = form self._M = FreeModule(R, form.ncols()) cat = MagmaticAlgebras(R).Commutative().Unital().FiniteDimensional().WithBasis() self._no_generic_basering_coercion = True # Remove once 16492 is fixed Parent.__init__(self, base=R, names=names, category=cat) def _repr_(self): """ Return a string representation of ``self``. EXAMPLES:: sage: m = matrix([[-2,3],[3,4]]) sage: JordanAlgebra(m) Jordan algebra over Integer Ring given by the symmetric bilinear form: [-2 3] [ 3 4] """ return "Jordan algebra over {} given by the symmetric bilinear" \ " form:\n{}".format(self.base_ring(), self._form) def _element_constructor_(self, *args): """ Construct an element of ``self`` from ``s``. Here ``s`` can be a pair of an element of `R` and an element of `M`, or an element of `R`, or an element of `M`, or an element of a(nother) Jordan algebra given by a symmetric bilinear form. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J(2) 2 + (0, 0) sage: J((-4, (2, 5))) -4 + (2, 5) sage: J((-4, (ZZ^2)((2, 5)))) -4 + (2, 5) sage: J(2, (-2, 3)) 2 + (-2, 3) sage: J(2, (ZZ^2)((-2, 3))) 2 + (-2, 3) sage: J(-1, 1, 0) -1 + (1, 0) sage: J((ZZ^2)((1, 3))) 0 + (1, 3) sage: m = matrix([[2]]) sage: J = JordanAlgebra(m) sage: J(2) 2 + (0) sage: J((-4, (2,))) -4 + (2) sage: J(2, (-2,)) 2 + (-2) sage: J(-1, 1) -1 + (1) sage: J((ZZ^1)((3,))) 0 + (3) sage: m = Matrix(QQ, []) sage: J = JordanAlgebra(m) sage: J(2) 2 + () sage: J((-4, ())) -4 + () sage: J(2, ()) 2 + () sage: J(-1) -1 + () sage: J((ZZ^0)(())) 0 + () """ R = self.base_ring() if len(args) == 1: s = args[0] if isinstance(s, JordanAlgebraSymmetricBilinear.Element): if s.parent() is self: return s return self.element_class(self, R(s._s), self._M(s._v)) if isinstance(s, (list, tuple)): if len(s) != 2: raise ValueError("must be length 2") return self.element_class(self, R(s[0]), self._M(s[1])) if s in self._M: return self.element_class(self, R.zero(), self._M(s)) return self.element_class(self, R(s), self._M.zero()) if len(args) == 2 and (isinstance(args[1], (list, tuple)) or args[1] in self._M): return self.element_class(self, R(args[0]), self._M(args[1])) if len(args) == self._form.ncols() + 1: return self.element_class(self, R(args[0]), self._M(args[1:])) raise ValueError("unable to construct an element from the given data") @cached_method def basis(self): """ Return a basis of ``self``. The basis returned begins with the unity of `R` and continues with the standard basis of `M`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.basis() Family (1 + (0, 0), 0 + (1, 0), 0 + (0, 1)) """ R = self.base_ring() ret = (self.element_class(self, R.one(), self._M.zero()),) ret += tuple(self.element_class(self, R.zero(), x) for x in self._M.basis()) return Family(ret) algebra_generators = basis def gens(self): """ Return the generators of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.basis() Family (1 + (0, 0), 0 + (1, 0), 0 + (0, 1)) """ return tuple(self.algebra_generators()) @cached_method def zero(self): """ Return the element 0. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.zero() 0 + (0, 0) """ return self.element_class(self, self.base_ring().zero(), self._M.zero()) @cached_method def one(self): """ Return the element 1 if it exists. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J = JordanAlgebra(m) sage: J.one() 1 + (0, 0) """ return self.element_class(self, self.base_ring().one(), self._M.zero()) class Element(AlgebraElement): """ An element of a Jordan algebra defined by a symmetric bilinear form. """ def __init__(self, parent, s, v): """ Initialize ``self``. TESTS:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: TestSuite(a + 2*b - c).run() """ self._s = s self._v = v AlgebraElement.__init__(self, parent) def _repr_(self): """ Return a string representation of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a + 2*b - c 1 + (2, -1) """ return "{} + {}".format(self._s, self._v) def _latex_(self): r""" Return a latex representation of ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: latex(a + 2*b - c) 1 + \left(2,\,-1\right) """ from sage.misc.latex import latex return "{} + {}".format(latex(self._s), latex(self._v)) def __bool__(self): """ Return if ``self`` is non-zero. TESTS:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: bool(1) True sage: bool(b) True sage: bool(a + 2*b - c) True """ return bool(self._s) or bool(self._v) __nonzero__ = __bool__ def __eq__(self, other): """ Check equality. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x == J((4, (-1, 3))) True sage: a == x False sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 4*a - b + 3*c == x False """ if not isinstance(other, JordanAlgebraSymmetricBilinear.Element): return False if other.parent() != self.parent(): return False return self._s == other._s and self._v == other._v def __ne__(self, other): """ Check inequality. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x != J((4, (-1, 3))) False sage: a != x True sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 4*a - b + 3*c != x True """ return not self == other def _add_(self, other): """ Add ``self`` and ``other``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a + b 1 + (1, 0) sage: b + c 0 + (1, 1) """ return self.__class__(self.parent(), self._s + other._s, self._v + other._v) def _neg_(self): """ Negate ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: -(a + b - 2*c) -1 + (-1, 2) """ return self.__class__(self.parent(), -self._s, -self._v) def _sub_(self, other): """ Subtract ``other`` from ``self``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: a - b 1 + (-1, 0) sage: b - c 0 + (1, -1) """ return self.__class__(self.parent(), self._s - other._s, self._v - other._v) def _mul_(self, other): """ Multiply ``self`` and ``other``. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (4*a - b + 3*c)*(2*a + 2*b - c) 12 + (6, 2) sage: m = matrix([[-2,3],[3,4]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (4*a - b + 3*c)*(2*a + 2*b - c) 21 + (6, 2) """ P = self.parent() return self.__class__(P, self._s * other._s + (self._v * P._form * other._v.column())[0], other._s * self._v + self._s * other._v) def _lmul_(self, other): """ Multiply ``self`` by the scalar ``other`` on the left. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: (a + b - c) * 2 2 + (2, -2) """ return self.__class__(self.parent(), self._s * other, self._v * other) def _rmul_(self, other): """ Multiply ``self`` with the scalar ``other`` by the right action. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: 2 * (a + b - c) 2 + (2, -2) """ return self.__class__(self.parent(), other * self._s, other * self._v) def monomial_coefficients(self, copy=True): """ Return a dictionary whose keys are indices of basis elements in the support of ``self`` and whose values are the corresponding coefficients. INPUT: - ``copy`` -- ignored EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: elt = a + 2*b - c sage: elt.monomial_coefficients() {0: 1, 1: 2, 2: -1} """ d = {0: self._s} for i,c in enumerate(self._v): d[i+1] = c return d def trace(self): r""" Return the trace of ``self``. The trace of an element `\alpha + x \in M^*` is given by `t(\alpha + x) = 2 \alpha`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x.trace() 8 """ return 2 * self._s def norm(self): r""" Return the norm of ``self``. The norm of an element `\alpha + x \in M^*` is given by `n(\alpha + x) = \alpha^2 - (x, x)`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c; x 4 + (-1, 3) sage: x.norm() 13 """ return self._s * self._s - (self._v * self.parent()._form * self._v.column())[0] def bar(self): r""" Return the result of the bar involution of ``self``. The bar involution `\bar{\cdot}` is the `R`-linear endomorphism of `M^*` defined by `\bar{1} = 1` and `\bar{x} = -x` for `x \in M`. EXAMPLES:: sage: m = matrix([[0,1],[1,1]]) sage: J.<a,b,c> = JordanAlgebra(m) sage: x = 4*a - b + 3*c sage: x.bar() 4 + (1, -3) We check that it is an algebra morphism:: sage: y = 2*a + 2*b - c sage: x.bar() * y.bar() == (x*y).bar() True """ return self.__class__(self.parent(), self._s, -self._v)
class DegreeGrading( Grading_abstract ) : r""" This class implements a monomial grading for a polynomial ring `R[x_1, .., x_n]`. """ def __init__( self, degrees ) : r""" INPUT: - ``degrees`` -- A list or tuple of `n` positive integers. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading((1,2)) sage: g = DegreeGrading([5,2,3]) """ self.__degrees = tuple(degrees) self.__module = FreeModule(ZZ, len(degrees)) self.__module_basis = self.__module.basis() def ngens(self) : r""" The number of generators of the polynomial ring. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading((1,2)).ngens() 2 sage: DegreeGrading([5,2,3]).ngens() 3 """ return len(self.__degrees) def gen(self, i) : r""" The number of generators of the polynomial ring. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading([5,2,3]).gen(0) 5 sage: DegreeGrading([5,2,3]).gen(3) Traceback (most recent call last): ... ValueError: Generator 3 does not exist. """ if i < len(self.__degrees) : return self.__degrees[i] raise ValueError("Generator %s does not exist." % (i,)) def gens(self) : r""" The gradings of the generators of the polynomial ring. OUTPUT: A tuple of integers. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading((1,2)).gens() (1, 2) sage: DegreeGrading([5,2,3]).gens() (5, 2, 3) """ return self.__degrees def index(self, x) : r""" The grading value of `x` with respect to this grading. INPUT: - `x` -- A tuple of length `n`. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.index((2,4,5)) 33 sage: g.index((2,3)) Traceback (most recent call last): ... ValueError: Tuple must have length 3. """ ## TODO: We shouldn't need this. #if len(x) == 0 : return infinity if len(x) != len(self.__degrees) : raise ValueError( "Tuple must have length %s." % (len(self.__degrees),)) return sum( map(mul, x, self.__degrees) ) def basis(self, index, vars = None) : r""" All monomials that are have given grading index involving only the given variables. INPUT: - ``index`` -- A grading index. - ``vars`` -- A list of integers from `0` to `n - 1` or ``None`` (default: ``None``); If ``None`` all variables will be considered. OUTPUT: A list of elements in `\Z^n`. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.basis(2) [(0, 1, 0)] sage: g.basis(10, vars = [0]) [(2, 0, 0)] sage: g.basis(20, vars = [1,2]) [(0, 10, 0), (0, 7, 2), (0, 4, 4), (0, 1, 6)] sage: g.basis(-1) [] sage: g.basis(0) [(0, 0, 0)] """ if index < 0 : return [] elif index == 0 : return [self.__module.zero_vector()] if vars == None : vars = [m for (m,d) in enumerate(self.__degrees) if d <= index] if len(vars) == 0 : return [] res = [ self.__module_basis[vars[0]] + m for m in self.basis(index - self.__degrees[vars[0]], vars) ] res += self.basis(index, vars[1:]) return res def subgrading(self, gens) : r""" The grading of same type for the ring with only the variables given by ``gens``. INPUT: - ``gens`` - A list of integers. OUTPUT: An instance of :class:~`.DegreeGrading`. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.subgrading([2]) Degree grading (3,) sage: g.subgrading([]) Degree grading () """ return DegreeGrading([self.__degrees[i] for i in gens]) def __contains__(self, x) : r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: 5 in g True sage: "t" in g False """ return isinstance(x, (int, Integer)) def __cmp__(self, other) : r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g == DegreeGrading([5,2,3]) True sage: g == DegreeGrading((2,4)) False """ c = cmp(type(self), type(other)) if c == 0 : c = cmp(self.__degrees, other.__degrees) return c def __hash__(self) : r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: hash( DegreeGrading([5,2,3]) ) -1302562269 # 32-bit 7573306103633312291 # 64-bit """ return hash(self.__degrees) def _repr_(self) : r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading([5,2,3]) Degree grading (5, 2, 3) """ return "Degree grading %s" % str(self.__degrees) def _latex_(self) : r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: latex( DegreeGrading([5,2,3]) ) \text{Degree grading } \left(5, 2, 3\right) """ return r"\text{Degree grading }" + latex(self.__degrees)
def herm_modform_space(D, HermWeight, B_cF=10, parallelization=None, reduction_method_flags=-1): """ This calculates the vectorspace of Fourier expansions to Hermitian modular forms of weight `HermWeight` over \Gamma, where \Gamma = \Sp_2(\curlO) and \curlO is the maximal order of \QQ(\sqrt{D}). Each Fourier coefficient vector is indexed up to a precision \curlF which is given by `B_cF` such that for every [a,b,c] \in \curlF \subset \Lambda, we have 0 \le a,c \le B_cF. The function `herm_modform_indexset()` returns reduced matrices of that precision index set \curlF. """ if HermWeight % 3 != 0: raise TypeError, "the modulform is trivial/zero if HermWeight is not divisible by 3" # Transform these into native Python objects. We don't want to have # any Sage objects (e.g. Integer) here so that the cache index stays # unique. D = int(D) HermWeight = int(HermWeight) B_cF = int(B_cF) reduction_method_flags = int(reduction_method_flags) calc = C.Calc() calc.init(D = D, HermWeight = HermWeight, B_cF=B_cF) calc.calcReducedCurlF() reducedCurlFSize = calc.matrixColumnCount # Calculate the dimension of Hermitian modular form space. dim = herm_modform_space_dim(D=D, HermWeight=HermWeight) cacheIdx = (D, HermWeight, B_cF) if reduction_method_flags != -1: cacheIdx += (reduction_method_flags,) try: herm_modform_fe_expannsion, calc, curlS_denoms, pending_tasks = hermModformSpaceCache[cacheIdx] if not isinstance(calc, C.Calc): raise TypeError print "Resuming from %s" % hermModformSpaceCache._filename_for_key(cacheIdx) except (TypeError, ValueError, KeyError, EOFError): # old format or not cached or cache incomplete herm_modform_fe_expannsion = FreeModule(QQ, reducedCurlFSize) curlS_denoms = set() # the denominators of the visited matrices S pending_tasks = () current_dimension = herm_modform_fe_expannsion.dimension() verbose("current dimension: %i, wanted: %i" % (herm_modform_fe_expannsion.dimension(), dim)) if dim == 0: print "dim == 0 -> exit" return def task_iter_func(): # Iterate S \in Mat_2^T(\curlO), S > 0. while True: # Get the next S. # If calc.curlS is not empty, this is because we have recovered from a resume. if len(calc.curlS) == 0: S = calc.getNextS() else: assert len(calc.curlS) == 1 S = calc.curlS[0] l = S.det() l = toInt(l) curlS_denoms.add(l) verbose("trying S={0}, det={1}".format(S, l)) if reduction_method_flags & Method_Elliptic_reduction: yield CalcTask( func=modform_restriction_info, calc=calc, kwargs={"S":S, "l":l}) if reduction_method_flags & Method_EllipticCusp_reduction: precLimit = calcPrecisionDimension(B_cF=B_cF, S=S) yield CalcTask( func=modform_cusp_info, calc=calc, kwargs={"S":S, "l":l, "precLimit": precLimit}) calc.curlS_clearMatrices() # In the C++ internal curlS, clear previous matrices. task_iter = task_iter_func() if parallelization: parallelization.task_iter = task_iter if parallelization and pending_tasks: for func, name in pending_tasks: parallelization.exec_task(func=func, name=name) step_counter = 0 while True: if parallelization: new_task_count = 0 spaces = [] for task, exc, newspace in parallelization.get_all_ready_results(): if exc: raise exc if newspace is None: verbose("no data from %r" % task) continue if newspace.dimension() == reducedCurlFSize: verbose("no information gain from %r" % task) continue spacecomment = task assert newspace.dimension() >= dim, "%r, %r" % (task, newspace) if newspace.dimension() < herm_modform_fe_expannsion.dimension(): # Swap newspace with herm_modform_fe_expannsion. herm_modform_fe_expannsion, newspace = newspace, herm_modform_fe_expannsion current_dimension = herm_modform_fe_expannsion.dimension() if current_dimension == dim: if not isinstance(task, IntersectSpacesTask): verbose("warning: we expected IntersectSpacesTask for final dim but got: %r" % task) verbose("new dimension: %i, wanted: %i" % (current_dimension, dim)) if current_dimension <= 20: pprint(herm_modform_fe_expannsion.basis()) spacecomment = "<old base space>" spaces += [(spacecomment, newspace)] if spaces: parallelization.exec_task(IntersectSpacesTask(herm_modform_fe_expannsion, spaces)) new_task_count += 1 new_task_count += parallelization.maybe_queue_tasks() time.sleep(0.1) else: # no parallelization new_task_count = 1 if pending_tasks: # from some resuming task,_ = pending_tasks[0] pending_tasks = pending_tasks[1:] else: task = next(task_iter) newspace = task() if newspace is None: verbose("no data from %r" % task) if newspace is not None and newspace.dimension() == reducedCurlFSize: verbose("no information gain from %r" % task) newspace = None if newspace is not None: new_task_count += 1 spacecomment = task herm_modform_fe_expannsion_new = IntersectSpacesTask( herm_modform_fe_expannsion, [(spacecomment, newspace)])() if herm_modform_fe_expannsion_new is not None: herm_modform_fe_expannsion = herm_modform_fe_expannsion_new current_dimension = herm_modform_fe_expannsion.dimension() verbose("new dimension: %i, wanted: %i" % (current_dimension, dim)) if new_task_count > 0: step_counter += 1 if step_counter % 10 == 0: verbose("save state after %i steps to %s" % (step_counter, os.path.basename(hermModformSpaceCache._filename_for_key(cacheIdx)))) if parallelization: pending_tasks = parallelization.get_pending_tasks() hermModformSpaceCache[cacheIdx] = (herm_modform_fe_expannsion, calc, curlS_denoms, pending_tasks) if current_dimension == dim: verbose("finished!") break # Test for some other S with other not-yet-seen denominator. check_herm_modform_space( calc, herm_modform_space=herm_modform_fe_expannsion, used_curlS_denoms=curlS_denoms ) return herm_modform_fe_expannsion
class LieAlgebraWithStructureCoefficients(FinitelyGeneratedLieAlgebra, IndexedGenerators): r""" A Lie algebra with a set of specified structure coefficients. The structure coefficients are specified as a dictionary `d` whose keys are pairs of basis indices, and whose values are dictionaries which in turn are indexed by basis indices. The value of `d` at a pair `(u, v)` of basis indices is the dictionary whose `w`-th entry (for `w` a basis index) is the coefficient of `b_w` in the Lie bracket `[b_u, b_v]` (where `b_x` means the basis element with index `x`). INPUT: - ``R`` -- a ring, to be used as the base ring - ``s_coeff`` -- a dictionary, indexed by pairs of basis indices (see below), and whose values are dictionaries which are indexed by (single) basis indices and whose values are elements of `R` - ``names`` -- list or tuple of strings - ``index_set`` -- (default: ``names``) list or tuple of hashable and comparable elements OUTPUT: A Lie algebra over ``R`` which (as an `R`-module) is free with a basis indexed by the elements of ``index_set``. The `i`-th basis element is displayed using the name ``names[i]``. If we let `b_i` denote this `i`-th basis element, then the Lie bracket is given by the requirement that the `b_k`-coefficient of `[b_i, b_j]` is ``s_coeff[(i, j)][k]`` if ``s_coeff[(i, j)]`` exists, otherwise ``-s_coeff[(j, i)][k]`` if ``s_coeff[(j, i)]`` exists, otherwise `0`. EXAMPLES: We create the Lie algebra of `\QQ^3` under the Lie bracket defined by `\times` (cross-product):: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'z':1}, ('y','z'): {'x':1}, ('z','x'): {'y':1}}) sage: (x,y,z) = L.gens() sage: L.bracket(x, y) z sage: L.bracket(y, x) -z TESTS: We can input structure coefficients that fail the Jacobi identity, but the test suite will call us out on it:: sage: Fake = LieAlgebra(QQ, 'x,y,z', {('x','y'):{'z':3}, ('y','z'):{'z':1}, ('z','x'):{'y':1}}) sage: TestSuite(Fake).run() Failure in _test_jacobi_identity: ... Old tests !!!!!placeholder for now!!!!!:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'):{'x':1}}) sage: L.basis() Finite family {'x': x, 'y': y} """ @staticmethod def __classcall_private__(cls, R, s_coeff, names=None, index_set=None, **kwds): """ Normalize input to ensure a unique representation. EXAMPLES:: sage: L1 = LieAlgebra(QQ, 'x,y', {('x','y'): {'x':1}}) sage: L2 = LieAlgebra(QQ, 'x,y', {('y','x'): {'x':-1}}) sage: L1 is L2 True Check that we convert names to the indexing set:: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'z':1}, ('y','z'): {'x':1}, ('z','x'): {'y':1}}, index_set=list(range(3))) sage: (x,y,z) = L.gens() sage: L[x,y] L[2] """ names, index_set = standardize_names_index_set(names, index_set) # Make sure the structure coefficients are given by the index set if names is not None and names != tuple(index_set): d = {x: index_set[i] for i, x in enumerate(names)} get_pairs = lambda X: X.items() if isinstance(X, dict) else X try: s_coeff = {(d[k[0]], d[k[1]]): [(d[x], y) for x, y in get_pairs(s_coeff[k])] for k in s_coeff} except KeyError: # At this point we assume they are given by the index set pass s_coeff = LieAlgebraWithStructureCoefficients._standardize_s_coeff( s_coeff, index_set) if s_coeff.cardinality() == 0: from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra return AbelianLieAlgebra(R, names, index_set, **kwds) if (names is None and len(index_set) <= 1) or len(names) <= 1: from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra return AbelianLieAlgebra(R, names, index_set, **kwds) return super(LieAlgebraWithStructureCoefficients, cls).__classcall__(cls, R, s_coeff, names, index_set, **kwds) @staticmethod def _standardize_s_coeff(s_coeff, index_set): """ Helper function to standardize ``s_coeff`` into the appropriate form (dictionary indexed by pairs, whose values are dictionaries). Strips items with coefficients of 0 and duplicate entries. This does not check the Jacobi relation (nor antisymmetry if the cardinality is infinite). EXAMPLES:: sage: from sage.algebras.lie_algebras.structure_coefficients import LieAlgebraWithStructureCoefficients sage: d = {('y','x'): {'x':-1}} sage: LieAlgebraWithStructureCoefficients._standardize_s_coeff(d, ('x', 'y')) Finite family {('x', 'y'): (('x', 1),)} """ # Try to handle infinite basis (once/if supported) #if isinstance(s_coeff, AbstractFamily) and s_coeff.cardinality() == infinity: # return s_coeff index_to_pos = {k: i for i, k in enumerate(index_set)} sc = {} # Make sure the first gen is smaller than the second in each key for k in s_coeff.keys(): v = s_coeff[k] if isinstance(v, dict): v = v.items() if index_to_pos[k[0]] > index_to_pos[k[1]]: key = (k[1], k[0]) vals = tuple((g, -val) for g, val in v if val != 0) else: if not index_to_pos[k[0]] < index_to_pos[k[1]]: if k[0] == k[1]: if not all(val == 0 for g, val in v): raise ValueError( "elements {} are equal but their bracket is not set to 0" .format(k)) continue key = tuple(k) vals = tuple((g, val) for g, val in v if val != 0) if key in sc.keys() and sorted(sc[key]) != sorted(vals): raise ValueError( "two distinct values given for one and the same bracket") if vals: sc[key] = vals return Family(sc) def __init__(self, R, s_coeff, names, index_set, category=None, prefix=None, bracket=None, latex_bracket=None, string_quotes=None, **kwds): """ Initialize ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'): {'x':1}}) sage: TestSuite(L).run() """ default = (names != tuple(index_set)) if prefix is None: if default: prefix = 'L' else: prefix = '' if bracket is None: bracket = default if latex_bracket is None: latex_bracket = default if string_quotes is None: string_quotes = default #self._pos_to_index = dict(enumerate(index_set)) self._index_to_pos = {k: i for i, k in enumerate(index_set)} if "sorting_key" not in kwds: kwds["sorting_key"] = self._index_to_pos.__getitem__ cat = LieAlgebras(R).WithBasis().FiniteDimensional().or_subcategory( category) FinitelyGeneratedLieAlgebra.__init__(self, R, names, index_set, cat) IndexedGenerators.__init__(self, self._indices, prefix=prefix, bracket=bracket, latex_bracket=latex_bracket, string_quotes=string_quotes, **kwds) self._M = FreeModule(R, len(index_set)) # Transform the values in the structure coefficients to elements def to_vector(tuples): vec = [R.zero()] * len(index_set) for k, c in tuples: vec[self._index_to_pos[k]] = c vec = self._M(vec) vec.set_immutable() return vec self._s_coeff = {(self._index_to_pos[k[0]], self._index_to_pos[k[1]]): to_vector(s_coeff[k]) for k in s_coeff.keys()} # For compatibility with CombinatorialFreeModuleElement _repr_term = IndexedGenerators._repr_generator _latex_term = IndexedGenerators._latex_generator def structure_coefficients(self, include_zeros=False): """ Return the dictionary of structure coefficients of ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'x':1}}) sage: L.structure_coefficients() Finite family {('x', 'y'): x} sage: S = L.structure_coefficients(True); S Finite family {('x', 'y'): x, ('x', 'z'): 0, ('y', 'z'): 0} sage: S['x','z'].parent() is L True TESTS: Check that :trac:`23373` is fixed:: sage: L = lie_algebras.sl(QQ, 2) sage: sorted(L.structure_coefficients(True), key=str) [-2*E[-alpha[1]], -2*E[alpha[1]], h1] """ if not include_zeros: pos_to_index = dict(enumerate(self._indices)) return Family({(pos_to_index[k[0]], pos_to_index[k[1]]): self.element_class(self, self._s_coeff[k]) for k in self._s_coeff}) ret = {} zero = self._M.zero() for i, x in enumerate(self._indices): for j, y in enumerate(self._indices[i + 1:]): if (i, j + i + 1) in self._s_coeff: elt = self._s_coeff[i, j + i + 1] elif (j + i + 1, i) in self._s_coeff: elt = -self._s_coeff[j + i + 1, i] else: elt = zero ret[x, y] = self.element_class(self, elt) # +i+1 for offset return Family(ret) def dimension(self): """ Return the dimension of ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'):{'x':1}}) sage: L.dimension() 2 """ return self.basis().cardinality() def module(self, sparse=True): """ Return ``self`` as a free module. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'):{'z':1}}) sage: L.module() Sparse vector space of dimension 3 over Rational Field """ return FreeModule(self.base_ring(), self.dimension(), sparse=sparse) @cached_method def zero(self): """ Return the element `0` in ``self``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.zero() 0 """ return self.element_class(self, self._M.zero()) def monomial(self, k): """ Return the monomial indexed by ``k``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.monomial('x') x """ return self.element_class(self, self._M.basis()[self._index_to_pos[k]]) def term(self, k, c=None): """ Return the term indexed by ``i`` with coefficient ``c``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.term('x', 4) 4*x """ if c is None: c = self.base_ring().one() else: c = self.base_ring()(c) return self.element_class(self, c * self._M.basis()[self._index_to_pos[k]]) def from_vector(self, v): """ Return an element of ``self`` from the vector ``v``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.from_vector([1, 2, -2]) x + 2*y - 2*z """ return self.element_class(self, self._M(v)) def some_elements(self): """ Return some elements of ``self``. EXAMPLES:: sage: L = lie_algebras.three_dimensional(QQ, 4, 1, -1, 2) sage: L.some_elements() [X, Y, Z, X + Y + Z] """ return list(self.basis()) + [self.sum(self.basis())] def change_ring(self, R): r""" Return a Lie algebra with identical structure coefficients over ``R``. INPUT: - ``R`` -- a ring EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(ZZ, {('x','y'): {'z':1}}) sage: L.structure_coefficients() Finite family {('x', 'y'): z} sage: LQQ = L.change_ring(QQ) sage: LQQ.structure_coefficients() Finite family {('x', 'y'): z} sage: LSR = LQQ.change_ring(SR) sage: LSR.structure_coefficients() Finite family {('x', 'y'): z} """ return LieAlgebraWithStructureCoefficients( R, self.structure_coefficients(), names=self.variable_names(), index_set=self.indices()) class Element(StructureCoefficientsElement): def _sorted_items_for_printing(self): """ Return a list of pairs ``(k, c)`` used in printing. .. WARNING:: The internal representation order is fixed, whereas this depends on ``"sorting_key"`` print option as it is used only for printing. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: elt = x + y/2 - z; elt x + 1/2*y - z sage: elt._sorted_items_for_printing() [('x', 1), ('y', 1/2), ('z', -1)] sage: key = {'x': 2, 'y': 1, 'z': 0} sage: L.print_options(sorting_key=key.__getitem__) sage: elt._sorted_items_for_printing() [('z', -1), ('y', 1/2), ('x', 1)] sage: elt -z + 1/2*y + x """ print_options = self.parent().print_options() pos_to_index = dict(enumerate(self.parent()._indices)) v = [(pos_to_index[k], c) for k, c in self.value.items()] try: v.sort(key=lambda monomial_coeff: print_options['sorting_key'] (monomial_coeff[0]), reverse=print_options['sorting_reverse']) except Exception: # Sorting the output is a plus, but if we can't, no big deal pass return v
class DegreeGrading(Grading_abstract): r""" This class implements a monomial grading for a polynomial ring `R[x_1, .., x_n]`. """ def __init__(self, degrees): r""" INPUT: - ``degrees`` -- A list or tuple of `n` positive integers. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading((1,2)) sage: g = DegreeGrading([5,2,3]) """ self.__degrees = tuple(degrees) self.__module = FreeModule(ZZ, len(degrees)) self.__module_basis = self.__module.basis() def ngens(self): r""" The number of generators of the polynomial ring. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading((1,2)).ngens() 2 sage: DegreeGrading([5,2,3]).ngens() 3 """ return len(self.__degrees) def gen(self, i): r""" The number of generators of the polynomial ring. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading([5,2,3]).gen(0) 5 sage: DegreeGrading([5,2,3]).gen(3) Traceback (most recent call last): ... ValueError: Generator 3 does not exist. """ if i < len(self.__degrees): return self.__degrees[i] raise ValueError("Generator %s does not exist." % (i, )) def gens(self): r""" The gradings of the generators of the polynomial ring. OUTPUT: A tuple of integers. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading((1,2)).gens() (1, 2) sage: DegreeGrading([5,2,3]).gens() (5, 2, 3) """ return self.__degrees def index(self, x): r""" The grading value of `x` with respect to this grading. INPUT: - `x` -- A tuple of length `n`. OUTPUT: An integer. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.index((2,4,5)) 33 sage: g.index((2,3)) Traceback (most recent call last): ... ValueError: Tuple must have length 3. """ ## TODO: We shouldn't need this. #if len(x) == 0 : return infinity if len(x) != len(self.__degrees): raise ValueError("Tuple must have length %s." % (len(self.__degrees), )) return sum(map(mul, x, self.__degrees)) def basis(self, index, vars=None): r""" All monomials that are have given grading index involving only the given variables. INPUT: - ``index`` -- A grading index. - ``vars`` -- A list of integers from `0` to `n - 1` or ``None`` (default: ``None``); If ``None`` all variables will be considered. OUTPUT: A list of elements in `\Z^n`. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.basis(2) [(0, 1, 0)] sage: g.basis(10, vars = [0]) [(2, 0, 0)] sage: g.basis(20, vars = [1,2]) [(0, 10, 0), (0, 7, 2), (0, 4, 4), (0, 1, 6)] sage: g.basis(-1) [] sage: g.basis(0) [(0, 0, 0)] """ if index < 0: return [] elif index == 0: return [self.__module.zero_vector()] if vars == None: vars = [m for (m, d) in enumerate(self.__degrees) if d <= index] if len(vars) == 0: return [] res = [ self.__module_basis[vars[0]] + m for m in self.basis(index - self.__degrees[vars[0]], vars) ] res += self.basis(index, vars[1:]) return res def subgrading(self, gens): r""" The grading of same type for the ring with only the variables given by ``gens``. INPUT: - ``gens`` - A list of integers. OUTPUT: An instance of :class:~`.DegreeGrading`. TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g.subgrading([2]) Degree grading (3,) sage: g.subgrading([]) Degree grading () """ return DegreeGrading([self.__degrees[i] for i in gens]) def __contains__(self, x): r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: 5 in g True sage: "t" in g False """ return isinstance(x, (int, Integer)) def __cmp__(self, other): r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: g = DegreeGrading([5,2,3]) sage: g == DegreeGrading([5,2,3]) True sage: g == DegreeGrading((2,4)) False """ c = cmp(type(self), type(other)) if c == 0: c = cmp(self.__degrees, other.__degrees) return c def __hash__(self): r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: hash( DegreeGrading([5,2,3]) ) -1302562269 # 32-bit 7573306103633312291 # 64-bit """ return hash(self.__degrees) def _repr_(self): r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: DegreeGrading([5,2,3]) Degree grading (5, 2, 3) """ return "Degree grading %s" % str(self.__degrees) def _latex_(self): r""" TESTS:: sage: from psage.modform.fourier_expansion_framework.gradedexpansions.gradedexpansion_grading import * sage: latex( DegreeGrading([5,2,3]) ) \text{Degree grading } \left(5, 2, 3\right) """ return r"\text{Degree grading }" + latex(self.__degrees)
class LieAlgebraWithStructureCoefficients(FinitelyGeneratedLieAlgebra, IndexedGenerators): r""" A Lie algebra with a set of specified structure coefficients. The structure coefficients are specified as a dictionary `d` whose keys are pairs of basis indices, and whose values are dictionaries which in turn are indexed by basis indices. The value of `d` at a pair `(u, v)` of basis indices is the dictionary whose `w`-th entry (for `w` a basis index) is the coefficient of `b_w` in the Lie bracket `[b_u, b_v]` (where `b_x` means the basis element with index `x`). INPUT: - ``R`` -- a ring, to be used as the base ring - ``s_coeff`` -- a dictionary, indexed by pairs of basis indices (see below), and whose values are dictionaries which are indexed by (single) basis indices and whose values are elements of `R` - ``names`` -- list or tuple of strings - ``index_set`` -- (default: ``names``) list or tuple of hashable and comparable elements OUTPUT: A Lie algebra over ``R`` which (as an `R`-module) is free with a basis indexed by the elements of ``index_set``. The `i`-th basis element is displayed using the name ``names[i]``. If we let `b_i` denote this `i`-th basis element, then the Lie bracket is given by the requirement that the `b_k`-coefficient of `[b_i, b_j]` is ``s_coeff[(i, j)][k]`` if ``s_coeff[(i, j)]`` exists, otherwise ``-s_coeff[(j, i)][k]`` if ``s_coeff[(j, i)]`` exists, otherwise `0`. EXAMPLES: We create the Lie algebra of `\QQ^3` under the Lie bracket defined by `\times` (cross-product):: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'z':1}, ('y','z'): {'x':1}, ('z','x'): {'y':1}}) sage: (x,y,z) = L.gens() sage: L.bracket(x, y) z sage: L.bracket(y, x) -z TESTS: We can input structure coefficients that fail the Jacobi identity, but the test suite will call us out on it:: sage: Fake = LieAlgebra(QQ, 'x,y,z', {('x','y'):{'z':3}, ('y','z'):{'z':1}, ('z','x'):{'y':1}}) sage: TestSuite(Fake).run() Failure in _test_jacobi_identity: ... Old tests !!!!!placeholder for now!!!!!:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'):{'x':1}}) sage: L.basis() Finite family {'y': y, 'x': x} """ @staticmethod def __classcall_private__(cls, R, s_coeff, names=None, index_set=None, **kwds): """ Normalize input to ensure a unique representation. EXAMPLES:: sage: L1 = LieAlgebra(QQ, 'x,y', {('x','y'): {'x':1}}) sage: L2 = LieAlgebra(QQ, 'x,y', {('y','x'): {'x':-1}}) sage: L1 is L2 True Check that we convert names to the indexing set:: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'z':1}, ('y','z'): {'x':1}, ('z','x'): {'y':1}}, index_set=list(range(3))) sage: (x,y,z) = L.gens() sage: L[x,y] L[2] """ names, index_set = standardize_names_index_set(names, index_set) # Make sure the structure coefficients are given by the index set if names is not None and names != tuple(index_set): d = {x: index_set[i] for i,x in enumerate(names)} get_pairs = lambda X: X.items() if isinstance(X, dict) else X try: s_coeff = {(d[k[0]], d[k[1]]): [(d[x], y) for x,y in get_pairs(s_coeff[k])] for k in s_coeff} except KeyError: # At this point we assume they are given by the index set pass s_coeff = LieAlgebraWithStructureCoefficients._standardize_s_coeff(s_coeff, index_set) if s_coeff.cardinality() == 0: from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra return AbelianLieAlgebra(R, names, index_set, **kwds) if (names is None and len(index_set) <= 1) or len(names) <= 1: from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra return AbelianLieAlgebra(R, names, index_set, **kwds) return super(LieAlgebraWithStructureCoefficients, cls).__classcall__( cls, R, s_coeff, names, index_set, **kwds) @staticmethod def _standardize_s_coeff(s_coeff, index_set): """ Helper function to standardize ``s_coeff`` into the appropriate form (dictionary indexed by pairs, whose values are dictionaries). Strips items with coefficients of 0 and duplicate entries. This does not check the Jacobi relation (nor antisymmetry if the cardinality is infinite). EXAMPLES:: sage: from sage.algebras.lie_algebras.structure_coefficients import LieAlgebraWithStructureCoefficients sage: d = {('y','x'): {'x':-1}} sage: LieAlgebraWithStructureCoefficients._standardize_s_coeff(d, ('x', 'y')) Finite family {('x', 'y'): (('x', 1),)} """ # Try to handle infinite basis (once/if supported) #if isinstance(s_coeff, AbstractFamily) and s_coeff.cardinality() == infinity: # return s_coeff index_to_pos = {k: i for i,k in enumerate(index_set)} sc = {} # Make sure the first gen is smaller than the second in each key for k in s_coeff.keys(): v = s_coeff[k] if isinstance(v, dict): v = v.items() if index_to_pos[k[0]] > index_to_pos[k[1]]: key = (k[1], k[0]) vals = tuple((g, -val) for g, val in v if val != 0) else: if not index_to_pos[k[0]] < index_to_pos[k[1]]: if k[0] == k[1]: if not all(val == 0 for g, val in v): raise ValueError("elements {} are equal but their bracket is not set to 0".format(k)) continue key = tuple(k) vals = tuple((g, val) for g, val in v if val != 0) if key in sc.keys() and sorted(sc[key]) != sorted(vals): raise ValueError("two distinct values given for one and the same bracket") if vals: sc[key] = vals return Family(sc) def __init__(self, R, s_coeff, names, index_set, category=None, prefix=None, bracket=None, latex_bracket=None, string_quotes=None, **kwds): """ Initialize ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'): {'x':1}}) sage: TestSuite(L).run() """ default = (names != tuple(index_set)) if prefix is None: if default: prefix = 'L' else: prefix = '' if bracket is None: bracket = default if latex_bracket is None: latex_bracket = default if string_quotes is None: string_quotes = default #self._pos_to_index = dict(enumerate(index_set)) self._index_to_pos = {k: i for i,k in enumerate(index_set)} if "sorting_key" not in kwds: kwds["sorting_key"] = self._index_to_pos.__getitem__ cat = LieAlgebras(R).WithBasis().FiniteDimensional().or_subcategory(category) FinitelyGeneratedLieAlgebra.__init__(self, R, names, index_set, cat) IndexedGenerators.__init__(self, self._indices, prefix=prefix, bracket=bracket, latex_bracket=latex_bracket, string_quotes=string_quotes, **kwds) self._M = FreeModule(R, len(index_set)) # Transform the values in the structure coefficients to elements def to_vector(tuples): vec = [R.zero()]*len(index_set) for k,c in tuples: vec[self._index_to_pos[k]] = c vec = self._M(vec) vec.set_immutable() return vec self._s_coeff = {(self._index_to_pos[k[0]], self._index_to_pos[k[1]]): to_vector(s_coeff[k]) for k in s_coeff.keys()} # For compatibility with CombinatorialFreeModuleElement _repr_term = IndexedGenerators._repr_generator _latex_term = IndexedGenerators._latex_generator def structure_coefficients(self, include_zeros=False): """ Return the dictionary of structure coefficients of ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y,z', {('x','y'): {'x':1}}) sage: L.structure_coefficients() Finite family {('x', 'y'): x} sage: S = L.structure_coefficients(True); S Finite family {('x', 'y'): x, ('x', 'z'): 0, ('y', 'z'): 0} sage: S['x','z'].parent() is L True TESTS: Check that :trac:`23373` is fixed:: sage: L = lie_algebras.sl(QQ, 2) sage: sorted(L.structure_coefficients(True), key=str) [-2*E[-alpha[1]], -2*E[alpha[1]], h1] """ if not include_zeros: pos_to_index = dict(enumerate(self._indices)) return Family({(pos_to_index[k[0]], pos_to_index[k[1]]): self.element_class(self, self._s_coeff[k]) for k in self._s_coeff}) ret = {} zero = self._M.zero() for i,x in enumerate(self._indices): for j, y in enumerate(self._indices[i+1:]): if (i, j+i+1) in self._s_coeff: elt = self._s_coeff[i, j+i+1] elif (j+i+1, i) in self._s_coeff: elt = -self._s_coeff[j+i+1, i] else: elt = zero ret[x,y] = self.element_class(self, elt) # +i+1 for offset return Family(ret) def dimension(self): """ Return the dimension of ``self``. EXAMPLES:: sage: L = LieAlgebra(QQ, 'x,y', {('x','y'):{'x':1}}) sage: L.dimension() 2 """ return self.basis().cardinality() def module(self, sparse=True): """ Return ``self`` as a free module. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'):{'z':1}}) sage: L.module() Sparse vector space of dimension 3 over Rational Field """ return FreeModule(self.base_ring(), self.dimension(), sparse=sparse) @cached_method def zero(self): """ Return the element `0` in ``self``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.zero() 0 """ return self.element_class(self, self._M.zero()) def monomial(self, k): """ Return the monomial indexed by ``k``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.monomial('x') x """ return self.element_class(self, self._M.basis()[self._index_to_pos[k]]) def term(self, k, c=None): """ Return the term indexed by ``i`` with coefficient ``c``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.term('x', 4) 4*x """ if c is None: c = self.base_ring().one() else: c = self.base_ring()(c) return self.element_class(self, c * self._M.basis()[self._index_to_pos[k]]) def from_vector(self, v): """ Return an element of ``self`` from the vector ``v``. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: L.from_vector([1, 2, -2]) x + 2*y - 2*z """ return self.element_class(self, self._M(v)) def some_elements(self): """ Return some elements of ``self``. EXAMPLES:: sage: L = lie_algebras.three_dimensional(QQ, 4, 1, -1, 2) sage: L.some_elements() [X, Y, Z, X + Y + Z] """ return list(self.basis()) + [self.sum(self.basis())] class Element(StructureCoefficientsElement): def _sorted_items_for_printing(self): """ Return a list of pairs ``(k, c)`` used in printing. .. WARNING:: The internal representation order is fixed, whereas this depends on ``"sorting_key"`` print option as it is used only for printing. EXAMPLES:: sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'): {'z':1}}) sage: elt = x + y/2 - z; elt x + 1/2*y - z sage: elt._sorted_items_for_printing() [('x', 1), ('y', 1/2), ('z', -1)] sage: key = {'x': 2, 'y': 1, 'z': 0} sage: L.print_options(sorting_key=key.__getitem__) sage: elt._sorted_items_for_printing() [('z', -1), ('y', 1/2), ('x', 1)] sage: elt -z + 1/2*y + x """ print_options = self.parent().print_options() pos_to_index = dict(enumerate(self.parent()._indices)) v = [(pos_to_index[k], c) for k, c in iteritems(self.value)] try: v.sort(key=lambda monomial_coeff: print_options['sorting_key'](monomial_coeff[0]), reverse=print_options['sorting_reverse']) except Exception: # Sorting the output is a plus, but if we can't, no big deal pass return v
def maximal_grading(L): r""" Return a maximal grading of a Lie algebra defined over an algebraically closed field. A maximal grading of a Lie algebra `\mathfrak{g}` is the finest possible grading of `\mathfrak{g}` over torsion free abelian groups. If `\mathfrak{g} = \bigoplus_{n\in \mathbb{Z}^k} \mathfrak{g}_n` is a maximal grading, then there exists no other grading `\mathfrak{g} = \bigoplus_{a\in A} \mathfrak{g}_a` over a torsion free abelian group `A` such that every `\mathfrak{g}_a` is contained in some `\mathfrak{g}_n`. EXAMPLES: A maximal grading of an abelian Lie algebra puts each basis element into an independent layer:: sage: import sys, pathlib sage: sys.path.append(str(pathlib.Path().absolute())) sage: from lie_gradings.gradings.grading import maximal_grading sage: L = LieAlgebra(QQbar, 4, abelian=True) sage: maximal_grading(L) Grading over Additive abelian group isomorphic to Z + Z + Z + Z of Abelian Lie algebra on 4 generators (L[0], L[1], L[2], L[3]) over Algebraic Field with nonzero layers (1, 0, 0, 0) : (L[3],) (0, 1, 0, 0) : (L[2],) (0, 0, 1, 0) : (L[1],) (0, 0, 0, 1) : (L[0],) A maximal grading of a free nilpotent Lie algebra decomposes the Lie algebra based on how many times each generator appears in the defining Lie bracket:: sage: L = LieAlgebra(QQbar, 3, step=3) sage: maximal_grading(L) Grading over Additive abelian group isomorphic to Z + Z + Z of Free Nilpotent Lie algebra on 14 generators (X_1, X_2, X_3, X_12, X_13, X_23, X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233) over Algebraic Field with nonzero layers (1, 0, 0) : (X_1,) (0, 1, 0) : (X_2,) (1, 1, 0) : (X_12,) (1, 2, 0) : (X_122,) (2, 1, 0) : (X_112,) (0, 0, 1) : (X_3,) (0, 1, 1) : (X_23,) (0, 1, 2) : (X_233,) (0, 2, 1) : (X_223,) (1, 0, 1) : (X_13,) (1, 0, 2) : (X_133,) (1, 1, 1) : (X_123, X_132) (2, 0, 1) : (X_113,) """ # define utilities to convert from matrices to vectors and back R = L.base_ring() n = L.dimension() MS = MatrixSpace(R, n, n) def matrix_to_vec(A): return vector(R, sum((list(Ar) for Ar in A.rows()), [])) db = L.derivations_basis() def derivation_lincomb(vec): return sum((vk * dk for vk, dk in zip(vec, db)), MS.zero()) # iteratively construct larger and larger tori of derivations t = [] while True: # compute the centralizer of the torus in the derivation algebra ker = FreeModule(R, len(db)) for der in t: # form the matrix of ad(der) with rows # the images of basis derivations A = matrix([matrix_to_vec(der * X - X * der) for X in db]) ker = ker.intersection(A.left_kernel()) cb = [derivation_lincomb(v) for v in ker.basis()] # check the basis of the centralizer for semisimple parts outside of t gl = FreeModule(R, n * n) t_submodule = gl.submodule([matrix_to_vec(der) for der in t]) for A in cb: As, An = jordan_decomposition(A) if matrix_to_vec(As) not in t_submodule: # extend the torus by As t.append(As) break else: # no new elements found, so the torus is maximal break # compute the eigenspace intersections to get the concrete grading common_eigenspaces = [([], FreeModule(R, n))] for A in t: new_eigenspaces = [] eig = A.right_eigenspaces() for ev, V in common_eigenspaces: for ew, W in eig: VW = V.intersection(W) if VW.dimension() > 0: new_eigenspaces.append((ev + [ew], VW)) common_eigenspaces = new_eigenspaces if not t: # zero dimensional maximal torus # the only grading is the trivial grading magma = AdditiveAbelianGroup([]) layers = {magma.zero(): L.basis().list()} return grading(L, layers, magma=magma) # define a grading with layers indexed by tuples of eigenvalues cm = get_coercion_model() all_eigenvalues = sum((ev for ev, V in common_eigenspaces), []) k = len(common_eigenspaces[0][0]) evR = cm.common_parent(*all_eigenvalues) layers = { tuple(ev): [L.from_vector(v) for v in V.basis()] for ev, V in common_eigenspaces } magma = evR.cartesian_product(*[evR] * (k - 1)) gr = grading(L, layers, magma=magma) # convert to a grading over Z^k return gr.universal_realization()
class Lattice_class (SageObject): """ A class representing a lattice $L$. NOTE We build this class as a sort of wrapper around \code{FreeModule(ZZ,n)}. This is reflecting the fact that a lattice is not a free module, but a pair consisting of a free module and a scalar product. In addition, we avoid in this way to add new items to the Sage category system, which we better leave to the specialists. EXAMPLES sage: L = Lattice_class( [2,1,2]); L The ZZ-lattice (ZZ^2, x^tGy), where G = [2 1] [1 2] sage: L.is_even() True sage: L.gram_matrix() [2 1] [1 2] sage: L.dual_vectors() {(2/3, -1/3), (0, 0), (4/3, -2/3)} sage: L.representatives(2) {} sage: L.representatives(1) {(2/3, -1/3), (4/3, -2/3)} sage: L.values() {0: {(0, 0)}, 1: {(2/3, -1/3), (4/3, -2/3)}} sage: L.det() 3 sage: g = lambda n: [1] if 1 == n else [1] + (n-1)*[0] + g(n-1) sage: Z8 = Lattice_class( g(8)) sage: a,A8 = Z8.ev() sage: M8 = A8.fqm() sage: M8.jordan_decomposition().genus_symbol() '2^2' """ def __init__( self, q): """ We initialize by a list of integers $[a_1,...,a_N]$. The lattice self is then $L = (L,\beta) = (G^{-1}\ZZ^n/\ZZ^n, G[x])$, where $G$ is the symmetric matrix $G=[a_1,...,a_n;*,a_{n+1},....;..;* ... * a_N]$, i.e.~the $a_j$ denote the elements above the diagonal of $G$. """ self.__form = q # We compute the rank N = len(q) assert is_square( 1+8*N) n = Integer( (-1+isqrt(1+8*N))/2) self.__rank = n # We set up the Gram matrix self.__G = matrix( IntegerRing(), n, n) i = j = 0 for a in q: self.__G[i,j] = self.__G[j,i] = Integer(a) if j < n-1: j += 1 else: i += 1 j = i # We compute the level Gi = self.__G**-1 self.__Gi = Gi a = lcm( map( lambda x: x.denominator(), Gi.list())) I = Gi.diagonal() b = lcm( map( lambda x: (x/2).denominator(), I)) self.__level = lcm( a, b) # We define the undelying module and the ambient space self.__module = FreeModule( IntegerRing(), n) self.__space = self.__module.ambient_vector_space() # We compute a shadow vector self.__shadow_vector = self.__space([(a%2)/2 for a in self.__G.diagonal()])*Gi # We define a basis M = Matrix( IntegerRing(), n, n, 1) self.__basis = self.__module.basis() # We prepare a cache self.__dual_vectors = None self.__values = None self.__chi = {} def _latex_( self): return 'The ZZ-lattice $(ZZ^{%d}, x\'Gy)$, where $G = %s$' % (self.__rank, latex(self.__G)) def _repr_( self): return 'The ZZ-lattice (ZZ^%d, x^tGy), where G = \n%r' % (self.__rank, self.__G) def rank( self): return self.__rank def level( self): """ Return the smallest integer $l$ such that $lG[x]/2 \in \Z$ for all $x$ in $L^*$. """ return self.__level def basis( self): return self.__basis def module( self): """ Return the underlying module. """ return self.__module def hom( self, im_gens, codomain=None, check=True): # return self.module().hom( im_gens, codomain = codomain, check = check) if not codomain: raise NotImplementedError() A = matrix( im_gens) if codomain and True == check: assert self.gram_matrix() == A*codomain.gram_matrix()*A.transpose() return Embedding( self, matrix( im_gens), codomain) def space( self): """ Return the ambient vector space. """ return self.__space def is_positive( self): pass def is_even( self): I = self.gram_matrix().diagonal() for a in I: if is_odd(a): return False return True def is_maximal( self): pass def shadow_level( self): """ Return the level of $L_ev$, where $L_ev$ is the kernel of the map $x\mapsto e(G[x]/2)$. REMARK Lemma: If $L$ is odd, if $s$ is a shadow vector, and if $N$ denotes the denominator of $G[s]/2$, then the level of $L_ev$ equals the lcm of the order of $s$, $N$ and the level of $L$. (For the proof: the requested level is the smallest integer $l$ such that $lG[x]/2$ is integral for all $x$ in $L^*$ and $x$ in $s+L^*$ since $L_ev^* = L^* \cup s+L^*$. This $l$ is the smallest integer such that $lG[s]/2$, $lG[x]/2$ and $ls^tGx$ are integral for all $x$ in $L^*$.) """ if self.is_even(): return self.level() s = self.a_shadow_vector() N = self.beta(s).denominator() h = lcm( [x.denominator() for x in s]) return lcm( [h, N, self.level()]) def gram_matrix( self): return self.__G def beta(self, x, y = None): if None == y: return (x*self.gram_matrix()*x)/2 else: return x*self.gram_matrix()*y def det( self): return self.gram_matrix().det() def dual_vectors( self): """ Return a set of representatives for $L^#/L$. """ D,U,V = self.gram_matrix().smith_form() # hence D = U * self.gram_matrix() * V if None == self.__dual_vectors: W = V*D**-1 S = self.space() self.__dual_vectors = [ W*S(v) for v in mrange( D.diagonal())] return self.__dual_vectors def is_in_dual( self, y): """ Checks whether $y$ is in the dual of self. """ return self._is_int_vec( self.gram_matrix()*y) def a_shadow_vector( self): """ Return a vector in the shadow $L^\bullet$ of $L$. REMARK As shadow vector one can take the diagnal of the Gram matrix reduced modulo 2 and multiplied by $1/2G^{-1}$. Note that this vector $s$ is arbitrary (in particular, it does not satisfy in general $2s \in L$). """ return self.__shadow_vector def shadow_vectors( self): """ Return a list of representatives for the vectors in $L^\bullet/L$. """ if self.is_even(): return self.dual_vectors() R = self.dual_vectors() s = self.a_shadow_vector() return [s+r for r in R] def shadow_vectors_of_order_2( self): """ Return the list of representatives for those vectors $x$ in $L^\bullet/L$ such that $2x=0$. """ R = self.shadow_vectors() return [r for r in R if self._is_int_vec( 2*r)] def is_in_shadow( self, r): """ Checks whether $r$ is a shadow vector. """ c = self.a_shadow_vector() return self.is_in_dual( r - c) def o_invariant( self): """ Return $0$ if $L$ is even, otherwise the parity of $G[2*s]$, where $s$ is a shadow vector such that $2s$ is in $L$ (so that $(-1)^parity = e(G[2*s]/2)$. REMARK The o_invariant equals the parity of $n_2$, where $n_2$ is as in Lemma 3.1 of [Joli-I]. """ V = self.shadow_vectors_of_order_2() s = V[0] return Integer(4*(s*self.gram_matrix()*s))%2 def values( self): """ Return a dictionary N \mapsto set of representatives r in $L^\bullet/L$ which satisfy $\beta(r) \equiv n/l \bmod \ZZ$, where $l$ is the shadow_level (i.e. the level of $L_ev$). REMARK $\beta(r)+ \Z$ does only depend on $r+L$ if $r$ is a shadow vector, otherwise this is not necessarily true. Hence we return only shadow vectors here. """ if None == self.__values: self.__values = {} G = self.gram_matrix() l = self.shadow_level() R = self.dual_vectors() if not self.is_even(): s = self.a_shadow_vector() R = [s+r for r in R] for r in R: N = Integer( self.beta(r)*l) N = N%l if not self.__values.has_key(N): self.__values[N] = [r] else: self.__values[N] += [r] return self.__values def representatives( self, N): """ Return the subset of representatives $r$ in $L^\bullet/L$ which satisfy $\beta(r) \equiv N/level \bmod \ZZ$. """ v = self.values() return v.get(N%self.shadow_level(), []) def chi( self, t): """ Return the value of the Gauss sum $\sum_{x\in L^\bullet/L} e(tG[x]/2)/\sqrt D$, where $D = |L^\bullet/L|$. """ t = Integer(t)%self.shadow_level() if self.__chi.has_key(t): return self.__chi[t] l = self.shadow_level() z = QQbar.zeta(l) w = QQbar(self.det().sqrt()) V = self.values() self.__chi[t] = sum(len(V[a])*z**(t*a) for a in V)/w return self.__chi[t] def ev( self): """ Return a pair $alpha, L_{ev}$, where $L_{ev}$ is isomorphic to the kernel of $L\rightarrow \{\pm 1\}$, $x\mapsto e(\beta(x))$, and where $alpha$ is an embedding of $L_{ev}$ into $L$ whose image is this kernel. REMARK We have to find the kernel of the map $x\mapsto G[x] \equiv \sum_{j\in S}x_2 \bmod 2$. Here $S$ is the set of indices $i$ such that the $i$-diagonal element of $G$ is odd. A basis is given by $e_i$ ($i not\in S$) and $e_i + e_j$ ($i \in S$, $i\not=j$) and $2e_j$, where $j$ is any fixed index in $S$. """ if self.is_even(): return self.hom( self.basis(), self.module()), self D = self.gram_matrix().diagonal() S = [ i for i in range( len(D)) if is_odd(D[i])] j = min(S) e = self.basis() n = len(e) form = lambda i: e[i] if i not in S else 2*e[j] if i == j else e[i]+e[j] a = [ form(i) for i in range( n)] # A = matrix( a).transpose() # return A, Lattice_class( [ self.beta( a[i],a[j]) for i in range(n) for j in range(i,n)]) Lev = Lattice_class( [ self.beta( a[i],a[j]) for i in range(n) for j in range(i,n)]) alpha = Lev.hom( a, self.module()) return alpha, Lev def twist( self, a): """ Return the lattice self rescaled by the integer $a$. """ e = self.basis() n = len(e) return Lattice_class( [ a*self.beta( e[i],e[j]) for i in range(n) for j in range(i,n)]) def fqm( self): """ Return a pair $(f,M)$, where $M$ is the discriminant module of $L_ev$ up to isomorphism and $f:L\rightarrow M$ the canonical map. TODO Currently returns only M. """ if self.is_even(): return FiniteQuadraticModule( self.gram_matrix()) else: a,Lev = self.ev() return FiniteQuadraticModule( Lev.gram_matrix()) def ZN_embeddings( self): """ Return a list of all embeddings of $L$ into $\ZZ^N$ modulo the action of $O(\ZZ^N)$. """ def cs_range( f, subset = None): """ For a symmetric semi-positive integral matrix $f$, return a list of all integral $n$-vectors $v$ such that $x^tfx - (v*x)^2 >= 0$ for all $x$. """ n = f.dimensions()[0] b = vector( s.isqrt() for s in f.diagonal()) zv = vector([0]*n) box = [b - vector(t) for t in mrange( (2*b + vector([1]*n)).list()) if b - vector(t) > zv] if subset: box = [v for v in box if v in subset] rge = [v for v in box if min( (f - matrix(v).transpose()*matrix(v)).eigenvalues()) >=0] return rge def embs( f, subset = None): """ For a semipositive matrix $f$ return a list of all integral matrices $M$ such that $f=M^t * M$ modulo the left action of $\lim O(\ZZ^N)$ on the set of these matrices. """ res = [] csr = cs_range( f, subset) for v in csr: fv = f - matrix(v).transpose()*matrix(v) if 0 == fv: res.append( matrix(v)) else: tmp = embs( fv, csr[ csr.index( v):]) for e in tmp: res.append( e.insert_row(0, v)) return res l_embs = embs( self.gram_matrix()) import lattice_index return map( lambda a: self.hom( a.columns(), lattice_index.LatticeIndex( 'Z^'+ str(a.nrows()))), l_embs) def vectors_in_shell ( self, bound, max = 10000): """ Return the list of all $x\not=0$ in self such that $\beta(x) <= bound$. """ M = self.module() return map( lambda x: M(x), self._vectors_in_shell ( self.gram_matrix(), 2*bound, max)) def dual_vectors_in_shell ( self, bound, max = 10000): """ Return the list of all $x\not=0$ in the dual of self such that $\beta(x) <= bound$. """ l = self.level() F = l*self.__Gi S = self.space() return map( lambda x: S(self.__Gi*x), self._vectors_in_shell ( F, 2*l*bound, max)) def shadow_vectors_in_shell (self, bound, max = 10000): """ Return the list of all $x\not=0$ in the bullet of self such that $\beta(x) <= bound$. """ if self.is_even(): return self.dual_vectors_in_shell ( bound, max) a, Lev = L.ev() l = Lev.dual_vectors_in_shell ( bound, max) A = a.matrix() return filter( lambda x: self.is_in_shadow(x) ,[y*A for y in l]) @staticmethod def _vectors_in_shell( F, bound, max = 1000): """ For an (integral) positive symmetric matrix $F$ return a list of vectors such that $x^tFx <= bound$. The method returns only nonzero vectors and for each pair $v$ and $-v$ only one. NOTE A wrapper for the pari method qfminim(). """ # assert bound >=1, 'Pari bug: bound should be >= 2' if bound < 1: return [] p = F._pari_() po = p.qfminim( bound, max, flag = 0).sage() if max == po[0]: raise ArithmeticError( 'Increase max') ves = [vector([0]*F.nrows())] for x in po[2].columns(): ves += [x,-x] # return po[2].columns() return ves @staticmethod def _is_int_vec( v): for c in v: if c.denominator() > 1: return False return True