예제 #1
0
    def __init__(self, vertexes: List[_Vertex]):
        """
        Manages a partition of nodes which are kept in separate classes
        according to their rank. Each vertex should already have a non-`None`
        `vertex.rank` field. The partition created by this constructor
        respects the constraints (if any) imposed with the attribute
        `initial_partition_block_id` in each vertex.

        This object is iterable (returns the classes of vertexes ordered
        by rank) and may be accessed with the operator `[i]` (returns the
        `i`-th class in the list). Also, the length of this object is defined
        as the number of classes of vertexes.

        :param vertexes: The list of vertexes in the partition.
        """

        self._nvertexes = len(vertexes)

        max_rank = max(vertex.rank for vertex in vertexes)
        list_positions = RankedPartition.rank_to_partition_idx(max_rank) + 1

        # a list of dicts, one dict for each rank index. in each dict there
        # are different blocks for different blocks of the labeling set
        rank_label = [{} for _ in range(list_positions)]

        for vertex in vertexes:
            rank_idx = RankedPartition.rank_to_partition_idx(vertex.rank)

            if vertex.initial_partition_block_id not in rank_label[rank_idx]:
                # we want to reuse the same xblock for all the vertexes
                # having the same rank
                if len(rank_label[rank_idx]) == 0:
                    xblock = _XBlock()
                else:
                    xblock = list(rank_label[rank_idx].values())[0].xblock

                rank_label[rank_idx][
                    vertex.initial_partition_block_id] = _Block([], xblock)
            rank_label[rank_idx][
                vertex.initial_partition_block_id].append_vertex(vertex)

        # we may not have leafs whith rank -inf, we create a shallow block to
        # fix the issue
        if len(rank_label[0]) == 0:
            rank_label[0][-1] = _Block([], _XBlock())

        # at this point there's no need to keep the blocks in a dictionary,
        # therefore we flatten the innermost dimension
        self._partition = [
            list(rank_idx_dict.values()) for rank_idx_dict in rank_label
        ]
예제 #2
0
def test_choose_qblock():
    compoundblock = _XBlock()
    qblocks = [
        _QBlock([_Vertex(i) for i in [0, 3, 5]], compoundblock),
        _QBlock([_Vertex(i) for i in [2, 4]], compoundblock),
        _QBlock([_Vertex(i) for i in [1, 6, 8]], compoundblock),
    ]

    splitter = extract_splitter(compoundblock)
    assert splitter == qblocks[1]

    # check if compound block has been modified properly
    assert compoundblock.qblocks.size == 2

    compoundblock_qblocks = set()
    for i in range(2):
        compoundblock_qblocks.add(compoundblock.qblocks.nodeat(i).value)
    assert compoundblock_qblocks == set([qblocks[0], qblocks[2]])
예제 #3
0
def ranked_split(current_partition: List[_QBlock], B_qblock: _QBlock,
                 max_rank: int) -> List[Tuple[_Vertex]]:
    """Split the given partition using the block `B_qblock` as *splitter*, then
    use Ranked *Paige-Tarjan*'s algorithm on the resulting partition.

    :param current_partition: The current partition as a list of
        :class:`bispy.utilities.graph_entities._QBlock`.
    :param B_qblock: The block to be used as *splitter*.
    :param max_rank: The maximum rank which may be found in the graph.
    :returns: The output of Ranked *Paige-Tarjan*'s algorithm as a list of
        tuples of vertexes.
    """

    # initialize x_partition and q_partition
    x_partition = [
        # this also overwrites any information about parent xblocks for qblock
        _XBlock().append_qblock(qblock) for qblock in current_partition
    ]
    q_partition = current_partition

    #  perform Split(B,Q)
    B_counterimage = build_block_counterimage(B_qblock)
    new_qblocks, new_compound_xblocks, chaned_qblocks = split(B_counterimage)

    # reset aux_count
    for vx in B_counterimage:
        vx.aux_count = None

    q_partition.extend(new_qblocks)

    if max_rank == float("-inf"):
        max_rank = -1

    # note that only new compound xblock are compound xblocks
    compound_xblocks = RankedCompoundXBlocksContainer(new_compound_xblocks,
                                                      max_rank)

    return pta(x_partition, q_partition, compound_xblocks)
