diff --git a/README.md b/README.md index 858da5ed6..ec61a2796 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ nx-cugraph requires the following: * NVIDIA GPU, Volta architecture or later, with [compute capability](https://developer.nvidia.com/cuda-gpus) 7.0+ * CUDA 11.4-11.8 or 12.0-12.5 * Python version 3.10, 3.11, or 3.12 - * NetworkX >= version 3.0 (version 3.4 or higher recommended) + * NetworkX >= version 3.2 (version 3.4 or higher recommended) More details about system requirements can be found in the [RAPIDS System Requirements documentation](https://docs.rapids.ai/install#system-req). diff --git a/_nx_cugraph/__init__.py b/_nx_cugraph/__init__.py index cbc22e033..2c899f855 100644 --- a/_nx_cugraph/__init__.py +++ b/_nx_cugraph/__init__.py @@ -270,7 +270,7 @@ def get_info(): - """Target of ``networkx.plugin_info`` entry point. + """Target of ``networkx.backend_info`` entry point. This tells NetworkX about the cugraph backend without importing nx_cugraph. """ @@ -330,7 +330,6 @@ def update_env_var(varname): update_env_var("NETWORKX_BACKEND_PRIORITY") # And for older NetworkX versions update_env_var("NETWORKX_AUTOMATIC_BACKENDS") # For NetworkX 3.2 - update_env_var("NETWORKX_GRAPH_CONVERT") # For NetworkX 3.0 and 3.1 # Automatically create nx-cugraph Graph from graph generators update_env_var("NETWORKX_BACKEND_PRIORITY_GENERATORS") # Run default NetworkX implementation (in >=3.4) if not implemented by nx-cugraph diff --git a/benchmarks/nx-cugraph/pytest-based/bench_algos.py b/benchmarks/nx-cugraph/pytest-based/bench_algos.py index 612f69d05..015318c5a 100644 --- a/benchmarks/nx-cugraph/pytest-based/bench_algos.py +++ b/benchmarks/nx-cugraph/pytest-based/bench_algos.py @@ -20,15 +20,6 @@ import nx_cugraph as nxcg -# Attempt to import the NetworkX dispatching module, which is only needed when -# testing with NX <3.2 in order to dynamically switch backends. NX >=3.2 allows -# the backend to be specified directly in the API call. -try: - from networkx.classes import backends # NX <3.2 -except ImportError: - backends = None - - ################################################################################ # Fixtures and params @@ -133,45 +124,10 @@ def nx_graph_from_dataset(dataset_obj): return G -def get_legacy_backend_wrapper(backend_name): - """ - Returns a callable that wraps an algo function with either the default - dispatcher (which dispatches based on input graph type), or the "testing" - dispatcher (which autoconverts and unconditionally dispatches). - This is only supported for NetworkX <3.2 - """ - backends.plugin_name = "cugraph" - orig_dispatch = backends._dispatch - testing_dispatch = backends.test_override_dispatch - - if backend_name == "cugraph": - dispatch = testing_dispatch - else: - dispatch = orig_dispatch - - def wrap_callable_for_dispatch(func, exhaust_returned_iterator=False): - # Networkx <3.2 registers functions when the dispatch decorator is - # applied (called) and errors if re-registered, so clear bookkeeping to - # allow it to be called repeatedly. - backends._registered_algorithms = {} - actual_func = dispatch(func) # returns the func the dispatcher picks - - def wrapper(*args, **kwargs): - retval = actual_func(*args, **kwargs) - if exhaust_returned_iterator: - retval = list(retval) - return retval - - return wrapper - - return wrap_callable_for_dispatch - - def get_backend_wrapper(backend_name): """ Returns a callable that wraps an algo function in order to set the "backend" kwarg on it. - This is only supported for NetworkX >= 3.2 """ def wrap_callable_for_dispatch(func, exhaust_returned_iterator=False): @@ -210,12 +166,7 @@ def backend_wrapper(request): if backend_name == "cugraph-preconverted": actual_backend_name = "cugraph" - # NX <3.2 does not support the backends= kwarg, so the backend must be - # enabled differently - if backends is not None: - wrapper = get_legacy_backend_wrapper(actual_backend_name) - else: - wrapper = get_backend_wrapper(actual_backend_name) + wrapper = get_backend_wrapper(actual_backend_name) wrapper.backend_name = backend_name return wrapper diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml index 08624db32..b220e0b7f 100644 --- a/conda/environments/all_cuda-118_arch-x86_64.yaml +++ b/conda/environments/all_cuda-118_arch-x86_64.yaml @@ -12,7 +12,7 @@ dependencies: - graphviz - ipython - nbsphinx -- networkx>=3.0 +- networkx>=3.2 - numpy>=1.23,<3.0a0 - numpydoc - pandas diff --git a/conda/environments/all_cuda-125_arch-x86_64.yaml b/conda/environments/all_cuda-125_arch-x86_64.yaml index d85df9d80..f989626c5 100644 --- a/conda/environments/all_cuda-125_arch-x86_64.yaml +++ b/conda/environments/all_cuda-125_arch-x86_64.yaml @@ -12,7 +12,7 @@ dependencies: - graphviz - ipython - nbsphinx -- networkx>=3.0 +- networkx>=3.2 - numpy>=1.23,<3.0a0 - numpydoc - pandas diff --git a/conda/recipes/nx-cugraph/meta.yaml b/conda/recipes/nx-cugraph/meta.yaml index 08f53106c..a006d95fd 100644 --- a/conda/recipes/nx-cugraph/meta.yaml +++ b/conda/recipes/nx-cugraph/meta.yaml @@ -23,7 +23,7 @@ requirements: - setuptools>=61.0.0 run: - pylibcugraph ={{ minor_version }} - - networkx >=3.0 + - networkx >=3.2 - cupy >=12.0.0 - python diff --git a/dependencies.yaml b/dependencies.yaml index dddfb027f..bf2b7f714 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -177,7 +177,7 @@ dependencies: common: - output_types: [conda, pyproject] packages: - - networkx>=3.0 + - networkx>=3.2 - &numpy numpy>=1.23,<3.0a0 test_python_nx_cugraph: common: diff --git a/docs/nx-cugraph/source/index.rst b/docs/nx-cugraph/source/index.rst index 259a36b8f..c9243f63e 100644 --- a/docs/nx-cugraph/source/index.rst +++ b/docs/nx-cugraph/source/index.rst @@ -23,7 +23,7 @@ Required hardware/software for cuGraph and `RAPIDS 7.0+`_ * CUDA 11.2-11.8, 12.0-12.5 * Python version 3.10, 3.11, or 3.12 - * NetworkX version 3.0 or newer in order to use use the nx-cuGraph backend. NetworkX version 3.4 or newer is recommended. (`see below <#cugraph-using-networkx-code>`). + * NetworkX version 3.2 or newer in order to use use the nx-cuGraph backend. NetworkX version 3.4 or newer is recommended. (`see below <#cugraph-using-networkx-code>`). Installation The latest RAPIDS System Requirements documentation is located `here `_. diff --git a/docs/nx-cugraph/source/nx_cugraph/installation.md b/docs/nx-cugraph/source/nx_cugraph/installation.md index 8d221f16f..5260b2282 100644 --- a/docs/nx-cugraph/source/nx_cugraph/installation.md +++ b/docs/nx-cugraph/source/nx_cugraph/installation.md @@ -10,7 +10,7 @@ This guide describes how to install ``nx-cugraph`` and use it in your workflows. - **Volta architecture or later NVIDIA GPU, with [compute capability](https://developer.nvidia.com/cuda-gpus) 7.0+** - **[CUDA](https://docs.nvidia.com/cuda/index.html) 11.2, 11.4, 11.5, 11.8, 12.0, 12.2, or 12.5** - **Python >= 3.10** - - **[NetworkX](https://networkx.org/documentation/stable/install.html#) >= 3.0 (version 3.2 or higher recommended)** + - **[NetworkX](https://networkx.org/documentation/stable/install.html#) >= 3.2 (version 3.4 or higher recommended)** More details about system requirements can be found in the [RAPIDS System Requirements Documentation](https://docs.rapids.ai/install#system-req). diff --git a/nx_cugraph/classes/graph.py b/nx_cugraph/classes/graph.py index ac075f730..11c6215d8 100644 --- a/nx_cugraph/classes/graph.py +++ b/nx_cugraph/classes/graph.py @@ -86,8 +86,7 @@ def clear(self) -> None: class Graph(nx.Graph): # Tell networkx to dispatch calls with this object to nx-cugraph - __networkx_backend__: ClassVar[str] = "cugraph" # nx >=3.2 - __networkx_plugin__: ClassVar[str] = "cugraph" # nx <3.2 + __networkx_backend__: ClassVar[str] = "cugraph" # Core attributes of NetowkrX graphs that will be copied and cleared as appropriate. # These attributes comprise the edge and node data model for NetworkX graphs. @@ -513,8 +512,7 @@ def from_dcsc( class CudaGraph: # Tell networkx to dispatch calls with this object to nx-cugraph - __networkx_backend__: ClassVar[str] = "cugraph" # nx >=3.2 - __networkx_plugin__: ClassVar[str] = "cugraph" # nx <3.2 + __networkx_backend__: ClassVar[str] = "cugraph" # Allow networkx dispatch machinery to cache conversions. # This means we should clear the cache if we ever mutate the object! diff --git a/nx_cugraph/interface.py b/nx_cugraph/interface.py index ff02de4cc..0f4190fa7 100644 --- a/nx_cugraph/interface.py +++ b/nx_cugraph/interface.py @@ -13,7 +13,6 @@ from __future__ import annotations import os -import sys import networkx as nx @@ -24,19 +23,10 @@ class BackendInterface: # Required conversions @staticmethod - def convert_from_nx(graph, *args, edge_attrs=None, weight=None, **kwargs): - if weight is not None: - # MAINT: networkx 3.0, 3.1 - # For networkx 3.0 and 3.1 compatibility - if edge_attrs is not None: - raise TypeError( - "edge_attrs and weight arguments should not both be given" - ) - edge_attrs = {weight: 1} + def convert_from_nx(graph, *args, **kwargs): return nxcg.from_networkx( graph, *args, - edge_attrs=edge_attrs, use_compat_graph=_nxver < (3, 3) or nx.config.backends.cugraph.use_compat_graphs, **kwargs, @@ -77,13 +67,11 @@ def key(testpath): fallback = use_compat_graph or nx.utils.backends._dispatchable._fallback_to_nx # Reasons for xfailing - # For nx version <= 3.1 - no_weights = "weighted implementation not currently supported" - no_multigraph = "multigraphs not currently supported" # For nx version <= 3.2 nx_cugraph_in_test_setup = ( "nx-cugraph Graph is incompatible in test setup in nx versions < 3.3" ) + different_iteration_order = "Different graph data iteration order" # For all versions louvain_different = "Louvain may be different due to RNG" sssp_path_different = "sssp may choose a different valid path" @@ -262,166 +250,55 @@ def key(testpath): key( "test_vf2pp_helpers.py:TestDiGraphTinoutUpdating.test_restoring" ): nx_cugraph_in_test_setup, - } - ) - - if _nxver < (3, 2): - # MAINT: networkx 3.0, 3.1 - # NetworkX 3.2 added the ability to "fallback to nx" if backend algorithms - # raise NotImplementedError or `can_run` returns False. The tests below - # exercise behavior we have not implemented yet, so we mark them as xfail - # for previous versions of NX. - xfail.update( - { - key( - "test_agraph.py:TestAGraph.test_no_warnings_raised" - ): "pytest.warn(None) deprecated", - key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality.test_K5" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality.test_P3_normalized" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality.test_P3" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality.test_krackhardt_kite_graph" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality." - "test_krackhardt_kite_graph_normalized" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality." - "test_florentine_families_graph" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality.test_les_miserables_graph" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality.test_ladder_graph" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality.test_G" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality.test_G2" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality.test_G3" - ): no_multigraph, + # Different iteration key( - "test_betweenness_centrality.py:" - "TestWeightedBetweennessCentrality.test_G4" - ): no_multigraph, + "test_cycles.py:TestMinimumCycleBasis." + "test_gh6787_and_edge_attribute_names" + ): different_iteration_order, key( - "test_betweenness_centrality.py:" - "TestWeightedEdgeBetweennessCentrality.test_K5" - ): no_weights, + "test_euler.py:TestEulerianCircuit." + "test_eulerian_circuit_cycle" + ): different_iteration_order, key( - "test_betweenness_centrality.py:" - "TestWeightedEdgeBetweennessCentrality.test_C4" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedEdgeBetweennessCentrality.test_P4" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedEdgeBetweennessCentrality.test_balanced_tree" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedEdgeBetweennessCentrality.test_weighted_graph" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedEdgeBetweennessCentrality." - "test_normalized_weighted_graph" - ): no_weights, - key( - "test_betweenness_centrality.py:" - "TestWeightedEdgeBetweennessCentrality.test_weighted_multigraph" - ): no_multigraph, - key( - "test_betweenness_centrality.py:" - "TestWeightedEdgeBetweennessCentrality." - "test_normalized_weighted_multigraph" - ): no_multigraph, + "test_gml.py:TestGraph.test_special_float_label" + ): different_iteration_order, } ) else: + xfail.update( + { + key("test_louvain.py:test_max_level"): louvain_different, + } + ) + + xfail.update( + { + key("test_louvain.py:test_karate_club_partition"): louvain_different, + key("test_louvain.py:test_none_weight_param"): louvain_different, + key("test_louvain.py:test_multigraph"): louvain_different, + # See networkx#6630 + key( + "test_louvain.py:test_undirected_selfloops" + ): "self-loops not handled in Louvain", + } + ) + if not fallback: xfail.update( { key( - "test_louvain.py:test_karate_club_partition" - ): louvain_different, - key("test_louvain.py:test_none_weight_param"): louvain_different, - key("test_louvain.py:test_multigraph"): louvain_different, - # See networkx#6630 + "test_convert_pandas.py:TestConvertPandas." + "test_from_edgelist_multi_attr_incl_target" + ): no_string_dtype, + key( + "test_convert_pandas.py:TestConvertPandas." + "test_from_edgelist_multidigraph_and_edge_attr" + ): no_string_dtype, key( - "test_louvain.py:test_undirected_selfloops" - ): "self-loops not handled in Louvain", + "test_convert_pandas.py:TestConvertPandas." + "test_from_edgelist_int_attr_name" + ): no_string_dtype, } ) - if sys.version_info[:2] == (3, 9): - # This test is sensitive to RNG, which depends on Python version - xfail[key("test_louvain.py:test_threshold")] = ( - "Louvain does not support seed parameter" - ) - if _nxver >= (3, 2): - if not fallback: - xfail.update( - { - key( - "test_convert_pandas.py:TestConvertPandas." - "test_from_edgelist_multi_attr_incl_target" - ): no_string_dtype, - key( - "test_convert_pandas.py:TestConvertPandas." - "test_from_edgelist_multidigraph_and_edge_attr" - ): no_string_dtype, - key( - "test_convert_pandas.py:TestConvertPandas." - "test_from_edgelist_int_attr_name" - ): no_string_dtype, - } - ) - if _nxver[1] == 2: - different_iteration_order = "Different graph data iteration order" - xfail.update( - { - key( - "test_cycles.py:TestMinimumCycleBasis." - "test_gh6787_and_edge_attribute_names" - ): different_iteration_order, - key( - "test_euler.py:TestEulerianCircuit." - "test_eulerian_circuit_cycle" - ): different_iteration_order, - key( - "test_gml.py:TestGraph.test_special_float_label" - ): different_iteration_order, - } - ) - elif _nxver[1] >= 3: - xfail.update( - { - key("test_louvain.py:test_max_level"): louvain_different, - } - ) if _nxver == (3, 4, 2): xfail[key("test_pylab.py:test_return_types")] = "Ephemeral NetworkX bug" @@ -479,11 +356,9 @@ def key(testpath): @classmethod def can_run(cls, name, args, kwargs): """Can this backend run the specified algorithms with the given arguments?""" - # TODO: drop hasattr when networkx 3.0 support is dropped - return hasattr(cls, name) and getattr(cls, name).can_run(*args, **kwargs) + return getattr(cls, name).can_run(*args, **kwargs) @classmethod def should_run(cls, name, args, kwargs): """Should this backend run the specified algorithms with the given arguments?""" - # TODO: drop hasattr when networkx 3.0 support is dropped - return hasattr(cls, name) and getattr(cls, name).should_run(*args, **kwargs) + return getattr(cls, name).should_run(*args, **kwargs) diff --git a/nx_cugraph/tests/test_bfs.py b/nx_cugraph/tests/test_bfs.py index ad2c62c1f..52edfb2f6 100644 --- a/nx_cugraph/tests/test_bfs.py +++ b/nx_cugraph/tests/test_bfs.py @@ -11,12 +11,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import networkx as nx -import pytest - -from nx_cugraph import _nxver - -if _nxver < (3, 2): - pytest.skip("Need NetworkX >=3.2 to test clustering", allow_module_level=True) def test_generic_bfs_edges(): diff --git a/nx_cugraph/tests/test_cluster.py b/nx_cugraph/tests/test_cluster.py index fd8e1b3cf..28546dbd3 100644 --- a/nx_cugraph/tests/test_cluster.py +++ b/nx_cugraph/tests/test_cluster.py @@ -11,12 +11,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import networkx as nx -import pytest - -from nx_cugraph import _nxver - -if _nxver < (3, 2): - pytest.skip("Need NetworkX >=3.2 to test clustering", allow_module_level=True) def test_selfloops(): diff --git a/nx_cugraph/tests/test_convert.py b/nx_cugraph/tests/test_convert.py index 3d109af8a..a35889e2f 100644 --- a/nx_cugraph/tests/test_convert.py +++ b/nx_cugraph/tests/test_convert.py @@ -207,9 +207,7 @@ def test_convert(graph_class): cp.testing.assert_array_equal(Gcg.node_values["bar"], [0, 1000, 0]) assert Gcg.node_masks == {} - with pytest.raises( - TypeError, match="edge_attrs and weight arguments should not both be given" - ): + with pytest.raises(TypeError, match="unexpected keyword argument 'weight'"): interface.BackendInterface.convert_from_nx(G, edge_attrs={"x": 1}, weight="x") with pytest.raises(TypeError, match="Expected networkx.Graph"): nxcg.from_networkx({}) diff --git a/nx_cugraph/tests/test_ego_graph.py b/nx_cugraph/tests/test_ego_graph.py index f3d0a8d37..0fb4c4960 100644 --- a/nx_cugraph/tests/test_ego_graph.py +++ b/nx_cugraph/tests/test_ego_graph.py @@ -18,9 +18,6 @@ from .testing_utils import assert_graphs_equal -if _nxver < (3, 2): - pytest.skip("Need NetworkX >=3.2 to test ego_graph", allow_module_level=True) - @pytest.mark.parametrize( "create_using", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph] diff --git a/nx_cugraph/tests/test_generators.py b/nx_cugraph/tests/test_generators.py index 5c405f1c9..86f521499 100644 --- a/nx_cugraph/tests/test_generators.py +++ b/nx_cugraph/tests/test_generators.py @@ -19,9 +19,6 @@ from .testing_utils import assert_graphs_equal -if _nxver < (3, 2): - pytest.skip("Need NetworkX >=3.2 to test generators", allow_module_level=True) - def compare(name, create_using, *args, is_vanilla=False): exc1 = exc2 = None diff --git a/nx_cugraph/tests/test_match_api.py b/nx_cugraph/tests/test_match_api.py index 1a61c69b3..9c917e07f 100644 --- a/nx_cugraph/tests/test_match_api.py +++ b/nx_cugraph/tests/test_match_api.py @@ -25,21 +25,15 @@ def test_match_signature_and_names(): if not isinstance(func, networkx_algorithm): continue - # nx version >=3.2 uses utils.backends, version >=3.0,<3.2 uses classes.backends - is_nx_30_or_31 = hasattr(nx.classes, "backends") - nx_backends = nx.classes.backends if is_nx_30_or_31 else nx.utils.backends + nx_backends = nx.utils.backends - if is_nx_30_or_31 and name in {"louvain_communities"}: + if name in {"louvain_communities"}: continue if name not in nx_backends._registered_algorithms: print(f"{name} not dispatched from networkx") continue dispatchable_func = nx_backends._registered_algorithms[name] - # nx version >=3.2 uses orig_func, version >=3.0,<3.2 uses _orig_func - if is_nx_30_or_31: - orig_func = dispatchable_func._orig_func - else: - orig_func = dispatchable_func.orig_func + orig_func = dispatchable_func.orig_func # Matching signatures? orig_sig = inspect.signature(orig_func) @@ -67,11 +61,7 @@ def test_match_signature_and_names(): assert func.__name__ == dispatchable_func.__name__ == orig_func.__name__, name # Matching dispatch names? - # nx version >=3.2 uses name, version >=3.0,<3.2 uses dispatchname - if is_nx_30_or_31: - dispatchname = dispatchable_func.dispatchname - else: - dispatchname = dispatchable_func.name + dispatchname = dispatchable_func.name assert func.name == dispatchname, name # Matching modules (i.e., where function defined)? diff --git a/pyproject.toml b/pyproject.toml index 300e17a7a..de61aa606 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ classifiers = [ ] dependencies = [ "cupy-cuda11x>=12.0.0", - "networkx>=3.0", + "networkx>=3.2", "numpy>=1.23,<3.0a0", "pylibcugraph==24.12.*,>=0.0.0a0", ] # This list was generated by `rapids-dependency-file-generator`. To make changes, edit dependencies.yaml and run `rapids-dependency-file-generator`. @@ -54,14 +54,6 @@ test = [ Homepage = "https://github.com/rapidsai/nx-cugraph" Documentation = "https://docs.rapids.ai/api/cugraph/stable/" -# "plugin" used in nx version < 3.2 -[project.entry-points."networkx.plugins"] -cugraph = "nx_cugraph.interface:BackendInterface" - -[project.entry-points."networkx.plugin_info"] -cugraph = "_nx_cugraph:get_info" - -# "backend" used in nx version >= 3.2 [project.entry-points."networkx.backends"] cugraph = "nx_cugraph.interface:BackendInterface" diff --git a/run_nx_tests.sh b/run_nx_tests.sh index 5fb173cf9..108554e36 100755 --- a/run_nx_tests.sh +++ b/run_nx_tests.sh @@ -2,10 +2,6 @@ # # Copyright (c) 2023-2024, NVIDIA CORPORATION. # -# NETWORKX_GRAPH_CONVERT=cugraph -# Used by networkx versions 3.0 and 3.1 -# Must be set to "cugraph" to test the nx-cugraph backend. -# # NETWORKX_TEST_BACKEND=cugraph # Replaces NETWORKX_GRAPH_CONVERT for networkx versions >=3.2 # Must be set to "cugraph" to test the nx-cugraph backend. @@ -29,7 +25,6 @@ # so be mindful of its contents and the CWD when running. # FIXME: should something be added to detect/prevent the above? set -e -NETWORKX_GRAPH_CONVERT=cugraph \ NETWORKX_TEST_BACKEND=cugraph \ NETWORKX_FALLBACK_TO_NX=True \ pytest \