Exemplo n.º 1
0
def spanning_forest(M):
    r"""
    Return a list of edges of a spanning forest of the bipartite
    graph defined by `M`

    INPUT:

    - ``M`` -- a matrix defining a bipartite graph G. The vertices are the 
      rows and columns, if `M[i,j]` is non-zero, then there is an edge
      between row `i` and column `j`.

    OUTPUT:

    A list of tuples `(r_i,c_i)` representing edges between row `r_i` and column `c_i`.

    EXAMPLES::

        sage: len(sage.matroids.utilities.spanning_forest(matrix([[1,1,1],[1,1,1],[1,1,1]])))
        5
        sage: len(sage.matroids.utilities.spanning_forest(matrix([[0,0,1],[0,1,0],[0,1,0]])))
        3
    """
    # Given a matrix, produce a spanning tree
    G = Graph()
    m = M.ncols()
    for (x, y) in M.dict():
        G.add_edge(x + m, y)
    T = []
    # find spanning tree in each component
    for component in G.connected_components():
        spanning_tree = kruskal(G.subgraph(component))
        for (x, y, z) in spanning_tree:
            if x < m:
                t = x
                x = y
                y = t
            T.append((x - m, y))
    return T
Exemplo n.º 2
0
def spanning_forest(M):
    r"""
    Return a list of edges of a spanning forest of the bipartite
    graph defined by `M`

    INPUT:

    - ``M`` -- a matrix defining a bipartite graph G. The vertices are the
      rows and columns, if `M[i,j]` is non-zero, then there is an edge
      between row `i` and column `j`.

    OUTPUT:

    A list of tuples `(r_i,c_i)` representing edges between row `r_i` and column `c_i`.

    EXAMPLES::

        sage: len(sage.matroids.utilities.spanning_forest(matrix([[1,1,1],[1,1,1],[1,1,1]])))
        5
        sage: len(sage.matroids.utilities.spanning_forest(matrix([[0,0,1],[0,1,0],[0,1,0]])))
        3
    """
    # Given a matrix, produce a spanning tree
    G = Graph()
    m = M.ncols()
    for (x,y) in M.dict():
        G.add_edge(x+m,y)
    T = []
    # find spanning tree in each component
    for component in G.connected_components():
        spanning_tree = kruskal(G.subgraph(component))
        for (x,y,z) in spanning_tree:
            if x < m:
                t = x
                x = y
                y = t
            T.append((x-m,y))
    return T
