Ejemplo n.º 1
0
 def godement_sheaf(self):
     """
       Returns the Godement sheaf of ``self``.
     """
     g0_stalks = dict()
     g0_res = dict()
     for p in self._domain_poset.list():
         g0_stalks[p] = sum(self._stalk_dict[x] for x in self._domain_poset.order_filter([p]))
     for relation in self._domain_poset.cover_relations():
         frombase = sorted(self._domain_poset.order_filter([relation[0]]))
         tobase = sorted(self._domain_poset.order_filter([relation[1]]))
         rows = []
         for row in tobase:
             m = self._stalk_dict[row]
             blocks = []
             for x in frombase:
                 if x == row:
                     blocks.append(identity_matrix(m))
                 else:
                     blocks.append(Matrix(m, self._stalk_dict[x]))
             rows.append(blocks)    
         g0_res[tuple(relation)] = block_matrix(rows, subdivide=False)    
     G0 = LocallyFreeSheafFinitePoset(g0_stalks, g0_res, self._base_ring, self._domain_poset)
     hom = Hom(self, G0)
     eps_dict = dict()
     for p in self._domain_poset.list():
         rows = []
         for x in sorted(self._domain_poset.order_filter([p])):
             if x == p:
                 rows.append([identity_matrix(self._base_ring, self._stalk_dict[p])])
             else:
                 rows.append([self.restriction(p, x).matrix()])
         eps_dict[p] = block_matrix(rows, subdivide=False)
     epsilon = hom(eps_dict)
     return epsilon, G0  
Ejemplo n.º 2
0
 def _godement_complex_differential(self, start_base, end_base):
     """
       Construct the differential of the godement cochain complex from 
       the free module with basis ``start_base`` to the free module with basis
       ``end_base``.
     """
     
     rows = []
     for to_chain in end_base:
         blocks = []
         m = self._stalk_dict[to_chain[-1]]
         for from_chain in start_base:
             n = self._stalk_dict[from_chain[-1]]
             if all(x in to_chain for x in from_chain):
                 for point in to_chain:
                     if point not in from_chain:
                         index = to_chain.index(point)
                         sign = 1 if index % 2 == 0 else - 1
                         if index != len(to_chain) - 1:
                             blocks.append(sign*identity_matrix(self._base_ring, m))
                         else:
                             mat = self.restriction(from_chain[-1], point).matrix()
                             blocks.append(sign*mat)
                         break 
             else:
                 blocks.append(Matrix(self._base_ring, m, n))
         rows.append(blocks)
     return block_matrix(rows, subdivide=False)
Ejemplo n.º 3
0
    def isotypic_projection_matrix(self, i):
        r"""
        TESTS::

            sage: from surface_dynamics.all import *
            sage: p = iet.GeneralizedPermutation('a b b','c c a')
            sage: c = p.cover(['(1,2,3)','(1,3,2)','()'])
            sage: c.isotypic_projection_matrix(0)
            [1/3   0   0 1/3   0   0 1/3   0   0]
            [  0 1/3   0   0 1/3   0   0 1/3   0]
            [  0   0 1/3   0   0 1/3   0   0 1/3]
            [1/3   0   0 1/3   0   0 1/3   0   0]
            [  0 1/3   0   0 1/3   0   0 1/3   0]
            [  0   0 1/3   0   0 1/3   0   0 1/3]
            [1/3   0   0 1/3   0   0 1/3   0   0]
            [  0 1/3   0   0 1/3   0   0 1/3   0]
            [  0   0 1/3   0   0 1/3   0   0 1/3]
        """
        from sage.matrix.special import identity_matrix
        from surface_dynamics.misc.group_representation import isotypic_projection_matrix
        m = isotypic_projection_matrix(
               self.automorphism_group(),
               self._degree_cover,
               self._real_characters()[0][i],
               self._real_characters()[1][i],
               self._cc_mats())
        return m.tensor_product(identity_matrix(len(self._base)), subdivide=False)
Ejemplo n.º 4
0
    def isotypic_projection_matrix(self, i):
        r"""
        TESTS::

            sage: from surface_dynamics.all import *
            sage: p = iet.GeneralizedPermutation('a b b','c c a')
            sage: c = p.cover(['(1,2,3)','(1,3,2)','()'])
            sage: c.isotypic_projection_matrix(0)
            [1/3   0   0 1/3   0   0 1/3   0   0]
            [  0 1/3   0   0 1/3   0   0 1/3   0]
            [  0   0 1/3   0   0 1/3   0   0 1/3]
            [1/3   0   0 1/3   0   0 1/3   0   0]
            [  0 1/3   0   0 1/3   0   0 1/3   0]
            [  0   0 1/3   0   0 1/3   0   0 1/3]
            [1/3   0   0 1/3   0   0 1/3   0   0]
            [  0 1/3   0   0 1/3   0   0 1/3   0]
            [  0   0 1/3   0   0 1/3   0   0 1/3]
        """
        from sage.matrix.special import identity_matrix
        from surface_dynamics.misc.group_representation import isotypic_projection_matrix
        m = isotypic_projection_matrix(
               self.automorphism_group(),
               self._degree_cover,
               self._real_characters()[0][i],
               self._real_characters()[1][i],
               self._cc_mats())
        return m.tensor_product(identity_matrix(len(self._base)), subdivide=False)
        def centralizer_basis(self, S):
            """
            Return a basis of the centralizer of ``S`` in ``self``.

            INPUT:

            - ``S`` -- a subalgebra of ``self`` or a list of elements that
              represent generators for a subalgebra

            .. SEEALSO::

                :meth:`centralizer`

            EXAMPLES::

                sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example()
                sage: a,b,c = L.lie_algebra_generators()
                sage: L.centralizer_basis([a + b, 2*a + c])
                [(1, 0, 0), (0, 1, 0), (0, 0, 1)]

                sage: H = lie_algebras.Heisenberg(QQ, 2)
                sage: H.centralizer_basis(H)
                [z]


                sage: D = DescentAlgebra(QQ, 4).D()
                sage: L = LieAlgebra(associative=D)
                sage: L.centralizer_basis(L)
                [D{},
                 D{1} + D{1, 2} + D{2, 3} + D{3},
                 D{1, 2, 3} + D{1, 3} + D{2}]
                sage: D.center_basis()
                (D{},
                 D{1} + D{1, 2} + D{2, 3} + D{3},
                 D{1, 2, 3} + D{1, 3} + D{2})
            """
            #from sage.algebras.lie_algebras.subalgebra import LieSubalgebra
            #if isinstance(S, LieSubalgebra) or S is self:
            if S is self:
                from sage.matrix.special import identity_matrix
                m = identity_matrix(self.base_ring(), self.dimension())
            elif isinstance(S, (list, tuple)):
                m = matrix([v.to_vector() for v in self.echelon_form(S)])
            else:
                m = self.subalgebra(S).basis_matrix()

            S = self.structure_coefficients()
            sc = {}
            for k in S.keys():
                v = S[k].to_vector()
                sc[k] = v
                sc[k[1], k[0]] = -v
            X = self.basis().keys()
            d = len(X)
            c_mat = matrix(self.base_ring(), [[
                sum(m[i, j] * sc[x, xp][k]
                    for j, xp in enumerate(X) if (x, xp) in sc) for x in X
            ] for i in range(d) for k in range(d)])
            C = c_mat.right_kernel().basis_matrix()
            return [self.from_vector(v) for v in C]
Ejemplo n.º 6
0
 def component_matrix(self, point):
     if self._components[point] == 0:
         return matrix(self._base_ring,
                       self.codomain()._stalk_dict[point],
                       self.domain()._stalk_dict[point])
     if self._components[point] == 1:
         return identity_matrix(self._base_ring,
                                self.domain()._stalk_dict[point])
     return matrix(self._components[point])
Ejemplo n.º 7
0
    def __init__(self, R, ngens=None, gram_matrix=None, names=None,
                 index_set=None):
        """
        Initialize self.

        TESTS::

            sage: V = lie_conformal_algebras.FreeFermions(QQ)
            sage: TestSuite(V).run()
        """
        from sage.matrix.matrix_space import MatrixSpace
        from sage.matrix.special import identity_matrix
        if (gram_matrix is not None):
            if ngens is None:
                ngens = gram_matrix.dimensions()[0]
            try:
                assert (gram_matrix in MatrixSpace(R,ngens,ngens))
            except AssertionError:
                raise ValueError("The gram_matrix should be a symmetric " +
                    "{0} x {0} matrix, got {1}".format(ngens,gram_matrix))
            if not gram_matrix.is_symmetric():
                raise ValueError("The gram_matrix should be a symmetric " +
                    "{0} x {0} matrix, got {1}".format(ngens,gram_matrix))
        else:
            if ngens is None:
                ngens = 1;
            gram_matrix = identity_matrix(R,ngens,ngens)

        latex_names=None

        if (names is None) and (index_set is None):
            if ngens==1:
                names = 'psi'
            else:
                names = 'psi_'
            latex_names = tuple(r"\psi_{%d}" % i \
                                      for i in range (ngens)) + ('K',)

        from sage.structure.indexed_generators import \
                                                standardize_names_index_set
        names,index_set = standardize_names_index_set(names=names,
                                                      index_set=index_set,
                                                      ngens=ngens)
        fermiondict = { (i,j): {0: {('K',0): gram_matrix[index_set.rank(i),
                    index_set.rank(j)]}} for i in index_set for j in index_set}

        from sage.rings.rational_field import QQ
        weights = (QQ(1/2),)*ngens
        parity = (1,)*ngens
        GradedLieConformalAlgebra.__init__(self,R,fermiondict,names=names,
                                           latex_names=latex_names,
                                           index_set=index_set,weights=weights,
                                           parity=parity,
                                           central_elements=('K',))

        self._gram_matrix = gram_matrix
Ejemplo n.º 8
0
    def dual(self):
        T = block_matrix(ZZ, [[self._A[:, [self._groundset_to_index[e] for e in self._E]], self._Q]]).transpose()
        I = identity_matrix(ZZ, T.nrows())

        # create temporary names for new groundset elements
        temp_elements = [-i for i in xrange(self._Q.ncols())]
        while len(frozenset(temp_elements).intersection(self.groundset())) > 0:
            temp_elements = [e-1 for e in temp_elements]

        M = ToricArithmeticMatroid(arrangement_matrix=I, torus_matrix=T, ordered_groundset=list(self._E)+temp_elements)
        return M.minor(deletions=temp_elements)
Ejemplo n.º 9
0
 def _cover_relation_to_restriction(self, relation):
     """
       Returns the restriction morphism associated to a cover relation of the domain
       poset of ``self``. 
     """
     hom = Hom(self.stalk(relation[0]), self.stalk(relation[1]))
     if self._res_dict[relation] == 0:
         return hom.zero()
     elif self._res_dict[relation] == 1:
         return hom(identity_matrix(self._stalk_dict[relation[0]]))
     else:
         return hom(self._res_dict[relation])
Ejemplo n.º 10
0
def diameter_support_function(A, b):
    r"""Compute the diameter of a polytope using the H-representation.

    The diameter is computed in the supremum norm.

    INPUT:

    Polyhedron in H-representation, `Ax \leq b`.

    OUTPUT:

    - ``diam`` -- scalar, diameter of the polyhedron in the supremum norm

    - ``u`` -- vector with components `u_j = \max x_j` for `x` in `P`

    - ``l`` -- vector with components `l_j = \min x_j` for `x` in `P`

    EXAMPLES::

        sage: from polyhedron_tools.misc import diameter_support_function, polyhedron_to_Hrep
        sage: [A, b] = polyhedron_to_Hrep(7*polytopes.hypercube(5))
        sage: diameter_support_function(A, b)
        (14.0, (7.0, 7.0, 7.0, 7.0, 7.0), (-7.0, -7.0, -7.0, -7.0, -7.0))
    """
    from polyhedron_tools.misc import support_function

    # ambient dimension
    n = A.ncols()

    # number of constraints
    m = A.rows()

    In = identity_matrix(n)

    # lower : min x_j
    l = []
    for j in range(n):
        l += [-support_function([A, b], -In.column(j))]
    l = vector(l)

    # upper : max x_j
    u = []
    for j in range(n):
        u += [support_function([A, b], In.column(j))]
    u = vector(u)

    diam = max(u - l)

    return diam, u, l
