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 0b649b2

Browse files
authored
Add floyd_warshall_predecessor_and_distance (#43)
* Add floyd_warshall_predecessor_and_distance * Use upper triangle for undirected graphs * Save memory when we don't need to compute predecessors
1 parent 6dd93bd commit 0b649b2

File tree

14 files changed

+307
-52
lines changed

14 files changed

+307
-52
lines changed

‎.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ repos:
2020
- id: mixed-line-ending
2121
- id: trailing-whitespace
2222
- repo: https://github.com/abravalheri/validate-pyproject
23-
rev: v0.11
23+
rev: v0.12.1
2424
hooks:
2525
- id: validate-pyproject
2626
name: Validate pyproject.toml
2727
- repo: https://github.com/myint/autoflake
28-
rev: v2.0.0
28+
rev: v2.0.1
2929
hooks:
3030
- id: autoflake
3131
args: [--in-place]
@@ -44,7 +44,7 @@ repos:
4444
- id: auto-walrus
4545
args: [--line-length, "100"]
4646
- repo: https://github.com/psf/black
47-
rev: 22.12.0
47+
rev: 23.1.0
4848
hooks:
4949
- id: black
5050
args: [--target-version=py38]
Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,100 @@
1-
from graphblas import Matrix, Vector, binary
2-
from graphblas.select import offdiag
3-
from graphblas.semiring import any_plus
1+
from graphblas import Matrix, Vector, binary, indexunary, replace, select
2+
from graphblas.semiring import any_plus, any_second
43

5-
__all__ = ["floyd_warshall"]
4+
__all__ = ["floyd_warshall", "floyd_warshall_predecessor_and_distance"]
65

76

87
def floyd_warshall(G, is_weighted=False):
8+
return floyd_warshall_predecessor_and_distance(G, is_weighted, compute_predecessors=False)[1]
9+
10+
11+
def floyd_warshall_predecessor_and_distance(G, is_weighted=False, *, compute_predecessors=True):
912
# By using `offdiag` instead of `G._A`, we ensure that D will not become dense.
1013
# Dense D may be better at times, but not including the diagonal will result in less work.
1114
# Typically, Floyd-Warshall algorithms sets the diagonal of D to 0 at the beginning.
1215
# This is unnecessary with sparse matrices, and we set the diagonal to 0 at the end.
1316
# We also don't iterate over index `i` if either row i or column i are empty.
14-
if G.is_directed():
17+
if is_directed:=G.is_directed():
1518
A, row_degrees, column_degrees = G.get_properties("offdiag row_degrees- column_degrees-")
1619
nonempty_nodes = binary.pair(row_degrees & column_degrees).new(name="nonempty_nodes")
1720
else:
18-
A, nonempty_nodes = G.get_properties("offdiag degrees-")
21+
A, nonempty_nodes = G.get_properties("U- degrees-")
1922

2023
if A.dtype == bool or not is_weighted:
2124
dtype = int
2225
else:
2326
dtype = A.dtype
2427
n = A.nrows
25-
D = Matrix(dtype, nrows=n, ncols=n, name="floyd_warshall")
28+
D = Matrix(dtype, nrows=n, ncols=n, name="floyd_warshall_dist")
2629
if is_weighted:
2730
D << A
2831
else:
2932
D(A.S) << 1 # Like `D << unary.one[int](A)`
3033
del A
31-
3234
Row = Matrix(dtype, nrows=1, ncols=n, name="Row")
33-
Col = Matrix(dtype, nrows=n, ncols=1, name="Col")
35+
if is_directed:
36+
Col = Matrix(dtype, nrows=n, ncols=1, name="Col")
37+
else:
38+
Col = None
3439
Outer = Matrix(dtype, nrows=n, ncols=n, name="Outer")
40+
if compute_predecessors:
41+
Mask = Matrix(bool, nrows=n, ncols=n, name="Mask")
42+
P = indexunary.rowindex(D).new(name="floyd_warshall_pred")
43+
if P.dtype == dtype:
44+
P_row = Row
45+
else:
46+
P_row = Matrix(P.dtype, nrows=1, ncols=n, name="P_row")
47+
else:
48+
Mask = P = P_row = None
49+
3550
for i in nonempty_nodes:
36-
Col << D[:, [i]]
3751
Row << D[[i], :]
52+
if is_directed:
53+
Col << D[:, [i]]
54+
else:
55+
Row(binary.any) << D.T[[i], :]
56+
Col = Row.T
3857
Outer << any_plus(Col @ Row) # Like `col.outer(row, binary.plus)`
39-
D(binary.min) << offdiag(Outer)
58+
59+
if not compute_predecessors:
60+
# It is faster (approx 10%-30%) to use a mask as is done below when computing
61+
# predecessors, but we choose to use less memory here by not using a mask.
62+
if is_directed:
63+
D(binary.min) << select.offdiag(Outer)
64+
else:
65+
D(binary.min) << select.triu(Outer, 1)
66+
else:
67+
# Update Outer to only include off-diagonal values that will update D and P.
68+
if is_directed:
69+
Mask << indexunary.offdiag(Outer)
70+
else:
71+
Mask << indexunary.triu(Outer, 1)
72+
Mask(binary.second) << binary.lt(Outer & D)
73+
Outer(Mask.V, replace) << Outer
74+
75+
# Update distances; like `D(binary.min) << offdiag(any_plus(Col @ Row))`
76+
D(Outer.S) << Outer
77+
78+
# Broadcast predecessors in P_row to updated values
79+
P_row << P[[i], :]
80+
if not is_directed:
81+
P_row(binary.any) << P.T[[i], :]
82+
Col = P_row.T
83+
P(Outer.S) << any_second(Col @ P_row)
84+
del Outer, Mask, Col, Row, P_row
85+
86+
if not is_directed:
87+
# Symmetrize the results.
88+
# It may be nice to be able to return these as upper-triangular.
89+
D(binary.any) << D.T
90+
if compute_predecessors:
91+
P(binary.any) << P.T
4092

4193
# Set diagonal values to 0 (this way seems fast).
4294
# 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
95+
diag_mask = Vector(bool, size=n, name="diag_mask")
96+
diag_mask << True
97+
Diag_mask = diag_mask.diag(name="Diag_mask")
98+
D(Diag_mask.S) << 0
99+
100+
return P, D

‎graphblas_algorithms/classes/_utils.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,26 +109,27 @@ def set_to_vector(self, nodes, dtype=bool, *, ignore_extra=False, size=None, nam
109109
return Vector.from_coo(index, True, size=size, dtype=dtype, name=name)
110110

111111

112-
def vector_to_dict(self, v, *, mask=None, fillvalue=None):
112+
def vector_to_dict(self, v, *, mask=None, fill_value=None):
113113
if mask is not None:
114-
if fillvalue is not None and v.nvals < mask.parent.nvals:
115-
v(mask, binary.first) << fillvalue
116-
elif fillvalue is not None and v.nvals < v.size:
117-
v(mask=~v.S) << fillvalue
114+
if fill_value is not None and v.nvals < mask.parent.nvals:
115+
v(mask, binary.first) << fill_value
116+
elif fill_value is not None and v.nvals < v.size:
117+
v(mask=~v.S) << fill_value
118118
id_to_key = self.id_to_key
119119
return {id_to_key[index]: value for index, value in zip(*v.to_coo(sort=False))}
120120

121121

122-
def vector_to_nodemap(self, v, *, mask=None, fillvalue=None):
122+
def vector_to_nodemap(self, v, *, mask=None, fill_value=None, values_are_keys=False):
123123
from .nodemap import NodeMap
124124

125125
if mask is not None:
126-
if fillvalue is not None and v.nvals < mask.parent.nvals:
127-
v(mask, binary.first) << fillvalue
128-
elif fillvalue is not None and v.nvals < v.size:
129-
v(mask=~v.S) << fillvalue
126+
if fill_value is not None and v.nvals < mask.parent.nvals:
127+
v(mask, binary.first) << fill_value
128+
fill_value = None
130129

131-
rv = NodeMap(v, key_to_id=self._key_to_id)
130+
rv = NodeMap(
131+
v, fill_value=fill_value, values_are_keys=values_are_keys, key_to_id=self._key_to_id
132+
)
132133
rv._id_to_key = self._id_to_key
133134
return rv
134135

@@ -147,7 +148,25 @@ def vector_to_set(self, v):
147148
return {id_to_key[index] for index in indices}
148149

149150

150-
def matrix_to_dicts(self, A, *, use_row_index=False, use_column_index=False):
151+
def matrix_to_nodenodemap(self, A, *, fill_value=None, values_are_keys=False):
152+
from .nodemap import NodeNodeMap
153+
154+
rv = NodeNodeMap(
155+
A, fill_value=fill_value, values_are_keys=values_are_keys, key_to_id=self._key_to_id
156+
)
157+
rv._id_to_key = self._id_to_key
158+
return rv
159+
160+
161+
def matrix_to_vectornodemap(self, A):
162+
from .nodemap import VectorNodeMap
163+
164+
rv = VectorNodeMap(A, key_to_id=self._key_to_id)
165+
rv._id_to_key = self._id_to_key
166+
return rv
167+
168+
169+
def matrix_to_dicts(self, A, *, use_row_index=False, use_column_index=False, values_are_keys=False):
151170
"""Convert a Matrix to a dict of dicts of the form ``{row: {col: val}}``
152171
153172
Use ``use_row_index=True`` to return the row index as keys in the dict,
@@ -167,6 +186,8 @@ def matrix_to_dicts(self, A, *, use_row_index=False, use_column_index=False):
167186
indptr = d["indptr"]
168187
values = d["values"].tolist()
169188
id_to_key = self.id_to_key
189+
if values_are_keys:
190+
values = [id_to_key[val] for val in values]
170191
it = zip(rows, np.lib.stride_tricks.sliding_window_view(indptr, 2).tolist())
171192
if use_row_index and use_column_index:
172193
return {

‎graphblas_algorithms/classes/digraph.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,8 @@ def __init__(self, incoming_graph_data=None, *, key_to_id=None, **attr):
569569
list_to_mask = _utils.list_to_mask
570570
list_to_ids = _utils.list_to_ids
571571
matrix_to_dicts = _utils.matrix_to_dicts
572+
matrix_to_nodenodemap = _utils.matrix_to_nodenodemap
573+
matrix_to_vectornodemap = _utils.matrix_to_vectornodemap
572574
set_to_vector = _utils.set_to_vector
573575
to_networkx = _utils.to_networkx
574576
vector_to_dict = _utils.vector_to_dict

‎graphblas_algorithms/classes/graph.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ def __init__(self, incoming_graph_data=None, *, key_to_id=None, **attr):
275275
list_to_ids = _utils.list_to_ids
276276
list_to_keys = _utils.list_to_keys
277277
matrix_to_dicts = _utils.matrix_to_dicts
278+
matrix_to_nodenodemap = _utils.matrix_to_nodenodemap
279+
matrix_to_vectornodemap = _utils.matrix_to_vectornodemap
278280
set_to_vector = _utils.set_to_vector
279281
to_networkx = _utils.to_networkx
280282
vector_to_dict = _utils.vector_to_dict

0 commit comments

Comments
(0)

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