Exemplo n.º 3
0
def lift_cross_ratios(A, lift_map=None):
    r"""
    Return a matrix which arises from the given matrix by lifting cross ratios.

    INPUT:

    - ``A`` -- a matrix over a ring ``source_ring``.
    - ``lift_map`` -- a python dictionary, mapping each cross ratio of ``A`` to some element
      of a target ring, and such that ``lift_map[source_ring(1)] = target_ring(1)``.

    OUTPUT:

    - ``Z`` -- a matrix over the ring ``target_ring``.

    The intended use of this method is to create a (reduced) matrix representation of a
    matroid ``M`` over a ring ``target_ring``, given a (reduced) matrix representation of
    ``A`` of ``M`` over a ring ``source_ring`` and a map ``lift_map`` from ``source_ring``
    to ``target_ring``.

    This method will create a unique candidate representation ``Z``, but will not verify
    if ``Z`` is indeed a representation of ``M``. However, this is guaranteed if the
    conditions of the lift theorem (see [PvZ2010]_) hold for the lift map in combination with
    the matrix ``A``.

    For a lift map `f` and a matrix `A` these conditions are as follows. First of all
    `f: S \rightarrow T`, where `S` is a set of invertible elements of the source ring and
    `T` is a set of invertible elements of the target ring. The matrix `A` has entries
    from the source ring, and each cross ratio of `A` is contained in `S`. Moreover:

    - `1 \in S`, `1 \in T`;
    - for all `x \in S`: `f(x) = 1` if and only if `x = 1`;
    - for all `x, y \in S`: if `x + y = 0` then `f(x) + f(y) = 0`;
    - for all `x, y \in S`: if `x + y = 1` then `f(x) + f(y) = 1`;
    - for all `x, y, z \in S`: if  `xy = z` then `f(x)f(y) = f(z)`.

    Any ring homomorphism `h: P \rightarrow R` induces a lift map from the set of units `S` of
    `P` to the set of units `T` of `R`. There exist lift maps which do not arise in
    this manner. Several such maps can be created by the function
    :meth:`lift_map() <sage.matroids.utilities.lift_map>`.

    .. SEEALSO::

        :meth:`lift_map() <sage.matroids.utilities.lift_map>`

    EXAMPLES::

        sage: from sage.matroids.advanced import lift_cross_ratios, lift_map, LinearMatroid
        sage: R = GF(7)
        sage: to_sixth_root_of_unity = lift_map('sru')
        sage: A = Matrix(R, [[1, 0, 6, 1, 2],[6, 1, 0, 0, 1],[0, 6, 3, 6, 0]])
        sage: A
        [1 0 6 1 2]
        [6 1 0 0 1]
        [0 6 3 6 0]
        sage: Z = lift_cross_ratios(A, to_sixth_root_of_unity)
        sage: Z
        [ 1  0  1  1  1]
        [ 1  1  0  0  z]
        [ 0  z - 1  1  -z + 1  0]
        sage: M = LinearMatroid(reduced_matrix = A)
        sage: sorted(M.cross_ratios())
        [3, 5]
        sage: N = LinearMatroid(reduced_matrix = Z)
        sage: sorted(N.cross_ratios())
        [-z + 1, z]
        sage: M.is_isomorphism(N, {e:e for e in M.groundset()})
        True

    """
    for s, t in iteritems(lift_map):
        source_ring = s.parent()
        target_ring = t.parent()
        break
    plus_one1 = source_ring(1)
    minus_one1 = source_ring(-1)
    plus_one2 = target_ring(1)
    minus_one2 = target_ring(-1)

    G = Graph([((r, 0), (c, 1), (r, c)) for r, c in A.nonzero_positions()])
    # write the entries of (a scaled version of) A as products of cross ratios of A
    T = set()
    for C in G.connected_components():
        T.update(G.subgraph(C).min_spanning_tree())
    # - fix a tree of the support graph G to units (= empty dict, product of 0 terms)
    F = {entry[2]: dict() for entry in T}
    W = set(G.edges()) - set(T)
    H = G.subgraph(edges=T)
    while W:
        # - find an edge in W to process, closing a circuit in H which is induced in G
        edge = W.pop()
        path = H.shortest_path(edge[0], edge[1])
        retry = True
        while retry:
            retry = False
            for edge2 in W:
                if edge2[0] in path and edge2[1] in path:
                    W.add(edge)
                    edge = edge2
                    W.remove(edge)
                    path = H.shortest_path(edge[0], edge[1])
                    retry = True
                    break
        entry = edge[2]
        entries = []
        for i in range(len(path) - 1):
            v = path[i]
            w = path[i + 1]
            if v[1] == 0:
                entries.append((v[0], w[0]))
            else:
                entries.append((w[0], v[0]))
        # - compute the cross ratio `cr` of this whirl
        cr = source_ring(A[entry])
        div = True
        for entry2 in entries:
            if div:
                cr = cr / A[entry2]
            else:
                cr = cr * A[entry2]
            div = not div

        monomial = dict()
        if len(path) % 4 == 0:
            if not cr == plus_one1:
                monomial[cr] = 1
        else:
            cr = -cr
            if not cr == plus_one1:
                monomial[cr] = 1
            if minus_one1 in monomial:
                monomial[minus_one1] = monomial[minus_one1] + 1
            else:
                monomial[minus_one1] = 1

        if cr != plus_one1 and not cr in lift_map:
            raise ValueError("Input matrix has a cross ratio " + str(cr) +
                             ", which is not in the lift_map")
        # - write the entry as a product of cross ratios of A
        div = True
        for entry2 in entries:
            if div:
                for cr, degree in iteritems(F[entry2]):
                    if cr in monomial:
                        monomial[cr] = monomial[cr] + degree
                    else:
                        monomial[cr] = degree
            else:
                for cr, degree in iteritems(F[entry2]):
                    if cr in monomial:
                        monomial[cr] = monomial[cr] - degree
                    else:
                        monomial[cr] = -degree
            div = not div
        F[entry] = monomial
        # - current edge is done, can be used in next iteration
        H.add_edge(edge)

    # compute each entry of Z as the product of lifted cross ratios
    Z = Matrix(target_ring, A.nrows(), A.ncols())
    for entry, monomial in iteritems(F):
        Z[entry] = plus_one2
        for cr, degree in iteritems(monomial):
            if cr == minus_one1:
                Z[entry] = Z[entry] * (minus_one2**degree)
            else:
                Z[entry] = Z[entry] * (lift_map[cr]**degree)

    return Z
