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 6dd93bd

Browse files
authored
Add floyd_warshall (#42)
* Add `floyd_warshall` * Optimization: better handle sparsity such as skip empty nodes * Simplify (so this PR can be a reference) * Better name ("Outer", not "temp")
1 parent 140bea8 commit 6dd93bd

File tree

6 files changed

+71
-8
lines changed

6 files changed

+71
-8
lines changed

‎README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ dispatch pattern shown above.
147147
- is_k_regular
148148
- is_regular
149149
- Shortest Paths
150+
- floyd_warshall
150151
- has_path
151152
- Simple Paths
152153
- is_simple_path
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
from .dense import *
12
from .generic import *
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from graphblas import Matrix, Vector, binary
2+
from graphblas.select import offdiag
3+
from graphblas.semiring import any_plus
4+
5+
__all__ = ["floyd_warshall"]
6+
7+
8+
def floyd_warshall(G, is_weighted=False):
9+
# By using `offdiag` instead of `G._A`, we ensure that D will not become dense.
10+
# Dense D may be better at times, but not including the diagonal will result in less work.
11+
# Typically, Floyd-Warshall algorithms sets the diagonal of D to 0 at the beginning.
12+
# This is unnecessary with sparse matrices, and we set the diagonal to 0 at the end.
13+
# We also don't iterate over index `i` if either row i or column i are empty.
14+
if G.is_directed():
15+
A, row_degrees, column_degrees = G.get_properties("offdiag row_degrees- column_degrees-")
16+
nonempty_nodes = binary.pair(row_degrees & column_degrees).new(name="nonempty_nodes")
17+
else:
18+
A, nonempty_nodes = G.get_properties("offdiag degrees-")
19+
20+
if A.dtype == bool or not is_weighted:
21+
dtype = int
22+
else:
23+
dtype = A.dtype
24+
n = A.nrows
25+
D = Matrix(dtype, nrows=n, ncols=n, name="floyd_warshall")
26+
if is_weighted:
27+
D << A
28+
else:
29+
D(A.S) << 1 # Like `D << unary.one[int](A)`
30+
del A
31+
32+
Row = Matrix(dtype, nrows=1, ncols=n, name="Row")
33+
Col = Matrix(dtype, nrows=n, ncols=1, name="Col")
34+
Outer = Matrix(dtype, nrows=n, ncols=n, name="Outer")
35+
for i in nonempty_nodes:
36+
Col << D[:, [i]]
37+
Row << D[[i], :]
38+
Outer << any_plus(Col @ Row) # Like `col.outer(row, binary.plus)`
39+
D(binary.min) << offdiag(Outer)
40+
41+
# Set diagonal values to 0 (this way seems fast).
42+
# The missing values are implied to be infinity, so we set diagonals explicitly to 0.
43+
mask = Vector(bool, size=n, name="mask")
44+
mask << True
45+
Mask = mask.diag(name="Mask")
46+
D(Mask.S) << 0
47+
return D

‎graphblas_algorithms/interface.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class Dispatcher:
5555
is_k_regular = nxapi.regular.is_k_regular
5656
is_regular = nxapi.regular.is_regular
5757
# Shortest Paths
58+
floyd_warshall = nxapi.shortest_paths.dense.floyd_warshall
5859
has_path = nxapi.shortest_paths.generic.has_path
5960
# Simple Paths
6061
is_simple_path = nxapi.simple_paths.is_simple_path
@@ -99,14 +100,16 @@ def on_start_tests(items):
99100
import pytest
100101
except ImportError: # pragma: no cover (import)
101102
return
102-
skip = [
103-
("test_attributes", {"TestBoruvka", "test_mst.py"}),
104-
("test_weight_attribute", {"TestBoruvka", "test_mst.py"}),
105-
]
103+
multi_attributed = "unable to handle multi-attributed graphs"
104+
multidigraph = "unable to handle MultiDiGraph"
105+
freeze = frozenset
106+
skip = {
107+
("test_attributes", freeze({"TestBoruvka", "test_mst.py"})): multi_attributed,
108+
("test_weight_attribute", freeze({"TestBoruvka", "test_mst.py"})): multi_attributed,
109+
("test_zero_weight", freeze({"TestFloyd", "test_dense.py"})): multidigraph,
110+
}
106111
for item in items:
107112
kset = set(item.keywords)
108-
for test_name, keywordsin skip:
113+
for (test_name, keywords), reasonin skip.items():
109114
if item.name == test_name and keywords.issubset(kset):
110-
item.add_marker(
111-
pytest.mark.xfail(reason="unable to handle multi-attributed graphs")
112-
)
115+
item.add_marker(pytest.mark.xfail(reason=reason))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
from .dense import *
12
from .generic import *
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from graphblas_algorithms import algorithms
2+
from graphblas_algorithms.classes.digraph import to_graph
3+
4+
__all__ = ["floyd_warshall"]
5+
6+
7+
def floyd_warshall(G, weight="weight"):
8+
G = to_graph(G, weight=weight)
9+
D = algorithms.floyd_warshall(G, is_weighted=weight is not None)
10+
return G.matrix_to_dicts(D)

0 commit comments

Comments
(0)

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