예제 #4
0
def as_bispy_graph(
    graph: nx.Graph,
    initial_partition: List[Tuple[int]],
    build_image,
    set_count,
    set_xblock,
) -> Tuple[List[_Vertex], List[_QBlock]]:
    """
    Create the *BisPy* representation of the given graph. There are several
    options to enable/disable depending on which algorithm in *BisPy* you
    plan to use.

    :param graph: The graph.
    :param initial_partition: The initial partition (or labeling set) imposed
        on vertexes of the graph. Used to divide nodes in blocks.
    :param build_image: If `True`, we compute the image of each vertex.
    :param set_count: If `True`, we set the attribute `count` of each vertex
        to an appropriate instance of
        :class:`bispy.utilities.graph_entities._Count`.
    :param set_xblock: If `True` we set the attribute `xblock` of each block
        of the partition to an instance of
        :class:`bispy.utilities.graph_entities._XBlock` (the same for each
        block). If `False`, we set the attribute to `None`.
    :returns: A tuple whose items are:

        0. List of vertexes in the graph;
        1. List of blocks in the initial partition.

        Both the items are in *BisPy* representation.
    """

    if initial_partition is None:
        initial_partition = _trivial_initial_partition(len(graph.nodes))

    # instantiate QBlocks and Vertexes, put Vertexes into QBlocks and set their
    # initial block id
    vertexes = [None for _ in graph.nodes]
    qblocks = []

    initial_x_block = _XBlock() if set_xblock else None

    for idx, block in enumerate(initial_partition):
        qblock = _QBlock([], initial_x_block)
        qblocks.append(qblock)
        for vx in block:
            new_vertex = _Vertex(label=vx)
            vertexes[vx] = new_vertex
            qblock.append_vertex(new_vertex)
            new_vertex.initial_partition_block_id = idx

    if set_count:
        # holds the references to Count objects to assign to the edges.
        # count(x) = count(x,V) = |V \cap E({x})| = |E({x})|
        vertex_count = [None for _ in graph.nodes]
    else:
        vertex_count = None

    # build the counterimage. the image will be constructed using the order
    # imposed by the rank algorithm
    for edge in graph.edges:
        # create an instance of my class Edge
        my_edge = _Edge(vertexes[edge[0]], vertexes[edge[1]])

        if set_count:
            # if this is the first outgoing edge for the vertex edge[0], we
            # need to create a new Count instance
            if not vertex_count[edge[0]]:
                # in this case None represents the intitial XBlock, namely the
                # whole V
                vertex_count[edge[0]] = _Count(my_edge.source)

            my_edge.count = vertex_count[edge[0]]
            my_edge.count.value += 1

        if build_image:
            my_edge.source.add_to_image(my_edge)
        my_edge.destination.add_to_counterimage(my_edge)

    return (vertexes, qblocks)
예제 #5
0
파일: saha.py 프로젝트: fAndreuzzi/BisPy
def merge_split_phase(qpartition: List[_Block],
                      finishing_time_list: List[_Vertex]) -> List[_Block]:
    """
    The function `MergeAndSplitPhase` from the paper.

    :param qpartition: The current partition.
    :param finishing_time_list: List of vertexes in the graph ordered by
        finishing time.
    :returns: The updated partition.
    """

    max_rank = float("-inf")
    for block in qpartition:
        max_rank = max(max_rank, block.rank)

    # a dict of lists of blocks (the key is the initial partition ID)
    # where each couple can't be merged
    cant_merge_dict = {}

    # keep track in order to remove the 'visited' flag
    visited_vertexes = []

    # a partition containing all the touched blocks
    X = []

    # visit G in order of decreasing finishing times of the first DFS
    for vertex in finishing_time_list:
        # a vertex may be reached more than one time
        if not vertex.visited:
            merge_step(vertex, X, visited_vertexes, cant_merge_dict)

    X = list(filter(lambda block: not block.deteached, X))

    # clear visited flag
    for vx in visited_vertexes:
        vx.visited = False

    # reset block.visited flag (was set by first DFS) and tried_merge
    for block in qpartition:
        block.visited = False
        block.tried_merge = False

    # ------------
    # Split phase
    # ------------

    # we need to scale in order to use PTA (and then scale back)
    scaled_to_nonscaled = []

    xblock = _XBlock()
    for block in X:
        # this is needed for PTA
        xblock.append_qblock(block)
        # set visited flag in order to compute the set (qpartition - X) easily
        block.visited = True

        for vx in block.vertexes:
            # mark as reachable by PTA
            vx.allow_visit = True

            # scale label in order to use PTA
            vx.scale_label(len(scaled_to_nonscaled))
            scaled_to_nonscaled.append(vx.label)

    # build the new qpartition, without the blocks in X (which may be split).
    # this is just the set qpartition - X
    new_qpartition = []
    for block in qpartition:
        if not (block.visited or block.deteached):
            new_qpartition.append(block)
        else:
            # now we can clean the flag, this block was already discarded
            block.visited = False

    for block in X:
        for vx in block.vertexes:
            vx.restrict_to_subgraph(validation=lambda v: v.allow_visit)

    # apply PTA and append the blocks to the new partition
    preprocess_initial_partition(X)
    X2 = paige_tarjan_qblocks(X)
    new_qpartition.extend(X2)

    for block in X2:
        for vx in block.vertexes:
            # restore the original image/counterimage
            vx.back_to_original_graph()
            # clean allow_visit
            vx.allow_visit = False
            # restore original label
            vx.back_to_original_label()

    # select the blocks which are the result of a split
    # split, and clean block.visited
    for block in filter(attrgetter("is_new_qblock"), X2):
        # split
        new_qpartition = ranked_split(new_qpartition, block, max_rank)
        # clean
        block.is_new_qblock = False

    return new_qpartition