|  | 
|  | 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 | +""" | 
0 commit comments