From e004148d75c336892538ae76f0f76638a1702010 Mon Sep 17 00:00:00 2001 From: Erik Welch Date: Wed, 5 Aug 2026 01:30:01 -0500 Subject: [PATCH] Test the from_networkx int32 widening and correct its coverage claim The widening added while stabilizing CI was marked "pragma: no cover (win64 numpy < 2)", but the branch is reachable on every platform: np.int32 scalar edge weights infer an int32 array from the edge sequence anywhere, not only where python ints do. The pragma therefore hid live code from coverage, and the comment implied the change was platform-local when it is user-visible everywhere: int32-weighted graphs that previously round-tripped through scipy as INT32 now come back INT64. The widening itself is kept, deliberately. from_coo widens an int32 dtype inferred from sequence input to int64 on every platform (values_to_numpy_buffer), and from_networkx builds its values from a Python sequence, so identical weights now produce the same dtype through either constructor. Gating the widening to win64 instead would have re-introduced a from_coo/from_networkx disagreement everywhere else. Drop the pragma, restate the comment in terms of the real contract, and pin the behavior: np.int32 weights widen to INT64 and agree with from_coo, an explicit dtype= is preserved exactly, and np.int16 stays INT16 through both constructors. Release-note item: the from_networkx result dtype for int32-weighted graphs changes from INT32 to INT64. --- graphblas/io/_networkx.py | 12 +++++++----- graphblas/tests/test_io.py | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/graphblas/io/_networkx.py b/graphblas/io/_networkx.py index 19fee2702..6e4a26857 100644 --- a/graphblas/io/_networkx.py +++ b/graphblas/io/_networkx.py @@ -97,11 +97,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 729d73daf..1734e0fee 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 []