spatial-graph 0.0.3__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/PKG-INFO +2 -2
  2. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/pyproject.toml +1 -1
  3. spatial_graph-0.0.4/src/spatial_graph/_graph/graph.py +227 -0
  4. spatial_graph-0.0.4/src/spatial_graph/_graph/graph_base.py +321 -0
  5. spatial_graph-0.0.4/src/spatial_graph/_graph/views.py +173 -0
  6. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_rtree/line_rtree.py +1 -1
  7. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_rtree/rtree.py +133 -121
  8. spatial_graph-0.0.4/src/spatial_graph/_rtree/tree_base.py +0 -0
  9. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_spatial_graph.py +10 -13
  10. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/tests/test_bench.py +4 -12
  11. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/tests/test_invalid_inputs.py +0 -16
  12. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/tests/test_rtree.py +11 -11
  13. spatial_graph-0.0.3/src/spatial_graph/_graph/cgraph.pyi +0 -388
  14. spatial_graph-0.0.3/src/spatial_graph/_graph/graph.py +0 -391
  15. spatial_graph-0.0.3/tests/test_assert.py +0 -2
  16. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/.github/workflows/ci.yml +0 -0
  17. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/.github/workflows/docs.yaml +0 -0
  18. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/.gitignore +0 -0
  19. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/.pre-commit-config.yaml +0 -0
  20. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/Dockerfile +0 -0
  21. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/LICENSE +0 -0
  22. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/README.md +0 -0
  23. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/docs/index.md +0 -0
  24. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/examples/basic_usage.py +0 -0
  25. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/examples/query_nearest_vispy.py +0 -0
  26. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/mkdocs.yml +0 -0
  27. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/__init__.py +0 -0
  28. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_dtypes.py +0 -0
  29. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_graph/__init__.py +0 -0
  30. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_graph/src/LICENSE.txt +0 -0
  31. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_graph/src/graph_lite.h +0 -0
  32. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_graph/wrapper_template.pyx +0 -0
  33. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_rtree/__init__.py +0 -0
  34. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_rtree/point_rtree.py +0 -0
  35. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_rtree/src/ARCHITECTURE.md +0 -0
  36. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_rtree/src/LICENSE +0 -0
  37. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_rtree/src/config.h +0 -0
  38. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_rtree/src/rtree.c +0 -0
  39. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_rtree/src/rtree.h +0 -0
  40. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_rtree/wrapper_template.pyx +0 -0
  41. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/src/spatial_graph/_util.py +0 -0
  42. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/tests/test_attributes.py +0 -0
  43. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/tests/test_dtype.py +0 -0
  44. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/tests/test_graph.py +0 -0
  45. {spatial_graph-0.0.3 → spatial_graph-0.0.4}/tests/test_spatial_graph.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spatial-graph
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: A spatial graph datastructure for python.
5
5
  Project-URL: homepage, https://github.com/funkelab/spatial_graph
6
6
  Project-URL: repository, https://github.com/funkelab/spatial_graph
@@ -20,7 +20,7 @@ Requires-Python: >=3.9
20
20
  Requires-Dist: ct3>=3.3.3
21
21
  Requires-Dist: numpy
22
22
  Requires-Dist: setuptools>=75.8.0
23
- Requires-Dist: witty>=v0.2.1
23
+ Requires-Dist: witty>=v0.3.0
24
24
  Description-Content-Type: text/markdown
25
25
 
26
26
  # spatial-graph
@@ -27,7 +27,7 @@ classifiers = [
27
27
  "Programming Language :: Python :: 3.13",
28
28
  "Typing :: Typed",
29
29
  ]
30
- dependencies = ["witty>=v0.2.1", "CT3>=3.3.3", "numpy", "setuptools>=75.8.0"]
30
+ dependencies = ["witty>=v0.3.0", "CT3>=3.3.3", "numpy", "setuptools>=75.8.0"]
31
31
 
32
32
  [dependency-groups]
33
33
  test = ["pytest>=8.3.5", "pytest-cov>=6.1.1"]
