def __init__(self, B): r""" Initialize the ComputeMinimalPolynomials class. INPUT: - ``B`` -- a square matrix TESTS:: sage: from sage.matrix.compute_J_ideal import ComputeMinimalPolynomials sage: ComputeMinimalPolynomials(matrix([[1, 2]])) Traceback (most recent call last): ... TypeError: square matrix required """ from sage.rings.polynomial.polynomial_ring import polygen super(ComputeMinimalPolynomials, self).__init__() if not B.is_square(): raise TypeError("square matrix required") self._B = B self._D = B.base_ring() X = polygen(self._D) adjoint = (X - B).adjoint() d = B.nrows()**2 b = matrix(d, 1, adjoint.list()) self.chi_B = B.charpoly(X) self.mu_B = B.minimal_polynomial() self._A = matrix.block([[b , -self.chi_B*matrix.identity(d)]]) self._DX = X.parent() self._cache = {}
def invariant_cohomology(self, is_punctured): assert(self.is_self_map()) action = self.action_on_cohomology(is_punctured) m = action - matrix.identity(ZZ, action.nrows()) kernel = m.right_kernel_matrix() basis = self.domain.basis_of_cohomology(is_punctured) result = kernel * basis return result
def _cohomology_kernel(self): # this only works if domain and codomain are combinatorically same # the underlying foliations need not be the same tt = self.domain m = self.edge_matrix(SIGNED) # m = matrix([tt.path_to_vector(self.edge_map[edge], SIGNED) # for edge in tt.edges()]) m = m.transpose() - matrix.identity(ZZ, m.nrows()) mlist = m.rows() mlist.extend(tt.kernel_from_singularities()) return matrix(mlist).right_kernel_matrix()
def solve_linear_system(A, b, check): R = cm.base_ring() A_inv = A.solve_left(matrix.identity(A.ncols())) if check: # Verify validity of solution x = A_inv*b. Since b is a vector of # vectors, need to expand the matrix product by hand. M = A * A_inv for Mi, bk in zip(M.rows(), b): test_bk = sum((R(Mij) * bj for Mij,bj in zip(Mi,b)), cm.zero()) if test_bk != bk: raise ValueError("contradictory linear system") return [sum((R(Aij) * bk for Aij,bk in zip(Ai,b)), cm.zero()) for Ai in A_inv.rows()]
def matrix_to_reduce_dimension(self): # BUG: when the twist is longer than the first interval, this, and the # small matrix calculation is buggy if hasattr(self , '_reducing_matrix'): return self._reducing_matrix from copy import copy from sage.rings.rational import Rational X = self.coboundary_map_from_vertices(TRAIN_TRACK_MODULE) # print 'start' # print X, '\n' # flipping the matrix over vertically (so the echelonizing works # in the way we want) and transpose X = matrix(list(reversed(X.rows()))).transpose() X = X.echelon_form() # print X, '\n' # if the train track is non-orientable, the last row will be a # multiple of two, so we have to normalize it and re-echelonize. n = X.nrows() X = copy(X).with_row_set_to_multiple_of_row(n-1, n-1, Rational('1/2')) X = X.echelon_form() # print X, '\n' # now we need to cut off the identity part of the matrix and # append a small block of identity in the different direction identity_size = X.nrows() if self.sample_fol().is_bottom_side_moebius() \ else X.nrows() - 1 assert((X[X.nrows()-1, X.nrows()-1] == 1) == (identity_size == X.nrows())) X = matrix(list(reversed(X.rows()[:identity_size]))) X = -matrix(list(reversed(X.columns()[identity_size:]))).transpose() X = matrix(matrix.identity(X.ncols()).rows() + X.rows()) # print X, '\n' self._reducing_matrix = X return X
def lifting(p, t, A, G): r""" Compute generators of `\{f \in D[X]^d \mid Af \equiv 0 \pmod{p^{t}}\}` given generators of `\{f\in D[X]^d \mid Af \equiv 0\pmod{p^{t-1}}\}`. INPUT: - ``p`` -- a prime element of some principal ideal domain `D` - ``t`` -- a non-negative integer - ``A`` -- a `c\times d` matrix over `D[X]` - ``G`` -- a matrix over `D[X]`. The columns of `\begin{pmatrix}p^{t-1}I& G\end{pmatrix}` are generators of `\{ f\in D[X]^d \mid Af \equiv 0\pmod{p^{t-1}}\}`; can be set to ``None`` if ``t`` is zero OUTPUT: A matrix `F` over `D[X]` such that the columns of `\begin{pmatrix}p^tI&F&pG\end{pmatrix}` are generators of `\{ f\in D[X]^d \mid Af \equiv 0\pmod{p^t}\}`. EXAMPLES:: sage: from sage.matrix.compute_J_ideal import lifting sage: X = polygen(ZZ, 'X') sage: A = matrix([[1, X], [2*X, X^2]]) sage: G0 = lifting(5, 0, A, None) sage: G1 = lifting(5, 1, A, G0); G1 [] sage: (A*G1 % 5).is_zero() True sage: A = matrix([[1, X, X^2], [2*X, X^2, 3*X^3]]) sage: G0 = lifting(5, 0, A, None) sage: G1 = lifting(5, 1, A, G0); G1 [3*X^2] [ X] [ 1] sage: (A*G1 % 5).is_zero() True sage: G2 = lifting(5, 2, A, G1); G2 [15*X^2 23*X^2] [ 5*X X] [ 5 1] sage: (A*G2 % 25).is_zero() True sage: lifting(5, 10, A, G1) Traceback (most recent call last): ... ValueError: A*G not zero mod 5^9 ALGORITHM: [HR2016]_, Algorithm 1. TESTS:: sage: A = matrix([[1, X], [X, X^2]]) sage: G0 = lifting(5, 0, A, None) sage: G1 = lifting(5, 1, A, G0); G1 Traceback (most recent call last): ... ValueError: [ 1 X|] [ X X^2|] does not have full rank. """ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing DX = A.parent().base() (X,) = DX.gens() D = DX.base_ring() d = A.ncols() c = A.nrows() if t == 0: return matrix(DX, d, 0) if not (A*G % p**(t-1)).is_zero(): raise ValueError("A*G not zero mod %s^%s" % (p, t-1)) R = A*G/p**(t-1) R.change_ring(DX) AR = matrix.block([[A, R]]) Fp = D.quotient(p*D) FpX = PolynomialRing(Fp, name=X) ARb = AR.change_ring(FpX) (Db, Sb, Tb) = ARb.smith_form() #assert Sb * ARb * Tb == Db #assert all(i == j or Db[i, j].is_zero() # for i in range(Db.nrows()) # for j in range(Db.ncols())) r = Db.rank() if r != c: raise ValueError("{} does not have full rank.".format(ARb)) T = Tb.change_ring(DX) F1 = matrix.block([[p**(t-1) * matrix.identity(d), G]])*T F = F1.matrix_from_columns(range(r, F1.ncols())) assert (A*F % (p**t)).is_zero(), "A*F=%s" % (A*F) return F
def construct_free_chain(A): """ Construct the free chain for the hyperplanes ``A``. ALGORITHM: We follow Algorithm 6.5 in [BC2012]_. INPUT: - ``A`` -- a hyperplane arrangement EXAMPLES:: sage: from sage.geometry.hyperplane_arrangement.check_freeness import construct_free_chain sage: H.<x,y,z> = HyperplaneArrangements(QQ) sage: A = H(z, y+z, x+y+z) sage: construct_free_chain(A) [ [1 0 0] [ 1 0 0] [ 0 1 0] [0 1 0] [ 0 z -1] [y + z 0 -1] [0 0 z], [ 0 y 1], [ x 0 1] ] """ AL = list(A) if not AL: # Empty arrangement return [] S = A.parent().ambient_space().symmetric_space() # Compute the morphisms \phi_{H_j} : S^{1xR} \to S / <b_j> B = [H.to_symmetric_space() for H in AL] phi = [matrix(S, [[beta.derivative(x)] for x in S.gens()]) for beta in B] # Setup variables syz = fun_fact.ff.syz G = S.gens() r = len(G) indices = list(range(len(B))) X = [] # Helper function def next_step(indices, prev, T): for pos, i in enumerate(indices): U = prev * T mu = U * phi[i] mu = mu.stack(matrix.diagonal([B[i]]).dense_matrix()) row_syzygy = matrix(S, syz(mu.transpose())).matrix_from_columns( range(r)) Y = less_generators(row_syzygy) if not Y.is_square(): continue if len(indices) == 1: return [prev, Y] I = list(indices) I.pop(pos) ret = next_step(I, Y, U) if ret is not None: return [prev] + ret return None T = matrix.identity(S, r) for i in indices: mu = phi[i].stack(matrix.diagonal([B[i]]).dense_matrix()) row_syzygy = matrix(S, syz(mu.transpose())).matrix_from_columns(range(r)) Y = less_generators(row_syzygy) if not Y.is_square(): continue if len(indices) == 1: return [Y] I = list(indices) I.pop(i) ret = next_step(I, Y, T) if ret is not None: return ret return None
def rearrangement(p, ambient_dim=None, lattice=None): r""" The rearrangement cone of order ``p`` in ``ambient_dim`` dimensions, or living in ``lattice``. The rearrangement cone of order ``p`` in ``ambient_dim`` dimensions consists of all vectors of length ``ambient_dim`` whose smallest ``p`` components sum to a nonnegative number. For example, the rearrangement cone of order one has its single smallest component nonnegative. This implies that all components are nonnegative, and that therefore the rearrangement cone of order one is the nonnegative orthant in its ambient space. When ``p`` and ``ambient_dim`` are equal, all components of the cone's elements must sum to a nonnegative number. In other words, the rearrangement cone of order ``ambient_dim`` is a half-space. INPUT: - ``p`` -- a nonnegative integer; the number of components to "rearrange", between ``1`` and ``ambient_dim`` inclusive - ``ambient_dim`` -- a nonnegative integer (default: ``None``); the dimension of the ambient space - ``lattice`` -- a toric lattice (default: ``None``); the lattice in which the cone will live If ``ambient_dim`` is omitted, then it will be inferred from the rank of ``lattice``. If the ``lattice`` is omitted, then the default lattice of rank ``ambient_dim`` will be used. A ``ValueError`` is raised if neither ``ambient_dim`` nor ``lattice`` are specified. It is also a ``ValueError`` to specify both ``ambient_dim`` and ``lattice`` unless the rank of ``lattice`` is equal to ``ambient_dim``. It is also a ``ValueError`` to specify a non-integer ``p``. OUTPUT: A :class:`.ConvexRationalPolyhedralCone` representing the rearrangement cone of order ``p`` living in ``lattice``, with ambient dimension ``ambient_dim``. Each generating ray has the integer ring as its base ring. A ``ValueError`` can be raised if the inputs are incompatible or insufficient. See the INPUT documentation for details. ALGORITHM: Suppose that the ambient space is of dimension `n`. The extreme directions of the rearrangement cone for `1 \le p \le n-1` are given by [Jeong2017]_ Theorem 5.2.3. When `2 \le p \le n-2` (that is, if we ignore `p = 1` and `p = n-1`), they consist of - the standard basis `\left\{e_{1},e_{2},\ldots,e_{n}\right\}` for the ambient space, and - the `n` vectors `\left(1,1,\ldots,1\right)^{T} - pe_{i}` for `i = 1,2,\ldots,n`. Special cases are then given for `p = 1` and `p = n-1` in the theorem. However in SageMath we don't need conically-independent extreme directions. We only need a generating set, because the :func:`Cone` function will eliminate any redundant generators. And one can easily verify that the special-case extreme directions for `p = 1` and `p = n-1` are contained in the conic hull of the `2n` generators just described. The half space resulting from `p = n` is also covered by this set of generators, so for all valid `p` we simply take the conic hull of those `2n` vectors. REFERENCES: - [GJ2016]_, Section 4 - [HS2010]_, Example 2.21 - [Jeong2017]_, Section 5.2 EXAMPLES: The rearrangement cones of order one are nonnegative orthants:: sage: orthant = cones.nonnegative_orthant(6) sage: cones.rearrangement(1,6).is_equivalent(orthant) True When ``p`` and ``ambient_dim`` are equal, the rearrangement cone is a half-space, so we expect its lineality to be one less than ``ambient_dim`` because it will contain a hyperplane but is not the entire space:: sage: cones.rearrangement(5,5).lineality() 4 Jeong's Proposition 5.2.1 [Jeong2017]_ states that all rearrangement cones are proper when ``p`` is less than ``ambient_dim``:: sage: all( cones.rearrangement(p, ambient_dim).is_proper() ....: for ambient_dim in range(10) ....: for p in range(1, ambient_dim) ) True Jeong's Corollary 5.2.4 [Jeong2017]_ states that if `p = n-1` in an `n`-dimensional ambient space, then the Lyapunov rank of the rearrangement cone is `n`, and that for all other `p > 1` its Lyapunov rank is one:: sage: all( cones.rearrangement(p, ambient_dim).lyapunov_rank() ....: == ....: ambient_dim ....: for ambient_dim in range(2, 10) ....: for p in [ ambient_dim-1 ] ) True sage: all( cones.rearrangement(p, ambient_dim).lyapunov_rank() == 1 ....: for ambient_dim in range(3, 10) ....: for p in range(2, ambient_dim-1) ) True TESTS: Jeong's Proposition 5.2.1 [Jeong2017]_ states that rearrangement cones are permutation-invariant:: sage: ambient_dim = ZZ.random_element(2,10).abs() sage: p = ZZ.random_element(1, ambient_dim) sage: K = cones.rearrangement(p, ambient_dim) sage: P = SymmetricGroup(ambient_dim).random_element().matrix() sage: all( K.contains(P*r) for r in K ) True The smallest ``p`` components of every element of the rearrangement cone should sum to a nonnegative number. In other words, the generators really are what we think they are:: sage: set_random_seed() sage: def _has_rearrangement_property(v,p): ....: return sum( sorted(v)[0:p] ) >= 0 sage: all( ....: _has_rearrangement_property( ....: cones.rearrangement(p, ambient_dim).random_element(), ....: p ....: ) ....: for ambient_dim in range(2, 10) ....: for p in range(1, ambient_dim+1) ....: ) True The rearrangement cone of order ``p`` is, almost by definition, contained in the rearrangement cone of order ``p + 1``:: sage: set_random_seed() sage: ambient_dim = ZZ.random_element(2,10) sage: p = ZZ.random_element(1, ambient_dim) sage: K1 = cones.rearrangement(p, ambient_dim) sage: K2 = cones.rearrangement(p+1, ambient_dim) sage: all( x in K2 for x in K1 ) True Jeong's Proposition 5.2.1 [Jeong2017]_ states that the rearrangement cone of order ``p`` is linearly isomorphic to the rearrangement cone of order ``ambient_dim - p`` when ``p`` is less than ``ambient_dim``:: sage: set_random_seed() sage: ambient_dim = ZZ.random_element(2,10) sage: p = ZZ.random_element(1, ambient_dim) sage: K1 = cones.rearrangement(p, ambient_dim) sage: K2 = cones.rearrangement(ambient_dim-p, ambient_dim) sage: Mp = ((1/p)*matrix.ones(QQ, ambient_dim) ....: - matrix.identity(QQ, ambient_dim)) sage: Cone( (Mp*K2.rays()).columns() ).is_equivalent(K1) True The order ``p`` should be an integer between ``1`` and ``ambient_dim``, inclusive:: sage: cones.rearrangement(0,3) Traceback (most recent call last): ... ValueError: order p=0 should be an integer between 1 and ambient_dim=3, inclusive sage: cones.rearrangement(5,3) Traceback (most recent call last): ... ValueError: order p=5 should be an integer between 1 and ambient_dim=3, inclusive sage: cones.rearrangement(3/2, 3) Traceback (most recent call last): ... ValueError: order p=3/2 should be an integer between 1 and ambient_dim=3, inclusive If a ``lattice`` was given, it is actually used:: sage: L = ToricLattice(3, 'M') sage: cones.rearrangement(2, 3, lattice=L) 3-d cone in 3-d lattice M Unless the rank of the lattice disagrees with ``ambient_dim``:: sage: L = ToricLattice(1, 'M') sage: cones.rearrangement(2, 3, lattice=L) Traceback (most recent call last): ... ValueError: lattice rank=1 and ambient_dim=3 are incompatible We also get an error if neither the ambient dimension nor lattice are specified:: sage: cones.rearrangement(3) Traceback (most recent call last): ... ValueError: either the ambient dimension or the lattice must be specified """ from sage.geometry.cone import Cone from sage.matrix.constructor import matrix from sage.rings.all import ZZ (ambient_dim, lattice) = _preprocess_args(ambient_dim, lattice) if p < 1 or p > ambient_dim or not p in ZZ: raise ValueError("order p=%s should be an integer between 1 " "and ambient_dim=%d, inclusive" % (p, ambient_dim)) I = matrix.identity(ZZ, ambient_dim) M = matrix.ones(ZZ, ambient_dim) - p * I G = matrix.identity(ZZ, ambient_dim).rows() + M.rows() return Cone(G, lattice=lattice)
def nonnegative_orthant(ambient_dim=None, lattice=None): r""" The nonnegative orthant in ``ambient_dim`` dimensions, or living in ``lattice``. The nonnegative orthant consists of all componentwise-nonnegative vectors. It is the convex-conic hull of the standard basis. INPUT: - ``ambient_dim`` -- a nonnegative integer (default: ``None``); the dimension of the ambient space - ``lattice`` -- a toric lattice (default: ``None``); the lattice in which the cone will live If ``ambient_dim`` is omitted, then it will be inferred from the rank of ``lattice``. If the ``lattice`` is omitted, then the default lattice of rank ``ambient_dim`` will be used. A ``ValueError`` is raised if neither ``ambient_dim`` nor ``lattice`` are specified. It is also a ``ValueError`` to specify both ``ambient_dim`` and ``lattice`` unless the rank of ``lattice`` is equal to ``ambient_dim``. OUTPUT: A :class:`.ConvexRationalPolyhedralCone` living in ``lattice`` and having ``ambient_dim`` standard basis vectors as its generators. Each generating ray has the integer ring as its base ring. A ``ValueError`` can be raised if the inputs are incompatible or insufficient. See the INPUT documentation for details. REFERENCES: - Chapter 2 in [BV2009]_ (Examples 2.4, 2.14, and 2.23 in particular) EXAMPLES:: sage: cones.nonnegative_orthant(3).rays() N(1, 0, 0), N(0, 1, 0), N(0, 0, 1) in 3-d lattice N TESTS: We can construct the trivial cone as the nonnegative orthant in a trivial vector space:: sage: cones.nonnegative_orthant(0) 0-d cone in 0-d lattice N The nonnegative orthant is a proper cone:: sage: set_random_seed() sage: ambient_dim = ZZ.random_element(10) sage: K = cones.nonnegative_orthant(ambient_dim) sage: K.is_proper() True If a ``lattice`` was given, it is actually used:: sage: L = ToricLattice(3, 'M') sage: cones.nonnegative_orthant(lattice=L) 3-d cone in 3-d lattice M Unless the rank of the lattice disagrees with ``ambient_dim``:: sage: L = ToricLattice(1, 'M') sage: cones.nonnegative_orthant(3, lattice=L) Traceback (most recent call last): ... ValueError: lattice rank=1 and ambient_dim=3 are incompatible We also get an error if no arguments are given:: sage: cones.nonnegative_orthant() Traceback (most recent call last): ... ValueError: either the ambient dimension or the lattice must be specified """ from sage.geometry.cone import Cone from sage.matrix.constructor import matrix from sage.rings.all import ZZ (ambient_dim, lattice) = _preprocess_args(ambient_dim, lattice) I = matrix.identity(ZZ, ambient_dim) return Cone(I.rows(), lattice)
def IntegralLattice(data, basis=None): r""" Return the integral lattice spanned by ``basis`` in the ambient space. A lattice is a finitely generated free abelian group `L \cong \ZZ^r` equipped with a non-degenerate, symmetric bilinear form `L \times L \colon \rightarrow \ZZ`. Here, lattices have an ambient quadratic space `\QQ^n` and a distinguished basis. INPUT: The input is a descriptor of the lattice and a (optional) basis. - ``data`` -- can be one of the following: * a symmetric matrix over the rationals -- the inner product matrix * an integer -- the dimension for a euclidian lattice * a symmetric Cartan type or anything recognized by :class:`CartanMatrix` (see also :mod:`Cartan types <sage.combinat.root_system.cartan_type>`) -- for a root lattice * the string ``"U"`` or ``"H"`` -- for hyperbolic lattices - ``basis`` -- (optional) a matrix whose rows form a basis of the lattice, or a list of module elements forming a basis OUTPUT: A lattice in the ambient space defined by the inner_product_matrix. Unless specified, the basis of the lattice is the standard basis. EXAMPLES:: sage: H5 = Matrix(ZZ, 2, [2,1,1,-2]) sage: IntegralLattice(H5) Lattice of degree 2 and rank 2 over Integer Ring Basis matrix: [1 0] [0 1] Inner product matrix: [ 2 1] [ 1 -2] A basis can be specified too:: sage: IntegralLattice(H5, Matrix([1,1])) Lattice of degree 2 and rank 1 over Integer Ring Basis matrix: [1 1] Inner product matrix: [ 2 1] [ 1 -2] We can define a Euclidian lattice just by its dimension:: sage: IntegralLattice(3) Lattice of degree 3 and rank 3 over Integer Ring Basis matrix: [1 0 0] [0 1 0] [0 0 1] Inner product matrix: [1 0 0] [0 1 0] [0 0 1] Here is an example of the `A_2` root lattice in Euclidian space:: sage: basis = Matrix([[1,-1,0], [0,1,-1]]) sage: A2 = IntegralLattice(3, basis) sage: A2 Lattice of degree 3 and rank 2 over Integer Ring Basis matrix: [ 1 -1 0] [ 0 1 -1] Inner product matrix: [1 0 0] [0 1 0] [0 0 1] sage: A2.gram_matrix() [ 2 -1] [-1 2] We use ``"U"`` or ``"H"`` for defining a hyperbolic lattice:: sage: L1 = IntegralLattice("U") sage: L1 Lattice of degree 2 and rank 2 over Integer Ring Basis matrix: [1 0] [0 1] Inner product matrix: [0 1] [1 0] sage: L1 == IntegralLattice("H") True We can construct root lattices by specifying their type (see :mod:`Cartan types <sage.combinat.root_system.cartan_type>` and :class:`CartanMatrix`):: sage: IntegralLattice(["E", 7]) Lattice of degree 7 and rank 7 over Integer Ring Basis matrix: [1 0 0 0 0 0 0] [0 1 0 0 0 0 0] [0 0 1 0 0 0 0] [0 0 0 1 0 0 0] [0 0 0 0 1 0 0] [0 0 0 0 0 1 0] [0 0 0 0 0 0 1] Inner product matrix: [ 2 0 -1 0 0 0 0] [ 0 2 0 -1 0 0 0] [-1 0 2 -1 0 0 0] [ 0 -1 -1 2 -1 0 0] [ 0 0 0 -1 2 -1 0] [ 0 0 0 0 -1 2 -1] [ 0 0 0 0 0 -1 2] sage: IntegralLattice(["A", 2]) Lattice of degree 2 and rank 2 over Integer Ring Basis matrix: [1 0] [0 1] Inner product matrix: [ 2 -1] [-1 2] sage: IntegralLattice("D3") Lattice of degree 3 and rank 3 over Integer Ring Basis matrix: [1 0 0] [0 1 0] [0 0 1] Inner product matrix: [ 2 -1 -1] [-1 2 0] [-1 0 2] sage: IntegralLattice(["D", 4]) Lattice of degree 4 and rank 4 over Integer Ring Basis matrix: [1 0 0 0] [0 1 0 0] [0 0 1 0] [0 0 0 1] Inner product matrix: [ 2 -1 0 0] [-1 2 -1 -1] [ 0 -1 2 0] [ 0 -1 0 2] We can specify a basis as well:: sage: G = Matrix(ZZ, 2, [0,1,1,0]) sage: B = [vector([1,1])] sage: IntegralLattice(G, basis=B) Lattice of degree 2 and rank 1 over Integer Ring Basis matrix: [1 1] Inner product matrix: [0 1] [1 0] sage: IntegralLattice(["A", 3], [[1,1,1]]) Lattice of degree 3 and rank 1 over Integer Ring Basis matrix: [1 1 1] Inner product matrix: [ 2 -1 0] [-1 2 -1] [ 0 -1 2] sage: IntegralLattice(4, [[1,1,1,1]]) Lattice of degree 4 and rank 1 over Integer Ring Basis matrix: [1 1 1 1] Inner product matrix: [1 0 0 0] [0 1 0 0] [0 0 1 0] [0 0 0 1] sage: IntegralLattice("A2", [[1,1]]) Lattice of degree 2 and rank 1 over Integer Ring Basis matrix: [1 1] Inner product matrix: [ 2 -1] [-1 2] TESTS:: sage: IntegralLattice(["A", 1, 1]) Traceback (most recent call last): ... ValueError: lattices must be nondegenerate; use FreeQuadraticModule instead sage: IntegralLattice(["D", 3, 1]) Traceback (most recent call last): ... ValueError: lattices must be nondegenerate; use FreeQuadraticModule instead """ if is_Matrix(data): inner_product_matrix = data elif isinstance(data, Integer): inner_product_matrix = matrix.identity(ZZ, data) elif data == "U" or data == "H": inner_product_matrix = matrix([[0, 1], [1, 0]]) else: inner_product_matrix = CartanMatrix(data) if basis is None: basis = matrix.identity(ZZ, inner_product_matrix.ncols()) if inner_product_matrix != inner_product_matrix.transpose(): raise ValueError("the inner product matrix must be symmetric\n%s" % inner_product_matrix) A = FreeQuadraticModule(ZZ, inner_product_matrix.ncols(), inner_product_matrix=inner_product_matrix) return FreeQuadraticModule_integer_symmetric( ambient=A, basis=basis, inner_product_matrix=A.inner_product_matrix(), already_echelonized=False)
def stratification(L): r""" Return a stratification of the Lie algebra if one exists. INPUT: - ``L`` -- a Lie algebra OUTPUT: A grading of the Lie algebra `\mathfrak{g}` over the integers such that the layer `\mathfrak{g}_1` generates the full Lie algebra. EXAMPLES:: A stratification for a free nilpotent Lie algebra is the one based on the length of the defining bracket:: sage: from lie_gradings.gradings.grading import stratification sage: from lie_gradings.gradings.utilities import in_new_basis sage: L = LieAlgebra(QQ, 3, step=3) sage: strat = stratification(L) sage: strat Grading over Additive abelian group isomorphic to 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 Rational Field with nonzero layers (1) : (X_1, X_2, X_3) (2) : (X_12, X_13, X_23) (3) : (X_112, X_113, X_122, X_123, X_132, X_133, X_223, X_233) The main use case is when the original basis of the stratifiable Lie algebra is not adapted to a stratification. Consider the following quotient Lie algebra:: sage: X_1, X_2, X_3 = L.basis().list()[:3] sage: Q = L.quotient(L[X_2, X_3]) sage: Q Lie algebra quotient L/I of dimension 10 over Rational Field where L: 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 Rational Field I: Ideal (X_23) We switch to a basis which does not define a stratification:: sage: Y_1 = Q(X_1) sage: Y_2 = Q(X_2) + Q[X_1, X_2] sage: Y_3 = Q(X_3) sage: basis = [Y_1, Y_2, Y_3] + Q.basis().list()[3:] sage: Y_labels = ["Y_%d"%(k+1) for k in range(len(basis))] sage: K = in_new_basis(Q, basis,Y_labels) sage: K.inject_variables() Defining Y_1, Y_2, Y_3, Y_4, Y_5, Y_6, Y_7, Y_8, Y_9, Y_10 sage: K[Y_2, Y_3] Y_9 sage: K[[Y_1, Y_3], Y_2] Y_9 We may reconstruct a stratification in the new basis without any knowledge of the original stratification:: sage: stratification(K) Grading over Additive abelian group isomorphic to Z of Nilpotent Lie algebra on 10 generators (Y_1, Y_2, Y_3, Y_4, Y_5, Y_6, Y_7, Y_8, Y_9, Y_10) over Rational Field with nonzero layers (1) : (Y_1, Y_2 - Y_4, Y_3) (2) : (Y_4, Y_5) (3) : (Y_10, Y_6, Y_7, Y_8, Y_9) sage: K[Y_1, Y_2 - Y_4] Y_4 sage: K[Y_1, Y_3] Y_5 sage: K[Y_2 - Y_4, Y_3] 0 A non-stratifiable Lie algebra raises an error:: sage: L = LieAlgebra(QQ, {('X_1','X_3'): {'X_4': 1}, ....: ('X_1','X_4'): {'X_5': 1}, ....: ('X_2','X_3'): {'X_5': 1}}, ....: names='X_1,X_2,X_3,X_4,X_5') sage: stratification(L) Traceback (most recent call last): ... ValueError: Lie algebra on 5 generators (X_1, X_2, X_3, X_4, X_5) over Rational Field is not a stratifiable Lie algebra """ lcs = L.lower_central_series(submodule=True) quots = [V.quotient(W) for V, W in zip(lcs, lcs[1:])] # find a basis adapted to the filtration by the lower central series adapted_basis = [] weights = [] for k, q in enumerate(quots): weights += [k + 1] * q.dimension() for v in q.basis(): b = q.lift(v) adapted_basis.append(b) # define a submodule to compute structural # coefficients in the filtration adapted basis try: m = L.module() except AttributeError: m = FreeModule(L.base_ring(), L.dimension()) sm = m.submodule_with_basis(adapted_basis) # form the linear system Ax=b of constraints from the Leibniz rule paramspace = [(k, h) for k in range(L.dimension()) for h in range(L.dimension()) if weights[h] > weights[k]] Arows = [] bvec = [] zerovec = m.zero() for i in range(L.dimension()): Y_i = adapted_basis[i] w_i = weights[i] for j in range(i + 1, L.dimension()): Y_j = adapted_basis[j] w_j = weights[j] Y_ij = L.bracket(Y_i, Y_j) c_ij = sm.coordinate_vector(Y_ij.to_vector()) bcomp = L.zero() for k in range(L.dimension()): w_k = weights[k] Y_k = adapted_basis[k] bcomp += (w_k - w_i - w_j) * c_ij[k] * Y_k bv = bcomp.to_vector() Acomp = {} for k, h in paramspace: w_k = weights[k] Y_h = adapted_basis[h] if k == i: Acomp[(k, h)] = L.bracket(Y_h, Y_j).to_vector() elif k == j: Acomp[(k, h)] = L.bracket(Y_i, Y_h).to_vector() elif w_k >= w_i + w_j: Acomp[(k, h)] = -c_ij[k] * Y_h for r in range(L.dimension()): Arows.append( [Acomp.get((k, h), zerovec)[r] for k, h in paramspace]) bvec.append(bv[r]) A = matrix(L.base_ring(), Arows) b = vector(L.base_ring(), bvec) # solve the linear system Ax=b if possible try: coeffs_flat = A.solve_right(b) except ValueError: raise ValueError("%s is not a stratifiable Lie algebra" % L) coeffs = {(k, h): ckh for (k, h), ckh in zip(paramspace, coeffs_flat)} # define the matrix of the derivation determined by the solution # in the adapted basis cols = [] for k in range(L.dimension()): w_k = weights[k] Y_k = adapted_basis[k] hspace = [h for (l, h) in paramspace if l == k] Yk_im = w_k * Y_k + sum( (coeffs[(k, h)] * adapted_basis[h] for h in hspace), L.zero()) cols.append(sm.coordinate_vector(Yk_im.to_vector())) der = matrix(L.base_ring(), cols).transpose() # the layers of the stratification are V_k = ker(der - kI) layers = {} for k in range(len(quots)): degree = k + 1 B = der - degree * matrix.identity(L.dimension()) adapted_kernel = B.right_kernel() # convert back to the original basis Vk_basis = [sm.from_vector(X) for X in adapted_kernel.basis()] layers[(degree, )] = [ L.from_vector(v) for v in m.submodule(Vk_basis).basis() ] return grading(L, layers, magma=AdditiveAbelianGroup([0]), projections=True)
def construct_free_chain(A): """ Construct the free chain for the hyperplanes ``A``. ALGORITHM: We follow Algorithm 6.5 in [BC2012]_. INPUT: - ``A`` -- a hyperplane arrangement EXAMPLES:: sage: from sage.geometry.hyperplane_arrangement.check_freeness import construct_free_chain sage: H.<x,y,z> = HyperplaneArrangements(QQ) sage: A = H(z, y+z, x+y+z) sage: construct_free_chain(A) [ [1 0 0] [ 1 0 0] [ 0 1 0] [0 1 0] [ 0 z -1] [y + z 0 -1] [0 0 z], [ 0 y 1], [ x 0 1] ] """ AL = list(A) if not AL: # Empty arrangement return [] S = A.parent().ambient_space().symmetric_space() # Compute the morphisms \phi_{H_j} : S^{1xR} \to S / <b_j> B = [H.to_symmetric_space() for H in AL] phi = [matrix(S, [[beta.derivative(x)] for x in S.gens()]) for beta in B] # Setup variables syz = fun_fact.ff.syz G = S.gens() r = len(G) indices = list(range(len(B))) # Helper function def next_step(indices, prev, T): for pos,i in enumerate(indices): U = prev * T mu = U * phi[i] mu = mu.stack(matrix.diagonal([B[i]]).dense_matrix()) row_syzygy = matrix(S, syz(mu.transpose())).matrix_from_columns(range(r)) Y = less_generators(row_syzygy) if not Y.is_square(): continue if len(indices) == 1: return [prev, Y] I = list(indices) I.pop(pos) ret = next_step(I, Y, U) if ret is not None: return [prev] + ret return None T = matrix.identity(S, r) for i in indices: mu = phi[i].stack(matrix.diagonal([B[i]]).dense_matrix()) row_syzygy = matrix(S, syz(mu.transpose())).matrix_from_columns(range(r)) Y = less_generators(row_syzygy) if not Y.is_square(): continue if len(indices) == 1: return [Y] I = list(indices) I.pop(i) ret = next_step(I, Y, T) if ret is not None: return ret return None
def IntegralLattice(data, basis=None): r""" Return the integral lattice spanned by ``basis`` in the ambient space. A lattice is a finitely generated free abelian group `L \cong \ZZ^r` equipped with a non-degenerate, symmetric bilinear form `L \times L \colon \rightarrow \ZZ`. Here, lattices have an ambient quadratic space `\QQ^n` and a distinguished basis. INPUT: The input is a descriptor of the lattice and a (optional) basis. - ``data`` -- can be one of the following: * a symmetric matrix over the rationals -- the inner product matrix * an integer -- the dimension for a euclidian lattice * a symmetric Cartan type or anything recognized by :class:`CartanMatrix` (see also :mod:`Cartan types <sage.combinat.root_system.cartan_type>`) -- for a root lattice * the string ``"U"`` or ``"H"`` -- for hyperbolic lattices - ``basis`` -- (optional) a matrix whose rows form a basis of the lattice, or a list of module elements forming a basis OUTPUT: A lattice in the ambient space defined by the inner_product_matrix. Unless specified, the basis of the lattice is the standard basis. EXAMPLES:: sage: H5 = Matrix(ZZ, 2, [2,1,1,-2]) sage: IntegralLattice(H5) Lattice of degree 2 and rank 2 over Integer Ring Basis matrix: [1 0] [0 1] Inner product matrix: [ 2 1] [ 1 -2] A basis can be specified too:: sage: IntegralLattice(H5, Matrix([1,1])) Lattice of degree 2 and rank 1 over Integer Ring Basis matrix: [1 1] Inner product matrix: [ 2 1] [ 1 -2] We can define a Euclidian lattice just by its dimension:: sage: IntegralLattice(3) Lattice of degree 3 and rank 3 over Integer Ring Basis matrix: [1 0 0] [0 1 0] [0 0 1] Inner product matrix: [1 0 0] [0 1 0] [0 0 1] Here is an example of the `A_2` root lattice in Euclidian space:: sage: basis = Matrix([[1,-1,0], [0,1,-1]]) sage: A2 = IntegralLattice(3, basis) sage: A2 Lattice of degree 3 and rank 2 over Integer Ring Basis matrix: [ 1 -1 0] [ 0 1 -1] Inner product matrix: [1 0 0] [0 1 0] [0 0 1] sage: A2.gram_matrix() [ 2 -1] [-1 2] We use ``"U"`` or ``"H"`` for defining a hyperbolic lattice:: sage: L1 = IntegralLattice("U") sage: L1 Lattice of degree 2 and rank 2 over Integer Ring Basis matrix: [1 0] [0 1] Inner product matrix: [0 1] [1 0] sage: L1 == IntegralLattice("H") True We can construct root lattices by specifying their type (see :mod:`Cartan types <sage.combinat.root_system.cartan_type>` and :class:`CartanMatrix`):: sage: IntegralLattice(["E", 7]) Lattice of degree 7 and rank 7 over Integer Ring Basis matrix: [1 0 0 0 0 0 0] [0 1 0 0 0 0 0] [0 0 1 0 0 0 0] [0 0 0 1 0 0 0] [0 0 0 0 1 0 0] [0 0 0 0 0 1 0] [0 0 0 0 0 0 1] Inner product matrix: [ 2 0 -1 0 0 0 0] [ 0 2 0 -1 0 0 0] [-1 0 2 -1 0 0 0] [ 0 -1 -1 2 -1 0 0] [ 0 0 0 -1 2 -1 0] [ 0 0 0 0 -1 2 -1] [ 0 0 0 0 0 -1 2] sage: IntegralLattice(["A", 2]) Lattice of degree 2 and rank 2 over Integer Ring Basis matrix: [1 0] [0 1] Inner product matrix: [ 2 -1] [-1 2] sage: IntegralLattice("D3") Lattice of degree 3 and rank 3 over Integer Ring Basis matrix: [1 0 0] [0 1 0] [0 0 1] Inner product matrix: [ 2 -1 -1] [-1 2 0] [-1 0 2] sage: IntegralLattice(["D", 4]) Lattice of degree 4 and rank 4 over Integer Ring Basis matrix: [1 0 0 0] [0 1 0 0] [0 0 1 0] [0 0 0 1] Inner product matrix: [ 2 -1 0 0] [-1 2 -1 -1] [ 0 -1 2 0] [ 0 -1 0 2] We can specify a basis as well:: sage: G = Matrix(ZZ, 2, [0,1,1,0]) sage: B = [vector([1,1])] sage: IntegralLattice(G, basis=B) Lattice of degree 2 and rank 1 over Integer Ring Basis matrix: [1 1] Inner product matrix: [0 1] [1 0] sage: IntegralLattice(["A", 3], [[1,1,1]]) Lattice of degree 3 and rank 1 over Integer Ring Basis matrix: [1 1 1] Inner product matrix: [ 2 -1 0] [-1 2 -1] [ 0 -1 2] sage: IntegralLattice(4, [[1,1,1,1]]) Lattice of degree 4 and rank 1 over Integer Ring Basis matrix: [1 1 1 1] Inner product matrix: [1 0 0 0] [0 1 0 0] [0 0 1 0] [0 0 0 1] sage: IntegralLattice("A2", [[1,1]]) Lattice of degree 2 and rank 1 over Integer Ring Basis matrix: [1 1] Inner product matrix: [ 2 -1] [-1 2] TESTS:: sage: IntegralLattice(["A", 1, 1]) Traceback (most recent call last): ... ValueError: lattices must be nondegenerate; use FreeQuadraticModule instead sage: IntegralLattice(["D", 3, 1]) Traceback (most recent call last): ... ValueError: lattices must be nondegenerate; use FreeQuadraticModule instead """ if is_Matrix(data): inner_product_matrix = data elif isinstance(data, Integer): inner_product_matrix = matrix.identity(ZZ, data) elif data == "U" or data == "H": inner_product_matrix = matrix([[0,1],[1,0]]) else: inner_product_matrix = CartanMatrix(data) if basis is None: basis = matrix.identity(ZZ, inner_product_matrix.ncols()) if inner_product_matrix != inner_product_matrix.transpose(): raise ValueError("the inner product matrix must be symmetric\n%s" % inner_product_matrix) A = FreeQuadraticModule(ZZ, inner_product_matrix.ncols(), inner_product_matrix=inner_product_matrix) return FreeQuadraticModule_integer_symmetric(ambient=A, basis=basis, inner_product_matrix=A.inner_product_matrix(), already_echelonized=False)
def orthogonal_group(self, gens=None, is_finite=None): """ Return the orthogonal group of this lattice as a matrix group. The elements are isometries of the ambient vector space which preserve this lattice. They are represented by matrices with respect to the standard basis. INPUT: - ``gens`` -- a list of matrices (default:``None``) - ``is_finite`` -- bool (default: ``None``) If set to ``True``, then the group is placed in the category of finite groups. Sage does not check this. OUTPUT: The matrix group generated by ``gens``. If ``gens`` is not specified, then generators of the full orthogonal group of this lattice are computed. They are continued as the identity on the orthogonal complement of the lattice in its ambient space. Currently, we can only compute the orthogonal group for positive definite lattices. EXAMPLES:: sage: A4 = IntegralLattice("A4") sage: Aut = A4.orthogonal_group() sage: Aut Group of isometries with 5 generators ( [-1 0 0 0] [0 0 0 1] [-1 -1 -1 0] [ 1 0 0 0] [ 1 0 0 0] [ 0 -1 0 0] [0 0 1 0] [ 0 0 0 -1] [-1 -1 -1 -1] [ 0 1 0 0] [ 0 0 -1 0] [0 1 0 0] [ 0 0 1 1] [ 0 0 0 1] [ 0 0 1 1] [ 0 0 0 -1], [1 0 0 0], [ 0 1 0 0], [ 0 0 1 0], [ 0 0 0 -1] ) The group acts from the right on the lattice and its discriminant group:: sage: x = A4.an_element() sage: g = Aut.an_element() sage: g [ 1 1 1 0] [ 0 0 -1 0] [ 0 0 1 1] [ 0 -1 -1 -1] sage: x*g (1, 1, 1, 0) sage: (x*g).parent()==A4 True sage: (g*x).parent() Vector space of dimension 4 over Rational Field sage: y = A4.discriminant_group().an_element() sage: y*g (1) If the group is finite we can compute the usual things:: sage: Aut.order() 240 sage: conj = Aut.conjugacy_classes_representatives() sage: len(conj) 14 sage: Aut.structure_description() # optional - database_gap 'C2 x S5' The lattice can live in a larger ambient space:: sage: A2 = IntegralLattice(matrix.identity(3),Matrix(ZZ,2,3,[1,-1,0,0,1,-1])) sage: A2.orthogonal_group() Group of isometries with 3 generators ( [-1/3 2/3 2/3] [ 2/3 2/3 -1/3] [1 0 0] [ 2/3 -1/3 2/3] [ 2/3 -1/3 2/3] [0 0 1] [ 2/3 2/3 -1/3], [-1/3 2/3 2/3], [0 1 0] ) It can be negative definite as well:: sage: A2m = IntegralLattice(-Matrix(ZZ,2,[2,1,1,2])) sage: G = A2m.orthogonal_group() sage: G.order() 12 If the lattice is indefinite, sage does not know how to compute generators. Can you teach it?:: sage: U = IntegralLattice(Matrix(ZZ,2,[0,1,1,0])) sage: U.orthogonal_group() Traceback (most recent call last): ... NotImplementedError: currently, we can only compute generators for orthogonal groups over definite lattices. But we can define subgroups:: sage: S = IntegralLattice(Matrix(ZZ,2,[2, 3, 3, 2])) sage: f = Matrix(ZZ,2,[0,1,-1,3]) sage: S.orthogonal_group([f]) Group of isometries with 1 generator ( [ 0 1] [-1 3] ) TESTS: We can handle the trivial group:: sage: S = IntegralLattice(Matrix(ZZ,2,[2, 3, 3, 2])) sage: S.orthogonal_group([]) Group of isometries with 1 generator ( [1 0] [0 1] ) """ from sage.categories.groups import Groups from sage.groups.matrix_gps.isometries import GroupOfIsometries sig = self.signature_pair() if gens is None: gens = [] if sig[1]==0 or sig[0]==0: #definite from sage.quadratic_forms.quadratic_form import QuadraticForm is_finite = True # Compute transformation matrix to the ambient module. L = self.overlattice(self.ambient_module().gens()) Orthogonal = L.orthogonal_complement(self) B = self.basis_matrix().stack(Orthogonal.basis_matrix()) if sig[0] == 0: #negative definite q = QuadraticForm(ZZ, -2*self.gram_matrix()) else: # positve definite q = QuadraticForm(ZZ, 2*self.gram_matrix()) identity = matrix.identity(Orthogonal.rank()) for g in q.automorphism_group().gens(): g = g.matrix().T # We continue g as identity on the orthogonal complement. g = matrix.block_diagonal([g, identity]) g = B.inverse()*g*B gens.append(g) else: #indefinite raise NotImplementedError( "currently, we can only compute generators " "for orthogonal groups over definite lattices.") deg = self.degree() base = self.ambient_vector_space().base_ring() inv_bil = self.inner_product_matrix() if is_finite: cat = Groups().Finite() else: cat = Groups() D = self.discriminant_group() G = GroupOfIsometries(deg, base, gens, inv_bil, category=cat, invariant_submodule=self, invariant_quotient_module=D) return G
def find_p_neighbor_from_vec(self, p, y): r""" Return the `p`-neighbor of ``self`` defined by ``y``. Let `(L,q)` be a lattice with `b(L,L) \subseteq \ZZ` which is maximal at `p`. Let `y \in L` with `b(y,y) \in p^2\ZZ` then the `p`-neighbor of `L` at `y` is given by `\ZZ y/p + L_y` where `L_y = \{x \in L | b(x,y) \in p \ZZ \}` and `b(x,y) = q(x+y)-q(x)-q(y)` is the bilinear form associated to `q`. INPUT: - ``p`` -- a prime number - ``y`` -- a vector with `q(y) \in p \ZZ`. - ``odd`` -- (default=``False``) if `p=2` return also odd neighbors EXAMPLES:: sage: Q = DiagonalQuadraticForm(ZZ,[1,1,1,1]) sage: v = vector([0,2,1,1]) sage: X = Q.find_p_neighbor_from_vec(3,v); X Quadratic form in 4 variables over Integer Ring with coefficients: [ 1 0 0 0 ] [ * 1 4 4 ] [ * * 5 12 ] [ * * * 9 ] Since the base ring and the domain are not yet separate, for rational, half integral forms we just pretend the base ring is `ZZ`:: sage: Q = QuadraticForm(QQ,matrix.diagonal([1,1,1,1])) sage: v = vector([1,1,1,1]) sage: Q.find_p_neighbor_from_vec(2,v) Quadratic form in 4 variables over Rational Field with coefficients: [ 1/2 1 1 1 ] [ * 1 1 2 ] [ * * 1 2 ] [ * * * 2 ] """ p = ZZ(p) if not p.divides(self(y)): raise ValueError("y=%s must be of square divisible by p=%s"%(y,p)) if self.base_ring() not in [ZZ, QQ]: raise NotImplementedError("the base ring of this form must be the integers or the rationals") n = self.dim() G = self.Hessian_matrix() R = self.base_ring() odd = False if R is QQ: odd = True if G.denominator() != 1: raise ValueError("the associated bilinear form q(x+y)-q(x)-q(y) must be integral.") b = y*G*y if not b % p == 0: raise ValueError("y^2 must be divisible by p=%s"%p) y_dual = y*G if p != 2 and b % p**2 != 0: for k in range(n): if y_dual[k] % p != 0: z = (ZZ**n).gen(k) break else: raise ValueError("either y is not primitive or self is not maximal at %s"%p) z *= (2*y*G*z).inverse_mod(p) y = y - b*z # assert y*G*y % p^2 == 0 if p == 2: val = b.valuation(p) if val <= 1: raise ValueError("y=%s must be of square divisible by 2"%y) if val == 2 and not odd: # modify it to have square 4 for k in range(n): if y_dual[k] % p != 0: z = (ZZ**n).gen(k) break else: raise ValueError("either y is not primitive or self is not even, maximal at 2") y += 2*z # assert y*G*y % 8 == 0 y_dual = G*y Ly = y_dual.change_ring(GF(p)).column().kernel().matrix().lift() B = Ly.stack(p * matrix.identity(n)) # the rows of B now generate L_y = { x in L | (x,y)=0 mod p} B = y.row().stack(p*B) B = B.hermite_form()[:n, :] / p # the rows of B generate ZZ * y/p + L_y # by definition this is the p-neighbor of L at y # assert B.det().abs() == 1 QF = self.parent() Gnew = (B*G*B.T).change_ring(R) return QF(Gnew)
def orthogonal_group(self, gens=None, is_finite=None): """ Return the orthogonal group of this lattice as a matrix group. The elements are isometries of the ambient vector space which preserve this lattice. They are represented by matrices with respect to the standard basis. INPUT: - ``gens`` -- a list of matrices (default:``None``) - ``is_finite`` -- bool (default: ``None``) If set to ``True``, then the group is placed in the category of finite groups. Sage does not check this. OUTPUT: The matrix group generated by ``gens``. If ``gens`` is not specified, then generators of the full orthogonal group of this lattice are computed. They are continued as the identity on the orthogonal complement of the lattice in its ambient space. Currently, we can only compute the orthogonal group for positive definite lattices. EXAMPLES:: sage: A4 = IntegralLattice("A4") sage: Aut = A4.orthogonal_group() sage: Aut Group of isometries with 5 generators ( [-1 0 0 0] [0 0 0 1] [-1 -1 -1 0] [ 1 0 0 0] [ 1 0 0 0] [ 0 -1 0 0] [0 0 1 0] [ 0 0 0 -1] [-1 -1 -1 -1] [ 0 1 0 0] [ 0 0 -1 0] [0 1 0 0] [ 0 0 1 1] [ 0 0 0 1] [ 0 0 1 1] [ 0 0 0 -1], [1 0 0 0], [ 0 1 0 0], [ 0 0 1 0], [ 0 0 0 -1] ) The group acts from the right on the lattice and its discriminant group:: sage: x = A4.an_element() sage: g = Aut.an_element() sage: g [ 1 1 1 0] [ 0 0 -1 0] [ 0 0 1 1] [ 0 -1 -1 -1] sage: x*g (1, 1, 1, 0) sage: (x*g).parent()==A4 True sage: (g*x).parent() Vector space of dimension 4 over Rational Field sage: y = A4.discriminant_group().an_element() sage: y*g (1) If the group is finite we can compute the usual things:: sage: Aut.order() 240 sage: conj = Aut.conjugacy_classes_representatives() sage: len(conj) 14 sage: Aut.structure_description() # optional - database_gap 'C2 x S5' The lattice can live in a larger ambient space:: sage: A2 = IntegralLattice(matrix.identity(3),Matrix(ZZ,2,3,[1,-1,0,0,1,-1])) sage: A2.orthogonal_group() Group of isometries with 3 generators ( [-1/3 2/3 2/3] [ 2/3 2/3 -1/3] [1 0 0] [ 2/3 -1/3 2/3] [ 2/3 -1/3 2/3] [0 0 1] [ 2/3 2/3 -1/3], [-1/3 2/3 2/3], [0 1 0] ) It can be negative definite as well:: sage: A2m = IntegralLattice(-Matrix(ZZ,2,[2,1,1,2])) sage: G = A2m.orthogonal_group() sage: G.order() 12 If the lattice is indefinite, sage does not know how to compute generators. Can you teach it?:: sage: U = IntegralLattice(Matrix(ZZ,2,[0,1,1,0])) sage: U.orthogonal_group() Traceback (most recent call last): ... NotImplementedError: currently, we can only compute generators for orthogonal groups over definite lattices. But we can define subgroups:: sage: S = IntegralLattice(Matrix(ZZ,2,[2, 3, 3, 2])) sage: f = Matrix(ZZ,2,[0,1,-1,3]) sage: S.orthogonal_group([f]) Group of isometries with 1 generator ( [ 0 1] [-1 3] ) TESTS: We can handle the trivial group:: sage: S = IntegralLattice(Matrix(ZZ,2,[2, 3, 3, 2])) sage: S.orthogonal_group([]) Group of isometries with 1 generator ( [1 0] [0 1] ) """ from sage.categories.groups import Groups from sage.groups.matrix_gps.isometries import GroupOfIsometries sig = self.signature_pair() if gens is None: gens = [] if sig[1] == 0 or sig[0] == 0: #definite from sage.quadratic_forms.quadratic_form import QuadraticForm is_finite = True # Compute transformation matrix to the ambient module. L = self.overlattice(self.ambient_module().gens()) Orthogonal = L.orthogonal_complement(self) B = self.basis_matrix().stack(Orthogonal.basis_matrix()) if sig[0] == 0: #negative definite q = QuadraticForm(ZZ, -2 * self.gram_matrix()) else: # positve definite q = QuadraticForm(ZZ, 2 * self.gram_matrix()) identity = matrix.identity(Orthogonal.rank()) for g in q.automorphism_group().gens(): g = g.matrix().T # We continue g as identity on the orthogonal complement. g = matrix.block_diagonal([g, identity]) g = B.inverse() * g * B gens.append(g) else: #indefinite raise NotImplementedError( "currently, we can only compute generators " "for orthogonal groups over definite lattices.") deg = self.degree() base = self.ambient_vector_space().base_ring() inv_bil = self.inner_product_matrix() if is_finite: cat = Groups().Finite() else: cat = Groups() D = self.discriminant_group() G = GroupOfIsometries(deg, base, gens, inv_bil, category=cat, invariant_submodule=self, invariant_quotient_module=D) return G
def lifting(p, t, A, G): r""" Compute generators of `\{f \in D[X]^d \mid Af \equiv 0 \pmod{p^{t}}\}` given generators of `\{f\in D[X]^d \mid Af \equiv 0\pmod{p^{t-1}}\}`. INPUT: - ``p`` -- a prime element of some principal ideal domain `D` - ``t`` -- a non-negative integer - ``A`` -- a `c\times d` matrix over `D[X]` - ``G`` -- a matrix over `D[X]`. The columns of `\begin{pmatrix}p^{t-1}I& G\end{pmatrix}` are generators of `\{ f\in D[X]^d \mid Af \equiv 0\pmod{p^{t-1}}\}`; can be set to ``None`` if ``t`` is zero OUTPUT: A matrix `F` over `D[X]` such that the columns of `\begin{pmatrix}p^tI&F&pG\end{pmatrix}` are generators of `\{ f\in D[X]^d \mid Af \equiv 0\pmod{p^t}\}`. EXAMPLES:: sage: from sage.matrix.compute_J_ideal import lifting sage: X = polygen(ZZ, 'X') sage: A = matrix([[1, X], [2*X, X^2]]) sage: G0 = lifting(5, 0, A, None) sage: G1 = lifting(5, 1, A, G0); G1 [] sage: (A*G1 % 5).is_zero() True sage: A = matrix([[1, X, X^2], [2*X, X^2, 3*X^3]]) sage: G0 = lifting(5, 0, A, None) sage: G1 = lifting(5, 1, A, G0); G1 [3*X^2] [ X] [ 1] sage: (A*G1 % 5).is_zero() True sage: G2 = lifting(5, 2, A, G1); G2 [15*X^2 23*X^2] [ 5*X X] [ 5 1] sage: (A*G2 % 25).is_zero() True sage: lifting(5, 10, A, G1) Traceback (most recent call last): ... ValueError: A*G not zero mod 5^9 ALGORITHM: [HR2016]_, Algorithm 1. TESTS:: sage: A = matrix([[1, X], [X, X^2]]) sage: G0 = lifting(5, 0, A, None) sage: G1 = lifting(5, 1, A, G0); G1 Traceback (most recent call last): ... ValueError: [ 1 X|] [ X X^2|] does not have full rank. """ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing DX = A.parent().base() (X,) = DX.variable_names() D = DX.base_ring() d = A.ncols() c = A.nrows() if t == 0: return matrix(DX, d, 0) if not (A*G % p**(t-1)).is_zero(): raise ValueError("A*G not zero mod %s^%s" % (p, t-1)) R = A*G/p**(t-1) R.change_ring(DX) AR = matrix.block([[A, R]]) Fp = D.quotient(p*D) FpX = PolynomialRing(Fp, name=X) ARb = AR.change_ring(FpX) (Db, Sb, Tb) = ARb.smith_form() #assert Sb * ARb * Tb == Db #assert all(i == j or Db[i, j].is_zero() # for i in range(Db.nrows()) # for j in range(Db.ncols())) r = Db.rank() if r != c: raise ValueError("{} does not have full rank.".format(ARb)) T = Tb.change_ring(DX) F1 = matrix.block([[p**(t-1) * matrix.identity(d), G]])*T F = F1.matrix_from_columns(range(r, F1.ncols())) assert (A*F % (p**t)).is_zero(), "A*F=%s" % (A*F) return F