def direct_sum(self, M): r""" Return the direct sum of this lattice with ``M``. INPUT: - ``M`` -- a module over `\ZZ` EXAMPLES:: sage: A = IntegralLattice(1) sage: A.direct_sum(A) Lattice of degree 2 and rank 2 over Integer Ring Basis matrix: [1 0] [0 1] Inner product matrix: [1 0] [0 1] """ IM = matrix.block_diagonal( [self.inner_product_matrix(), M.inner_product_matrix()]) ambient = FreeQuadraticModule(ZZ, self.degree() + M.degree(), IM) smzero = matrix.zero(self.rank(), M.degree()) mszero = matrix.zero(M.rank(), self.degree()) basis = self.basis_matrix().augment(smzero).stack( mszero.augment(M.basis_matrix())) ipm = ambient.inner_product_matrix() return FreeQuadraticModule_integer_symmetric(ambient=ambient, basis=basis, inner_product_matrix=ipm, already_echelonized=False)
def direct_sum(self, M): r""" Return the direct sum of this lattice with ``M``. INPUT: - ``M`` -- a module over `\ZZ` EXAMPLES:: sage: A = IntegralLattice(1) sage: A.direct_sum(A) Lattice of degree 2 and rank 2 over Integer Ring Basis matrix: [1 0] [0 1] Inner product matrix: [1 0] [0 1] """ IM = matrix.block_diagonal([self.inner_product_matrix(), M.inner_product_matrix()]) ambient = FreeQuadraticModule(ZZ, self.degree() + M.degree(), IM) smzero = matrix.zero(self.rank(), M.degree()) mszero = matrix.zero(M.rank(), self.degree()) basis = self.basis_matrix().augment(smzero).stack( mszero.augment(M.basis_matrix())) ipm = ambient.inner_product_matrix() return FreeQuadraticModule_integer_symmetric(ambient=ambient, basis=basis, inner_product_matrix=ipm, already_echelonized=False)
def IntegralLatticeDirectSum(Lattices, return_embeddings=False): r""" Return the direct sum of the lattices contained in the list ``Lattices``. INPUT: - ``Lattices`` -- a list of lattices ``[L_1,...,L_n]`` - ``return_embeddings`` -- (default: ``False``) a boolean OUTPUT: The direct sum of the `L_i` if ``return_embeddings`` is ``False`` or the tuple ``[L, phi]`` where `L` is the direct sum of `L_i` and ``phi`` is the list of embeddings from `L_i` to `L`. EXAMPLES:: sage: from sage.modules.free_quadratic_module_integer_symmetric import IntegralLatticeDirectSum sage: L1 = IntegralLattice("D4") sage: L2 = IntegralLattice("A3", [[1, 1, 2]]) sage: L3 = IntegralLattice("A4", [[0, 1, 1, 2], [1, 2, 3, 1]]) sage: Lattices = [L1, L2, L3] sage: IntegralLatticeDirectSum([L1, L2, L3]) Lattice of degree 11 and rank 7 over Integer Ring Basis matrix: [1 0 0 0 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0 0 0 0] [0 0 1 0 0 0 0 0 0 0 0] [0 0 0 1 0 0 0 0 0 0 0] [0 0 0 0 1 1 2 0 0 0 0] [0 0 0 0 0 0 0 0 1 1 2] [0 0 0 0 0 0 0 1 2 3 1] Inner product matrix: [ 2 -1 0 0 0 0 0 0 0 0 0] [-1 2 -1 -1 0 0 0 0 0 0 0] [ 0 -1 2 0 0 0 0 0 0 0 0] [ 0 -1 0 2 0 0 0 0 0 0 0] [ 0 0 0 0 2 -1 0 0 0 0 0] [ 0 0 0 0 -1 2 -1 0 0 0 0] [ 0 0 0 0 0 -1 2 0 0 0 0] [ 0 0 0 0 0 0 0 2 -1 0 0] [ 0 0 0 0 0 0 0 -1 2 -1 0] [ 0 0 0 0 0 0 0 0 -1 2 -1] [ 0 0 0 0 0 0 0 0 0 -1 2] sage: [L, phi] = IntegralLatticeDirectSum([L1, L2, L3], True) sage: LL3 = L.sublattice(phi[2].image().basis_matrix()) sage: L3.discriminant() == LL3.discriminant() True sage: x = L3([1, 2, 3, 1]) sage: phi[2](x).inner_product(phi[2](x)) == x.inner_product(x) True TESTS:: sage: IntegralLatticeDirectSum([IntegralLattice("D4")]) 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] sage: L1 = IntegralLattice(2 * matrix.identity(2), [[1/2, 1/2]]) sage: L2 = IntegralLattice("A3", [[1, 1, 2]]) sage: [L, phi] = IntegralLatticeDirectSum([L1, L2], True) sage: L Lattice of degree 5 and rank 2 over Integer Ring Basis matrix: [1/2 1/2 0 0 0] [ 0 0 1 1 2] Inner product matrix: [ 2 0 0 0 0] [ 0 2 0 0 0] [ 0 0 2 -1 0] [ 0 0 -1 2 -1] [ 0 0 0 -1 2] """ for L in Lattices: if not isinstance(L, FreeQuadraticModule_integer_symmetric): raise ValueError("Lattices must be a list of lattices") N = len(Lattices) dims = [L_i.dimension() for L_i in Lattices] degrees = [L_i.degree() for L_i in Lattices] degree_tot = sum(degrees) sum_degree = [sum(degrees[:i]) for i in range(N + 1)] inner_product_list = [copy(L_i.inner_product_matrix()) for L_i in Lattices] IM = matrix.block_diagonal(inner_product_list) ambient = FreeQuadraticModule(ZZ, degree_tot, inner_product_matrix=IM) basis = [ matrix.block(1, 3, [ matrix.zero(dims[i], sum_degree[i]), Lattices[i].basis_matrix(), matrix.zero(dims[i], sum_degree[-1] - sum_degree[i + 1]) ]) for i in range(N) ] basis_matrix = matrix.block(N, 1, basis) ipm = ambient.inner_product_matrix() direct_sum = FreeQuadraticModule_integer_symmetric( ambient=ambient, basis=basis_matrix, inner_product_matrix=ipm, already_echelonized=False) if not return_embeddings: return direct_sum sum_dims = [sum(dims[:i]) for i in range(N + 1)] phi = [ Lattices[i].hom(direct_sum.basis()[sum_dims[i]:sum_dims[i + 1]]) for i in range(N) ] return [direct_sum, phi]
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() '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: # positive 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 IntegralLatticeDirectSum(Lattices, return_embeddings=False): r""" Return the direct sum of the lattices contained in the list ``Lattices``. INPUT: - ``Lattices`` -- a list of lattices ``[L_1,...,L_n]`` - ``return_embeddings`` -- (default: ``False``) a boolean OUTPUT: The direct sum of the `L_i` if ``return_embeddings`` is ``False`` or the tuple ``[L, phi]`` where `L` is the direct sum of `L_i` and ``phi`` is the list of embeddings from `L_i` to `L`. EXAMPLES:: sage: from sage.modules.free_quadratic_module_integer_symmetric import IntegralLatticeDirectSum sage: L1 = IntegralLattice("D4") sage: L2 = IntegralLattice("A3", [[1, 1, 2]]) sage: L3 = IntegralLattice("A4", [[0, 1, 1, 2], [1, 2, 3, 1]]) sage: Lattices = [L1, L2, L3] sage: IntegralLatticeDirectSum([L1, L2, L3]) Lattice of degree 11 and rank 7 over Integer Ring Basis matrix: [1 0 0 0 0 0 0 0 0 0 0] [0 1 0 0 0 0 0 0 0 0 0] [0 0 1 0 0 0 0 0 0 0 0] [0 0 0 1 0 0 0 0 0 0 0] [0 0 0 0 1 1 2 0 0 0 0] [0 0 0 0 0 0 0 0 1 1 2] [0 0 0 0 0 0 0 1 2 3 1] Inner product matrix: [ 2 -1 0 0 0 0 0 0 0 0 0] [-1 2 -1 -1 0 0 0 0 0 0 0] [ 0 -1 2 0 0 0 0 0 0 0 0] [ 0 -1 0 2 0 0 0 0 0 0 0] [ 0 0 0 0 2 -1 0 0 0 0 0] [ 0 0 0 0 -1 2 -1 0 0 0 0] [ 0 0 0 0 0 -1 2 0 0 0 0] [ 0 0 0 0 0 0 0 2 -1 0 0] [ 0 0 0 0 0 0 0 -1 2 -1 0] [ 0 0 0 0 0 0 0 0 -1 2 -1] [ 0 0 0 0 0 0 0 0 0 -1 2] sage: [L, phi] = IntegralLatticeDirectSum([L1, L2, L3], True) sage: LL3 = L.sublattice(phi[2].image().basis_matrix()) sage: L3.discriminant() == LL3.discriminant() True sage: x = L3([1, 2, 3, 1]) sage: phi[2](x).inner_product(phi[2](x)) == x.inner_product(x) True TESTS:: sage: IntegralLatticeDirectSum([IntegralLattice("D4")]) 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] sage: L1 = IntegralLattice(2 * matrix.identity(2), [[1/2, 1/2]]) sage: L2 = IntegralLattice("A3", [[1, 1, 2]]) sage: [L, phi] = IntegralLatticeDirectSum([L1, L2], True) sage: L Lattice of degree 5 and rank 2 over Integer Ring Basis matrix: [1/2 1/2 0 0 0] [ 0 0 1 1 2] Inner product matrix: [ 2 0 0 0 0] [ 0 2 0 0 0] [ 0 0 2 -1 0] [ 0 0 -1 2 -1] [ 0 0 0 -1 2] """ for L in Lattices: if not isinstance(L, FreeQuadraticModule_integer_symmetric): raise ValueError("Lattices must be a list of lattices") N = len(Lattices) dims = [L_i.dimension() for L_i in Lattices] degrees = [L_i.degree() for L_i in Lattices] dim_tot = sum(dims) degree_tot = sum(degrees) sum_degree = [sum(degrees[:i]) for i in range(N+1)] inner_product_list = [copy(L_i.inner_product_matrix()) for L_i in Lattices] IM = matrix.block_diagonal(inner_product_list) ambient = FreeQuadraticModule(ZZ, degree_tot, inner_product_matrix=IM) basis = [matrix.block(1, 3, [matrix.zero(dims[i], sum_degree[i]), Lattices[i].basis_matrix(), matrix.zero(dims[i], sum_degree[-1] - sum_degree[i+1]) ]) for i in range(N)] basis_matrix = matrix.block(N, 1, basis) ipm = ambient.inner_product_matrix() direct_sum = FreeQuadraticModule_integer_symmetric(ambient=ambient, basis=basis_matrix, inner_product_matrix=ipm, already_echelonized=False) if not return_embeddings: return direct_sum sum_dims = [sum(dims[:i]) for i in range(N+1)] phi = [Lattices[i].hom(direct_sum.basis()[sum_dims[i]:sum_dims[i+1]]) for i in range(N)] return [direct_sum, phi]
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