def is_product(n, den_tuple):
    r"""
    INPUT:

    - ``n`` - number of variables

    - ``den_tuple`` - tuple of pairs ``(vector, power)``

    TESTS::

        sage: from surface_dynamics.misc.generalized_multiple_zeta_values import is_product
        sage: is_product(3, [((1,0,0),2), ((0,1,0),5), ((1,1,0),1), ((0,0,1),3)])
        [(2, (((0, 1), 5), ((1, 0), 2), ((1, 1), 1))), (1, (((1), 3),))]
        sage: is_product(3, [((1,0,0),2), ((0,1,0),3), ((1,0,1),1), ((0,0,1),5)])
        [(2, (((0, 1), 5), ((1, 0), 2), ((1, 1), 1))), (1, (((1), 3),))]
        sage: is_product(3, [((1,1,1),3)]) is None
        True
    """
    D = DisjointSet(n)
    assert all(len(v) == n for v, p in den_tuple), (n, den_tuple)

    # 1. product structure
    for v, _ in den_tuple:
        i0 = 0
        while not v[i0]:
            i0 += 1
        i = i0 + 1
        while i < n:
            if v[i]:
                D.union(i0, i)
            i += 1
        if D.number_of_subsets() == 1:
            # no way to split variables
            return

    # split variables
    Rdict = D.root_to_elements_dict()
    keys = sorted(Rdict.keys())
    key_indices = {k: i for i, k in enumerate(keys)}
    values = [Rdict[k] for k in keys]
    values_indices = [{v: i for i, v in enumerate(v)} for v in values]
    n_list = [len(J) for J in values]
    F = [FreeModule(ZZ, nn) for nn in n_list]
    new_terms = [[] for _ in range(len(Rdict))]
    for v, p in den_tuple:
        i0 = 0
        while not v[i0]:
            i0 += 1
        i0 = D.find(i0)
        assert all(D.find(i) == i0 for i in range(n)
                   if v[i]), (i0, [D.find(i) for i in range(n) if v[i]])
        k = key_indices[i0]
        vv = F[k]()
        for i in range(n):
            if v[i]:
                vv[values_indices[k][i]] = v[i]
        vv.set_immutable()
        new_terms[k].append((vv, p))

    return list(zip(n_list, [tuple(sorted(terms)) for terms in new_terms]))
Example #2
0
    def poset_of_layers(self):
        """
        Compute the poset of layers of the associated toric arrangement, using Lenz's algorithm [Len17a].
        """
        # TODO: implement for Q != 0
        if self._Q.ncols() > 0:
            raise NotImplementedError


        A = self._A.transpose()
        E = range(A.nrows())

        data = {}

        # compute Smith normal forms of all submatrices
        for S in powerset(E):
            D, U, V = A[S,:].smith_form()   # D == U*A[S,:]*V
            diagonal = [D[i,i] if i < D.ncols() else 0 for i in xrange(len(S))]
            data[tuple(S)] = (diagonal, U)

        # generate al possible elements of the poset of layers
        elements = {tuple(S): list(vector(ZZ, x) for x in itertools.product(*(range(max(data[tuple(S)][0][i], 1)) for i in xrange(len(S))))) for S in powerset(E)}

        for l in elements.itervalues():
            for v in l:
                v.set_immutable()

        possible_layers = list((S, x) for (S, l) in elements.iteritems() for x in l)
        uf = DisjointSet(possible_layers)

        cover_relations = []

        for (S, l) in elements.iteritems():
            diagonal_S, U_S = data[S]
            rk_S = A[S,:].rank()

            for s in S:
                i = S.index(s)  # index where the element s appears in S
                T = tuple(t for t in S if t != s)

                diagonal_T, U_T = data[T]
                rk_T = A[T,:].rank()

                for x in l:
                    h = (S, x)

                    y = U_S**(-1) * x
                    z = U_T * vector(ZZ, y[:i].list() + y[i+1:].list())
                    w = vector(ZZ, (a % diagonal_T[j] if diagonal_T[j] > 0 else 0 for j, a in enumerate(z)))
                    w.set_immutable()

                    ph = (T, w)

                    if rk_S == rk_T:
                        uf.union(h, ph)

                    else:
                        cover_relations.append((ph, h))

        # find representatives for layers (we keep the representative (S,x) with maximal S)
        root_to_representative_dict = {}
        for root, subset in uf.root_to_elements_dict().iteritems():
            S, x = max(subset, key=lambda (S, x): len(S))
            S_labeled = tuple(self._E[i] for i in S)
            root_to_representative_dict[root] = (S_labeled, x)

        # get layers and cover relations
        layers = root_to_representative_dict.values()
        cover_relations = set(
            (root_to_representative_dict[uf.find(a)], root_to_representative_dict[uf.find(b)])
            for (a,b) in cover_relations)

        return Poset(data=(layers, cover_relations), cover_relations=True)
