From 3caced294b8eb2dba29e074b8940acd278dab8e6 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Fri, 25 Aug 2023 15:36:03 -0500 Subject: [PATCH 01/11] Update to work with new networkx dispatching (#68) --- .github/workflows/publish_pypi.yml | 2 +- .github/workflows/test.yml | 5 +- .pre-commit-config.yaml | 30 +++++---- graphblas_algorithms/interface.py | 69 ++++++++++++++++++--- graphblas_algorithms/tests/test_match_nx.py | 24 ++++++- pyproject.toml | 2 + run_nx_tests.sh | 7 ++- scripts/bench.py | 4 +- scripts/download_data.py | 2 +- 9 files changed, 113 insertions(+), 32 deletions(-) diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index 7841b5b..8ff188b 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -35,7 +35,7 @@ jobs: - name: Check with twine run: python -m twine check --strict dist/* - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.6 + uses: pypa/gh-action-pypi-publish@v1.8.10 with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a1b79e..103821c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: activate-environment: testing - name: Install dependencies run: | - conda install -c conda-forge python-graphblas scipy pandas pytest-cov pytest-randomly + conda install -c conda-forge python-graphblas scipy pandas pytest-cov pytest-randomly pytest-mpl # matplotlib lxml pygraphviz pydot sympy # Extra networkx deps we don't need yet pip install git+https://github.com/networkx/networkx.git@main --no-deps pip install -e . --no-deps @@ -39,7 +39,8 @@ jobs: python -c 'import sys, graphblas_algorithms; assert "networkx" not in sys.modules' coverage run --branch -m pytest --color=yes -v --check-structure coverage report - NETWORKX_GRAPH_CONVERT=graphblas pytest --color=yes --pyargs networkx --cov --cov-append + # NETWORKX_GRAPH_CONVERT=graphblas pytest --color=yes --pyargs networkx --cov --cov-append + ./run_nx_tests.sh --color=yes --cov --cov-append coverage report coverage xml - name: Coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c0c02d4..6b08ce0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ ci: # See: https://pre-commit.ci/#configuration autofix_prs: false - autoupdate_schedule: monthly + autoupdate_schedule: quarterly skip: [no-commit-to-branch] fail_fast: true default_language_version: @@ -17,21 +17,27 @@ repos: rev: v4.4.0 hooks: - id: check-added-large-files + - id: check-case-conflict + - id: check-merge-conflict + - id: check-symlinks - id: check-ast - id: check-toml - id: check-yaml - id: debug-statements - id: end-of-file-fixer + exclude_types: [svg] - id: mixed-line-ending - id: trailing-whitespace + - id: name-tests-test + args: ["--pytest-test-first"] - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.13 + rev: v0.14 hooks: - id: validate-pyproject name: Validate pyproject.toml # I don't yet trust ruff to do what autoflake does - repo: https://github.com/PyCQA/autoflake - rev: v2.1.1 + rev: v2.2.0 hooks: - id: autoflake args: [--in-place] @@ -40,7 +46,7 @@ repos: hooks: - id: isort - repo: https://github.com/asottile/pyupgrade - rev: v3.4.0 + rev: v3.10.1 hooks: - id: pyupgrade args: [--py38-plus] @@ -50,38 +56,38 @@ repos: - id: auto-walrus args: [--line-length, "100"] - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.7.0 hooks: - id: black # - id: black-jupyter - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.270 + rev: v0.0.285 hooks: - id: ruff args: [--fix-only, --show-fixes] - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 additional_dependencies: &flake8_dependencies # These versions need updated manually - - flake8==6.0.0 - - flake8-bugbear==23.5.9 + - flake8==6.1.0 + - flake8-bugbear==23.7.10 - flake8-simplify==0.20.0 - repo: https://github.com/asottile/yesqa - rev: v1.4.0 + rev: v1.5.0 hooks: - id: yesqa additional_dependencies: *flake8_dependencies - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 + rev: v2.2.5 hooks: - id: codespell types_or: [python, rst, markdown] additional_dependencies: [tomli] files: ^(graphblas_algorithms|docs)/ - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.270 + rev: v0.0.285 hooks: - id: ruff # `pyroma` may help keep our package standards up to date if best practices change. diff --git a/graphblas_algorithms/interface.py b/graphblas_algorithms/interface.py index a43b520..1d8c283 100644 --- a/graphblas_algorithms/interface.py +++ b/graphblas_algorithms/interface.py @@ -171,20 +171,71 @@ class Dispatcher: # End auto-generated code: dispatch @staticmethod - def convert_from_nx(graph, weight=None, *, name=None): + def convert_from_nx( + graph, + edge_attrs=None, + node_attrs=None, + preserve_edge_attrs=False, + preserve_node_attrs=False, + preserve_graph_attrs=False, + name=None, + graph_name=None, + *, + weight=None, # For nx.__version__ <= 3.1 + ): import networkx as nx from .classes import DiGraph, Graph, MultiDiGraph, MultiGraph + if preserve_edge_attrs: + if graph.is_multigraph(): + attrs = set().union( + *( + datadict + for nbrs in graph._adj.values() + for keydict in nbrs.values() + for datadict in keydict.values() + ) + ) + else: + attrs = set().union( + *(datadict for nbrs in graph._adj.values() for datadict in nbrs.values()) + ) + if len(attrs) == 1: + [attr] = attrs + edge_attrs = {attr: None} + elif attrs: + raise NotImplementedError("`preserve_edge_attrs=True` is not fully implemented") + if node_attrs: + raise NotImplementedError("non-None `node_attrs` is not yet implemented") + if preserve_node_attrs: + attrs = set().union(*(datadict for node, datadict in graph.nodes(data=True))) + if attrs: + raise NotImplementedError("`preserve_node_attrs=True` is not implemented") + if edge_attrs: + if len(edge_attrs) > 1: + raise NotImplementedError( + "Multiple edge attributes is not implemented (bad value for edge_attrs)" + ) + if weight is not None: + raise TypeError("edge_attrs and weight both given") + [[weight, default]] = edge_attrs.items() + if default is not None and default != 1: + raise NotImplementedError(f"edge default != 1 is not implemented; got {default}") + if isinstance(graph, nx.MultiDiGraph): - return MultiDiGraph.from_networkx(graph, weight=weight) - if isinstance(graph, nx.MultiGraph): - return MultiGraph.from_networkx(graph, weight=weight) - if isinstance(graph, nx.DiGraph): - return DiGraph.from_networkx(graph, weight=weight) - if isinstance(graph, nx.Graph): - return Graph.from_networkx(graph, weight=weight) - raise TypeError(f"Unsupported type of graph: {type(graph)}") + G = MultiDiGraph.from_networkx(graph, weight=weight) + elif isinstance(graph, nx.MultiGraph): + G = MultiGraph.from_networkx(graph, weight=weight) + elif isinstance(graph, nx.DiGraph): + G = DiGraph.from_networkx(graph, weight=weight) + elif isinstance(graph, nx.Graph): + G = Graph.from_networkx(graph, weight=weight) + else: + raise TypeError(f"Unsupported type of graph: {type(graph)}") + if preserve_graph_attrs: + G.graph.update(graph.graph) + return G @staticmethod def convert_to_nx(obj, *, name=None): diff --git a/graphblas_algorithms/tests/test_match_nx.py b/graphblas_algorithms/tests/test_match_nx.py index 225c970..1924ff7 100644 --- a/graphblas_algorithms/tests/test_match_nx.py +++ b/graphblas_algorithms/tests/test_match_nx.py @@ -22,13 +22,29 @@ "Matching networkx namespace requires networkx to be installed", allow_module_level=True ) else: - from networkx.classes import backends # noqa: F401 + try: + from networkx.utils import backends + + IS_NX_30_OR_31 = False + except ImportError: # pragma: no cover (import) + # This is the location in nx 3.1 + from networkx.classes import backends # noqa: F401 + + IS_NX_30_OR_31 = True def isdispatched(func): """Can this NetworkX function dispatch to other backends?""" + if IS_NX_30_OR_31: + return ( + callable(func) + and hasattr(func, "dispatchname") + and func.__module__.startswith("networkx") + ) return ( - callable(func) and hasattr(func, "dispatchname") and func.__module__.startswith("networkx") + callable(func) + and hasattr(func, "preserve_edge_attrs") + and func.__module__.startswith("networkx") ) @@ -37,7 +53,9 @@ def dispatchname(func): # Haha, there should be a better way to get this if not isdispatched(func): raise ValueError(f"Function is not dispatched in NetworkX: {func.__name__}") - return func.dispatchname + if IS_NX_30_OR_31: + return func.dispatchname + return func.name def fullname(func): diff --git a/pyproject.toml b/pyproject.toml index 36afd28..8fb2ffc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -214,12 +214,14 @@ ignore = [ "RET502", # Do not implicitly `return None` in function able to return non-`None` value "RET503", # Missing explicit `return` at the end of function able to return non-`None` value "RET504", # Unnecessary variable assignment before `return` statement + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` (Note: no annotations yet) "S110", # `try`-`except`-`pass` detected, consider logging the exception (Note: good advice, but we don't log) "S112", # `try`-`except`-`continue` detected, consider logging the exception (Note: good advice, but we don't log) "SIM102", # Use a single `if` statement instead of nested `if` statements (Note: often necessary) "SIM105", # Use contextlib.suppress(...) instead of try-except-pass (Note: try-except-pass is much faster) "SIM108", # Use ternary operator ... instead of if-else-block (Note: if-else better for coverage and sometimes clearer) "TRY003", # Avoid specifying long messages outside the exception class (Note: why?) + "FIX001", "FIX002", "FIX003", "FIX004", # flake8-fixme (like flake8-todos) # Ignored categories "C90", # mccabe (Too strict, but maybe we should make things less complex) diff --git a/run_nx_tests.sh b/run_nx_tests.sh index 08a5582..740ab26 100755 --- a/run_nx_tests.sh +++ b/run_nx_tests.sh @@ -1,3 +1,6 @@ #!/bin/bash -NETWORKX_GRAPH_CONVERT=graphblas pytest --pyargs networkx "$@" -# NETWORKX_GRAPH_CONVERT=graphblas pytest --pyargs networkx --cov --cov-report term-missing "$@" +NETWORKX_GRAPH_CONVERT=graphblas \ +NETWORKX_TEST_BACKEND=graphblas \ +NETWORKX_FALLBACK_TO_NX=True \ + pytest --pyargs networkx "$@" +# pytest --pyargs networkx --cov --cov-report term-missing "$@" diff --git a/scripts/bench.py b/scripts/bench.py index ba61300..3b3f4dc 100755 --- a/scripts/bench.py +++ b/scripts/bench.py @@ -19,7 +19,7 @@ datapaths = [ Path(__file__).parent / ".." / "data", - Path("."), + Path(), ] @@ -37,7 +37,7 @@ def find_data(dataname): if dataname not in download_data.data_urls: raise FileNotFoundError(f"Unable to find data file for {dataname}") curpath = Path(download_data.main([dataname])[0]) - return curpath.resolve().relative_to(Path(".").resolve()) + return curpath.resolve().relative_to(Path().resolve()) def get_symmetry(file_or_mminfo): diff --git a/scripts/download_data.py b/scripts/download_data.py index 009ebf0..b01626c 100755 --- a/scripts/download_data.py +++ b/scripts/download_data.py @@ -47,7 +47,7 @@ def main(datanames, overwrite=False): for name in datanames: target = datapath / f"{name}.mtx" filenames.append(target) - relpath = target.resolve().relative_to(Path(".").resolve()) + relpath = target.resolve().relative_to(Path().resolve()) if not overwrite and target.exists(): print(f"{relpath} already exists; skipping", file=sys.stderr) continue From 4fccd7e66e078c48d963f2d59a95266c309df1ac Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Sun, 8 Oct 2023 22:26:08 -0500 Subject: [PATCH 02/11] Update to NetworkX 3.2 (#77) * Update to NetworkX 3.2 * Use mamba instead for faster environment creation * Drop Python 3.8 --- .github/workflows/test.yml | 26 +++++- .pre-commit-config.yaml | 38 +++++---- _nx_graphblas/__init__.py | 107 ++++++++++++++++++++++++ graphblas_algorithms/classes/digraph.py | 3 +- graphblas_algorithms/classes/graph.py | 1 + graphblas_algorithms/nxapi/smetric.py | 19 +++-- graphblas_algorithms/tests/test_core.py | 1 + pyproject.toml | 18 +++- 8 files changed, 181 insertions(+), 32 deletions(-) create mode 100644 _nx_graphblas/__init__.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 103821c..6758b93 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,16 +21,34 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 - - name: Conda + - name: Setup mamba uses: conda-incubator/setup-miniconda@v2 + id: setup_mamba + continue-on-error: true + with: + miniforge-variant: Mambaforge + miniforge-version: latest + use-mamba: true + python-version: ${{ matrix.python-version }} + channels: conda-forge,${{ contains(matrix.python-version, 'pypy') && 'defaults' || 'nodefaults' }} + channel-priority: ${{ contains(matrix.python-version, 'pypy') && 'flexible' || 'strict' }} + activate-environment: graphblas + auto-activate-base: false + - name: Setup conda + uses: conda-incubator/setup-miniconda@v2 + id: setup_conda + if: steps.setup_mamba.outcome == 'failure' + continue-on-error: false with: auto-update-conda: true python-version: ${{ matrix.python-version }} - channels: conda-forge - activate-environment: testing + channels: conda-forge,${{ contains(matrix.python-version, 'pypy') && 'defaults' || 'nodefaults' }} + channel-priority: ${{ contains(matrix.python-version, 'pypy') && 'flexible' || 'strict' }} + activate-environment: graphblas + auto-activate-base: false - name: Install dependencies run: | - conda install -c conda-forge python-graphblas scipy pandas pytest-cov pytest-randomly pytest-mpl + $(command -v mamba || command -v conda) install python-graphblas scipy pandas pytest-cov pytest-randomly pytest-mpl # matplotlib lxml pygraphviz pydot sympy # Extra networkx deps we don't need yet pip install git+https://github.com/networkx/networkx.git@main --no-deps pip install -e . --no-deps diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6b08ce0..474539b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,21 +5,23 @@ # To update: `pre-commit autoupdate` # - &flake8_dependencies below needs updated manually ci: - # See: https://pre-commit.ci/#configuration - autofix_prs: false - autoupdate_schedule: quarterly - skip: [no-commit-to-branch] + # See: https://pre-commit.ci/#configuration + autofix_prs: false + autoupdate_schedule: quarterly + autoupdate_commit_msg: "chore: update pre-commit hooks" + autofix_commit_msg: "style: pre-commit fixes" + skip: [no-commit-to-branch] fail_fast: true default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-merge-conflict - - id: check-symlinks + # - id: check-symlinks - id: check-ast - id: check-toml - id: check-yaml @@ -37,7 +39,7 @@ repos: name: Validate pyproject.toml # I don't yet trust ruff to do what autoflake does - repo: https://github.com/PyCQA/autoflake - rev: v2.2.0 + rev: v2.2.1 hooks: - id: autoflake args: [--in-place] @@ -46,22 +48,22 @@ repos: hooks: - id: isort - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 + rev: v3.15.0 hooks: - id: pyupgrade - args: [--py38-plus] + args: [--py39-plus] - repo: https://github.com/MarcoGorelli/auto-walrus rev: v0.2.2 hooks: - id: auto-walrus args: [--line-length, "100"] - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.9.1 hooks: - id: black # - id: black-jupyter - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.285 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.292 hooks: - id: ruff args: [--fix-only, --show-fixes] @@ -72,22 +74,22 @@ repos: additional_dependencies: &flake8_dependencies # These versions need updated manually - flake8==6.1.0 - - flake8-bugbear==23.7.10 - - flake8-simplify==0.20.0 + - flake8-bugbear==23.9.16 + - flake8-simplify==0.21.0 - repo: https://github.com/asottile/yesqa rev: v1.5.0 hooks: - id: yesqa additional_dependencies: *flake8_dependencies - repo: https://github.com/codespell-project/codespell - rev: v2.2.5 + rev: v2.2.6 hooks: - id: codespell types_or: [python, rst, markdown] additional_dependencies: [tomli] files: ^(graphblas_algorithms|docs)/ - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.285 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.292 hooks: - id: ruff # `pyroma` may help keep our package standards up to date if best practices change. @@ -98,6 +100,6 @@ repos: - id: pyroma args: [-n, "10", .] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: no-commit-to-branch # no commit directly to main diff --git a/_nx_graphblas/__init__.py b/_nx_graphblas/__init__.py new file mode 100644 index 0000000..c3cc602 --- /dev/null +++ b/_nx_graphblas/__init__.py @@ -0,0 +1,107 @@ +def get_info(): + return { + "backend_name": "graphblas", + "project": "graphblas-algorithms", + "package": "graphblas_algorithms", + "url": "https://github.com/python-graphblas/graphblas-algorithms", + "short_summary": "Fast, OpenMP-enabled backend using GraphBLAS", + # "description": "TODO", + "functions": { + "adjacency_matrix": {}, + "all_pairs_bellman_ford_path_length": { + "extra_parameters": { + "chunksize": "Split the computation into chunks; " + 'may specify size as string or number of rows. Default "10 MiB"', + }, + }, + "all_pairs_shortest_path_length": { + "extra_parameters": { + "chunksize": "Split the computation into chunks; " + 'may specify size as string or number of rows. Default "10 MiB"', + }, + }, + "ancestors": {}, + "average_clustering": {}, + "bellman_ford_path": {}, + "bellman_ford_path_length": {}, + "bethe_hessian_matrix": {}, + "bfs_layers": {}, + "boundary_expansion": {}, + "clustering": {}, + "complement": {}, + "compose": {}, + "conductance": {}, + "cut_size": {}, + "degree_centrality": {}, + "descendants": {}, + "descendants_at_distance": {}, + "difference": {}, + "directed_modularity_matrix": {}, + "disjoint_union": {}, + "edge_boundary": {}, + "edge_expansion": {}, + "efficiency": {}, + "ego_graph": {}, + "eigenvector_centrality": {}, + "fast_could_be_isomorphic": {}, + "faster_could_be_isomorphic": {}, + "floyd_warshall": {}, + "floyd_warshall_numpy": {}, + "floyd_warshall_predecessor_and_distance": {}, + "full_join": {}, + "generalized_degree": {}, + "google_matrix": {}, + "has_path": {}, + "hits": {}, + "in_degree_centrality": {}, + "inter_community_edges": {}, + "intersection": {}, + "intra_community_edges": {}, + "is_connected": {}, + "is_dominating_set": {}, + "is_isolate": {}, + "is_k_regular": {}, + "isolates": {}, + "is_regular": {}, + "is_simple_path": {}, + "is_tournament": {}, + "is_triad": {}, + "is_weakly_connected": {}, + "katz_centrality": {}, + "k_truss": {}, + "laplacian_matrix": {}, + "lowest_common_ancestor": {}, + "mixing_expansion": {}, + "modularity_matrix": {}, + "mutual_weight": {}, + "negative_edge_cycle": {}, + "node_boundary": {}, + "node_connected_component": {}, + "node_expansion": {}, + "normalized_cut_size": {}, + "normalized_laplacian_matrix": {}, + "number_of_isolates": {}, + "out_degree_centrality": {}, + "overall_reciprocity": {}, + "pagerank": {}, + "reciprocity": {}, + "reverse": {}, + "score_sequence": {}, + "single_source_bellman_ford_path_length": {}, + "single_source_shortest_path_length": {}, + "single_target_shortest_path_length": {}, + "s_metric": {}, + "square_clustering": { + "extra_parameters": { + "chunksize": "Split the computation into chunks; " + 'may specify size as string or number of rows. Default "256 MiB"', + }, + }, + "symmetric_difference": {}, + "tournament_matrix": {}, + "transitivity": {}, + "triangles": {}, + "union": {}, + "volume": {}, + }, + } diff --git a/graphblas_algorithms/classes/digraph.py b/graphblas_algorithms/classes/digraph.py index 8da9c8a..1e9fe5f 100644 --- a/graphblas_algorithms/classes/digraph.py +++ b/graphblas_algorithms/classes/digraph.py @@ -442,6 +442,7 @@ def __missing__(self, key): class DiGraph(Graph): + __networkx_backend__ = "graphblas" __networkx_plugin__ = "graphblas" # "-" properties ignore self-edges, "+" properties include self-edges @@ -611,7 +612,7 @@ def to_undirected(self, reciprocal=False, as_view=False, *, name=None): return Graph(B, key_to_id=self._key_to_id) def reverse(self, copy=True): - # We could even re-use many of the cached values + # We could even reuse many of the cached values A = self._A.T # This probably mostly works, but does not yet support assignment if copy: A = A.new() diff --git a/graphblas_algorithms/classes/graph.py b/graphblas_algorithms/classes/graph.py index 06f82be..f3e2239 100644 --- a/graphblas_algorithms/classes/graph.py +++ b/graphblas_algorithms/classes/graph.py @@ -301,6 +301,7 @@ def __missing__(self, key): class Graph: + __networkx_backend__ = "graphblas" __networkx_plugin__ = "graphblas" # "-" properties ignore self-edges, "+" properties include self-edges diff --git a/graphblas_algorithms/nxapi/smetric.py b/graphblas_algorithms/nxapi/smetric.py index a363e1e..a1f60ab 100644 --- a/graphblas_algorithms/nxapi/smetric.py +++ b/graphblas_algorithms/nxapi/smetric.py @@ -1,13 +1,22 @@ +import warnings + from graphblas_algorithms import algorithms from graphblas_algorithms.classes.digraph import to_graph -from .exception import NetworkXError - __all__ = ["s_metric"] -def s_metric(G, normalized=True): - if normalized: - raise NetworkXError("Normalization not implemented") +def s_metric(G, **kwargs): + if kwargs: + if "normalized" in kwargs: + warnings.warn( + "\n\nThe `normalized` keyword is deprecated and will be removed\n" + "in the future. To silence this warning, remove `normalized`\n" + "when calling `s_metric`.\n\nThe value of `normalized` is ignored.", + DeprecationWarning, + stacklevel=2, + ) + else: + raise TypeError(f"s_metric got an unexpected keyword argument '{kwargs.popitem()[0]}'") G = to_graph(G) return algorithms.s_metric(G) diff --git a/graphblas_algorithms/tests/test_core.py b/graphblas_algorithms/tests/test_core.py index 5acd529..68dbeb7 100644 --- a/graphblas_algorithms/tests/test_core.py +++ b/graphblas_algorithms/tests/test_core.py @@ -27,6 +27,7 @@ def test_packages(): path = pathlib.Path(ga.__file__).parent pkgs = [f"graphblas_algorithms.{x}" for x in setuptools.find_packages(path)] pkgs.append("graphblas_algorithms") + pkgs.append("_nx_graphblas") pkgs.sort() pyproject = path.parent / "pyproject.toml" if not pyproject.exists(): diff --git a/pyproject.toml b/pyproject.toml index 8fb2ffc..78b07ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ name = "graphblas-algorithms" dynamic = ["version"] description = "Graph algorithms written in GraphBLAS and backend for NetworkX" readme = "README.md" -requires-python = ">=3.8" +requires-python = ">=3.9" license = {file = "LICENSE"} authors = [ {name = "Erik Welch", email = "erik.n.welch@gmail.com"}, @@ -43,7 +43,6 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -65,6 +64,12 @@ dependencies = [ [project.entry-points."networkx.plugins"] graphblas = "graphblas_algorithms.interface:Dispatcher" +[project.entry-points."networkx.backends"] +graphblas = "graphblas_algorithms.interface:Dispatcher" + +[project.entry-points."networkx.backend_info"] +graphblas = "_nx_graphblas:get_info" + [project.urls] homepage = "https://github.com/python-graphblas/graphblas-algorithms" # documentation = "https://graphblas-algorithms.readthedocs.io" @@ -90,6 +95,7 @@ all = [ # $ find graphblas_algorithms/ -name __init__.py -print | sort | sed -e 's/\/__init__.py//g' -e 's/\//./g' # $ python -c 'import tomli ; [print(x) for x in sorted(tomli.load(open("pyproject.toml", "rb"))["tool"]["setuptools"]["packages"])]' packages = [ + "_nx_graphblas", "graphblas_algorithms", "graphblas_algorithms.algorithms", "graphblas_algorithms.algorithms.centrality", @@ -127,7 +133,7 @@ dirty_template = "{tag}+{ccount}.g{sha}.dirty" [tool.black] line-length = 100 -target-version = ["py38", "py39", "py310", "py311"] +target-version = ["py39", "py310", "py311"] [tool.isort] sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] @@ -143,6 +149,7 @@ skip = [ ] [tool.pytest.ini_options] +minversion = "6.0" testpaths = "graphblas_algorithms" xfail_strict = false markers = [ @@ -169,7 +176,10 @@ exclude_lines = [ [tool.ruff] # https://github.com/charliermarsh/ruff/ line-length = 100 -target-version = "py38" +target-version = "py39" +unfixable = [ + "F841" # unused-variable (Note: can leave useless expression) +] select = [ "ALL", ] From 6c89017b45c1b4b99ab7a0a764c50674bbbaafb2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Oct 2023 11:09:51 -0500 Subject: [PATCH 03/11] Bump actions/checkout from 3 to 4 (#74) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- .github/workflows/lint.yml | 2 +- .github/workflows/publish_pypi.yml | 2 +- .github/workflows/test.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 81d9415..e094502 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: name: pre-commit-hooks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: "3.10" diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index 8ff188b..e48524f 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -14,7 +14,7 @@ jobs: shell: bash -l {0} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6758b93..0ff6ab9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: python-version: ["3.9", "3.10", "3.11"] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup mamba From 86cca31c6d6b68256ef07ea4c8b06a0a19ba5159 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Mon, 9 Oct 2023 14:30:40 -0500 Subject: [PATCH 04/11] Update short summary for nx docs (#78) --- _nx_graphblas/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/_nx_graphblas/__init__.py b/_nx_graphblas/__init__.py index c3cc602..6ffa061 100644 --- a/_nx_graphblas/__init__.py +++ b/_nx_graphblas/__init__.py @@ -4,19 +4,19 @@ def get_info(): "project": "graphblas-algorithms", "package": "graphblas_algorithms", "url": "https://github.com/python-graphblas/graphblas-algorithms", - "short_summary": "Fast, OpenMP-enabled backend using GraphBLAS", + "short_summary": "OpenMP-enabled sparse linear algebra backend.", # "description": "TODO", "functions": { "adjacency_matrix": {}, "all_pairs_bellman_ford_path_length": { "extra_parameters": { - "chunksize": "Split the computation into chunks; " + "chunksize : int or str, optional": "Split the computation into chunks; " 'may specify size as string or number of rows. Default "10 MiB"', }, }, "all_pairs_shortest_path_length": { "extra_parameters": { - "chunksize": "Split the computation into chunks; " + "chunksize : int or str, optional": "Split the computation into chunks; " 'may specify size as string or number of rows. Default "10 MiB"', }, }, @@ -93,7 +93,7 @@ def get_info(): "s_metric": {}, "square_clustering": { "extra_parameters": { - "chunksize": "Split the computation into chunks; " + "chunksize : int or str, optional": "Split the computation into chunks; " 'may specify size as string or number of rows. Default "256 MiB"', }, }, From 43c1731213f29e954126b678ccb26967c3a67576 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Wed, 18 Oct 2023 21:49:42 -0500 Subject: [PATCH 05/11] Support Python 3.12 and update pre-commit versions (#79) --- .github/workflows/publish_pypi.yml | 2 +- .github/workflows/test.yml | 3 ++- .pre-commit-config.yaml | 8 ++++---- .../algorithms/shortest_paths/weighted.py | 2 +- pyproject.toml | 5 +++-- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index e48524f..1970710 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -20,7 +20,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.8" + python-version: "3.9" - name: Install build dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0ff6ab9..e04e92f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,8 @@ jobs: auto-activate-base: false - name: Install dependencies run: | - $(command -v mamba || command -v conda) install python-graphblas scipy pandas pytest-cov pytest-randomly pytest-mpl + $(command -v mamba || command -v conda) install python-suitesparse-graphblas scipy pandas donfig pyyaml numpy python-graphblas \ + pytest-cov pytest-randomly pytest-mpl # matplotlib lxml pygraphviz pydot sympy # Extra networkx deps we don't need yet pip install git+https://github.com/networkx/networkx.git@main --no-deps pip install -e . --no-deps diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 474539b..55021f2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: - id: name-tests-test args: ["--pytest-test-first"] - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.14 + rev: v0.15 hooks: - id: validate-pyproject name: Validate pyproject.toml @@ -58,12 +58,12 @@ repos: - id: auto-walrus args: [--line-length, "100"] - 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.0 hooks: - id: ruff args: [--fix-only, --show-fixes] @@ -89,7 +89,7 @@ repos: additional_dependencies: [tomli] files: ^(graphblas_algorithms|docs)/ - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.292 + rev: v0.1.0 hooks: - id: ruff # `pyroma` may help keep our package standards up to date if best practices change. diff --git a/graphblas_algorithms/algorithms/shortest_paths/weighted.py b/graphblas_algorithms/algorithms/shortest_paths/weighted.py index 5afa0f4..0c2883c 100644 --- a/graphblas_algorithms/algorithms/shortest_paths/weighted.py +++ b/graphblas_algorithms/algorithms/shortest_paths/weighted.py @@ -27,7 +27,7 @@ def _bellman_ford_path_length(G, source, target=None, *, cutoff=None, name): is_negative, iso_value = G.get_properties("has_negative_edges+ iso_value") if not is_negative: if cutoff is not None: - cutoff = int(cutoff // iso_value) + cutoff = int(cutoff // iso_value.get()) d = _bfs_level(G, source, target, cutoff=cutoff, dtype=iso_value.dtype) if dst_id is not None: d = d.get(dst_id) diff --git a/pyproject.toml b/pyproject.toml index 78b07ef..6ed6386 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3 :: Only", "Intended Audience :: Developers", "Intended Audience :: Other Audience", @@ -80,7 +81,7 @@ changelog = "https://github.com/python-graphblas/graphblas-algorithms/releases" test = [ "pytest", "networkx >=3.0", - "scipy >=1.8", + "scipy >=1.9", "setuptools", "tomli", ] @@ -133,7 +134,7 @@ dirty_template = "{tag}+{ccount}.g{sha}.dirty" [tool.black] line-length = 100 -target-version = ["py39", "py310", "py311"] +target-version = ["py39", "py310", "py311", "py312"] [tool.isort] sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] From 10788face650cb292e62038e73ae9c2bc007912a Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Wed, 13 Dec 2023 08:18:43 -0600 Subject: [PATCH 06/11] Drop Python 3.9, add 3.12; update pre-commit (#84) * Drop Python 3.9, add 3.12; update pre-commit * Use latest networkx release, not dev version * Add .codecov.yml --- .codecov.yml | 14 ++++++++ .github/workflows/publish_pypi.yml | 2 +- .github/workflows/test.yml | 7 ++-- .pre-commit-config.yaml | 12 +++---- .../algorithms/operators/binary.py | 4 +-- .../algorithms/shortest_paths/weighted.py | 2 +- graphblas_algorithms/classes/_utils.py | 23 +++++++------ graphblas_algorithms/interface.py | 32 +++++++++---------- graphblas_algorithms/nxapi/boundary.py | 6 +++- pyproject.toml | 8 ++--- scripts/scipy_impl.py | 2 +- 11 files changed, 67 insertions(+), 45 deletions(-) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..4fd4800 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,14 @@ +coverage: + status: + project: + default: + informational: true + patch: + default: + informational: true + changes: false +comment: + layout: "header, diff" + behavior: default +github_checks: + annotations: false diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index 1970710..d9889a1 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -20,7 +20,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.9" + python-version: "3.10" - name: Install build dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e04e92f..8be0379 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: fail-fast: true matrix: os: ["ubuntu-latest", "macos-latest", "windows-latest"] - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12"] steps: - name: Checkout uses: actions/checkout@v4 @@ -49,9 +49,10 @@ jobs: - name: Install dependencies run: | $(command -v mamba || command -v conda) install python-suitesparse-graphblas scipy pandas donfig pyyaml numpy python-graphblas \ - pytest-cov pytest-randomly pytest-mpl + pytest-cov pytest-randomly pytest-mpl networkx # matplotlib lxml pygraphviz pydot sympy # Extra networkx deps we don't need yet - pip install git+https://github.com/networkx/networkx.git@main --no-deps + # Sometimes we prefer to use the latest release of NetworkX or the latest development from github + # pip install git+https://github.com/networkx/networkx.git@main --no-deps pip install -e . --no-deps - name: PyTest run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 55021f2..c9e708b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,26 +44,26 @@ repos: - id: autoflake args: [--in-place] - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.1 hooks: - id: isort - repo: https://github.com/asottile/pyupgrade rev: v3.15.0 hooks: - id: pyupgrade - args: [--py39-plus] + args: [--py310-plus] - repo: https://github.com/MarcoGorelli/auto-walrus rev: v0.2.2 hooks: - id: auto-walrus args: [--line-length, "100"] - repo: https://github.com/psf/black - rev: 23.10.0 + rev: 23.12.0 hooks: - id: black # - id: black-jupyter - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.0 + rev: v0.1.7 hooks: - id: ruff args: [--fix-only, --show-fixes] @@ -74,7 +74,7 @@ repos: additional_dependencies: &flake8_dependencies # These versions need updated manually - flake8==6.1.0 - - flake8-bugbear==23.9.16 + - flake8-bugbear==23.12.2 - flake8-simplify==0.21.0 - repo: https://github.com/asottile/yesqa rev: v1.5.0 @@ -89,7 +89,7 @@ repos: additional_dependencies: [tomli] files: ^(graphblas_algorithms|docs)/ - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.0 + rev: v0.1.7 hooks: - id: ruff # `pyroma` may help keep our package standards up to date if best practices change. diff --git a/graphblas_algorithms/algorithms/operators/binary.py b/graphblas_algorithms/algorithms/operators/binary.py index 11b5b19..4c14a11 100644 --- a/graphblas_algorithms/algorithms/operators/binary.py +++ b/graphblas_algorithms/algorithms/operators/binary.py @@ -67,7 +67,7 @@ def intersection(G, H, *, name="intersection"): ids = H.list_to_ids(keys) B = H._A[ids, ids].new(dtypes.unify(A.dtype, H._A.dtype), mask=A.S, name=name) B << unary.one(B) - return type(G)(B, key_to_id=dict(zip(keys, range(len(keys))))) + return type(G)(B, key_to_id=dict(zip(keys, range(len(keys)), strict=True))) def difference(G, H, *, name="difference"): @@ -142,7 +142,7 @@ def compose(G, H, *, name="compose"): C[A.nrows :, A.ncols :] = B[ids, ids] # Now make new `key_to_id` ids += A.nrows - key_to_id = dict(zip(newkeys, ids.tolist())) + key_to_id = dict(zip(newkeys, ids.tolist(), strict=True)) key_to_id.update(G._key_to_id) return type(G)(C, key_to_id=key_to_id) diff --git a/graphblas_algorithms/algorithms/shortest_paths/weighted.py b/graphblas_algorithms/algorithms/shortest_paths/weighted.py index 0c2883c..a83a060 100644 --- a/graphblas_algorithms/algorithms/shortest_paths/weighted.py +++ b/graphblas_algorithms/algorithms/shortest_paths/weighted.py @@ -199,7 +199,7 @@ def bellman_ford_path_lengths(G, nodes=None, *, expand_output=False): def _reconstruct_path_from_parents(G, parents, src, dst): indices, values = parents.to_coo(sort=False) - d = dict(zip(indices.tolist(), values.tolist())) + d = dict(zip(indices.tolist(), values.tolist(), strict=True)) if dst not in d: return [] cur = dst diff --git a/graphblas_algorithms/classes/_utils.py b/graphblas_algorithms/classes/_utils.py index d15a188..ecf66d9 100644 --- a/graphblas_algorithms/classes/_utils.py +++ b/graphblas_algorithms/classes/_utils.py @@ -61,7 +61,7 @@ def dict_to_vector(self, d, *, size=None, dtype=None, name=None): if size is None: size = len(self) key_to_id = self._key_to_id - indices, values = zip(*((key_to_id[key], val) for key, val in d.items())) + indices, values = zip(*((key_to_id[key], val) for key, val in d.items()), strict=True) return Vector.from_coo(indices, values, size=size, dtype=dtype, name=name) @@ -116,7 +116,7 @@ def vector_to_dict(self, v, *, mask=None, fill_value=None): elif fill_value is not None and v.nvals < v.size: v(mask=~v.S) << fill_value id_to_key = self.id_to_key - return {id_to_key[index]: value for index, value in zip(*v.to_coo(sort=False))} + return {id_to_key[index]: value for index, value in zip(*v.to_coo(sort=False), strict=True)} def vector_to_list(self, v, *, values_are_keys=False): @@ -198,26 +198,29 @@ def matrix_to_dicts(self, A, *, use_row_index=False, use_column_index=False, val id_to_key = self.id_to_key if values_are_keys: values = [id_to_key[val] for val in values] - it = zip(rows, np.lib.stride_tricks.sliding_window_view(indptr, 2).tolist()) + it = zip(rows, np.lib.stride_tricks.sliding_window_view(indptr, 2).tolist(), strict=True) if use_row_index and use_column_index: return { - row: dict(zip(col_indices[start:stop], values[start:stop])) for row, (start, stop) in it + row: dict(zip(col_indices[start:stop], values[start:stop], strict=True)) + for row, (start, stop) in it } if use_row_index: return { row: { - id_to_key[col]: val for col, val in zip(col_indices[start:stop], values[start:stop]) + id_to_key[col]: val + for col, val in zip(col_indices[start:stop], values[start:stop], strict=True) } for row, (start, stop) in it } if use_column_index: return { - id_to_key[row]: dict(zip(col_indices[start:stop], values[start:stop])) + id_to_key[row]: dict(zip(col_indices[start:stop], values[start:stop], strict=True)) for row, (start, stop) in it } return { id_to_key[row]: { - id_to_key[col]: val for col, val in zip(col_indices[start:stop], values[start:stop]) + id_to_key[col]: val + for col, val in zip(col_indices[start:stop], values[start:stop], strict=True) } for row, (start, stop) in it } @@ -239,9 +242,9 @@ def to_networkx(self, edge_attribute="weight"): rows = (id_to_key[row] for row in rows.tolist()) cols = (id_to_key[col] for col in cols.tolist()) if edge_attribute is None: - G.add_edges_from(zip(rows, cols)) + G.add_edges_from(zip(rows, cols, strict=True)) else: - G.add_weighted_edges_from(zip(rows, cols, vals), weight=edge_attribute) + G.add_weighted_edges_from(zip(rows, cols, vals, strict=True), weight=edge_attribute) # What else should we copy over? return G @@ -258,4 +261,4 @@ def renumber_key_to_id(self, indices): return {id_to_key[index]: i for i, index in enumerate(indices)} # Alternative (about the same performance) # keys = self.list_to_keys(indices) - # return dict(zip(keys, range(len(indices)))) + # return dict(zip(keys, range(len(indices)), strict=True)) diff --git a/graphblas_algorithms/interface.py b/graphblas_algorithms/interface.py index 1d8c283..c718371 100644 --- a/graphblas_algorithms/interface.py +++ b/graphblas_algorithms/interface.py @@ -281,31 +281,31 @@ def key(testpath): return (testname, frozenset({filename})) # Reasons to skip tests - multi_attributed = "unable to handle multi-attributed graphs" + # multi_attributed = "unable to handle multi-attributed graphs" multidigraph = "unable to handle MultiDiGraph" multigraph = "unable to handle MultiGraph" # Which tests to skip skip = { - key("test_mst.py:TestBoruvka.test_attributes"): multi_attributed, - key("test_mst.py:TestBoruvka.test_weight_attribute"): multi_attributed, + # key("test_mst.py:TestBoruvka.test_attributes"): multi_attributed, + # key("test_mst.py:TestBoruvka.test_weight_attribute"): multi_attributed, key("test_dense.py:TestFloyd.test_zero_weight"): multidigraph, key("test_dense_numpy.py:test_zero_weight"): multidigraph, key("test_weighted.py:TestBellmanFordAndGoldbergRadzik.test_multigraph"): multigraph, - key("test_binary.py:test_compose_multigraph"): multigraph, - key("test_binary.py:test_difference_multigraph_attributes"): multigraph, - key("test_binary.py:test_disjoint_union_multigraph"): multigraph, - key("test_binary.py:test_full_join_multigraph"): multigraph, - key("test_binary.py:test_intersection_multigraph_attributes"): multigraph, - key( - "test_binary.py:test_intersection_multigraph_attributes_node_set_different" - ): multigraph, - key("test_binary.py:test_symmetric_difference_multigraph"): multigraph, - key("test_binary.py:test_union_attributes"): multi_attributed, + # key("test_binary.py:test_compose_multigraph"): multigraph, + # key("test_binary.py:test_difference_multigraph_attributes"): multigraph, + # key("test_binary.py:test_disjoint_union_multigraph"): multigraph, + # key("test_binary.py:test_full_join_multigraph"): multigraph, + # key("test_binary.py:test_intersection_multigraph_attributes"): multigraph, + # key( + # "test_binary.py:test_intersection_multigraph_attributes_node_set_different" + # ): multigraph, + # key("test_binary.py:test_symmetric_difference_multigraph"): multigraph, + # key("test_binary.py:test_union_attributes"): multi_attributed, # TODO: move failing assertion from `test_union_and_compose` - key("test_binary.py:test_union_and_compose"): multi_attributed, - key("test_binary.py:test_union_multigraph"): multigraph, - key("test_vf2pp.py:test_custom_multigraph4_different_labels"): multigraph, + # key("test_binary.py:test_union_and_compose"): multi_attributed, + # key("test_binary.py:test_union_multigraph"): multigraph, + # key("test_vf2pp.py:test_custom_multigraph4_different_labels"): multigraph, } for item in items: kset = set(item.keywords) diff --git a/graphblas_algorithms/nxapi/boundary.py b/graphblas_algorithms/nxapi/boundary.py index 8907f09..662cfe4 100644 --- a/graphblas_algorithms/nxapi/boundary.py +++ b/graphblas_algorithms/nxapi/boundary.py @@ -29,15 +29,19 @@ def edge_boundary(G, nbunch1, nbunch2=None, data=False, keys=False, default=None (id_to_key[col] for col in cols), # Unsure about this; data argument may mean *all* edge attributes ({weight: val} for val in vals), + strict=True, ) else: it = zip( (id_to_key[row] for row in rows), (id_to_key[col] for col in cols), + strict=True, ) if is_multigraph: # Edge weights indicate number of times to repeat edges - it = itertools.chain.from_iterable(itertools.starmap(itertools.repeat, zip(it, vals))) + it = itertools.chain.from_iterable( + itertools.starmap(itertools.repeat, zip(it, vals, strict=True)) + ) return it diff --git a/pyproject.toml b/pyproject.toml index 6ed6386..b1625c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ name = "graphblas-algorithms" dynamic = ["version"] description = "Graph algorithms written in GraphBLAS and backend for NetworkX" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" license = {file = "LICENSE"} authors = [ {name = "Erik Welch", email = "erik.n.welch@gmail.com"}, @@ -43,7 +43,6 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", @@ -134,7 +133,7 @@ dirty_template = "{tag}+{ccount}.g{sha}.dirty" [tool.black] line-length = 100 -target-version = ["py39", "py310", "py311", "py312"] +target-version = ["py310", "py311", "py312"] [tool.isort] sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] @@ -177,7 +176,7 @@ exclude_lines = [ [tool.ruff] # https://github.com/charliermarsh/ruff/ line-length = 100 -target-version = "py39" +target-version = "py310" unfixable = [ "F841" # unused-variable (Note: can leave useless expression) ] @@ -205,6 +204,7 @@ ignore = [ # "SIM401", # Use dict.get ... instead of if-else-block (Note: if-else better for coverage and sometimes clearer) # "TRY004", # Prefer `TypeError` exception for invalid type (Note: good advice, but not worth the nuisance) # "TRY200", # Use `raise from` to specify exception cause (Note: sometimes okay to raise original exception) + "UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)` (Note: using `|` seems to be slower) # Intentionally ignored "COM812", # Trailing comma missing diff --git a/scripts/scipy_impl.py b/scripts/scipy_impl.py index 06244ea..35815a6 100644 --- a/scripts/scipy_impl.py +++ b/scripts/scipy_impl.py @@ -50,7 +50,7 @@ def pagerank( err = np.absolute(x - xlast).sum() if err < N * tol: return x - # return dict(zip(nodelist, map(float, x))) + # return dict(zip(nodelist, map(float, x), strict=True)) raise nx.PowerIterationFailedConvergence(max_iter) From 38a41d403117810ec3bea238a999826b6cbfe382 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 05:41:34 -0600 Subject: [PATCH 07/11] Bump actions/setup-python from 4 to 5 (#83) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/publish_pypi.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e094502..97bb856 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.10" - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index d9889a1..70ad3a7 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -18,7 +18,7 @@ jobs: with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install build dependencies From 3f02d4c11893775f1aa9eb988336de8df35e0840 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 05:36:25 -0600 Subject: [PATCH 08/11] chore: update pre-commit hooks (#88) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/isort: 5.13.1 → 5.13.2](https://github.com/pycqa/isort/compare/5.13.1...5.13.2) - [github.com/psf/black: 23.12.0 → 23.12.1](https://github.com/psf/black/compare/23.12.0...23.12.1) - [github.com/astral-sh/ruff-pre-commit: v0.1.7 → v0.1.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.7...v0.1.9) - [github.com/astral-sh/ruff-pre-commit: v0.1.7 → v0.1.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.7...v0.1.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c9e708b..e4525c5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,7 +44,7 @@ repos: - id: autoflake args: [--in-place] - repo: https://github.com/pycqa/isort - rev: 5.13.1 + rev: 5.13.2 hooks: - id: isort - repo: https://github.com/asottile/pyupgrade @@ -58,12 +58,12 @@ repos: - id: auto-walrus args: [--line-length, "100"] - repo: https://github.com/psf/black - rev: 23.12.0 + rev: 23.12.1 hooks: - id: black # - id: black-jupyter - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.7 + rev: v0.1.9 hooks: - id: ruff args: [--fix-only, --show-fixes] @@ -89,7 +89,7 @@ repos: additional_dependencies: [tomli] files: ^(graphblas_algorithms|docs)/ - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.7 + rev: v0.1.9 hooks: - id: ruff # `pyroma` may help keep our package standards up to date if best practices change. From fec54b667350378937f837acf7e6f6b3e4d813f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 05:56:58 -0600 Subject: [PATCH 09/11] Bump actions/upload-artifact from 3 to 4 (#87) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish_pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index 70ad3a7..64ca620 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -27,7 +27,7 @@ jobs: python -m pip install build twine - name: Build wheel and sdist run: python -m build --sdist --wheel - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: releases path: dist From dcaef80baf8fda3c07046e0c7f1aa8753f805c05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 06:38:59 -0600 Subject: [PATCH 10/11] Bump conda-incubator/setup-miniconda from 2 to 3 (#81) Bumps [conda-incubator/setup-miniconda](https://github.com/conda-incubator/setup-miniconda) from 2 to 3. - [Release notes](https://github.com/conda-incubator/setup-miniconda/releases) - [Changelog](https://github.com/conda-incubator/setup-miniconda/blob/main/CHANGELOG.md) - [Commits](https://github.com/conda-incubator/setup-miniconda/compare/v2...v3) --- updated-dependencies: - dependency-name: conda-incubator/setup-miniconda dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8be0379..47ca7bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: with: fetch-depth: 0 - name: Setup mamba - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v3 id: setup_mamba continue-on-error: true with: @@ -35,7 +35,7 @@ jobs: activate-environment: graphblas auto-activate-base: false - name: Setup conda - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v3 id: setup_conda if: steps.setup_mamba.outcome == 'failure' continue-on-error: false From 35dbc90e808c6bf51b63d51d8a63f59238c02975 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 07:22:35 -0600 Subject: [PATCH 11/11] Bump pypa/gh-action-pypi-publish from 1.8.10 to 1.8.11 (#80) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.10 to 1.8.11. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.10...v1.8.11) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish_pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_pypi.yml b/.github/workflows/publish_pypi.yml index 64ca620..f848ad6 100644 --- a/.github/workflows/publish_pypi.yml +++ b/.github/workflows/publish_pypi.yml @@ -35,7 +35,7 @@ jobs: - name: Check with twine run: python -m twine check --strict dist/* - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.10 + uses: pypa/gh-action-pypi-publish@v1.8.11 with: user: __token__ password: ${{ secrets.PYPI_TOKEN }}