@@ -0,0 +1,227 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Literal, overload
4
+
5
+ from .graph_base import GraphBase
6
+
7
+ if TYPE_CHECKING:
8
+ from collections.abc import Iterator
9
+
10
+ import numpy as np
11
+
12
+
13
+ class Graph(GraphBase):
14
+ directed: Literal[False] = False
15
+
16
+ def num_neighbors(self, nodes: np.ndarray) -> np.ndarray:
17
+ """Return the number of neighbors for each node.
18
+
19
+ For undirected graphs, this counts all adjacent nodes regardless
20
+ of edge direction since edges are bidirectional.
21
+
22
+ Parameters
23
+ ----------
24
+ nodes : np.ndarray
25
+ Array of node identifiers to count neighbors for.
26
+
27
+ Returns
28
+ -------
29
+ np.ndarray
30
+ Array of neighbor counts for each node in the input array.
31
+ """
32
+ return self._cgraph.num_neighbors(nodes)
33
+
34
+ @overload
35
+ def edges(
36
+ self, node: Any = ..., data: Literal[True] = ...
37
+ ) -> Iterator[tuple[tuple, Any]]: ...
38
+ @overload
39
+ def edges(self, node: Any = ..., data: Literal[False] = ...) -> Iterator[tuple]: ...
40
+ def edges(self, node: Any = None, data: bool = False) -> Iterator[tuple]:
41
+ """Iterate over edges in the graph.
42
+
43
+ For undirected graphs, each edge is yielded only once with nodes
44
+ ordered such that node1 < node2 to avoid duplicates.
45
+
46
+ Parameters
47
+ ----------
48
+ node : Any, optional
49
+ If provided, only iterate over edges incident to this node.
50
+ If None, iterate over all edges in the graph.
51
+ data : bool, default False
52
+ If True, yield (edge, edge_data) tuples. If False, yield
53
+ only edge tuples.
54
+
55
+ Yields
56
+ ------
57
+ tuple or tuple[tuple, Any]
58
+ If `data=False`: tuples of (node1, node2) representing edges.
59
+ If `data=True`: tuples of ((node1, node2), edge_data) where
60
+ edge_data provides access to edge attributes.
61
+ """
62
+ return self._cgraph.edges(node, data)
63
+
64
+ def edges_by_nodes(self, nodes: np.ndarray) -> np.ndarray:
65
+ """Get all edges incident to the specified nodes.
66
+
67
+ This method provides fast access to edges incident to an array
68
+ of nodes. Note that edges between nodes in the input array will
69
+ be reported multiple times (once for each incident node).
70
+
71
+ Parameters
72
+ ----------
73
+ nodes : np.ndarray
74
+ Array of node identifiers to find incident edges for.
75
+
76
+ Returns
77
+ -------
78
+ np.ndarray
79
+ 2D array of shape (n_edges, 2) where each row contains
80
+ [node1, node2] representing an edge. For undirected graphs,
81
+ node1 <= node2.
82
+ """
83
+ return self._cgraph.edges_by_nodes(nodes)
84
+
85
+
86
+ class DiGraph(GraphBase):
87
+ directed: Literal[True] = True
88
+
89
+ def num_in_neighbors(self, nodes: np.ndarray) -> np.ndarray:
90
+ """Return the number of incoming neighbors for each node.
91
+
92
+ This counts only nodes that have edges pointing to the specified nodes
93
+ (i.e., predecessors).
94
+
95
+ Parameters
96
+ ----------
97
+ nodes : np.ndarray
98
+ Array of node identifiers to count incoming neighbors for.
99
+
100
+ Returns
101
+ -------
102
+ np.ndarray
103
+ Array of incoming neighbor counts for each node in the input array.
104
+ """
105
+ return self._cgraph.num_in_neighbors(nodes)
106
+
107
+ def num_out_neighbors(self, nodes: np.ndarray) -> np.ndarray:
108
+ """Return the number of outgoing neighbors for each node.
109
+
110
+ This counts only nodes that the specified nodes
111
+ have edges pointing to (i.e., successors).
112
+
113
+ Parameters
114
+ ----------
115
+ nodes : np.ndarray
116
+ Array of node identifiers to count outgoing neighbors for.
117
+
118
+ Returns
119
+ -------
120
+ np.ndarray
121
+ Array of outgoing neighbor counts for each node in the input array.
122
+ """
123
+ return self._cgraph.num_out_neighbors(nodes)
124
+
125
+ @overload
126
+ def in_edges(
127
+ self, node: Any = ..., data: Literal[True] = ...
128
+ ) -> Iterator[tuple[tuple, Any]]: ...
129
+ @overload
130
+ def in_edges(
131
+ self, node: Any = ..., data: Literal[False] = ...
132
+ ) -> Iterator[tuple]: ...
133
+ def in_edges(self, node: Any = None, data: bool = False) -> Iterator[tuple]:
134
+ """Iterate over incoming edges to a node.
135
+
136
+ Only edges directed toward the specified node are yielded.
137
+
138
+ Parameters
139
+ ----------
140
+ node : Any
141
+ The target node to find incoming edges for.
142
+ data : bool
143
+ If True, yield (edge, edge_data) tuples. If False, yield
144
+ only edge tuples.
145
+
146
+ Yields
147
+ ------
148
+ tuple or tuple[tuple, Any]
149
+ If `data=False`: tuples of (source_node, target_node) representing
150
+ incoming edges where target_node is the specified node.
151
+ If `data=True`: tuples of ((source_node, target_node), edge_data)
152
+ where edge_data provides access to edge attributes.
153
+ """
154
+ return self._cgraph.in_edges(node, data)
155
+
156
+ def in_edges_by_nodes(self, nodes: np.ndarray) -> np.ndarray:
157
+ """Get all incoming edges to the specified nodes.
158
+
159
+ This method provides fast access to incoming edges for an array
160
+ of nodes. Edges between nodes in the input array will be reported
161
+ multiple times if both source and target are in the array.
162
+
163
+ Parameters
164
+ ----------
165
+ nodes : np.ndarray
166
+ Array of node identifiers to find incoming edges for.
167
+
168
+ Returns
169
+ -------
170
+ np.ndarray
171
+ 2D array of shape (n_edges, 2) where each row contains
172
+ [source_node, target_node] representing an incoming edge
173
+ to one of the specified nodes.
174
+ """
175
+ return self._cgraph.in_edges_by_nodes(nodes)
176
+
177
+ @overload
178
+ def out_edges(
179
+ self, node: Any = ..., data: Literal[True] = ...
180
+ ) -> Iterator[tuple[tuple, Any]]: ...
181
+ @overload
182
+ def out_edges(
183
+ self, node: Any = ..., data: Literal[False] = ...
184
+ ) -> Iterator[tuple]: ...
185
+ def out_edges(self, node: Any = None, data: bool = False) -> Iterator[tuple]:
186
+ """Iterate over outgoing edges from a node.
187
+
188
+ Only edges directed away from the specified node are yielded.
189
+
190
+ Parameters
191
+ ----------
192
+ node : Any
193
+ The source node to find outgoing edges for.
194
+ data : bool
195
+ If True, yield (edge, edge_data) tuples. If False, yield
196
+ only edge tuples.
197
+
198
+ Yields
199
+ ------
200
+ tuple or tuple[tuple, Any]
201
+ If `data=False`: tuples of (source_node, target_node) representing
202
+ outgoing edges where source_node is the specified node.
203
+ If `data=True`: tuples of ((source_node, target_node), edge_data)
204
+ where edge_data provides access to edge attributes.
205
+ """
206
+ return self._cgraph.out_edges(node, data)
207
+
208
+ def out_edges_by_nodes(self, nodes: np.ndarray) -> np.ndarray:
209
+ """Get all outgoing edges from the specified nodes.
210
+
211
+ This method provides fast access to outgoing edges for an array
212
+ of nodes. Edges between nodes in the input array will be reported
213
+ multiple times if both source and target are in the array.
214
+
215
+ Parameters
216
+ ----------
217
+ nodes : np.ndarray
218
+ Array of node identifiers to find outgoing edges for.
219
+
220
+ Returns
221
+ -------
222
+ np.ndarray
223
+ 2D array of shape (n_edges, 2) where each row contains
224
+ [source_node, target_node] representing an outgoing edge
225
+ from one of the specified nodes.
226
+ """
227
+ return self._cgraph.out_edges_by_nodes(nodes)
@@ -0,0 +1,321 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import witty
8
+ from Cheetah.Template import Template
9
+
10
+ from spatial_graph._dtypes import DType
11
+
12
+ from .views import EdgeAttrs, NodeAttrs
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Iterator, Mapping
16
+
17
+ import numpy as np
18
+
19
+
20
+ # Set platform-specific compile arguments
21
+ if sys.platform == "win32": # pragma: no cover
22
+ # Use /O2 for optimization and /std:c++20 for C++20
23
+ EXTRA_COMPILE_ARGS = ["/O2", "/std:c++20", "/wd4101"]
24
+ else:
25
+ # -O3 for optimization and -std=c++20 for C++20
26
+ EXTRA_COMPILE_ARGS = [
27
+ "-O3",
28
+ "-std=c++20",
29
+ "-Wno-unused-variable",
30
+ "-Wno-unreachable-code",
31
+ ]
32
+
33
+ SRC_DIR = Path(__file__).parent
34
+
35
+
36
+ def _build_wrapper(
37
+ node_dtype: str,
38
+ node_attr_dtypes: Mapping[str, str] | None = None,
39
+ edge_attr_dtypes: Mapping[str, str] | None = None,
40
+ directed: bool = False,
41
+ ) -> str:
42
+ if node_attr_dtypes is None:
43
+ node_attr_dtypes = {}
44
+ if edge_attr_dtypes is None:
45
+ edge_attr_dtypes = {}
46
+ if not all(str.isidentifier(name) for name in node_attr_dtypes):
47
+ raise ValueError("Node attribute names must be valid identifiers")
48
+ if not all(str.isidentifier(name) for name in edge_attr_dtypes):
49
+ raise ValueError("Edge attribute names must be valid identifiers")
50
+
51
+ wrapper_template = Template(
52
+ file=str(SRC_DIR / "wrapper_template.pyx"),
53
+ compilerSettings={"directiveStartToken": "%"},
54
+ )
55
+ wrapper_template.node_dtype = DType(node_dtype)
56
+ wrapper_template.node_attr_dtypes = {
57
+ name: DType(dtype) for name, dtype in node_attr_dtypes.items()
58
+ }
59
+ wrapper_template.edge_attr_dtypes = {
60
+ name: DType(dtype) for name, dtype in edge_attr_dtypes.items()
61
+ }
62
+ wrapper_template.directed = directed
63
+
64
+ return str(wrapper_template)
65
+
66
+
67
+ def _compile_graph(
68
+ node_dtype: str,
69
+ node_attr_dtypes: Mapping[str, str] | None = None,
70
+ edge_attr_dtypes: Mapping[str, str] | None = None,
71
+ directed: bool = False,
72
+ ) -> type:
73
+ wrapper_template = _build_wrapper(
74
+ node_dtype=node_dtype,
75
+ node_attr_dtypes=node_attr_dtypes,
76
+ edge_attr_dtypes=edge_attr_dtypes,
77
+ directed=directed,
78
+ )
79
+ wrapper = witty.compile_cython(
80
+ wrapper_template,
81
+ source_files=[str(SRC_DIR / "src" / "graph_lite.h")],
82
+ extra_compile_args=EXTRA_COMPILE_ARGS,
83
+ include_dirs=[str(SRC_DIR)],
84
+ language="c++",
85
+ quiet=True,
86
+ )
87
+ return wrapper.Graph
88
+
89
+
90
+ class GraphBase:
91
+ directed: bool = False
92
+
93
+ def __init__(
94
+ self,
95
+ node_dtype: str,
96
+ node_attr_dtypes: Mapping[str, str] | None = None,
97
+ edge_attr_dtypes: Mapping[str, str] | None = None,
98
+ ):
99
+ super().__init__()
100
+ self.node_dtype = node_dtype
101
+ self.node_attr_dtypes = node_attr_dtypes or {}
102
+ self.edge_attr_dtypes = edge_attr_dtypes or {}
103
+
104
+ cgraph_cls = _compile_graph(
105
+ node_dtype=self.node_dtype,
106
+ node_attr_dtypes=self.node_attr_dtypes,
107
+ edge_attr_dtypes=self.edge_attr_dtypes,
108
+ directed=self.directed,
109
+ )
110
+ self._cgraph = cgraph_cls()
111
+
112
+ self.node_attrs = NodeAttrs(self)
113
+ self.edge_attrs = EdgeAttrs(self)
114
+
115
+ def add_node(self, node: Any, *data: Any, **kwargs: Any) -> int:
116
+ """Add a single node to the graph.
117
+
118
+ The node attributes provided via *data and **kwargs must match the
119
+ data types and names specified in `node_attr_dtypes` when the graph
120
+ was created.
121
+
122
+ Parameters
123
+ ----------
124
+ node : Any
125
+ The node identifier to add to the graph.
126
+ *data : Any
127
+ Positional arguments for node attributes. Names/number of args
128
+ must match the `node_attr_dtypes`.
129
+ **kwargs : Any
130
+ Keyword arguments for node attributes. Names/number of kwargs
131
+ must match the `node_attr_dtypes`.
132
+
133
+ Returns
134
+ -------
135
+ int
136
+ Number of nodes added (1 if successful, 0 if node already exists).
137
+ """
138
+ return self._cgraph.add_node(node, *data, **kwargs)
139
+
140
+ def add_nodes(self, nodes: np.ndarray, *data: Any, **kwargs: Any) -> int:
141
+ """Add multiple nodes to the graph.
142
+
143
+ Node attributes provided via *data and **kwargs must match the
144
+ data types and names specified in `node_attr_dtypes`. Each attribute
145
+ array must have the same length as the `nodes` array.
146
+
147
+ Parameters
148
+ ----------
149
+ nodes : np.ndarray
150
+ Array of node identifiers to add to the graph.
151
+ *data : Any
152
+ Positional arguments for node attributes. Each argument should be
153
+ an array with length matching `nodes`. Names/number of args must
154
+ match the `node_attr_dtypes`.
155
+ **kwargs : Any
156
+ Keyword arguments for node attributes. Each argument should be
157
+ an array with length matching `nodes`. Names/number of kwargs
158
+ must match the `node_attr_dtypes`.
159
+
160
+ Returns
161
+ -------
162
+ int
163
+ Number of nodes successfully added.
164
+ """
165
+ return self._cgraph.add_nodes(nodes, *data, **kwargs)
166
+
167
+ def add_edge(self, edge: np.ndarray, *args: Any, **kwargs: Any) -> int:
168
+ """Add an edge to the graph.
169
+
170
+ The edge attributes provided via *args and **kwargs must match the
171
+ data types and names specified in `edge_attr_dtypes` when the graph
172
+ was created.
173
+
174
+ Parameters
175
+ ----------
176
+ edge : np.ndarray
177
+ Array of length 2 containing [source_node, target_node].
178
+ *args : Any
179
+ Positional arguments for edge attributes. Names/number of args
180
+ must match the `edge_attr_dtypes`.
181
+ **kwargs : Any
182
+ Keyword arguments for edge attributes. Names/number of kwargs
183
+ must match the `edge_attr_dtypes`.
184
+
185
+ Returns
186
+ -------
187
+ int
188
+ Number of edges added (1 if successful, 0 if edge already exists).
189
+ """
190
+ return self._cgraph.add_edge(edge, *args, **kwargs)
191
+
192
+ def add_edges(
193
+ self, edges: np.ndarray, *args: np.ndarray, **kwargs: np.ndarray
194
+ ) -> int:
195
+ """Add multiple edges to the graph.
196
+
197
+ Edge attributes provided via *args and **kwargs must match the
198
+ data types and names specified in `edge_attr_dtypes`. Each attribute
199
+ array must have the same length as the number of edges.
200
+
201
+ Parameters
202
+ ----------
203
+ edges : np.ndarray
204
+ 2D array of shape (n_edges, 2) where each row contains
205
+ [source_node, target_node].
206
+ *args : np.ndarray
207
+ Positional arguments for edge attributes. Each argument should be
208
+ an array with length matching the number of edges. Names/number
209
+ of args must match the `edge_attr_dtypes`.
210
+ **kwargs : np.ndarray
211
+ Keyword arguments for edge attributes. Each argument should be
212
+ an array with length matching the number of edges. Names/number
213
+ of kwargs must match the `edge_attr_dtypes`.
214
+
215
+ Returns
216
+ -------
217
+ int
218
+ Number of edges successfully added.
219
+ """
220
+ return self._cgraph.add_edges(edges, *args, **kwargs)
221
+
222
+ @property
223
+ def nodes(self) -> np.ndarray:
224
+ """Get all node IDs in the graph.
225
+
226
+ The returned array is a copy and modifications will not affect
227
+ the graph structure.
228
+
229
+ Returns
230
+ -------
231
+ np.ndarray
232
+ Array containing all node identifiers in the graph, ordered
233
+ by insertion order (earliest added first).
234
+ """
235
+ return self._cgraph.nodes()
236
+
237
+ def remove_node(self, node: Any) -> None:
238
+ """Remove a single node from the graph.
239
+
240
+ Removing a node will also remove all edges incident to that node.
241
+
242
+ Parameters
243
+ ----------
244
+ node : Any
245
+ The node identifier to remove from the graph.
246
+ """
247
+ return self._cgraph.remove_node(node)
248
+
249
+ def remove_nodes(self, nodes: np.ndarray) -> None:
250
+ """Remove multiple nodes from the graph.
251
+
252
+ Removing nodes will also remove all edges incident to those nodes.
253
+
254
+ Parameters
255
+ ----------
256
+ nodes : np.ndarray
257
+ Array of node identifiers to remove from the graph.
258
+ """
259
+ return self._cgraph.remove_nodes(nodes)
260
+
261
+ def nodes_data(self, nodes: np.ndarray | None = None) -> Iterator[tuple[Any, Any]]:
262
+ """Iterate over nodes and their associated data.
263
+
264
+ The node_data object provides access to node attributes as defined
265
+ by the `node_attr_dtypes` when the graph was created.
266
+
267
+ Parameters
268
+ ----------
269
+ nodes : np.ndarray, optional
270
+ Array of specific node identifiers to iterate over. If None,
271
+ iterates over all nodes in the graph.
272
+
273
+ Yields
274
+ ------
275
+ tuple[Any, Any]
276
+ Tuples of (node_id, node_data) where node_data is a view object
277
+ providing access to the node's attributes.
278
+ """
279
+ return self._cgraph.nodes_data(nodes)
280
+
281
+ def edges_data(self, us: np.ndarray, vs: np.ndarray) -> Iterator:
282
+ """Iterate over edge data for specified edges.
283
+
284
+ The arrays `us` and `vs` must have the same length. The edge data
285
+ objects provide access to edge attributes as defined by the
286
+ `edge_attr_dtypes` when the graph was created.
287
+
288
+ Parameters
289
+ ----------
290
+ us : np.ndarray
291
+ Array of source node identifiers.
292
+ vs : np.ndarray
293
+ Array of target node identifiers.
294
+
295
+ Yields
296
+ ------
297
+ Any
298
+ Edge data view objects providing access to edge attributes
299
+ for each edge (us[i], vs[i]).
300
+ """
301
+ return self._cgraph.edges_data(us, vs)
302
+
303
+ def num_edges(self) -> int:
304
+ """Get the total number of edges in the graph.
305
+
306
+ Returns
307
+ -------
308
+ int
309
+ The number of edges in the graph.
310
+ """
311
+ return self._cgraph.num_edges()
312
+
313
+ def __len__(self) -> int:
314
+ """Return the number of nodes in the graph.
315
+
316
+ Returns
317
+ -------
318
+ int
319
+ The number of nodes in the graph.
320
+ """
321
+ return self._cgraph.__len__()