Exemplo n.º 4
0
def lift_cross_ratios(A, lift_map = None):
    r"""
    Return a matrix which arises from the given matrix by lifting cross ratios.

    INPUT:

    - ``A`` -- a matrix over a ring ``source_ring``.
    - ``lift_map`` -- a python dictionary, mapping each cross ratio of ``A`` to some element
      of a target ring, and such that ``lift_map[source_ring(1)] = target_ring(1)``.

    OUTPUT:

    - ``Z`` -- a matrix over the ring ``target_ring``.

    The intended use of this method is to create a (reduced) matrix representation of a
    matroid ``M`` over a ring ``target_ring``, given a (reduced) matrix representation of
    ``A`` of ``M`` over a ring ``source_ring`` and a map ``lift_map`` from ``source_ring``
    to ``target_ring``.

    This method will create a unique candidate representation ``Z``, but will not verify
    if ``Z`` is indeed a representation of ``M``. However, this is guaranteed if the
    conditions of the lift theorem (see [PvZ2010]_) hold for the lift map in combination with
    the matrix ``A``.

    For a lift map `f` and a matrix `A` these conditions are as follows. First of all
    `f: S \rightarrow T`, where `S` is a set of invertible elements of the source ring and
    `T` is a set of invertible elements of the target ring. The matrix `A` has entries
    from the source ring, and each cross ratio of `A` is contained in `S`. Moreover:

    - `1 \in S`, `1 \in T`;
    - for all `x \in S`: `f(x) = 1` if and only if `x = 1`;
    - for all `x, y \in S`: if `x + y = 0` then `f(x) + f(y) = 0`;
    - for all `x, y \in S`: if `x + y = 1` then `f(x) + f(y) = 1`;
    - for all `x, y, z \in S`: if  `xy = z` then `f(x)f(y) = f(z)`.

    Any ring homomorphism `h: P \rightarrow R` induces a lift map from the set of units `S` of
    `P` to the set of units `T` of `R`. There exist lift maps which do not arise in
    this manner. Several such maps can be created by the function
    :meth:`lift_map() <sage.matroids.utilities.lift_map>`.

    .. SEEALSO::

        :meth:`lift_map() <sage.matroids.utilities.lift_map>`

    EXAMPLES::

        sage: from sage.matroids.advanced import lift_cross_ratios, lift_map, LinearMatroid
        sage: R = GF(7)
        sage: to_sixth_root_of_unity = lift_map('sru')
        sage: A = Matrix(R, [[1, 0, 6, 1, 2],[6, 1, 0, 0, 1],[0, 6, 3, 6, 0]])
        sage: A
        [1 0 6 1 2]
        [6 1 0 0 1]
        [0 6 3 6 0]
        sage: Z = lift_cross_ratios(A, to_sixth_root_of_unity)
        sage: Z
        [ 1  0  1  1  1]
        [ 1  1  0  0  z]
        [ 0  z - 1  1  -z + 1  0]
        sage: M = LinearMatroid(reduced_matrix = A)
        sage: sorted(M.cross_ratios())
        [3, 5]
        sage: N = LinearMatroid(reduced_matrix = Z)
        sage: sorted(N.cross_ratios())
        [-z + 1, z]
        sage: M.is_isomorphism(N, {e:e for e in M.groundset()})
        True

    """
    for s, t in iteritems(lift_map):
        source_ring = s.parent()
        target_ring = t.parent()
        break
    plus_one1 = source_ring(1)
    minus_one1 = source_ring(-1)
    plus_one2 = target_ring(1)
    minus_one2 = target_ring(-1)

    G = Graph([((r,0),(c,1),(r,c)) for r,c in A.nonzero_positions()])
    # write the entries of (a scaled version of) A as products of cross ratios of A
    T = set()
    for C in G.connected_components():
        T.update(G.subgraph(C).min_spanning_tree())
    # - fix a tree of the support graph G to units (= empty dict, product of 0 terms)
    F = {entry[2]: dict() for entry in T}
    W = set(G.edges()) - set(T)
    H = G.subgraph(edges = T)
    while W:
        # - find an edge in W to process, closing a circuit in H which is induced in G
        edge = W.pop()
        path = H.shortest_path(edge[0], edge[1])
        retry = True
        while retry:
            retry = False
            for edge2 in W:
                if edge2[0] in path and edge2[1] in path:
                    W.add(edge)
                    edge = edge2
                    W.remove(edge)
                    path = H.shortest_path(edge[0], edge[1])
                    retry = True
                    break
        entry = edge[2]
        entries = []
        for i in range(len(path) - 1):
            v = path[i]
            w = path[i+1]
            if v[1] == 0:
                entries.append((v[0],w[0]))
            else:
                entries.append((w[0],v[0]))
        # - compute the cross ratio `cr` of this whirl
        cr = source_ring(A[entry])
        div = True
        for entry2 in entries:
            if div:
                cr = cr/A[entry2]
            else:
                cr = cr* A[entry2]
            div = not div

        monomial = dict()
        if len(path) % 4 == 0:
            if not cr == plus_one1:
                monomial[cr] = 1
        else:
            cr = -cr
            if not cr ==plus_one1:
                monomial[cr] = 1
            if  minus_one1 in monomial:
                monomial[minus_one1] = monomial[minus_one1] + 1
            else:
                monomial[minus_one1] = 1

        if cr != plus_one1 and not cr in lift_map:
            raise ValueError("Input matrix has a cross ratio "+str(cr)+", which is not in the lift_map")
        # - write the entry as a product of cross ratios of A
        div = True
        for entry2 in entries:
            if div:
                for cr, degree in iteritems(F[entry2]):
                    if cr in monomial:
                        monomial[cr] = monomial[cr]+ degree
                    else:
                        monomial[cr] = degree
            else:
                for cr, degree in iteritems(F[entry2]):
                    if cr in monomial:
                        monomial[cr] = monomial[cr] - degree
                    else:
                        monomial[cr] = -degree
            div = not div
        F[entry] = monomial
        # - current edge is done, can be used in next iteration
        H.add_edge(edge)

    # compute each entry of Z as the product of lifted cross ratios
    Z = Matrix(target_ring, A.nrows(), A.ncols())
    for entry, monomial in iteritems(F):
        Z[entry] = plus_one2
        for cr,degree in iteritems(monomial):
            if cr == minus_one1:
                Z[entry] = Z[entry] * (minus_one2**degree)
            else:
                Z[entry] = Z[entry] * (lift_map[cr]**degree)

    return Z