예제 #1
0
def clique_shrink(subgraph: list, graph: nx.Graph) -> list:
    """Shrinks an input subgraph until it forms a clique.

    Proceeds by removing nodes in the input subgraph one at a time until the result is a clique
    that satisfies :func:`~strawberryfields.apps.graph.utils.is_clique`. Upon each iteration,
    this function selects the node with the lowest degree relative to the subgraph and removes it.

    Args:
        subgraph (list[int]): a subgraph specified by a list of nodes
        graph (nx.Graph): the input graph

    Returns:
        list[int]: a clique of size smaller than or equal to the input subgraph
    """

    if not utils.is_subgraph(subgraph, graph):
        raise ValueError("Input is not a valid subgraph")

    subgraph = graph.subgraph(
        subgraph).copy()  # A copy is required to be able to modify the
    # structure of the subgraph (https://networkx.github.io/documentation/stable/reference/classes/generated/networkx.Graph.subgraph.html)

    while not utils.is_clique(subgraph):
        degrees = list(subgraph.degree())
        np.random.shuffle(
            degrees
        )  # used to make sure selection of node with lowest degree is not
        # deterministic in case of a tie (https://docs.python.org/3/library/functions.html#min)

        to_remove = min(degrees, key=lambda x: x[1])
        subgraph.remove_node(to_remove[0])

    return sorted(subgraph.nodes())
예제 #2
0
def clique_swap(clique: list, graph: nx.Graph, node_select: str = "uniform") -> list:
    """If possible, generates a new clique by swapping a node in the input clique with a node
    outside the clique.

    Proceeds by calculating the set :math:`C_1` of nodes in the rest of the graph that are
    connected to all but one of the nodes in the clique. If this set is not empty, this function
    randomly picks a node and swaps it with the corresponding node in the clique that is not
    connected to it. The set :math:`C_1` and corresponding nodes in the clique are provided by the
    :func:`~strawberryfields.apps.graph.utils.c_1` function.

    Whenever there are multiple nodes within :math:`C_1`, one must choose which node to add to
    the growing clique. This function allows a method of choosing nodes to be set with the
    ``node_select`` argument, with node selection based on uniform randomness and node degree
    supported. Degree-based node selection involves picking the node with the greatest degree,
    with ties settled by uniform random choice.

    Example usage:

    >>> graph = nx.wheel_graph(5)
    >>> graph.remove_edge(0, 4)
    >>> clique = [0, 1, 2]
    >>> clique_swap(clique, graph)
    [0, 2, 3]

    Args:
        clique (list[int]): a subgraph specified by a list of nodes; the subgraph must be a clique
        graph (nx.Graph): the input graph
        node_select (str): method of selecting nodes from :math:`C_0` during growth. Can be either
            ``"uniform"`` for uniform random selection or ``"degree"`` for degree-based selection.
            Defaults to ``"uniform"``.

       Returns:
           list[int]: a new clique subgraph of equal size as the input
       """

    if not utils.is_subgraph(clique, graph):
        raise ValueError("Input is not a valid subgraph")

    if not utils.is_clique(graph.subgraph(clique)):
        raise ValueError("Input subgraph is not a clique")

    clique = set(clique)
    c_1 = utils.c_1(clique, graph)

    if c_1:
        if node_select == "uniform":
            swap_index = np.random.choice(len(c_1))
            swap_nodes = c_1[swap_index]
        elif node_select == "degree":
            degrees = np.array([graph.degree(n[1]) for n in c_1])
            to_swap_index = np.random.choice(np.where(degrees == degrees.max())[0])
            swap_nodes = c_1[to_swap_index]
        else:
            raise ValueError("Node selection method not recognized")

        clique.remove(swap_nodes[0])
        clique.add(swap_nodes[1])

    return sorted(clique)
 def test_is_output_clique(self, dim):
     """Test that the output subgraph is a valid clique, in this case the maximum clique
     in a lollipop graph"""
     graph = nx.lollipop_graph(dim, dim)
     subgraph = list(range(2 * dim))  # subgraph is the entire graph
     resized = resize.clique_shrink(subgraph, graph)
     assert utils.is_clique(graph.subgraph(resized))
     assert resized == list(range(dim))
예제 #4
0
def clique_grow(clique: list, graph: nx.Graph, node_select: str = "uniform") -> list:
    """Iteratively adds new nodes to the input clique to generate a larger clique.

    Each iteration involves calculating the set :math:`C_0` (provided by the function
    :func:`~strawberryfields.apps.graph.utils.c_0`) with respect to the current clique. This set
    represents the nodes in the rest of the graph that are connected to all of the nodes in the
    current clique. Therefore, adding any of the nodes in :math:`C_0` will create a larger clique.
    This function proceeds by repeatedly evaluating :math:`C_0` and selecting and adding a node
    from this set to add to the current clique. Growth is continued until :math:`C_0` becomes empty.

    Whenever there are multiple nodes within :math:`C_0`, one must choose which node to add to
    the growing clique. This function allows a method of choosing nodes to be set with the
    ``node_select`` argument, with node selection based on uniform randomness and node degree
    supported. Degree-based node selection involves picking the node with the greatest degree,
    with ties settled by uniform random choice.

    Example usage:

    >>> graph = nx.complete_graph(10)
    >>> clique = [0, 1, 2, 3, 4]
    >>> clique_grow(clique, graph)
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    Args:
        clique (list[int]): a subgraph specified by a list of nodes; the subgraph must be a clique
        graph (nx.Graph): the input graph
        node_select (str): method of selecting nodes from :math:`C_0` during growth. Can be either
            ``"uniform"`` for uniform random selection or ``"degree"`` for degree-based selection.
            Defaults to ``"uniform"``.

    Returns:
        list[int]: a new clique subgraph of equal or larger size than the input
    """

    if not utils.is_subgraph(clique, graph):
        raise ValueError("Input is not a valid subgraph")

    if not utils.is_clique(graph.subgraph(clique)):
        raise ValueError("Input subgraph is not a clique")

    clique = set(clique)
    c_0 = utils.c_0(clique, graph)

    while c_0:
        if node_select == "uniform":
            clique.add(np.random.choice(c_0))
        elif node_select == "degree":
            degrees = np.array([graph.degree(n) for n in c_0])
            to_add_index = np.random.choice(np.where(degrees == degrees.max())[0])
            to_add = c_0[to_add_index]
            clique.add(to_add)
        else:
            raise ValueError("Node selection method not recognized")

        c_0 = utils.c_0(clique, graph)

    return sorted(clique)
예제 #5
0
    def test_c_1_swap_to_clique(self, dim):
        """Tests that :math:`c_1` set gives a valid clique after swapping """
        A = nx.complete_graph(dim)
        A.remove_edge(0, 1)
        S = [i for i in range(1, dim)]
        c1 = utils.c_1(S, A)
        swap_nodes = c1[0]
        S.remove(swap_nodes[0])
        S.append(swap_nodes[1])

        assert utils.is_clique(A.subgraph(S))
예제 #6
0
 def test_no_false_positives(self, dim):
     """Tests that non-cliques are labelled as such"""
     g = nx.empty_graph(dim)
     assert not utils.is_clique(g)
예제 #7
0
 def test_no_false_negatives(self, dim):
     """Tests that cliques are labelled as such"""
     g = nx.complete_graph(dim)
     assert utils.is_clique(g)