Skip to content

Commit

Permalink
Merge branch 'branch-23.12' into cugraph_dgl
Browse files Browse the repository at this point in the history
  • Loading branch information
drivanov authored Oct 26, 2023
2 parents 8d379ec + c90fd94 commit bf61d80
Show file tree
Hide file tree
Showing 24 changed files with 339 additions and 134 deletions.
2 changes: 1 addition & 1 deletion ci/test_wheel_cugraph.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ RAPIDS_PY_WHEEL_NAME="pylibcugraph_${RAPIDS_PY_CUDA_SUFFIX}" rapids-download-whe
python -m pip install --no-deps ./local-pylibcugraph-dep/pylibcugraph*.whl

# Always install latest dask for testing
python -m pip install git+https://github.com/dask/dask.git@2023.9.2 git+https://github.com/dask/distributed.git@2023.9.2
python -m pip install git+https://github.com/dask/dask.git@main git+https://github.com/dask/distributed.git@main

./ci/test_wheel.sh cugraph python/cugraph
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-118_arch-x86_64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dependencies:
- cupy>=12.0.0
- cxx-compiler
- cython>=3.0.0
- dask-core==2023.9.2
- dask-core>=2023.9.2
- dask-cuda==23.12.*
- dask-cudf==23.12.*
- dask>=2023.7.1
Expand Down
2 changes: 1 addition & 1 deletion conda/environments/all_cuda-120_arch-x86_64.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dependencies:
- cupy>=12.0.0
- cxx-compiler
- cython>=3.0.0
- dask-core==2023.9.2
- dask-core>=2023.9.2
- dask-cuda==23.12.*
- dask-cudf==23.12.*
- dask>=2023.7.1
Expand Down
2 changes: 1 addition & 1 deletion conda/recipes/cugraph-pyg/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ requirements:
- python
- scikit-build >=0.13.1
run:
- distributed ==2023.9.2
- distributed >=2023.9.2
- numba >=0.57
- numpy >=1.21
- python
Expand Down
2 changes: 1 addition & 1 deletion conda/recipes/cugraph-service/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ outputs:
- cupy >=12.0.0
- dask-cuda ={{ minor_version }}
- dask-cudf ={{ minor_version }}
- distributed ==2023.9.2
- distributed >=2023.9.2
- numba >=0.57
- numpy >=1.21
- python
Expand Down
6 changes: 3 additions & 3 deletions conda/recipes/cugraph/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ requirements:
- cupy >=12.0.0
- dask-cuda ={{ minor_version }}
- dask-cudf ={{ minor_version }}
- dask ==2023.9.2
- dask-core ==2023.9.2
- distributed ==2023.9.2
- dask >=2023.9.2
- dask-core >=2023.9.2
- distributed >=2023.9.2
- fsspec>=0.6.0
- libcugraph ={{ version }}
- pylibcugraph ={{ version }}
Expand Down
2 changes: 1 addition & 1 deletion dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ dependencies:
- output_types: conda
packages:
- aiohttp
- &dask-core_conda dask-core==2023.9.2
- &dask-core_conda dask-core>=2023.9.2
- fsspec>=0.6.0
- libcudf==23.12.*
- requests
Expand Down
5 changes: 5 additions & 0 deletions python/nx-cugraph/_nx_cugraph/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,16 @@
"functions": {
# BEGIN: functions
"betweenness_centrality",
"degree_centrality",
"edge_betweenness_centrality",
"in_degree_centrality",
"is_isolate",
"isolates",
"k_truss",
"louvain_communities",
"number_of_isolates",
"number_of_selfloops",
"out_degree_centrality",
# END: functions
},
"extra_docstrings": {
Expand Down
6 changes: 3 additions & 3 deletions python/nx-cugraph/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ repos:
- id: pyupgrade
args: [--py39-plus]
- repo: https://github.com/psf/black
rev: 23.9.1
rev: 23.10.0
hooks:
- id: black
# - id: black-jupyter
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.292
rev: v0.1.1
hooks:
- id: ruff
args: [--fix-only, --show-fixes]
Expand All @@ -77,7 +77,7 @@ repos:
additional_dependencies: [tomli]
files: ^(nx_cugraph|docs)/
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.292
rev: v0.1.1
hooks:
- id: ruff
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand Down
1 change: 1 addition & 0 deletions python/nx-cugraph/nx_cugraph/algorithms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
# limitations under the License.
from . import centrality, community
from .centrality import *
from .core import *
from .isolate import *
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from .betweenness import *
from .degree_alg import *
48 changes: 48 additions & 0 deletions python/nx-cugraph/nx_cugraph/algorithms/centrality/degree_alg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright (c) 2023, 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 nx_cugraph.convert import _to_directed_graph, _to_graph
from nx_cugraph.utils import networkx_algorithm, not_implemented_for

__all__ = ["degree_centrality", "in_degree_centrality", "out_degree_centrality"]


@networkx_algorithm
def degree_centrality(G):
G = _to_graph(G)
if len(G) <= 1:
return dict.fromkeys(G, 1)
deg = G._degrees_array()
centrality = deg * (1 / (len(G) - 1))
return G._nodearray_to_dict(centrality)


@not_implemented_for("undirected")
@networkx_algorithm
def in_degree_centrality(G):
G = _to_directed_graph(G)
if len(G) <= 1:
return dict.fromkeys(G, 1)
deg = G._in_degrees_array()
centrality = deg * (1 / (len(G) - 1))
return G._nodearray_to_dict(centrality)


@not_implemented_for("undirected")
@networkx_algorithm
def out_degree_centrality(G):
G = _to_directed_graph(G)
if len(G) <= 1:
return dict.fromkeys(G, 1)
deg = G._out_degrees_array()
centrality = deg * (1 / (len(G) - 1))
return G._nodearray_to_dict(centrality)
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def louvain_communities(
# NetworkX allows both directed and undirected, but cugraph only allows undirected.
seed = _seed_to_int(seed) # Unused, but ensure it's valid for future compatibility
G = _to_undirected_graph(G, weight)
if G.row_indices.size == 0:
if G.src_indices.size == 0:
# TODO: PLC doesn't handle empty graphs gracefully!
return [{key} for key in G._nodeiter_to_iter(range(len(G)))]
if max_level is None:
Expand Down
96 changes: 96 additions & 0 deletions python/nx-cugraph/nx_cugraph/algorithms/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Copyright (c) 2023, 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 numpy as np
import pylibcugraph as plc

import nx_cugraph as nxcg
from nx_cugraph.utils import networkx_algorithm, not_implemented_for

__all__ = ["k_truss"]


@not_implemented_for("directed")
@not_implemented_for("multigraph")
@networkx_algorithm
def k_truss(G, k):
if is_nx := isinstance(G, nx.Graph):
G = nxcg.from_networkx(G, preserve_all_attrs=True)
if nxcg.number_of_selfloops(G) > 0:
raise nx.NetworkXError(
"Input graph has self loops which is not permitted; "
"Consider using G.remove_edges_from(nx.selfloop_edges(G))."
)
# TODO: create renumbering helper function(s)
if k < 3:
# k-truss graph is comprised of nodes incident on k-2 triangles, so k<3 is a
# boundary condition. Here, all we need to do is drop nodes with zero degree.
# Technically, it would be okay to delete this branch of code, because
# plc.k_truss_subgraph behaves the same for 0 <= k < 3. We keep this branch b/c
# it's faster and has an "early return" if there are no nodes with zero degree.
degrees = G._degrees_array()
# Renumber step 0: node indices
node_indices = degrees.nonzero()[0]
if degrees.size == node_indices.size:
# No change
return G if is_nx else G.copy()
src_indices = G.src_indices
dst_indices = G.dst_indices
# Renumber step 1: edge values (no changes needed)
edge_values = {key: val.copy() for key, val in G.edge_values.items()}
edge_masks = {key: val.copy() for key, val in G.edge_masks.items()}
else:
# int dtype for edge_indices would be preferred
edge_indices = cp.arange(G.src_indices.size, dtype=np.float64)
src_indices, dst_indices, edge_indices, _ = plc.k_truss_subgraph(
resource_handle=plc.ResourceHandle(),
graph=G._get_plc_graph(edge_array=edge_indices),
k=k,
do_expensive_check=False,
)
# Renumber step 0: node indices
node_indices = cp.unique(cp.concatenate([src_indices, dst_indices]))
# Renumber step 1: edge values
edge_indices = edge_indices.astype(np.int64)
edge_values = {key: val[edge_indices] for key, val in G.edge_values.items()}
edge_masks = {key: val[edge_indices] for key, val in G.edge_masks.items()}
# Renumber step 2: edge indices
mapper = cp.zeros(len(G), src_indices.dtype)
mapper[node_indices] = cp.arange(node_indices.size, dtype=mapper.dtype)
src_indices = mapper[src_indices]
dst_indices = mapper[dst_indices]
# Renumber step 3: node values
node_values = {key: val[node_indices] for key, val in G.node_values.items()}
node_masks = {key: val[node_indices] for key, val in G.node_masks.items()}
# Renumber step 4: key_to_id
if (id_to_key := G.id_to_key) is not None:
key_to_id = {
id_to_key[old_index]: new_index
for new_index, old_index in enumerate(node_indices.tolist())
}
else:
key_to_id = None
# Same as calling `G.from_coo`, but use __class__ to indicate it's a classmethod.
new_graph = G.__class__.from_coo(
node_indices.size,
src_indices,
dst_indices,
edge_values,
edge_masks,
node_values,
node_masks,
key_to_id=key_to_id,
)
new_graph.graph.update(G.graph)
return new_graph
8 changes: 4 additions & 4 deletions python/nx-cugraph/nx_cugraph/algorithms/isolate.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ def is_isolate(G, n):
G = _to_graph(G)
index = n if G.key_to_id is None else G.key_to_id[n]
return not (
(G.row_indices == index).any().tolist()
(G.src_indices == index).any().tolist()
or G.is_directed()
and (G.col_indices == index).any().tolist()
and (G.dst_indices == index).any().tolist()
)


def _mark_isolates(G) -> cp.ndarray[bool]:
"""Return a boolean mask array indicating indices of isolated nodes."""
mark_isolates = cp.ones(len(G), bool)
mark_isolates[G.row_indices] = False
mark_isolates[G.src_indices] = False
if G.is_directed():
mark_isolates[G.col_indices] = False
mark_isolates[G.dst_indices] = False
return mark_isolates


Expand Down
5 changes: 3 additions & 2 deletions python/nx-cugraph/nx_cugraph/classes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from .graph import Graph
from .digraph import DiGraph
from .multigraph import MultiGraph
from .multidigraph import MultiDiGraph

from .digraph import DiGraph # isort:skip
from .multidigraph import MultiDiGraph # isort:skip
from .function import *
13 changes: 12 additions & 1 deletion python/nx-cugraph/nx_cugraph/classes/digraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from typing import TYPE_CHECKING

import cupy as cp
import networkx as nx

import nx_cugraph as nxcg
Expand Down Expand Up @@ -48,7 +49,7 @@ def number_of_edges(
) -> int:
if u is not None or v is not None:
raise NotImplementedError
return self.row_indices.size
return self.src_indices.size

##########################
# NetworkX graph methods #
Expand All @@ -59,3 +60,13 @@ def reverse(self, copy: bool = True) -> DiGraph:
return self._copy(not copy, self.__class__, reverse=True)

# Many more methods to implement...

###################
# Private methods #
###################

def _in_degrees_array(self):
return cp.bincount(self.dst_indices, minlength=self._N)

def _out_degrees_array(self):
return cp.bincount(self.src_indices, minlength=self._N)
23 changes: 23 additions & 0 deletions python/nx-cugraph/nx_cugraph/classes/function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (c) 2023, 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 nx_cugraph.convert import _to_graph
from nx_cugraph.utils import networkx_algorithm

__all__ = ["number_of_selfloops"]


@networkx_algorithm
def number_of_selfloops(G):
G = _to_graph(G)
is_selfloop = G.src_indices == G.dst_indices
return is_selfloop.sum().tolist()
Loading

0 comments on commit bf61d80

Please sign in to comment.