diff --git a/graphblas/io/_networkx.py b/graphblas/io/_networkx.py index c4a31e6db..96e587cdc 100644 --- a/graphblas/io/_networkx.py +++ b/graphblas/io/_networkx.py @@ -96,11 +96,13 @@ def from_networkx(G, nodelist=None, dtype=None, weight="weight", name=None): dup_op = plus values = np.array(vals, dtype=dtype) - if dtype is None and values.dtype == np.int32: # pragma: no cover (win64 numpy < 2) - # numpy < 2 infers the platform C long for a sequence of Python ints, which - # is 32-bit on Windows. values_to_numpy_buffer widens the same way for - # non-numpy input, so this keeps from_networkx agreeing with - # Matrix.from_coo on INT64 for an unweighted graph on every platform. + if dtype is None and values.dtype == np.int32: + # ``vals`` is a Python sequence, and from_coo widens an int32 dtype + # INFERRED from sequence input to int64 (values_to_numpy_buffer): python + # ints infer int32 on win64 with numpy < 2, and np.int32 scalar weights + # infer it on every platform. Widen the same way so from_networkx and + # Matrix.from_coo given the same weights agree on the result dtype. + # An explicit dtype= is applied above and never reaches this branch. values = values.astype(np.int64) if values.ndim != 1 or values.dtype.kind not in "biufc": # Defer to scipy so the error matches the previous behavior exactly. diff --git a/graphblas/tests/test_io.py b/graphblas/tests/test_io.py index e5eb0020d..f9eb79b70 100644 --- a/graphblas/tests/test_io.py +++ b/graphblas/tests/test_io.py @@ -176,6 +176,28 @@ def test_from_networkx_undirected(): assert M_none.isequal(expected_none, check_dtype=True) +@pytest.mark.skipif("not nx") +def test_from_networkx_int32_weights_widen_like_from_coo(): + # np.int32 scalar weights infer an int32 array from the edge sequence on + # every platform (python ints do too on win64 with numpy < 2), and from_coo + # widens int32 inferred from sequence input to INT64. from_networkx must + # widen the same way or identical weights would produce different dtypes + # depending on which constructor they came through. + G = nx.DiGraph() + G.add_weighted_edges_from([(0, 1, np.int32(2)), (1, 0, np.int32(3))]) + M = gb.io.from_networkx(G) + expected = gb.Matrix.from_coo([0, 1], [1, 0], [np.int32(2), np.int32(3)]) + assert expected.dtype == dtypes.INT64 + assert M.isequal(expected, check_dtype=True) + # An explicit dtype bypasses inference and is preserved exactly. + assert gb.io.from_networkx(G, dtype="int32").dtype == dtypes.INT32 + # Narrower ints are not special-cased; this also matches from_coo. + H = nx.DiGraph() + H.add_weighted_edges_from([(0, 1, np.int16(2))]) + assert gb.io.from_networkx(H).dtype == dtypes.INT16 + assert gb.Matrix.from_coo([0], [1], [np.int16(2)]).dtype == dtypes.INT16 + + @pytest.mark.skipif("not nx or not ss") @pytest.mark.parametrize( "graph_cls", [nx.Graph, nx.DiGraph, nx.MultiGraph, nx.MultiDiGraph] if nx else []