Skip to content

Commit

Permalink
nx-cugraph: add is_tree, etc.
Browse files Browse the repository at this point in the history
  • Loading branch information
eriknw committed Jan 17, 2024
1 parent 8672534 commit 591fd2c
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 10 deletions.
4 changes: 4 additions & 0 deletions python/nx-cugraph/_nx_cugraph/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,13 @@
"house_x_graph",
"icosahedral_graph",
"in_degree_centrality",
"is_arborescence",
"is_branching",
"is_connected",
"is_forest",
"is_isolate",
"is_strongly_connected",
"is_tree",
"is_weakly_connected",
"isolates",
"k_truss",
Expand Down
4 changes: 3 additions & 1 deletion python/nx-cugraph/nx_cugraph/algorithms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2023, NVIDIA CORPORATION.
# Copyright (c) 2023-2024, NVIDIA CORPORATION.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
Expand All @@ -18,6 +18,7 @@
link_analysis,
shortest_paths,
traversal,
tree,
)
from .bipartite import complete_bipartite_graph
from .centrality import *
Expand All @@ -28,3 +29,4 @@
from .link_analysis import *
from .shortest_paths import *
from .traversal import *
from .tree.recognition import *
2 changes: 1 addition & 1 deletion python/nx-cugraph/nx_cugraph/algorithms/isolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ def isolates(G):
@networkx_algorithm(version_added="23.10")
def number_of_isolates(G):
G = _to_graph(G)
return _mark_isolates(G).sum().tolist()
return int(cp.count_nonzero(_mark_isolates(G)))
13 changes: 13 additions & 0 deletions python/nx-cugraph/nx_cugraph/algorithms/tree/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (c) 2024, NVIDIA CORPORATION.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .recognition import *
70 changes: 70 additions & 0 deletions python/nx-cugraph/nx_cugraph/algorithms/tree/recognition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright (c) 2024, NVIDIA CORPORATION.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import cupy as cp
import networkx as nx

import nx_cugraph as nxcg
from nx_cugraph.convert import _to_directed_graph, _to_graph
from nx_cugraph.utils import networkx_algorithm, not_implemented_for

__all__ = ["is_arborescence", "is_branching", "is_forest", "is_tree"]


@not_implemented_for("undirected")
@networkx_algorithm(plc="weakly_connected_components", version_added="24.02")
def is_arborescence(G):
G = _to_directed_graph(G)
return is_tree(G) and int(G._in_degrees_array().max()) <= 1


@not_implemented_for("undirected")
@networkx_algorithm(plc="weakly_connected_components", version_added="24.02")
def is_branching(G):
G = _to_directed_graph(G)
return is_forest(G) and int(G._in_degrees_array().max()) <= 1


@networkx_algorithm(plc="weakly_connected_components", version_added="24.02")
def is_forest(G):
G = _to_graph(G)
if len(G) == 0:
raise nx.NetworkXPointlessConcept("G has no nodes.")
if is_directed := G.is_directed():
connected_components = nxcg.weakly_connected_components
else:
connected_components = nxcg.connected_components
for components in connected_components(G):
node_ids = G._list_to_nodearray(list(components))
# TODO: create utilities for creating subgraphs
mask = cp.isin(G.src_indices, node_ids) & cp.isin(G.dst_indices, node_ids)
if is_directed:
if int(cp.count_nonzero(mask)) != len(components) - 1:
return False
else:
src_indices = G.src_indices[mask]
dst_indices = G.dst_indices[mask]
if int(cp.count_nonzero(src_indices <= dst_indices)) != len(components) - 1:
return False
return True


@networkx_algorithm(plc="weakly_connected_components", version_added="24.02")
def is_tree(G):
G = _to_graph(G)
if len(G) == 0:
raise nx.NetworkXPointlessConcept("G has no nodes.")
if G.is_directed():
is_connected = nxcg.is_weakly_connected
else:
is_connected = nxcg.is_connected
return len(G) - 1 == G.number_of_edges() and is_connected(G)
15 changes: 9 additions & 6 deletions python/nx-cugraph/nx_cugraph/classes/digraph.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2023, NVIDIA CORPORATION.
# Copyright (c) 2023-2024, NVIDIA CORPORATION.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
Expand All @@ -16,13 +16,14 @@

import cupy as cp
import networkx as nx
import numpy as np

import nx_cugraph as nxcg

from .graph import Graph

if TYPE_CHECKING: # pragma: no cover
from nx_cugraph.typing import NodeKey
from nx_cugraph.typing import AttrKey

__all__ = ["DiGraph"]

Expand All @@ -44,10 +45,8 @@ def to_networkx_class(cls) -> type[nx.DiGraph]:
return nx.DiGraph

@networkx_api
def number_of_edges(
self, u: NodeKey | None = None, v: NodeKey | None = None
) -> int:
if u is not None or v is not None:
def size(self, weight: AttrKey | None = None) -> int:
if weight is not None:
raise NotImplementedError
return self.src_indices.size

Expand All @@ -66,7 +65,11 @@ def reverse(self, copy: bool = True) -> DiGraph:
###################

def _in_degrees_array(self):
if self.dst_indices.size == 0:
return cp.zeros(self._N, dtype=np.int64)
return cp.bincount(self.dst_indices, minlength=self._N)

def _out_degrees_array(self):
if self.src_indices.size == 0:
return cp.zeros(self._N, dtype=np.int64)
return cp.bincount(self.src_indices, minlength=self._N)
4 changes: 3 additions & 1 deletion python/nx-cugraph/nx_cugraph/classes/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import cupy as cp

from nx_cugraph.convert import _to_graph
from nx_cugraph.utils import networkx_algorithm

Expand All @@ -20,4 +22,4 @@
def number_of_selfloops(G):
G = _to_graph(G)
is_selfloop = G.src_indices == G.dst_indices
return is_selfloop.sum().tolist()
return int(cp.count_nonzero(is_selfloop))
4 changes: 3 additions & 1 deletion python/nx-cugraph/nx_cugraph/classes/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ def size(self, weight: AttrKey | None = None) -> int:
if weight is not None:
raise NotImplementedError
# If no self-edges, then `self.src_indices.size // 2`
return int((self.src_indices <= self.dst_indices).sum())
return int(cp.count_nonzero(self.src_indices <= self.dst_indices))

@networkx_api
def to_directed(self, as_view: bool = False) -> nxcg.DiGraph:
Expand Down Expand Up @@ -733,6 +733,8 @@ def _become(self, other: Graph):
return self

def _degrees_array(self):
if self.src_indices.size == 0:
return cp.zeros(self._N, dtype=np.int64)
degrees = cp.bincount(self.src_indices, minlength=self._N)
if self.is_directed():
degrees += cp.bincount(self.dst_indices, minlength=self._N)
Expand Down

0 comments on commit 591fd2c

Please sign in to comment.