Example #3
0
def RandomBlockGraph(m, k, kmax=None, incidence_structure=False):
    r"""
    Return a Random Block Graph.

    A block graph is a connected graph in which every biconnected component
    (block) is a clique.

    .. SEEALSO::

        - :wikipedia:`Block_graph` for more details on these graphs
        - :meth:`~sage.graphs.graph.Graph.is_block_graph` -- test if a graph is a block graph
        - :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cut_vertices`
        - :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cuts_tree`
        - :meth:`~sage.combinat.designs.incidence_structures.IncidenceStructure` 

    INPUT:

    - ``m`` -- integer; number of blocks (at least one).

    - ``k`` -- integer; minimum number of vertices of a block (at least two).

    - ``kmax`` -- integer (default: ``None``) By default, each block has `k`
      vertices. When the parameter `kmax` is specified (with `kmax \geq k`), the
      number of vertices of each block is randomly chosen between `k` and
      `kmax`.

    - ``incidence_structure`` -- boolean (default: ``False``) when set to
      ``True``, the incidence structure of the graphs is returned instead of the
      graph itself, that is the list of the lists of vertices in each
      block. This is useful for the creation of some hypergraphs.

    OUTPUT:

    A Graph when ``incidence_structure==False`` (default), and otherwise an
    incidence structure.

    EXAMPLES:

    A block graph with a single block is a clique::

        sage: B = graphs.RandomBlockGraph(1, 4)
        sage: B.is_clique()
        True

    A block graph with blocks of order 2 is a tree::

        sage: B = graphs.RandomBlockGraph(10, 2)
        sage: B.is_tree()
        True

    Every biconnected component of a block graph is a clique::

        sage: B = graphs.RandomBlockGraph(5, 3, kmax=6)
        sage: blocks,cuts = B.blocks_and_cut_vertices()
        sage: all(B.is_clique(block) for block in blocks)
        True

    A block graph with blocks of order `k` has `m*(k-1)+1` vertices::

        sage: m, k = 6, 4
        sage: B = graphs.RandomBlockGraph(m, k)
        sage: B.order() == m*(k-1)+1
        True

    Test recognition methods::

        sage: B = graphs.RandomBlockGraph(6, 2, kmax=6)
        sage: B.is_block_graph()
        True
        sage: B in graph_classes.Block
        True

    Asking for the incidence structure::

        sage: m, k = 6, 4
        sage: IS = graphs.RandomBlockGraph(m, k, incidence_structure=True)
        sage: from sage.combinat.designs.incidence_structures import IncidenceStructure
        sage: IncidenceStructure(IS)
        Incidence structure with 19 points and 6 blocks
        sage: m*(k-1)+1
        19

    TESTS:

    A block graph has at least one block, so `m\geq 1`::

        sage: B = graphs.RandomBlockGraph(0, 1)
        Traceback (most recent call last):
        ...
        ValueError: the number `m` of blocks must be >= 1

    A block has at least 2 vertices, so `k\geq 2`::

        sage: B = graphs.RandomBlockGraph(1, 1)
        Traceback (most recent call last):
        ...
        ValueError: the minimum number `k` of vertices in a block must be >= 2

    The maximum size of a block is at least its minimum size, so `k\leq kmax`::

        sage: B = graphs.RandomBlockGraph(1, 3, kmax=2)
        Traceback (most recent call last):
        ...
        ValueError: the maximum number `kmax` of vertices in a block must be >= `k`
    """
    import itertools
    from sage.misc.prandom import choice
    from sage.sets.disjoint_set import DisjointSet

    if m < 1:
        raise ValueError("the number `m` of blocks must be >= 1")
    if k < 2:
        raise ValueError(
            "the minimum number `k` of vertices in a block must be >= 2")
    if kmax is None:
        kmax = k
    elif kmax < k:
        raise ValueError(
            "the maximum number `kmax` of vertices in a block must be >= `k`")

    if m == 1:
        # A block graph with a single block is a clique
        IS = [list(range(randint(k, kmax)))]

    elif kmax == 2:
        # A block graph with blocks of order 2 is a tree
        IS = [list(e) for e in RandomTree(m + 1).edges(labels=False)]

    else:
        # We start with a random tree of order m
        T = RandomTree(m)

        # We create a block of order in range [k,kmax] per vertex of the tree
        B = {u: [(u, i) for i in range(randint(k, kmax))] for u in T}

        # For each edge of the tree, we choose 1 vertex in each of the
        # corresponding blocks and we merge them. We use a disjoint set data
        # structure to keep a unique identifier per merged vertices
        DS = DisjointSet([i for u in B for i in B[u]])
        for u, v in T.edges(labels=0):
            DS.union(choice(B[u]), choice(B[v]))

        # We relabel vertices in the range [0, m*(k-1)] and build the incidence
        # structure
        new_label = {
            root: i
            for i, root in enumerate(DS.root_to_elements_dict())
        }
        IS = [[new_label[DS.find(v)] for v in B[u]] for u in B]

    if incidence_structure:
        return IS

    # We finally build the block graph
    if k == kmax:
        BG = Graph(
            name="Random Block Graph with {} blocks of order {}".format(m, k))
    else:
        BG = Graph(
            name="Random Block Graph with {} blocks of order {} to {}".format(
                m, k, kmax))
    for block in IS:
        BG.add_clique(block)
    return BG