Ejemplo n.º 11
0
    def __init__(self, R, ngens=2, names=None, index_set=None):
        """
        Initialize self.

        TESTS::

            sage: V = lie_conformal_algebras.BosonicGhosts(QQ)
            sage: TestSuite(V).run()
        """
        try:
            assert (ngens > 0 and ngens % 2 == 0)
        except AssertionError:
            raise ValueError("ngens should be an even positive integer, " +
                             "got {}".format(ngens))
        latex_names = None
        if (names is None) and (index_set is None):
            from sage.misc.defaults import variable_names as varnames
            from sage.misc.defaults import latex_variable_names as laxnames
            names = varnames(ngens / 2, 'b') + varnames(ngens / 2, 'c')
            latex_names =  tuple(laxnames(ngens/2,'b') +\
                                          laxnames(ngens/2,'c')) + ('K',)

        from sage.structure.indexed_generators import \
                                                standardize_names_index_set
        names, index_set = standardize_names_index_set(names=names,
                                                       index_set=index_set,
                                                       ngens=ngens)
        from sage.matrix.special import identity_matrix
        A = identity_matrix(R, ngens / 2)
        from sage.matrix.special import block_matrix
        gram_matrix = block_matrix([[R.zero(), A], [A, R.zero()]])
        ghostsdict = {(i, j): {
            0: {
                ('K', 0): gram_matrix[index_set.rank(i),
                                      index_set.rank(j)]
            }
        }
                      for i in index_set for j in index_set}
        weights = (1, ) * (ngens // 2) + (0, ) * (ngens // 2)
        parity = (1, ) * ngens
        super(FermionicGhostsLieConformalAlgebra,
              self).__init__(R,
                             ghostsdict,
                             names=names,
                             latex_names=latex_names,
                             index_set=index_set,
                             weights=weights,
                             parity=parity,
                             central_elements=('K', ))
Ejemplo n.º 12
0
def conjugate_matrix_Z(M):
    r"""
    Return the conjugate matrix Z as defined in [1].

    EXAMPLES::

        sage: from slabbe.matrices import conjugate_matrix_Z
        sage: M = matrix(2, [11,29,14,-1])
        sage: conjugate_matrix_Z(M)       # abs tol 1e-8
        [11.674409930010482  27.69820597163912]
        [14.349386111618157  -1.67440993001048]
        sage: conjugate_matrix_Z(M)^2     # abs tol 1e-8
        [533.7440993001048 276.9820597163913]
        [143.4938611161816 400.2559006998952]

    ::

        sage: M = matrix(2, [-11,14,-26,29])
        sage: conjugate_matrix_Z(M)     # abs tol 1e-8
        [ 7.200000000000004  4.199999999999998]
        [ 7.799999999999995 10.800000000000002]
        sage: conjugate_matrix_Z(M) * 5     # abs tol 1e-8
        [ 36.00000000000002 20.999999999999993]
        [ 38.99999999999998 54.000000000000014]

    ::

        sage: M = matrix(2, [-11,26,-14,29]) / 15
        sage: conjugate_matrix_Z(M)     # abs tol 1e-8
        [ 0.5999999999999999  0.3999999999999999]
        [0.39999999999999986  0.5999999999999999]

    REFERENCES:

    [1] Labbé, Jean-Philippe, et Sébastien Labbé. « A Perron theorem for matrices
    with negative entries and applications to Coxeter groups ». arXiv:1511.04975
    [math], 16 novembre 2015. http://arxiv.org/abs/1511.04975.
    """
    from sage.matrix.special import identity_matrix
    eig, v = perron_right_eigenvector(M)
    v /= sum(v)
    nrows = M.nrows()
    UNtop = matrix([1] * nrows)
    v = matrix.column(v)
    id = identity_matrix(nrows)
    Z = eig * v * UNtop + (id - v * UNtop) * M
    return Z
Ejemplo n.º 13
0
def conjugate_matrix_Z(M):
    r"""
    Return the conjugate matrix Z as defined in [1].

    EXAMPLES::

        sage: from slabbe.matrices import conjugate_matrix_Z
        sage: M = matrix(2, [11,29,14,-1])
        sage: conjugate_matrix_Z(M)       # abs tol 1e-8
        [11.674409930010482  27.69820597163912]
        [14.349386111618157  -1.67440993001048]
        sage: conjugate_matrix_Z(M)^2     # abs tol 1e-8
        [533.7440993001048 276.9820597163913]
        [143.4938611161816 400.2559006998952]

    ::

        sage: M = matrix(2, [-11,14,-26,29])
        sage: conjugate_matrix_Z(M)     # abs tol 1e-8
        [ 7.200000000000004  4.199999999999998]
        [ 7.799999999999995 10.800000000000002]
        sage: conjugate_matrix_Z(M) * 5     # abs tol 1e-8
        [ 36.00000000000002 20.999999999999993]
        [ 38.99999999999998 54.000000000000014]

    ::

        sage: M = matrix(2, [-11,26,-14,29]) / 15
        sage: conjugate_matrix_Z(M)     # abs tol 1e-8
        [ 0.5999999999999999  0.3999999999999999]
        [0.39999999999999986  0.5999999999999999]

    REFERENCES:

    [1] Labbé, Jean-Philippe, et Sébastien Labbé. « A Perron theorem for matrices
    with negative entries and applications to Coxeter groups ». arXiv:1511.04975
    [math], 16 novembre 2015. http://arxiv.org/abs/1511.04975.
    """
    from sage.matrix.special import identity_matrix
    eig, v = perron_right_eigenvector(M)
    v /= sum(v)
    nrows = M.nrows()
    UNtop = matrix([1]*nrows)
    v = matrix.column(v)
    id = identity_matrix(nrows)
    Z = eig*v*UNtop + (id - v*UNtop)*M
    return Z
Ejemplo n.º 14
0
def dualizing_complex(poset, base_ring=ZZ, rank=1):
    dim = poset.height() - 1
    bound_below = -1 * dim
    data = [bound_below]
    end_base = sorted(
        filter(lambda c: len(c) == dim + 1, poset.maximal_chains()))
    for p in range(-1 * dim, 0):
        start_base = end_base
        end_base = sorted(filter(lambda c: len(c) == -1 * p, poset.chains()))
        #print("making differential from degree {}\n start: {}\n end:{}".format(p, start_base, end_base))
        differential = dict()
        for x in poset.list():
            point_start_base = filter(lambda c: poset.is_lequal(x, c[-1]),
                                      start_base)
            point_end_base = filter(lambda c: poset.is_lequal(x, c[-1]),
                                    end_base)
            #print("------for point {}: \n---------start:{}\n---------end:{}".format(x, point_start_base, point_end_base))
            rows = []
            for end_chain in point_end_base:
                blocks = []
                for start_chain in point_start_base:
                    if all(p in start_chain for p in end_chain):
                        for y in start_chain:
                            if y not in end_chain and y != start_chain[-1]:
                                sign = 1 if start_chain.index(
                                    y) % 2 == 0 else -1
                                blocks.append(sign *
                                              identity_matrix(base_ring, rank))
                                break
                        else:
                            blocks.append(matrix(base_ring, rank, rank))
                    else:
                        blocks.append(matrix(base_ring, rank, rank))
                rows.append(blocks)
            differential[x] = block_matrix(rows, subdivide=False)
        data.append(_dualizing_sheaf(poset, p, base_ring, rank))
        data.append(differential)
    data.append(_dualizing_sheaf(poset, 0, base_ring, rank))
    if rank == 1:
        name = "dualizing complex of ({}, {})".format(poset, base_ring)
    else:
        name = "dualizing complex of ({}, rank-{} free module over {})".format(
            poset, rank, base_ring)
    return LocFreeSheafComplex(data, name=name)
Ejemplo n.º 15
0
    def __init__(self, R, ngens=None, gram_matrix=None, names=None,
                 index_set=None):
        """
        Initialize self.

        TESTS::

            sage: V = lie_conformal_algebras.FreeBosons(QQ)
            sage: TestSuite(V).run()
        """
        from sage.matrix.matrix_space import MatrixSpace
        if (gram_matrix is not None):
            if ngens is None:
                ngens = gram_matrix.dimensions()[0]
            try:
                assert (gram_matrix in MatrixSpace(R,ngens,ngens))
            except AssertionError:
                raise ValueError("the gram_matrix should be a symmetric " +
                    "{0} x {0} matrix, got {1}".format(ngens,gram_matrix))
            if not gram_matrix.is_symmetric():
                raise ValueError("the gram_matrix should be a symmetric " +
                    "{0} x {0} matrix, got {1}".format(ngens,gram_matrix))
        else:
            if ngens is None:
                ngens = 1;
            gram_matrix = identity_matrix(R,ngens,ngens)

        latex_names = None
        if (names is None) and (index_set is None):
            names = 'alpha'
            latex_names = tuple(r'\alpha_{%d}' % i \
                                      for i in range (ngens)) + ('K',)
        names,index_set = standardize_names_index_set(names=names,
                                                      index_set=index_set,
                                                      ngens=ngens)
        bosondict = { (i,j): {1: {('K',0): gram_matrix[index_set.rank(i),
                    index_set.rank(j)]}} for i in index_set for j in index_set}

        GradedLieConformalAlgebra.__init__(self,R,bosondict,names=names,
                                           latex_names=latex_names,
                                           index_set=index_set,
                                           central_elements=('K',))

        self._gram_matrix = gram_matrix
Ejemplo n.º 16
0
        def reflection_length(self, in_unitary_group=False):
            r"""
            Return the reflection length of ``self``.

            This is the minimal numbers of reflections needed to
            obtain ``self``.

            INPUT:

            - ``in_unitary_group`` -- (default: ``False``) if ``True``,
              the reflection length is computed in the unitary group
              which is the dimension of the move space of ``self``

            EXAMPLES::

                sage: W = ReflectionGroup((1,1,3))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 1, 2, 2]

                sage: W = ReflectionGroup((2,1,2))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 1, 1, 2, 2, 2]

                sage: W = ReflectionGroup((2,2,2))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 2]

                sage: W = ReflectionGroup((3,1,2))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
            """
            W = self.parent()
            if in_unitary_group or W.is_real():
                from sage.matrix.special import identity_matrix
                I = identity_matrix(self.parent().rank())
                return W.rank() - (self.canonical_matrix() - I).right_nullity()
            else:
                return len(self.reduced_word_in_reflections())
        def reflection_length(self, in_unitary_group=False):
            r"""
            Return the reflection length of ``self``.

            This is the minimal numbers of reflections needed to
            obtain ``self``.

            INPUT:

            - ``in_unitary_group`` -- (default: ``False``) if ``True``,
              the reflection length is computed in the unitary group
              which is the dimension of the move space of ``self``

            EXAMPLES::

                sage: W = ReflectionGroup((1,1,3))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 1, 2, 2]

                sage: W = ReflectionGroup((2,1,2))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 1, 1, 2, 2, 2]

                sage: W = ReflectionGroup((2,2,2))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 2]

                sage: W = ReflectionGroup((3,1,2))                      # optional - gap3
                sage: sorted([t.reflection_length() for t in W])        # optional - gap3
                [0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
            """
            W = self.parent()
            if in_unitary_group or W.is_real():
                from sage.matrix.special import identity_matrix
                I = identity_matrix(self.parent().rank())
                return W.rank() - (self.canonical_matrix() - I).right_nullity()
            else:
                return len(self.reduced_word_in_reflections())
Ejemplo n.º 18
0
def lift_for_SL(A, N=None):
    r"""
    Lift a matrix `A` from `SL_m(\ZZ / N\ZZ)` to `SL_m(\ZZ)`.

    This follows [Shi1971]_, Lemma 1.38, p. 21.

    INPUT:

    - ``A`` -- a square matrix with coefficients in `\ZZ / N\ZZ` (or `\ZZ`)

    - ``N`` -- the modulus (optional) required only if the matrix ``A``
      has coefficients in `\ZZ`

    EXAMPLES::

        sage: from sage.modular.local_comp.liftings import lift_for_SL
        sage: A = matrix(Zmod(11), 4, 4, [6, 0, 0, 9, 1, 6, 9, 4, 4, 4, 8, 0, 4, 0, 0, 8])
        sage: A.det()
        1
        sage: L = lift_for_SL(A)
        sage: L.det()
        1
        sage: (L - A) == 0
        True

        sage: B = matrix(Zmod(19), 4, 4, [1, 6, 10, 4, 4, 14, 15, 4, 13, 0, 1, 15, 15, 15, 17, 10])
        sage: B.det()
        1
        sage: L = lift_for_SL(B)
        sage: L.det()
        1
        sage: (L - B) == 0
        True

    TESTS::

        sage: lift_for_SL(matrix(3,3,[1,2,0,3,4,0,0,0,1]),3)
        [10 14  3]
        [ 9 10  3]
        [ 3  3  1]

        sage: A = matrix(Zmod(7), 2, [1,0,0,1])
        sage: L = lift_for_SL(A)
        sage: L.parent()
        Full MatrixSpace of 2 by 2 dense matrices over Integer Ring

        sage: A = matrix(Zmod(7), 1, [1])
        sage: L = lift_for_SL(A); L
        [1]

        sage: A = matrix(ZZ, 2, [1,0,0,1])
        sage: lift_for_SL(A)
        Traceback (most recent call last):
        ...
        ValueError: you must choose the modulus

        sage: for _ in range(100):
        ....:     d = randint(0, 10)
        ....:     p = choice([2,3,5,7,11])
        ....:     M = random_matrix(Zmod(p), d, algorithm='unimodular')
        ....:     assert lift_for_SL(M).det() == 1
    """
    from sage.matrix.special import (identity_matrix, diagonal_matrix,
                                     block_diagonal_matrix)
    from sage.misc.misc_c import prod

    ring = A.parent().base_ring()
    if N is None:
        if ring is ZZ:
            raise ValueError('you must choose the modulus')
        else:
            N = ring.characteristic()

    m = A.nrows()
    if m <= 1:
        return identity_matrix(ZZ, m)

    AZZ = A.change_ring(ZZ)
    D, U, V = AZZ.smith_form()
    diag = diagonal_matrix([-1] + [1] * (m - 1))
    if U.det() == -1:
        U = diag * U
    if V.det() == -1:
        V = V * diag

    a = [U.row(i) * AZZ * V.column(i) for i in range(m)]
    b = prod(a[1:])

    Winv = identity_matrix(m)
    Winv[1, 0] = 1 - b
    Winv[0, 1] = -1
    Winv[1, 1] = b

    Xinv = identity_matrix(m)
    Xinv[0, 1] = a[1]

    Cp = diagonal_matrix(a[1:])
    Cp[0, 0] *= a[0]
    C = lift_for_SL(Cp, N)

    Cpp = block_diagonal_matrix(identity_matrix(1), C)
    Cpp[1, 0] = 1 - a[0]

    return (~U * Winv * Cpp * Xinv * ~V).change_ring(ZZ)
Ejemplo n.º 19
0
    def __init__(self,
                 R,
                 ngens=None,
                 gram_matrix=None,
                 names=None,
                 index_set=None):
        """
        Initialize self.

        TESTS::

            sage: V = lie_conformal_algebras.Weyl(QQ)
            sage: TestSuite(V).run()
        """
        from sage.matrix.matrix_space import MatrixSpace
        if ngens:
            try:
                from sage.rings.integer_ring import ZZ
                assert ngens in ZZ and ngens % 2 == 0
            except AssertionError:
                raise ValueError("ngens needs to be an even positive " +
                                 "Integer, got {}".format(ngens))
        if (gram_matrix is not None):
            if ngens is None:
                ngens = gram_matrix.dimensions()[0]
            try:
                assert (gram_matrix in MatrixSpace(R, ngens, ngens))
            except AssertionError:
                raise ValueError(
                    "The gram_matrix should be a skew-symmetric " +
                    "{0} x {0} matrix, got {1}".format(ngens, gram_matrix))
            if (not gram_matrix.is_skew_symmetric()) or \
                                                (gram_matrix.is_singular()):
                raise ValueError("The gram_matrix should be a non degenerate " +
                                 "skew-symmetric {0} x {0} matrix, got {1}"\
                                 .format(ngens,gram_matrix))
        elif (gram_matrix is None):
            if ngens is None:
                ngens = 2
            A = identity_matrix(R, ngens // 2)
            from sage.matrix.special import block_matrix
            gram_matrix = block_matrix([[R.zero(), A], [-A, R.zero()]])

        latex_names = None
        if (names is None) and (index_set is None):
            names = 'alpha'
            latex_names = tuple(r'\alpha_{%d}' % i \
                                      for i in range (ngens)) + ('K',)
        names, index_set = standardize_names_index_set(names=names,
                                                       index_set=index_set,
                                                       ngens=ngens)
        weyldict = {(i, j): {
            0: {
                ('K', 0): gram_matrix[index_set.rank(i),
                                      index_set.rank(j)]
            }
        }
                    for i in index_set for j in index_set}

        super(WeylLieConformalAlgebra, self).__init__(R,
                                                      weyldict,
                                                      names=names,
                                                      latex_names=latex_names,
                                                      index_set=index_set,
                                                      central_elements=('K', ))
        self._gram_matrix = gram_matrix
Ejemplo n.º 20
0
def _identity_matrix(point, eps):
    Scalars = ComplexBallField(utilities.prec_from_eps(eps))
    return matrix.identity_matrix(Scalars, point.dop.order())
Ejemplo n.º 21
0
    def test_monodromy(self, nsteps_x, nsteps_y, save=False):
        import os.path, csv
        from csv import DictReader, DictWriter
        import sage.all
        from sage.matrix.special import identity_matrix
        from sage.rings.complex_double import CDF
        from sage.symbolic.constants import e

        x_list = lin_space(self._xmin, self._xmax, nsteps_x)
        y_list = lin_space(self._ymin, self._ymax, nsteps_y)

        tab = [[self.section_f(x, y) for y in y_list] for x in x_list]
        fieldnames = [
            'Exp', 'i', 'j', 'x_plot', 'y_plot', 'dist_id', 'dist_ev'
        ]

        f_name = self.f_name(nsteps_x, nsteps_y, -1, -1)

        if os.path.isfile(f_name):
            with open(f_name, 'r') as csvfile:
                reader = csv.DictReader(csvfile, delimiter=',')
                test = 1
                for row in reader:
                    test = 0
                if test:
                    last_i, last_j = 0, 0
                else:
                    last_i, last_j = int(row['i']), int(row['j'])

        else:
            with open(f_name, 'w') as csvfile:
                writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
                writer.writeheader()
                last_i, last_j = 0, 0

        with open(f_name, 'a') as csvfile:
            from cmath import exp, pi

            from __builtin__ import set
            from sage.misc.functional import numerical_approx

            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            if not ((last_i == nsteps_x - 1) and (last_i == nsteps_y - 1)):
                for i in range(last_i, nsteps_x):
                    for j in range(last_j + 1 if i == last_i else 0, nsteps_y):
                        print i, j
                        M0, M1, MInfty = tab[i][j].monodromy_matrices()
                        n = tab[i][j]._dimension
                        beta = tab[i][j]._beta
                        dist_id = (M0 * M1 * MInfty -
                                   identity_matrix(CDF, n)).norm()
                        ev = set(MInfty.eigenvalues())
                        d = []
                        for b in beta:
                            val = exp(-2 * 1j * pi * b)
                            c_val = min(ev, key=lambda z: (z - val).norm())
                            d.append((c_val - val).norm())
                            ev.remove(c_val)
                        dist_ev = max(d)
                        print max(dist_ev, dist_id)
                        writer.writerow({
                            'Exp': tab[i][j],
                            'i': i,
                            'j': j,
                            'x_plot': tab[i][j].x_plot,
                            'y_plot': tab[i][j].y_plot,
                            'dist_id': dist_id,
                            'dist_ev': dist_ev
                        })

        return True
Ejemplo n.º 22
0
def signed_hermite_normal_form(A):
    """
    Signed Hermite normal form of an integer matrix A, see [PP19, Section 6].
    This is a normal form up to left-multiplication by invertible matrices and change of sign of the columns.
    A matrix in signed Hermite normal form is also in left Hermite normal form.
    """
    r = A.nrows()
    n = A.ncols()
    G_basis = []  # Z_2-basis of G
    m = 0  # rank of A[:,:j]

    for j in range(n):
        A = A.echelon_form()
        q = A[m, j] if m < r else 0  # pivot

        phi = []
        for S in G_basis:
            H, U = (A[:, :j] * S[:j, :j]).echelon_form(transformation=True)
            assert H == A[:, :j]
            phi.append(U)

        G_basis.append(diagonal_matrix([-1 if i == j else 1 for i in range(n)]))
        phi.append(diagonal_matrix([-1 if i < m else 1 for i in range(r)]))
        assert len(G_basis) == len(phi)

        for i in reversed(range(m)):
            # find possible values of A[i,j]
            x = A[i, j]
            if q > 0:
                x %= q

            orbit = [x]
            columns = [A[:, j]]
            construction = [identity_matrix(n)]  # which element of G gives a certain element of the orbit

            new_elements = True
            while new_elements:
                new_elements = False
                for h, U in enumerate(phi):
                    for k, v in enumerate(columns):
                        w = U * v
                        y = w[i, 0]
                        if q > 0:
                            y %= q
                        if y not in orbit:
                            orbit.append(y)
                            columns.append(w)
                            construction.append(G_basis[h] * construction[k])
                            new_elements = True

            assert len(orbit) in [1, 2, 4]

            # find action of G on the orbit
            action = []
            for h, U in enumerate(phi):
                if q > 0:
                    action.append({x: (U * columns[k])[i, 0] % q for k, x in enumerate(orbit)})
                else:
                    action.append({x: (U * columns[k])[i, 0] for k, x in enumerate(orbit)})

            # select the minimal possible value
            u = min(orbit)
            k = orbit.index(u)
            S = construction[k]

            # change A
            A = (A * S).echelon_form()
            assert A[i, j] == u

            # update the stabilizer G
            G_new_basis = []  # basis for the new stabilizer
            new_phi = []  # value of phi on the new basis elements
            complement_basis = {}  # dictionary of the form {x: h}, where G_basis[h] sends u to x
            for h, S in enumerate(G_basis):
                if action[h][u] == u:
                    # this basis element fixes u
                    G_new_basis.append(S)
                    new_phi.append(phi[h])

                elif action[h][u] in complement_basis:
                    # we already encountered a basis element which sends u to action[h][u]
                    G_new_basis.append(S * G_basis[complement_basis[action[h][u]]])
                    new_phi.append(phi[h] * phi[complement_basis[action[h][u]]])

                elif len(complement_basis) == 2:
                    # the product of the two basis elements of the complement sends u to action[h][u]
                    x, y = list(complement_basis)
                    G_new_basis.append(S * G_basis[complement_basis[x]] * G_basis[complement_basis[y]])
                    new_phi.append(phi[h] * phi[complement_basis[x]] * phi[complement_basis[y]])

                else:
                    # add S to the basis of the complement
                    complement_basis[action[h][u]] = h

            assert len(G_new_basis) + len(complement_basis) == len(G_basis)
            G_basis = G_new_basis
            phi = new_phi
            assert len(G_basis) == len(phi)

        if q != 0:
            m += 1

    return A
Ejemplo n.º 23
0
    def test_monodromy(self, nsteps_x, nsteps_y, save=False):
        import os.path, csv
        from csv import DictReader, DictWriter
        import sage.all
        from sage.matrix.special import identity_matrix
        from sage.rings.complex_double import CDF
        from sage.symbolic.constants import e

        x_list = lin_space(self._xmin, self._xmax, nsteps_x)
        y_list = lin_space(self._ymin, self._ymax, nsteps_y)

        tab = [[self.section_f(x,y) for y in y_list] for x in x_list]
        fieldnames = ['Exp', 'i', 'j', 'x_plot', 'y_plot', 'dist_id', 'dist_ev']

        f_name = self.f_name(nsteps_x, nsteps_y, -1, -1)

        if os.path.isfile(f_name) :
            with open(f_name, 'r') as csvfile :
                reader= csv.DictReader(csvfile, delimiter=',')
                test = 1
                for row in reader :
                    test = 0
                if test:
                    last_i, last_j = 0, 0
                else:
                    last_i, last_j = int(row['i']), int(row['j'])

        else :
            with open(f_name, 'w') as csvfile :
                writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
                writer.writeheader()
                last_i, last_j = 0, 0

        with open(f_name, 'a') as csvfile :
            from cmath import exp, pi

            from __builtin__ import set
            from sage.misc.functional import numerical_approx

            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            if not ((last_i == nsteps_x-1) and (last_i == nsteps_y-1)):
                for i in range(last_i,nsteps_x):
                    for j in range(last_j+1 if i == last_i else 0, nsteps_y):
                        print i,j
                        M0, M1, MInfty = tab[i][j].monodromy_matrices()
                        n = tab[i][j]._dimension
                        beta = tab[i][j]._beta
                        dist_id = (M0*M1*MInfty - identity_matrix(CDF,n)).norm()
                        ev = set(MInfty.eigenvalues())
                        d = []
                        for b in beta:
                            val = exp(-2*1j*pi*b)
                            c_val = min(ev, key=lambda z: (z-val).norm())
                            d.append((c_val-val).norm())
                            ev.remove(c_val)
                        dist_ev = max(d)
                        print max(dist_ev, dist_id)
                        writer.writerow({'Exp' : tab[i][j], 'i' : i, 'j' : j,
                                         'x_plot' : tab[i][j].x_plot, 'y_plot' : tab[i][j].y_plot,
                                         'dist_id' : dist_id, 'dist_ev' : dist_ev})

        return True
Ejemplo n.º 24
0
    def amortized_padic_H_values(self,
                                 t,
                                 testp=None,
                                 verbose=False,
                                 use_c=False):
        """
        INPUT:

        - `t` -- a rational number

        OUTPUT:

        - a dictionary with inputs `p` and outputs the corresponding p-adic H value at `t`.

        TESTS::

            sage: for cyca, cycb, t in [
            ...:    ([6], [1, 1], 331),
            ...:    ([4, 2, 2], [3, 1, 1],  3678),
            ...:    ([22], [1, 1, 20], 1337/507734),
            ...:    ([5],[1,1,1,1], 2313),
            ...:    ([12],[2,2,1,1], 313)
            ...:]:
            ...:    H = AmortizingHypergeometricData(1000, cyclotomic=(cyca, cycb))
            ...:    for p, v in H.amortized_padic_H_values(t).items():
            ...:        if v != H.naive_padic_H_value(t=t, p=p, verbose=False):
            ...:            print(p, cyca, cycb, t)
        """
        ## TODO: skip over intermediate ranges with positive p-shift
        #pshift = self.pshift
        #for last_zero, s in reversed(list(enumerate(pshift))):
        #    if s == 0:
        #        break
        #breaks = self.breaks[:last_zero+2] # also include the endpoint of the last interval
        #pshift = pshift[:last_zero+1]
        #starts = breaks[:-1]
        #ends = breaks[1:]
        # TODO: fix global sign from (-p)^eta
        #        forests = {}
        tmp = identity_matrix(2)
        vectors = {p: tmp for p in self._prime_range(t)[1][0]}

        #        def update(p, A):
        #            # If the interval was empty we get A=1
        #            if not (isinstance(A, Integer) and A == 1):
        #                vectors[p][0] *= A
        #                vectors[p][1] *= A[0,0]
        #                vectors[p] %= p
        def multiplier(x):
            return -x if x else -1

        def functional_eqn(a, g, c, d):
            return (multiplier(a - g - c / d) if a >= g else
                    1) * (multiplier(a - g - c / d + 1) if a <= g else 1)


#        feq_seed = prod(a if a else -1 for a in self._alpha) / prod(b if b else -1 for b in self._beta)
#        for p in self._prime_range(t)[1][0]:
#            R = vectors[p].base_ring()
#            update(p, self.fix_break(t, ZZ(0), p, R, feq_seed))
#            if p == testp:
#                P_start = R(t)**1 * H.pochhammer_quotient(p, 1)
#                assert vectors[p][1] == P_start, "brk = 0, %s != %s" % (vectors[p][1], P_start)

        for start, end in zip(self.starts, self.ends):
            d = start.denominator()
            for pclass in range(d):
                if d.gcd(pclass) != 1:
                    continue
                c = (d * start * (pclass - 1)) % d
                feq_seed = t * prod(
                    functional_eqn(a, start, c, d)
                    for a in self._alpha) / prod(
                        functional_eqn(b, start, c, d) for b in self._beta)
                feq_seed_num = feq_seed.numer()
                feq_seed_den = feq_seed.denom()
                if pclass == 1:
                    pmult = self.break_mults_p1[start]
                else:
                    pmult = self.break_mults[start]
                fixbreak = matrix(
                    ZZ, 2, 2,
                    [feq_seed_den, 0, feq_seed_den * pmult, feq_seed_num])
                # this updates vectors
                self.amortized_padic_H_values_interval(
                    vectors,
                    t,
                    start,
                    end,
                    pclass,
                    fixbreak,
                    testp=testp,
                    use_c=use_c,
                )
        return {p: (vectors[p][1, 0] / vectors[p][0, 0]) % p for p in vectors}
Ejemplo n.º 25
0
    def _matrix_smith_form(self, M, transformation, integral, exact):
        r"""
        Return the Smith normal form of the matrix `M`.

        This method gets called by
        :meth:`sage.matrix.matrix2.Matrix.smith_form` to compute the Smith
        normal form over local rings and fields.

        The entries of the Smith normal form are normalized such that non-zero
        entries of the diagonal are powers of the distinguished uniformizer.

        INPUT:

        - ``M`` -- a matrix over this ring

        - ``transformation`` -- a boolean; whether the transformation matrices
        are returned

        - ``integral`` -- a subring of the base ring or ``True``; the entries
        of the transformation matrices are in this ring.  If ``True``, the
        entries are in the ring of integers of the base ring.

        - ``exact`` -- boolean.  If ``True``, the diagonal smith form will
          be exact, or raise a ``PrecisionError`` if this is not posssible.
          If ``False``, the diagonal entries will be inexact, but the
          transformation matrices will be exact.

        EXAMPLES::

            sage: A = Zp(5, prec=10, print_mode="digits")
            sage: M = matrix(A, 2, 2, [2, 7, 1, 6])

            sage: S, L, R = M.smith_form()  # indirect doctest
            sage: S
            [ ...1     0]
            [    0 ...10]
            sage: L
            [...222222223          ...]
            [...444444444         ...2]
            sage: R
            [...0000000001 ...2222222214]
            [            0 ...0000000001]

        If not needed, it is possible to avoid the computation of
        the transformations matrices `L` and `R`::

            sage: M.smith_form(transformation=False)  # indirect doctest
            [ ...1     0]
            [    0 ...10]

        This method works for rectangular matrices as well::

            sage: M = matrix(A, 3, 2, [2, 7, 1, 6, 3, 8])
            sage: S, L, R = M.smith_form()  # indirect doctest
            sage: S
            [ ...1     0]
            [    0 ...10]
            [    0     0]
            sage: L
            [...222222223          ...          ...]
            [...444444444         ...2          ...]
            [...444444443         ...1         ...1]
            sage: R
            [...0000000001 ...2222222214]
            [            0 ...0000000001]

        If some of the elementary divisors have valuation larger than the
        minimum precision of any entry in the matrix, then they are
        reported as an inexact zero::

            sage: A = ZpCA(5, prec=10)
            sage: M = matrix(A, 2, 2, [5, 5, 5, 5])
            sage: M.smith_form(transformation=False, exact=False)  # indirect doctest
            [5 + O(5^10)     O(5^10)]
            [    O(5^10)     O(5^10)]

        However, an error is raised if the precision on the entries is
        not enough to determine which column to use as a pivot at some point::

            sage: M = matrix(A, 2, 2, [A(0,5), A(5^6,10), A(0,8), A(5^7,10)]); M
            [       O(5^5) 5^6 + O(5^10)]
            [       O(5^8) 5^7 + O(5^10)]
            sage: M.smith_form(transformation=False, exact=False)  # indirect doctest
            Traceback (most recent call last):
            ...
            PrecisionError: not enough precision to compute Smith normal form

        TESTS::

            sage: A = ZpCR(5, prec=10)
            sage: M = zero_matrix(A, 2)
            sage: M.smith_form(transformation=False)  # indirect doctest
            [0 0]
            [0 0]

            sage: M = matrix(2, 2, [ A(0,10), 0, 0, 0] )
            sage: M.smith_form(transformation=False)  # indirect doctest
            Traceback (most recent call last):
            ...
            PrecisionError: some elementary divisors indistinguishable from zero (try exact=False)
            sage: M.smith_form(transformation=False, exact=False)  # indirect doctest
            [O(5^10) O(5^10)]
            [O(5^10) O(5^10)]
        """
        from sage.rings.all import infinity
        from sage.matrix.constructor import matrix
        from .precision_error import PrecisionError
        from copy import copy
        n = M.nrows()
        m = M.ncols()
        if m > n:
            ## It's easier below if we can always deal with precision on left.
            if transformation:
                d, u, v = self._matrix_smith_form(M.transpose(), True,
                                                  integral, exact)
                return d.transpose(), v.transpose(), u.transpose()
            else:
                return self._matrix_smith_form(M.transpose(), False, integral,
                                               exact).transpose()
        smith = M.parent()(0)
        S = copy(M)
        Z = self.integer_ring()
        if integral is None or integral is self or integral is (
                not self.is_field()):
            integral = not self.is_field()
            R = self
        elif integral is True or integral is Z:
            # This is a field, but we want the integral smith form
            # The diagonal matrix may not be integral, but the transformations should be
            R = Z
            integral = True
        elif integral is False or integral is self.fraction_field():
            # This is a ring, but we want the field smith form
            # The diagonal matrix should be over this ring, but the transformations should not
            R = self.fraction_field()
            integral = False
        else:
            raise NotImplementedError("Smith normal form over this subring")
        ## the difference between ball_prec and inexact_ring is just for lattice precision.
        ball_prec = R._prec_type() in ['capped-rel', 'capped-abs']
        inexact_ring = R._prec_type() not in ['fixed-mod', 'floating-point']
        precM = min(x.precision_absolute() for x in M.list())

        if transformation:
            from sage.matrix.special import identity_matrix
            left = identity_matrix(R, n)
            right = identity_matrix(R, m)

        if ball_prec and precM is infinity:  # capped-rel and M = 0 exactly
            return (smith, left, right) if transformation else smith

        val = -infinity
        for piv in range(m):  # m <= n
            curval = infinity
            pivi = pivj = piv
            # allzero tracks whether every possible pivot is zero.
            # if so, we can stop.  allzero is also used in detecting some
            # precision problems: if we can't determine what pivot has
            # the smallest valuation, or if exact=True and some elementary
            # divisor is zero modulo the working precision
            allzero = True
            # allexact is tracked because there is one case where we can correctly
            # deduce the exact smith form even with some elementary divisors zero:
            # if the bottom right block consists entirely of exact zeros.
            allexact = True
            for i in range(piv, n):
                for j in range(piv, m):
                    Sij = S[i, j]
                    v = Sij.valuation()
                    allzero = allzero and Sij.is_zero()
                    if exact:  # we only care in this case
                        allexact = allexact and Sij.precision_absolute(
                        ) is infinity
                    if v < curval:
                        pivi = i
                        pivj = j
                        curval = v
                        if v == val: break
                else: continue
                break
            val = curval

            if inexact_ring and not allzero and val >= precM:
                if ball_prec:
                    raise PrecisionError(
                        "not enough precision to compute Smith normal form")
                precM = min([
                    S[i, j].precision_absolute() for i in range(piv, n)
                    for j in range(piv, m)
                ])
                if val >= precM:
                    raise PrecisionError(
                        "not enough precision to compute Smith normal form")

            if allzero:
                if exact:
                    if allexact:
                        # We need to finish checking allexact since we broke out of the loop early
                        for i in range(i, n):
                            for j in range(piv, m):
                                allexact = allexact and S[
                                    i, j].precision_absolute() is infinity
                                if not allexact: break
                            else: continue
                            break
                    if not allexact:
                        raise PrecisionError(
                            "some elementary divisors indistinguishable from zero (try exact=False)"
                        )
                break

            # We swap the lowest valuation pivot into position
            S.swap_rows(pivi, piv)
            S.swap_columns(pivj, piv)
            if transformation:
                left.swap_rows(pivi, piv)
                right.swap_columns(pivj, piv)

            # ... and clear out this row and column.  Note that we
            # will deal with precision later, thus the call to lift_to_precision
            smith[piv, piv] = self(1) << val
            inv = (S[piv, piv] >> val).inverse_of_unit()
            if ball_prec:
                inv = inv.lift_to_precision()
            for i in range(piv + 1, n):
                scalar = -inv * Z(S[i, piv] >> val)
                if ball_prec:
                    scalar = scalar.lift_to_precision()
                S.add_multiple_of_row(i, piv, scalar, piv + 1)
                if transformation:
                    left.add_multiple_of_row(i, piv, scalar)
            if transformation:
                left.rescale_row(piv, inv)
                for j in range(piv + 1, m):
                    scalar = -inv * Z(S[piv, j] >> val)
                    if ball_prec:
                        scalar = scalar.lift_to_precision()
                    right.add_multiple_of_column(j, piv, scalar)
        else:
            # We use piv as an upper bound on a range below, and need to set it correctly
            # in the case that we didn't break out of the loop
            piv = m
        # We update the precision on left
        # The bigoh measures the effect of multiplying by row operations
        # on the left in order to clear out the digits in the smith form
        # with valuation at least precM
        if ball_prec and exact and transformation:
            for j in range(n):
                delta = min(left[i, j].valuation() - smith[i, i].valuation()
                            for i in range(piv))
                if delta is not infinity:
                    for i in range(n):
                        left[i, j] = left[i, j].add_bigoh(precM + delta)
        ## Otherwise, we update the precision on smith
        if ball_prec and not exact:
            smith = smith.apply_map(lambda x: x.add_bigoh(precM))
        ## We now have to adjust the elementary divisors (and precision) in the non-integral case
        if not integral:
            for i in range(piv):
                v = smith[i, i].valuation()
                if transformation:
                    for j in range(n):
                        left[i, j] = left[i, j] >> v
                if exact:
                    smith[i, i] = self(1)
                else:
                    for j in range(n):
                        smith[i, j] = smith[i, j] >> v
        if transformation:
            return smith, left, right
        else:
            return smith
Ejemplo n.º 26
0
def _monodromy_matrices(dop, base, eps=1e-16, sing=None):
    r"""
    EXAMPLES::

        sage: from ore_algebra import *
        sage: from ore_algebra.analytic.monodromy import _monodromy_matrices
        sage: Dops, x, Dx = DifferentialOperators()
        sage: rat = 1/(x^2-1)
        sage: dop = (rat*Dx - rat.derivative()).lclm(Dx*x*Dx)
        sage: [rec.point for rec in _monodromy_matrices(dop, 0) if not rec.is_scalar]
        [0]
    """
    dop = DifferentialOperator(dop)
    base = QQbar.coerce(base)
    eps = RBF(eps)
    if sing is None:
        sing = dop._singularities(QQbar)
    else:
        sing = [QQbar.coerce(s) for s in sing]

    todo = {
        x: PointWithMonodromyData(x, dop, want_self=True, want_conj=False)
        for x in sing
    }
    base = todo.setdefault(base, PointWithMonodromyData(base, dop))
    if not base.is_regular():
        raise ValueError("irregular singular base point")
    # If the coefficients are rational, reduce to handling singularities in the
    # same half-plane as the base point.
    need_conjugates = _try_merge_conjugate_singularities(dop, sing, base, todo)

    Scalars = ComplexBallField(utilities.prec_from_eps(eps))
    id_mat = matrix.identity_matrix(Scalars, dop.order())

    def matprod(elts):
        return prod(reversed(elts), id_mat)

    for key, point in list(todo.items()):
        # We could call _local_monodromy_loop() if point is irregular, but
        # delaying it may allow us to start returning results earlier.
        if point.is_regular():
            mon, scalar = _formal_monodromy_naive(dop.shift(point), Scalars)
            if scalar:
                # No need to compute the connection matrices then!
                # XXX When we do need them, though, it would be better to get
                # the formal monodromy as a byproduct of their computation.
                if point.want_self:
                    yield LocalMonodromyData(QQbar(point), mon, True)
                if point.want_conj:
                    conj = point.conjugate()
                    logger.info(
                        "Computing local monodromy around %s by "
                        "complex conjugation", conj)
                    conj_mat = ~mon.conjugate()
                    yield LocalMonodromyData(QQbar(conj), conj_mat, True)
                if point is not base:
                    del todo[key]
                    continue
            point.local_monodromy = [mon]
            point.polygon = [point]

    if need_conjugates:
        base_conj_mat = dop.numerical_transition_matrix(
            [base, base.conjugate()], eps, assume_analytic=True)

        def conjugate_monodromy(mat):
            return ~base_conj_mat * ~mat.conjugate() * base_conj_mat

    tree = _spanning_tree(base, todo.values())

    def dfs(x, path, path_mat):

        logger.info("Computing local monodromy around %s via %s", x, path)

        local_mat = matprod(x.local_monodromy)
        based_mat = (~path_mat) * local_mat * path_mat

        if x.want_self:
            yield LocalMonodromyData(QQbar(x), based_mat, False)
        if x.want_conj:
            conj = x.conjugate()
            logger.info(
                "Computing local monodromy around %s by complex "
                "conjugation", conj)
            conj_mat = conjugate_monodromy(based_mat)
            yield LocalMonodromyData(QQbar(x), conj_mat, False)

        x.done = True

        for y in tree.neighbors(x):
            if y.done:
                continue
            if y.local_monodromy is None:
                y.polygon, y.local_monodromy = _local_monodromy_loop(
                    dop, y, eps)
            new_path_mat = _extend_path_mat(dop, path_mat, x, y, eps, matprod)
            yield from dfs(y, path + [y], new_path_mat)

    yield from dfs(base, [base], id_mat)
Ejemplo n.º 27
0
def _monodromy_matrices(dop, base, eps=1e-16, sing=None):
    r"""
    EXAMPLES::

        sage: from ore_algebra import *
        sage: from ore_algebra.analytic.monodromy import _monodromy_matrices
        sage: Dops, x, Dx = DifferentialOperators()
        sage: rat = 1/(x^2-1)
        sage: dop = (rat*Dx - rat.derivative()).lclm(Dx*x*Dx)
        sage: [rec.point for rec in _monodromy_matrices(dop, 0) if not rec.is_scalar]
        [0]

    TESTS::

        sage: from ore_algebra.examples import fcc
        sage: mon = list(_monodromy_matrices(fcc.dop5, -1, 2**(-2**7))) # long time (2.3 s)
        sage: [rec.monodromy[0][0] for rec in mon if rec.point == -5/3] # long time
        [[1.01088578589319884254557667137848...]]

    Thanks to Alexandre Goyer for this example::

        sage: L1 = ((x^5 - x^4 + x^3)*Dx^3 + (27/8*x^4 - 25/9*x^3 + 8*x^2)*Dx^2
        ....:      + (37/24*x^3 - 25/9*x^2 + 14*x)*Dx - 2*x^2 - 3/4*x + 4)
        sage: L2 = ((x^5 - 9/4*x^4 + x^3)*Dx^3 + (11/6*x^4 - 31/4*x^3 + 7*x^2)*Dx^2
        ....:      + (7/30*x^3 - 101/20*x^2 + 10*x)*Dx + 4/5*x^2 + 5/6*x + 2)
        sage: L = L1*L2
        sage: L = L.parent()(L.annihilator_of_composition(x+1))
        sage: mon = list(_monodromy_matrices(L, 0, eps=1e-30)) # long time (1.3-1.7 s)
        sage: mon[-1][0], mon[-1][1][0][0] # long time
        (0.6403882032022075?,
        [1.15462187280628880820271...] + [-0.018967673022432256251718...]*I)
    """
    dop = DifferentialOperator(dop)
    base = QQbar.coerce(base)
    eps = RBF(eps)
    if sing is None:
        sing = dop._singularities(QQbar)
    else:
        sing = [QQbar.coerce(s) for s in sing]

    todo = {x: TodoItem(x, dop, want_self=True, want_conj=False) for x in sing}
    base = todo.setdefault(base, TodoItem(base, dop))
    if not base.point().is_regular():
        raise ValueError("irregular singular base point")
    # If the coefficients are rational, reduce to handling singularities in the
    # same half-plane as the base point, and share some computations between
    # Galois conjugates.
    need_conjugates = False
    crit_cache = None
    if all(c in QQ for pol in dop for c in pol):
        need_conjugates = _merge_conjugate_singularities(dop, sing, base, todo)
        # TODO: do something like that even over number fields?
        # XXX this is actually a bit costly: do it only after checking that the
        # monodromy is not scalar?
        # XXX keep the cache from one run to the next when increasing prec?
        crit_cache = {}

    Scalars = ComplexBallField(utilities.prec_from_eps(eps))
    id_mat = matrix.identity_matrix(Scalars, dop.order())

    def matprod(elts):
        return prod(reversed(elts), id_mat)

    for key, todoitem in list(todo.items()):
        point = todoitem.point()
        # We could call _local_monodromy_loop() if point is irregular, but
        # delaying it may allow us to start returning results earlier.
        if point.is_regular():
            if crit_cache is None or point.algdeg() == 1:
                crit = _critical_monomials(dop.shift(point))
                emb = point.value.parent().hom(Scalars)
            else:
                mpol = point.value.minpoly()
                try:
                    NF, crit = crit_cache[mpol]
                except KeyError:
                    NF = point.value.parent()
                    crit = _critical_monomials(dop.shift(point))
                    # Only store the critical monomials for reusing when all
                    # local exponents are rational. We need to restrict to this
                    # case because we do not have the technology in place to
                    # follow algebraic exponents along the embedding of NF in ℂ.
                    # (They are represented as elements of "new" number fields
                    # given by as_embedded_number_field_element(), even when
                    # they actually lie in NF itself as opposed to a further
                    # algebraic extension. XXX: Ideally, LocalBasisMapper should
                    # give us access to the tower of extensions in which the
                    # exponents "naturally" live.)
                    if all(sol.leftmost.parent() is QQ for sol in crit):
                        crit_cache[mpol] = NF, crit
                emb = NF.hom([Scalars(point.value.parent().gen())],
                             check=False)
            mon, scalar = _formal_monodromy_from_critical_monomials(crit, emb)
            if scalar:
                # No need to compute the connection matrices then!
                # XXX When we do need them, though, it would be better to get
                # the formal monodromy as a byproduct of their computation.
                if todoitem.want_self:
                    yield LocalMonodromyData(key, mon, True)
                if todoitem.want_conj:
                    conj = key.conjugate()
                    logger.info(
                        "Computing local monodromy around %s by "
                        "complex conjugation", conj)
                    conj_mat = ~mon.conjugate()
                    yield LocalMonodromyData(conj, conj_mat, True)
                if todoitem is not base:
                    del todo[key]
                    continue
            todoitem.local_monodromy = [mon]
            todoitem.polygon = [point]

    if need_conjugates:
        base_conj_mat = dop.numerical_transition_matrix(
            [base.point(), base.point().conjugate()],
            eps,
            assume_analytic=True)

        def conjugate_monodromy(mat):
            return ~base_conj_mat * ~mat.conjugate() * base_conj_mat

    tree = _spanning_tree(base, todo.values())

    def dfs(x, path, path_mat):

        logger.info("Computing local monodromy around %s via %s", x, path)

        local_mat = matprod(x.local_monodromy)
        based_mat = (~path_mat) * local_mat * path_mat

        if x.want_self:
            yield LocalMonodromyData(x.alg, based_mat, False)
        if x.want_conj:
            conj = x.alg.conjugate()
            logger.info(
                "Computing local monodromy around %s by complex "
                "conjugation", conj)
            conj_mat = conjugate_monodromy(based_mat)
            yield LocalMonodromyData(conj, conj_mat, False)

        x.done = True

        for y in tree.neighbors(x):
            if y.done:
                continue
            if y.local_monodromy is None:
                y.polygon, y.local_monodromy = _local_monodromy_loop(
                    dop, y.point(), eps)
            new_path_mat = _extend_path_mat(dop, path_mat, x, y, eps, matprod)
            yield from dfs(y, path + [y], new_path_mat)

    yield from dfs(base, [base], id_mat)
Ejemplo n.º 28
0
def _monodromy_matrices(dop, base, eps=1e-16, sing=None):
    r"""
    EXAMPLES::

        sage: from ore_algebra import *
        sage: from ore_algebra.analytic.monodromy import _monodromy_matrices
        sage: Dops, x, Dx = DifferentialOperators()
        sage: rat = 1/(x^2-1)
        sage: dop = (rat*Dx - rat.derivative()).lclm(Dx*x*Dx)
        sage: [rec.point for rec in _monodromy_matrices(dop, 0) if not rec.is_scalar]
        [0]

    TESTS::

        sage: from ore_algebra.examples import fcc
        sage: mon = list(_monodromy_matrices(fcc.dop5, -1, 2**(-2**7))) # long time (2.3 s)
        sage: [rec.monodromy[0][0] for rec in mon if rec.point == -5/3] # long time
        [[1.01088578589319884254557667137848...]]
    """
    dop = DifferentialOperator(dop)
    base = QQbar.coerce(base)
    eps = RBF(eps)
    if sing is None:
        sing = dop._singularities(QQbar)
    else:
        sing = [QQbar.coerce(s) for s in sing]

    todo = {x: TodoItem(x, dop, want_self=True, want_conj=False) for x in sing}
    base = todo.setdefault(base, TodoItem(base, dop))
    if not base.point().is_regular():
        raise ValueError("irregular singular base point")
    # If the coefficients are rational, reduce to handling singularities in the
    # same half-plane as the base point, and share some computations between
    # Galois conjugates.
    need_conjugates = False
    crit_cache = None
    if all(c in QQ for pol in dop for c in pol):
        need_conjugates = _merge_conjugate_singularities(dop, sing, base, todo)
        # TODO: do something like that even over number fields?
        # XXX this is actually a bit costly: do it only after checking that the
        # monodromy is not scalar?
        # XXX keep the cache from one run to the next when increasing prec?
        crit_cache = {}

    Scalars = ComplexBallField(utilities.prec_from_eps(eps))
    id_mat = matrix.identity_matrix(Scalars, dop.order())

    def matprod(elts):
        return prod(reversed(elts), id_mat)

    for key, todoitem in list(todo.items()):
        point = todoitem.point()
        # We could call _local_monodromy_loop() if point is irregular, but
        # delaying it may allow us to start returning results earlier.
        if point.is_regular():
            if crit_cache is None or point.algdeg() == 1:
                crit = _critical_monomials(dop.shift(point))
                emb = point.value.parent().hom(Scalars)
            else:
                mpol = point.value.minpoly()
                try:
                    NF, crit = crit_cache[mpol]
                except KeyError:
                    NF = point.value.parent()
                    crit = _critical_monomials(dop.shift(point))
                    crit_cache[mpol] = NF, crit
                emb = NF.hom([Scalars(point.value.parent().gen())],
                             check=False)
            mon, scalar = _formal_monodromy_from_critical_monomials(crit, emb)
            if scalar:
                # No need to compute the connection matrices then!
                # XXX When we do need them, though, it would be better to get
                # the formal monodromy as a byproduct of their computation.
                if todoitem.want_self:
                    yield LocalMonodromyData(key, mon, True)
                if todoitem.want_conj:
                    conj = key.conjugate()
                    logger.info(
                        "Computing local monodromy around %s by "
                        "complex conjugation", conj)
                    conj_mat = ~mon.conjugate()
                    yield LocalMonodromyData(conj, conj_mat, True)
                if todoitem is not base:
                    del todo[key]
                    continue
            todoitem.local_monodromy = [mon]
            todoitem.polygon = [point]

    if need_conjugates:
        base_conj_mat = dop.numerical_transition_matrix(
            [base.point(), base.point().conjugate()],
            eps,
            assume_analytic=True)

        def conjugate_monodromy(mat):
            return ~base_conj_mat * ~mat.conjugate() * base_conj_mat

    tree = _spanning_tree(base, todo.values())

    def dfs(x, path, path_mat):

        logger.info("Computing local monodromy around %s via %s", x, path)

        local_mat = matprod(x.local_monodromy)
        based_mat = (~path_mat) * local_mat * path_mat

        if x.want_self:
            yield LocalMonodromyData(x.alg, based_mat, False)
        if x.want_conj:
            conj = x.alg.conjugate()
            logger.info(
                "Computing local monodromy around %s by complex "
                "conjugation", conj)
            conj_mat = conjugate_monodromy(based_mat)
            yield LocalMonodromyData(conj, conj_mat, False)

        x.done = True

        for y in tree.neighbors(x):
            if y.done:
                continue
            if y.local_monodromy is None:
                y.polygon, y.local_monodromy = _local_monodromy_loop(
                    dop, y.point(), eps)
            new_path_mat = _extend_path_mat(dop, path_mat, x, y, eps, matprod)
            yield from dfs(y, path + [y], new_path_mat)

    yield from dfs(base, [base], id_mat)
        def centralizer_basis(self, S):
            """
            Return a basis of the centralizer of ``S`` in ``self``.

            INPUT:

            - ``S`` -- a subalgebra of ``self`` or a list of elements that
              represent generators for a subalgebra

            .. SEEALSO::

                :meth:`centralizer`

            EXAMPLES::

                sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example()
                sage: a,b,c = L.lie_algebra_generators()
                sage: L.centralizer_basis([a + b, 2*a + c])
                [(1, 0, 0), (0, 1, 0), (0, 0, 1)]

                sage: H = lie_algebras.Heisenberg(QQ, 2)
                sage: H.centralizer_basis(H)
                [z]


                sage: D = DescentAlgebra(QQ, 4).D()
                sage: L = LieAlgebra(associative=D)
                sage: L.centralizer_basis(L)
                [D{},
                 D{1} + D{1, 2} + D{2, 3} + D{3},
                 D{1, 2, 3} + D{1, 3} + D{2}]
                sage: D.center_basis()
                (D{},
                 D{1} + D{1, 2} + D{2, 3} + D{3},
                 D{1, 2, 3} + D{1, 3} + D{2})
            """
            #from sage.algebras.lie_algebras.subalgebra import LieSubalgebra
            #if isinstance(S, LieSubalgebra) or S is self:
            if S is self:
                from sage.matrix.special import identity_matrix
                m = identity_matrix(self.base_ring(), self.dimension())
            elif isinstance(S, (list, tuple)):
                m = matrix([v.to_vector() for v in self.echelon_form(S)])
            else:
                m = self.subalgebra(S).basis_matrix()

            S = self.structure_coefficients()
            sc = {}
            for k in S.keys():
                v = S[k].to_vector()
                sc[k] = v
                sc[k[1],k[0]] = -v
            X = self.basis().keys()
            d = len(X)
            c_mat = matrix(self.base_ring(),
                           [[sum(m[i,j] * sc[x,xp][k] for j,xp in enumerate(X)
                                 if (x, xp) in sc)
                             for x in X]
                            for i in range(d) for k in range(d)])
            C = c_mat.right_kernel().basis_matrix()
            return [self.from_vector(v) for v in C]
Ejemplo n.º 30
0
    def _matrix_smith_form(self, M, transformation, integral, exact):
        r"""
        Return the Smith normal form of the matrix `M`.

        This method gets called by
        :meth:`sage.matrix.matrix2.Matrix.smith_form` to compute the Smith
        normal form over local rings and fields.

        The entries of the Smith normal form are normalized such that non-zero
        entries of the diagonal are powers of the distinguished uniformizer.

        INPUT:

        - ``M`` -- a matrix over this ring

        - ``transformation`` -- a boolean; whether the transformation matrices
        are returned

        - ``integral`` -- a subring of the base ring or ``True``; the entries
        of the transformation matrices are in this ring.  If ``True``, the
        entries are in the ring of integers of the base ring.

        - ``exact`` -- boolean.  If ``True``, the diagonal smith form will
          be exact, or raise a ``PrecisionError`` if this is not posssible.
          If ``False``, the diagonal entries will be inexact, but the
          transformation matrices will be exact.

        EXAMPLES::

            sage: A = Zp(5, prec=10, print_mode="digits")
            sage: M = matrix(A, 2, 2, [2, 7, 1, 6])

            sage: S, L, R = M.smith_form()  # indirect doctest
            sage: S
            [ ...1     0]
            [    0 ...10]
            sage: L
            [...222222223          ...]
            [...444444444         ...2]
            sage: R
            [...0000000001 ...2222222214]
            [            0 ...0000000001]

        If not needed, it is possible to avoid the computation of
        the transformations matrices `L` and `R`::

            sage: M.smith_form(transformation=False)  # indirect doctest
            [ ...1     0]
            [    0 ...10]

        This method works for rectangular matrices as well::

            sage: M = matrix(A, 3, 2, [2, 7, 1, 6, 3, 8])
            sage: S, L, R = M.smith_form()  # indirect doctest
            sage: S
            [ ...1     0]
            [    0 ...10]
            [    0     0]
            sage: L
            [...222222223          ...          ...]
            [...444444444         ...2          ...]
            [...444444443         ...1         ...1]
            sage: R
            [...0000000001 ...2222222214]
            [            0 ...0000000001]

        If some of the elementary divisors have valuation larger than the
        minimum precision of any entry in the matrix, then they are
        reported as an inexact zero::

            sage: A = ZpCA(5, prec=10)
            sage: M = matrix(A, 2, 2, [5, 5, 5, 5])
            sage: M.smith_form(transformation=False, exact=False)  # indirect doctest
            [5 + O(5^10)     O(5^10)]
            [    O(5^10)     O(5^10)]

        However, an error is raised if the precision on the entries is
        not enough to determine which column to use as a pivot at some point::

            sage: M = matrix(A, 2, 2, [A(0,5), A(5^6,10), A(0,8), A(5^7,10)]); M
            [       O(5^5) 5^6 + O(5^10)]
            [       O(5^8) 5^7 + O(5^10)]
            sage: M.smith_form(transformation=False, exact=False)  # indirect doctest
            Traceback (most recent call last):
            ...
            PrecisionError: not enough precision to compute Smith normal form

        TESTS::

            sage: A = ZpCR(5, prec=10)
            sage: M = zero_matrix(A, 2)
            sage: M.smith_form(transformation=False)  # indirect doctest
            [0 0]
            [0 0]

            sage: M = matrix(2, 2, [ A(0,10), 0, 0, 0] )
            sage: M.smith_form(transformation=False)  # indirect doctest
            Traceback (most recent call last):
            ...
            PrecisionError: some elementary divisors indistinguishable from zero (try exact=False)
            sage: M.smith_form(transformation=False, exact=False)  # indirect doctest
            [O(5^10) O(5^10)]
            [O(5^10) O(5^10)]
        """
        from sage.rings.all import infinity
        from sage.matrix.constructor import matrix
        from .precision_error import PrecisionError
        from copy import copy
        n = M.nrows()
        m = M.ncols()
        if m > n:
            ## It's easier below if we can always deal with precision on left.
            if transformation:
                d, u, v = self._matrix_smith_form(M.transpose(), True, integral, exact)
                return d.transpose(), v.transpose(), u.transpose()
            else:
                return self._matrix_smith_form(M.transpose(), False, integral, exact).transpose()
        smith = M.parent()(0)
        S = copy(M)
        Z = self.integer_ring()
        if integral is None or integral is self or integral is (not self.is_field()):
            integral = not self.is_field()
            R = self
        elif integral is True or integral is Z:
            # This is a field, but we want the integral smith form
            # The diagonal matrix may not be integral, but the transformations should be
            R = Z
            integral = True
        elif integral is False or integral is self.fraction_field():
            # This is a ring, but we want the field smith form
            # The diagonal matrix should be over this ring, but the transformations should not
            R = self.fraction_field()
            integral = False
        else:
            raise NotImplementedError("Smith normal form over this subring")
        ## the difference between ball_prec and inexact_ring is just for lattice precision.
        ball_prec = R._prec_type() in ['capped-rel','capped-abs']
        inexact_ring = R._prec_type() not in ['fixed-mod','floating-point']
        precM = min(x.precision_absolute() for x in M.list())

        if transformation:
            from sage.matrix.special import identity_matrix
            left = identity_matrix(R,n)
            right = identity_matrix(R,m)

        if ball_prec and precM is infinity: # capped-rel and M = 0 exactly
            return (smith, left, right) if transformation else smith

        val = -infinity
        for piv in range(m): # m <= n
            curval = infinity
            pivi = pivj = piv
            # allzero tracks whether every possible pivot is zero.
            # if so, we can stop.  allzero is also used in detecting some
            # precision problems: if we can't determine what pivot has
            # the smallest valuation, or if exact=True and some elementary
            # divisor is zero modulo the working precision
            allzero = True
            # allexact is tracked because there is one case where we can correctly
            # deduce the exact smith form even with some elementary divisors zero:
            # if the bottom right block consists entirely of exact zeros.
            allexact = True
            for i in range(piv,n):
                for j in range(piv,m):
                    Sij = S[i,j]
                    v = Sij.valuation()
                    allzero = allzero and Sij.is_zero()
                    if exact: # we only care in this case
                        allexact = allexact and Sij.precision_absolute() is infinity
                    if v < curval:
                        pivi = i; pivj = j
                        curval = v
                        if v == val: break
                else: continue
                break
            val = curval

            if inexact_ring and not allzero and val >= precM:
                if ball_prec:
                    raise PrecisionError("not enough precision to compute Smith normal form")
                precM = min([ S[i,j].precision_absolute() for i in range(piv,n) for j in range(piv,m) ])
                if val >= precM:
                    raise PrecisionError("not enough precision to compute Smith normal form")

            if allzero:
                if exact:
                    if allexact:
                        # We need to finish checking allexact since we broke out of the loop early
                        for i in range(i,n):
                            for j in range(piv,m):
                                allexact = allexact and S[i,j].precision_absolute() is infinity
                                if not allexact: break
                            else: continue
                            break
                    if not allexact:
                        raise PrecisionError("some elementary divisors indistinguishable from zero (try exact=False)")
                break

            # We swap the lowest valuation pivot into position
            S.swap_rows(pivi,piv)
            S.swap_columns(pivj,piv)
            if transformation:
                left.swap_rows(pivi,piv)
                right.swap_columns(pivj,piv)

            # ... and clear out this row and column.  Note that we
            # will deal with precision later, thus the call to lift_to_precision
            smith[piv,piv] = self(1) << val
            inv = (S[piv,piv] >> val).inverse_of_unit()
            if ball_prec:
                inv = inv.lift_to_precision()
            for i in range(piv+1,n):
                scalar = -inv * Z(S[i,piv] >> val)
                if ball_prec:
                    scalar = scalar.lift_to_precision()
                S.add_multiple_of_row(i,piv,scalar,piv+1)
                if transformation:
                    left.add_multiple_of_row(i,piv,scalar)
            if transformation:
                left.rescale_row(piv,inv)
                for j in range(piv+1,m):
                    scalar = -inv * Z(S[piv,j] >> val)
                    if ball_prec:
                        scalar = scalar.lift_to_precision()
                    right.add_multiple_of_column(j,piv,scalar)
        else:
            # We use piv as an upper bound on a range below, and need to set it correctly
            # in the case that we didn't break out of the loop
            piv = m
        # We update the precision on left
        # The bigoh measures the effect of multiplying by row operations
        # on the left in order to clear out the digits in the smith form
        # with valuation at least precM
        if ball_prec and exact and transformation:
            for j in range(n):
                delta = min(left[i,j].valuation() - smith[i,i].valuation() for i in range(piv))
                if delta is not infinity:
                    for i in range(n):
                        left[i,j] = left[i,j].add_bigoh(precM + delta)
        ## Otherwise, we update the precision on smith
        if ball_prec and not exact:
            smith = smith.apply_map(lambda x: x.add_bigoh(precM))
        ## We now have to adjust the elementary divisors (and precision) in the non-integral case
        if not integral:
            for i in range(piv):
                v = smith[i,i].valuation()
                if transformation:
                    for j in range(n):
                        left[i,j] = left[i,j] >> v
                if exact:
                    smith[i,i] = self(1)
                else:
                    for j in range(n):
                        smith[i,j] = smith[i,j] >> v
        if transformation:
            return smith, left, right
        else:
            return smith