Skip to content

Commit 86a2548

Browse files
Return the Python UndirectedGraph subclass from from_(unique_)edges
The core statics return the raw _core.UndirectedGraph, so results lacked the subclass convenience wrappers (find_edges on lists, ...) and failed isinstance checks against bic.graph.UndirectedGraph. Add a constructor overload taking (number_of_nodes, uvs, unique) — an __init__ overload constructs the derived Python class, unlike a static — and build the subclass classmethods on it. from_edges also gains the single-pass C++ construction instead of insert_edges. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1cac669 commit 86a2548

3 files changed

Lines changed: 60 additions & 3 deletions

File tree

src/bindings/graph.cxx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,6 +1903,23 @@ void bind_graph(nb::module_ &m) {
19031903
nb::arg("number_of_nodes") = 0,
19041904
nb::arg("reserve_number_of_edges") = 0
19051905
)
1906+
// Constructor equivalent of the from_edges / from_unique_edges
1907+
// statics. Unlike a def_static, an __init__ overload constructs an
1908+
// instance of the *derived* Python class, so the Python subclass in
1909+
// bioimage_cpp.graph can build itself from an edge array.
1910+
.def(
1911+
"__init__",
1912+
[](Graph *self, const std::uint64_t number_of_nodes, ConstUInt64Array uvs,
1913+
const bool unique) {
1914+
new (self) Graph(
1915+
unique ? graph_from_unique_edges(number_of_nodes, uvs)
1916+
: graph_from_edges(number_of_nodes, uvs)
1917+
);
1918+
},
1919+
nb::arg("number_of_nodes"),
1920+
nb::arg("uvs"),
1921+
nb::arg("unique")
1922+
)
19061923
.def(
19071924
"assign",
19081925
&Graph::assign,

src/bioimage_cpp/graph/__init__.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,19 @@ def edgesFromNodeList(self, nodes):
9696

9797
@classmethod
9898
def from_edges(cls, number_of_nodes: int, uvs):
99-
graph = cls(number_of_nodes)
100-
graph.insert_edges(uvs)
101-
return graph
99+
return cls(int(number_of_nodes), _as_uv_array(uvs, "uvs"), False)
100+
101+
@classmethod
102+
def from_unique_edges(cls, number_of_nodes: int, uvs):
103+
"""Bulk-construct from a pre-deduplicated ``(n, 2)`` edge array.
104+
105+
The caller asserts that no undirected ``(u, v)`` pair appears twice and
106+
that ``u != v`` in every row; edges receive ids matching their position
107+
in ``uvs``. This skips the per-edge dedup of :meth:`from_edges` and is
108+
the fast path for e.g. the merged edge set of
109+
:func:`bioimage_cpp.graph.distributed.merge_edges`.
110+
"""
111+
return cls(int(number_of_nodes), _as_uv_array(uvs, "uvs"), True)
102112

103113
@classmethod
104114
def deserialize(cls, serialization):

tests/graph/test_undirected_graph.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,33 @@ def test_undirected_graph_freeze_is_callable_after_inserts_and_after_reads():
155155
np.testing.assert_array_equal(
156156
np.sort(graph.node_adjacency(0)[:, 0]), np.array([1, 2], dtype=np.uint64)
157157
)
158+
159+
160+
def test_from_edges_and_from_unique_edges_return_python_subclass():
161+
uvs = np.array([[0, 1], [1, 2], [0, 2]], dtype=np.uint64)
162+
core = bic._core.UndirectedGraph.from_unique_edges(3, uvs)
163+
164+
for graph in (
165+
bic.graph.UndirectedGraph.from_unique_edges(3, uvs),
166+
bic.graph.UndirectedGraph.from_edges(3, uvs),
167+
):
168+
# The classmethods construct the Python subclass (the core statics
169+
# return the raw _core type), so the convenience wrappers work.
170+
assert isinstance(graph, bic.graph.UndirectedGraph)
171+
assert graph.number_of_nodes == core.number_of_nodes
172+
assert graph.number_of_edges == core.number_of_edges
173+
np.testing.assert_array_equal(graph.uv_ids(), core.uv_ids())
174+
np.testing.assert_array_equal(
175+
graph.find_edges([[0, 1], [2, 0]]), np.array([0, 2], dtype=np.int64)
176+
)
177+
178+
179+
def test_from_edges_deduplicates_but_from_unique_edges_requires_unique():
180+
uvs_with_duplicate = np.array([[0, 1], [1, 0], [1, 2]], dtype=np.uint64)
181+
graph = bic.graph.UndirectedGraph.from_edges(3, uvs_with_duplicate)
182+
assert graph.number_of_edges == 2
183+
184+
with pytest.raises(ValueError):
185+
bic.graph.UndirectedGraph.from_unique_edges(
186+
3, np.array([[0, 0]], dtype=np.uint64)
187+
)

0 commit comments

Comments
 (0)