-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add {all_pairs,single_source}_bellman_ford_path_length
#44
Changes from 1 commit
c9ed0ab
caa3e83
343fcb8
2ca6b0d
704937e
4cbbfe9
a644bf8
aaa19c1
b82c8af
ba337d4
4370b2d
7268d80
bbcdd07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
from .dense import * | ||
from .generic import * | ||
from .weighted import * |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from graphblas import Vector, binary, monoid, replace | ||
from graphblas.semiring import min_plus | ||
|
||
from ..exceptions import Unbounded | ||
|
||
__all__ = ["single_source_bellman_ford_path_length"] | ||
|
||
|
||
def single_source_bellman_ford_path_length(G, source): | ||
# No need for `is_weighted=` keyword, b/c this is assumed to be weighted (I think) | ||
index = G._key_to_id[source] | ||
A = G._A | ||
if A.dtype == bool: | ||
# Should we upcast e.g. INT8 to INT64 as well? | ||
dtype = int | ||
else: | ||
dtype = A.dtype | ||
n = A.nrows | ||
d = Vector(dtype, n, name="single_source_bellman_ford_path_length") | ||
d[index] = 0 | ||
cur = d.dup(name="cur") | ||
mask = Vector(bool, n, name="mask") | ||
for _i in range(n - 1): | ||
# This is a slightly modified Bellman-Ford algorithm. | ||
# `cur` is the current frontier of values that improved in the previous iteration. | ||
# This means that in this iteration we drop values from `cur` that are not better. | ||
cur << min_plus(cur @ A) | ||
|
||
# Mask is True where cur not in d or cur < d | ||
mask(cur.S, replace) << True # or: `mask << unary.one[bool](cur)` | ||
mask(binary.second) << binary.lt(cur & d) | ||
|
||
# Drop values from `cur` that didn't improve | ||
cur(mask.V, replace) << cur | ||
if cur.nvals == 0: | ||
break | ||
# Update `d` with values that improved | ||
d(cur.S) << cur | ||
else: | ||
# Check for negative cycle when for loop completes without breaking | ||
cur << min_plus(cur @ A) | ||
mask << binary.lt(cur & d) | ||
if mask.reduce(monoid.lor): | ||
raise Unbounded("Negative cycle detected.") | ||
return d |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
from .dense import * | ||
from .generic import * | ||
from .weighted import * |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from graphblas_algorithms import algorithms | ||
from graphblas_algorithms.classes.digraph import to_graph | ||
|
||
from ..exception import NetworkXUnbounded, NodeNotFound | ||
|
||
__all__ = [ | ||
"all_pairs_bellman_ford_path_length", | ||
"single_source_bellman_ford_path_length", | ||
] | ||
|
||
|
||
def all_pairs_bellman_ford_path_length(G, weight="weight"): | ||
# TODO: what if weight is a function? | ||
# How should we implement and call `algorithms.all_pairs_bellman_ford_path_length`? | ||
# Should we compute in chunks to expose more parallelism? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what the best API for a backend implementation (i.e., to have in Maybe |
||
G = to_graph(G, weight=weight) | ||
for source in G: | ||
try: | ||
d = algorithms.single_source_bellman_ford_path_length(G, source) | ||
except algorithms.exceptions.Unbounded as e: | ||
raise NetworkXUnbounded(*e.args) from e | ||
except KeyError as e: | ||
raise NodeNotFound(*e.args) from e | ||
yield (source, G.vector_to_nodemap(d)) | ||
|
||
|
||
def single_source_bellman_ford_path_length(G, source, weight="weight"): | ||
# TODO: what if weight is a function? | ||
G = to_graph(G, weight=weight) | ||
try: | ||
d = algorithms.single_source_bellman_ford_path_length(G, source) | ||
except algorithms.exceptions.Unbounded as e: | ||
raise NetworkXUnbounded(*e.args) from e | ||
except KeyError as e: | ||
raise NodeNotFound(*e.args) from e | ||
return G.vector_to_nodemap(d) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, a very similar pattern to this also showed up in Floyd-Warshall
graphblas-algorithms/graphblas_algorithms/algorithms/shortest_paths/dense.py
Lines 67 to 73 in 0b649b2
where we need to set a mask according to a comparison and set the mask to True for new values.
Just thought this was interesting. It's neat to see common patterns.