Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 09776b6

Browse files
authored
Add a few more BFS-based algorithms (#51)
* Add a few more BFS-based algorithms - Components - `is_connected` - `is_weakly_connected` - `node_connected_component` - Shortest Paths - `all_pairs_shortest_path_length` - `negative_edge_cycle` - `single_source_shortest_path_length` - `single_target_shortest_path_length` - Traversal - `bfs_layers` - `descendants_at_distance` * Fix `partition(evenly=True)` * Remove flake8-comprehensions (use ruff instead)
1 parent 888e092 commit 09776b6

40 files changed

+551
-56
lines changed

‎.pre-commit-config.yaml

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
# To run: `pre-commit run --all-files`
55
# To update: `pre-commit autoupdate`
66
# - &flake8_dependencies below needs updated manually
7+
ci:
8+
# See: https://pre-commit.ci/#configuration
9+
autofix_prs: false
10+
autoupdate_schedule: monthly
11+
skip: [no-commit-to-branch]
712
fail_fast: true
813
default_language_version:
914
python: python3
@@ -20,12 +25,13 @@ repos:
2025
- id: mixed-line-ending
2126
- id: trailing-whitespace
2227
- repo: https://github.com/abravalheri/validate-pyproject
23-
rev: v0.12.1
28+
rev: v0.12.2
2429
hooks:
2530
- id: validate-pyproject
2631
name: Validate pyproject.toml
32+
# I don't yet trust ruff to do what autoflake does
2733
- repo: https://github.com/myint/autoflake
28-
rev: v2.0.1
34+
rev: v2.0.2
2935
hooks:
3036
- id: autoflake
3137
args: [--in-place]
@@ -44,36 +50,47 @@ repos:
4450
- id: auto-walrus
4551
args: [--line-length, "100"]
4652
- repo: https://github.com/psf/black
47-
rev: 23.1.0
53+
rev: 23.3.0
4854
hooks:
4955
- id: black
5056
# - id: black-jupyter
57+
- repo: https://github.com/charliermarsh/ruff-pre-commit
58+
rev: v0.0.261
59+
hooks:
60+
- id: ruff
61+
args: [--fix-only, --show-fixes]
5162
- repo: https://github.com/PyCQA/flake8
5263
rev: 6.0.0
5364
hooks:
5465
- id: flake8
5566
additional_dependencies: &flake8_dependencies
5667
# These versions need updated manually
5768
- flake8==6.0.0
58-
- flake8-comprehensions==3.10.1
59-
- flake8-bugbear==23.2.13
60-
- flake8-simplify==0.19.3
69+
- flake8-bugbear==23.3.23
70+
- flake8-simplify==0.20.0
6171
- repo: https://github.com/asottile/yesqa
6272
rev: v1.4.0
6373
hooks:
6474
- id: yesqa
6575
additional_dependencies: *flake8_dependencies
6676
- repo: https://github.com/codespell-project/codespell
67-
rev: v2.2.2
77+
rev: v2.2.4
6878
hooks:
6979
- id: codespell
7080
types_or: [python, rst, markdown]
7181
additional_dependencies: [tomli]
7282
files: ^(graphblas_algorithms|docs)/
7383
- repo: https://github.com/charliermarsh/ruff-pre-commit
74-
rev: v0.0.253
84+
rev: v0.0.261
7585
hooks:
7686
- id: ruff
87+
# `pyroma` may help keep our package standards up to date if best practices change.
88+
# This is probably a "low value" check though and safe to remove if we want faster pre-commit.
89+
- repo: https://github.com/regebro/pyroma
90+
rev: "4.2"
91+
hooks:
92+
- id: pyroma
93+
args: [-n, "10", .]
7794
- repo: https://github.com/pre-commit/pre-commit-hooks
7895
rev: v4.4.0
7996
hooks:

‎README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ dispatch pattern shown above.
117117
- Community
118118
- inter_community_edges
119119
- intra_community_edges
120+
- Components
121+
- is_connected
122+
- is_weakly_connected
123+
- node_connected_component
120124
- Core
121125
- k_truss
122126
- Cuts
@@ -147,11 +151,15 @@ dispatch pattern shown above.
147151
- is_k_regular
148152
- is_regular
149153
- Shortest Paths
154+
- all_pairs_bellman_ford_path_length
155+
- all_pairs_shortest_path_length
150156
- floyd_warshall
151157
- floyd_warshall_predecessor_and_distance
152-
- single_source_bellman_ford_path_length
153-
- all_pairs_bellman_ford_path_length
154158
- has_path
159+
- negative_edge_cycle
160+
- single_source_bellman_ford_path_length
161+
- single_source_shortest_path_length
162+
- single_target_shortest_path_length
155163
- Simple Paths
156164
- is_simple_path
157165
- S Metric
@@ -162,5 +170,8 @@ dispatch pattern shown above.
162170
- is_tournament
163171
- score_sequence
164172
- tournament_matrix
173+
- Traversal
174+
- bfs_layers
175+
- descendants_at_distance
165176
- Triads
166177
- is_triad

‎graphblas_algorithms/algorithms/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .centrality import *
44
from .cluster import *
55
from .community import *
6+
from .components import *
67
from .core import *
78
from .cuts import *
89
from .dag import *
@@ -16,4 +17,5 @@
1617
from .smetric import *
1718
from .structuralholes import *
1819
from .tournament import *
20+
from .traversal import *
1921
from .triads import *

‎graphblas_algorithms/algorithms/centrality/eigenvector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def eigenvector_centrality(G, max_iter=100, tol=1.0e-6, nstart=None, name="eigen
2727
# Power iteration: make up to max_iter iterations
2828
A = G._A
2929
xprev = Vector(float, N, name="x_prev")
30-
for _ in range(max_iter):
30+
for _i in range(max_iter):
3131
xprev << x
3232
x += x @ A
3333
normalize(x, "L2")

‎graphblas_algorithms/algorithms/centrality/katz.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def katz_centrality(
4444

4545
# Power iteration: make up to max_iter iterations
4646
xprev = Vector(float, N, name="x_prev")
47-
for _ in range(max_iter):
47+
for _i in range(max_iter):
4848
xprev, x = x, xprev
4949
# x << alpha * semiring(xprev @ A) + beta
5050
x << semiring(xprev @ A)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .connected import *
2+
from .weakly_connected import *
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from graphblas import Vector, replace
2+
from graphblas.semiring import any_pair
3+
4+
from graphblas_algorithms.algorithms.exceptions import PointlessConcept
5+
6+
7+
def is_connected(G):
8+
if len(G) == 0:
9+
raise PointlessConcept("Connectivity is undefined for the null graph.")
10+
return _plain_bfs(G, next(iter(G))).nvals == len(G)
11+
12+
13+
def node_connected_component(G, n):
14+
return _plain_bfs(G, n)
15+
16+
17+
def _plain_bfs(G, source):
18+
index = G._key_to_id[source]
19+
A = G.get_property("offdiag")
20+
n = A.nrows
21+
v = Vector(bool, n, name="bfs_plain")
22+
q = Vector(bool, n, name="q")
23+
v[index] = True
24+
q[index] = True
25+
any_pair_bool = any_pair[bool]
26+
for _i in range(1, n):
27+
q(~v.S, replace) << any_pair_bool(q @ A)
28+
if q.nvals == 0:
29+
break
30+
v(q.S) << True
31+
return v
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from graphblas import Vector, binary, replace
2+
from graphblas.semiring import any_pair
3+
4+
from graphblas_algorithms.algorithms.exceptions import PointlessConcept
5+
6+
7+
def is_weakly_connected(G):
8+
if len(G) == 0:
9+
raise PointlessConcept("Connectivity is undefined for the null graph.")
10+
return _plain_bfs(G, next(iter(G))).nvals == len(G)
11+
12+
13+
# TODO: benchmark this and the version commented out below
14+
def _plain_bfs(G, source):
15+
# Bi-directional BFS w/o symmetrizing the adjacency matrix
16+
index = G._key_to_id[source]
17+
A = G.get_property("offdiag")
18+
# XXX: should we use `AT` if available?
19+
n = A.nrows
20+
v = Vector(bool, n, name="bfs_plain")
21+
q_out = Vector(bool, n, name="q_out")
22+
q_in = Vector(bool, n, name="q_in")
23+
v[index] = True
24+
q_in[index] = True
25+
any_pair_bool = any_pair[bool]
26+
is_out_empty = True
27+
is_in_empty = False
28+
for _i in range(1, n):
29+
# Traverse out-edges from the most recent `q_in` and `q_out`
30+
if is_out_empty:
31+
q_out(~v.S) << any_pair_bool(q_in @ A)
32+
else:
33+
q_out << binary.any(q_out | q_in)
34+
q_out(~v.S, replace) << any_pair_bool(q_out @ A)
35+
is_out_empty = q_out.nvals == 0
36+
if not is_out_empty:
37+
v(q_out.S) << True
38+
elif is_in_empty:
39+
break
40+
# Traverse in-edges from the most recent `q_in` and `q_out`
41+
if is_in_empty:
42+
q_in(~v.S) << any_pair_bool(A @ q_out)
43+
else:
44+
q_in << binary.any(q_out | q_in)
45+
q_in(~v.S, replace) << any_pair_bool(A @ q_in)
46+
is_in_empty = q_in.nvals == 0
47+
if not is_in_empty:
48+
v(q_in.S) << True
49+
elif is_out_empty:
50+
break
51+
return v
52+
53+
54+
"""
55+
def _plain_bfs(G, source):
56+
# Bi-directional BFS w/o symmetrizing the adjacency matrix
57+
index = G._key_to_id[source]
58+
A = G.get_property("offdiag")
59+
n = A.nrows
60+
v = Vector(bool, n, name="bfs_plain")
61+
q = Vector(bool, n, name="q")
62+
q2 = Vector(bool, n, name="q_2")
63+
v[index] = True
64+
q[index] = True
65+
any_pair_bool = any_pair[bool]
66+
for _i in range(1, n):
67+
q2(~v.S, replace) << any_pair_bool(q @ A)
68+
v(q2.S) << True
69+
q(~v.S, replace) << any_pair_bool(A @ q)
70+
if q.nvals == 0:
71+
if q2.nvals == 0:
72+
break
73+
q, q2 = q2, q
74+
elif q2.nvals != 0:
75+
q << binary.any(q | q2)
76+
return v
77+
"""

‎graphblas_algorithms/algorithms/dag.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from graphblas import Vector, replace
2-
from graphblas.semiring import lor_pair
2+
from graphblas.semiring import any_pair
33

44
__all__ = ["descendants", "ancestors"]
55

@@ -10,10 +10,12 @@ def descendants(G, source):
1010
raise KeyError(f"The node {source} is not in the graph")
1111
index = G._key_to_id[source]
1212
A = G.get_property("offdiag")
13-
q = Vector.from_coo(index, True, size=A.nrows, name="q")
13+
q = Vector(bool, size=A.nrows, name="q")
14+
q[index] = True
1415
rv = q.dup(name="descendants")
15-
for _ in range(A.nrows):
16-
q(~rv.S, replace) << lor_pair(q @ A)
16+
any_pair_bool = any_pair[bool]
17+
for _i in range(A.nrows):
18+
q(~rv.S, replace) << any_pair_bool(q @ A)
1719
if q.nvals == 0:
1820
break
1921
rv(q.S) << True
@@ -26,10 +28,12 @@ def ancestors(G, source):
2628
raise KeyError(f"The node {source} is not in the graph")
2729
index = G._key_to_id[source]
2830
A = G.get_property("offdiag")
29-
q = Vector.from_coo(index, True, size=A.nrows, name="q")
31+
q = Vector(bool, size=A.nrows, name="q")
32+
q[index] = True
3033
rv = q.dup(name="descendants")
31-
for _ in range(A.nrows):
32-
q(~rv.S, replace) << lor_pair(A @ q)
34+
any_pair_bool = any_pair[bool]
35+
for _i in range(A.nrows):
36+
q(~rv.S, replace) << any_pair_bool(A @ q)
3337
if q.nvals == 0:
3438
break
3539
rv(q.S) << True
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from graphblas.semiring import lor_pair
1+
from graphblas.semiring import any_pair
22

33
__all__ = ["is_dominating_set"]
44

55

66
def is_dominating_set(G, nbunch):
7-
nbrs = lor_pair(nbunch @ G._A).new(mask=~nbunch.S) # A or A.T?
7+
nbrs = any_pair[bool](nbunch @ G._A).new(mask=~nbunch.S) # A or A.T?
88
return nbrs.size - nbunch.nvals - nbrs.nvals == 0

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /