示例#1
0
    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()
示例#4
0
文件: morphism.py 项目: sagemath/sage
        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()]
示例#5
0
    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
示例#6
0
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
示例#7
0
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
示例#8
0
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)
示例#9
0
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)
示例#11
0
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)
示例#12
0
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
示例#17
0
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