Example #4
0
def RandomBlockGraph(m, k, kmax=None, incidence_structure=False):
    r"""
    Return a Random Block Graph.

    A block graph is a connected graph in which every biconnected component
    (block) is a clique.

    .. SEEALSO::

        - :wikipedia:`Block_graph` for more details on these graphs
        - :meth:`~sage.graphs.graph.Graph.is_block_graph` -- test if a graph is a block graph
        - :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cut_vertices`
        - :meth:`~sage.graphs.generic_graph.GenericGraph.blocks_and_cuts_tree`
        - :meth:`~sage.combinat.designs.incidence_structures.IncidenceStructure` 

    INPUT:

    - ``m`` -- integer; number of blocks (at least one).

    - ``k`` -- integer; minimum number of vertices of a block (at least two).

    - ``kmax`` -- integer (default: ``None``) By default, each block has `k`
      vertices. When the parameter `kmax` is specified (with `kmax \geq k`), the
      number of vertices of each block is randomly chosen between `k` and
      `kmax`.

    - ``incidence_structure`` -- boolean (default: ``False``) when set to
      ``True``, the incidence structure of the graphs is returned instead of the
      graph itself, that is the list of the lists of vertices in each
      block. This is useful for the creation of some hypergraphs.

    OUTPUT:

    A Graph when ``incidence_structure==False`` (default), and otherwise an
    incidence structure.

    EXAMPLES:

    A block graph with a single block is a clique::

        sage: B = graphs.RandomBlockGraph(1, 4)
        sage: B.is_clique()
        True

    A block graph with blocks of order 2 is a tree::

        sage: B = graphs.RandomBlockGraph(10, 2)
        sage: B.is_tree()
        True

    Every biconnected component of a block graph is a clique::

        sage: B = graphs.RandomBlockGraph(5, 3, kmax=6)
        sage: blocks,cuts = B.blocks_and_cut_vertices()
        sage: all(B.is_clique(block) for block in blocks)
        True

    A block graph with blocks of order `k` has `m*(k-1)+1` vertices::

        sage: m, k = 6, 4
        sage: B = graphs.RandomBlockGraph(m, k)
        sage: B.order() == m*(k-1)+1
        True

    Test recognition methods::

        sage: B = graphs.RandomBlockGraph(6, 2, kmax=6)
        sage: B.is_block_graph()
        True
        sage: B in graph_classes.Block
        True

    Asking for the incidence structure::

        sage: m, k = 6, 4
        sage: IS = graphs.RandomBlockGraph(m, k, incidence_structure=True)
        sage: from sage.combinat.designs.incidence_structures import IncidenceStructure
        sage: IncidenceStructure(IS)
        Incidence structure with 19 points and 6 blocks
        sage: m*(k-1)+1
        19

    TESTS:

    A block graph has at least one block, so `m\geq 1`::

        sage: B = graphs.RandomBlockGraph(0, 1)
        Traceback (most recent call last):
        ...
        ValueError: the number `m` of blocks must be >= 1

    A block has at least 2 vertices, so `k\geq 2`::

        sage: B = graphs.RandomBlockGraph(1, 1)
        Traceback (most recent call last):
        ...
        ValueError: the minimum number `k` of vertices in a block must be >= 2

    The maximum size of a block is at least its minimum size, so `k\leq kmax`::

        sage: B = graphs.RandomBlockGraph(1, 3, kmax=2)
        Traceback (most recent call last):
        ...
        ValueError: the maximum number `kmax` of vertices in a block must be >= `k`
    """
    import itertools
    from sage.misc.prandom import choice
    from sage.sets.disjoint_set import DisjointSet

    if m < 1:
        raise ValueError("the number `m` of blocks must be >= 1")
    if k < 2:
        raise ValueError("the minimum number `k` of vertices in a block must be >= 2")
    if kmax is None:
        kmax = k
    elif kmax < k:
        raise ValueError("the maximum number `kmax` of vertices in a block must be >= `k`")

    if m == 1:
        # A block graph with a single block is a clique
        IS = [ list(range(randint(k, kmax))) ]
        
    elif kmax == 2:
        # A block graph with blocks of order 2 is a tree
        IS = [ list(e) for e in RandomTree(m+1).edges(labels=False) ]

    else:
        # We start with a random tree of order m
        T = RandomTree(m)

        # We create a block of order in range [k,kmax] per vertex of the tree
        B = {u:[(u,i) for i in range(randint(k, kmax))] for u in T}

        # For each edge of the tree, we choose 1 vertex in each of the
        # corresponding blocks and we merge them. We use a disjoint set data
        # structure to keep a unique identifier per merged vertices
        DS = DisjointSet([i for u in B for i in B[u]])
        for u,v in T.edges(labels=0):
            DS.union(choice(B[u]), choice(B[v]))

        # We relabel vertices in the range [0, m*(k-1)] and build the incidence
        # structure
        new_label = {root:i for i,root in enumerate(DS.root_to_elements_dict())}
        IS = [ [new_label[DS.find(v)] for v in B[u]] for u in B ]

    if incidence_structure:
        return IS
    
    # We finally build the block graph
    if k == kmax:
        BG = Graph(name = "Random Block Graph with {} blocks of order {}".format(m, k))
    else:
        BG = Graph(name = "Random Block Graph with {} blocks of order {} to {}".format(m, k, kmax))
    for block in IS:
        BG.add_clique( block )
    return BG