Coverage for cuda/core/graph/_adjacency_set_proxy.pyx: 94.61%
167 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-10 02:27 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-10 02:27 +0000
1# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2#
3# SPDX-License-Identifier: Apache-2.0
5"""Mutable-set proxy for graph node predecessors and successors."""
7from libc.stddef cimport size_t
8from libcpp.vector cimport vector
9from cuda.bindings cimport cydriver
10from cuda.core.graph._graph_node cimport GraphNode, GN_check_valid
11from cuda.core._resource_handles cimport (
12 GraphHandle,
13 GraphNodeHandle,
14 as_cu,
15 graph_node_get_graph,
16)
17from cuda.core._utils.cuda_utils cimport HANDLE_RETURN
18from collections.abc import Iterable, Iterator, MutableSet, Set
19from typing import Any, TypeVar
21_S = TypeVar("_S")
24# ---- Python MutableSet wrapper ----------------------------------------------
26class AdjacencySetProxy(MutableSet[GraphNode]):
27 """Mutable set proxy for a node's predecessors or successors. Mutations
28 write through to the underlying CUDA graph."""
30 __slots__ = ("_core",)
32 def __init__(self, node: GraphNode, bint is_fwd) -> None:
33 self._core = _AdjacencySetCore(node, is_fwd) 1pq7gLMNcdjorsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTmik9aebf68Uhn
35 # Used by operators such as &|^ to create non-proxy views when needed.
36 @classmethod
37 def _from_iterable(cls, it: Iterable[_S]) -> set[_S]:
38 return set(it) 1a
40 # --- abstract methods required by MutableSet ---
42 def __contains__(self, x: object) -> bool:
43 if not isinstance(x, GraphNode): 1lpqgcdjomikaebfhn
44 return False
45 return (<_AdjacencySetCore>self._core).contains(<GraphNode>x) 1pqgcdjomikaebfhn
47 def __iter__(self) -> Iterator[GraphNode]:
48 return iter((<_AdjacencySetCore>self._core).query()) 17gLMNcdrsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTaebUh
50 def __len__(self) -> int:
51 return (<_AdjacencySetCore>self._core).count() 17cdaebUh
53 def add(self, value: GraphNode) -> None:
54 if not isinstance(value, GraphNode): 1jmkaef68Uhn
55 raise TypeError(
56 f"expected GraphNode, got {type(value).__name__}")
57 (<_AdjacencySetCore>self._core).check_mutation(value) 1jmkaef68Uhn
58 if value in self: 1jmkaefhn
59 return 1a
60 (<_AdjacencySetCore>self._core).add_edge(<GraphNode>value) 1jmkaefhn
62 def discard(self, value: GraphNode) -> None:
63 (<_AdjacencySetCore>self._core).check_owner_mutable() 1kaeh
64 if value not in self: 1kaeh
65 return 1ah
66 (<_AdjacencySetCore>self._core).check_mutation(value) 1kae
67 (<_AdjacencySetCore>self._core).remove_edge(<GraphNode>value) 1kae
69 # --- override for bulk efficiency ---
71 def clear(self) -> None:
72 """Remove all edges in a single driver call."""
73 (<_AdjacencySetCore>self._core).check_owner_mutable() 1gcdiabf8
74 members = (<_AdjacencySetCore>self._core).query() 1gcdiabf
75 if members: 1gcdiabf
76 (<_AdjacencySetCore>self._core).remove_edges(members) 1cdabf
78 def __isub__(self, it: Set[Any]) -> "AdjacencySetProxy":
79 """Remove edges to all nodes in *it* in a single driver call."""
80 (<_AdjacencySetCore>self._core).check_owner_mutable() 1a
81 if it is self: 1a
82 self.clear()
83 else:
84 to_remove = [v for v in it if isinstance(v, GraphNode) and v in self] 1a
85 if to_remove: 1a
86 (<_AdjacencySetCore>self._core).remove_edges(to_remove) 1a
87 return self 1a
89 def update(self, *others) -> None:
90 """Add edges to multiple nodes at once."""
91 (<_AdjacencySetCore>self._core).check_owner_mutable() 1gcdjiab
92 nodes = [] 1gcdjiab
93 for other in others: 1gcdjiab
94 if isinstance(other, GraphNode): 1gcdjiab
95 nodes.append(other)
96 else:
97 for n in other: 1gcdjiab
98 if not isinstance(n, GraphNode): 1gcdjiab
99 raise TypeError(
100 f"expected GraphNode, got {type(n).__name__}")
101 nodes.append(n) 1gcdjiab
102 for n in nodes: 1gcdjiab
103 (<_AdjacencySetCore>self._core).check_mutation(n) 1gcdjiab
104 if not nodes: 1gcdjiab
105 return 1b
106 new = [n for n in nodes if n not in self] 1gcdjiab
107 if new: 1gcdjiab
108 (<_AdjacencySetCore>self._core).add_edges(new) 1gcdjiab
110 def __ior__(self, it: Set[Any]) -> "AdjacencySetProxy": # type: ignore[misc]
111 """Add edges to all nodes in *it* in a single driver call."""
112 self.update(it) 1a
113 return self 1a
115 def __repr__(self) -> str:
116 return "{" + ", ".join(repr(n) for n in self) + "}" 1a
119# ---- cdef core holding a function pointer ------------------------------------
121# Signature shared by driver_get_preds and driver_get_succs.
122ctypedef cydriver.CUresult (*_adj_fn_t)(
123 cydriver.CUgraphNode, cydriver.CUgraphNode*, size_t*) noexcept nogil
126cdef class _AdjacencySetCore:
127 """Cythonized core implementing AdjacencySetProxy"""
128 cdef:
129 GraphNodeHandle _h_node
130 GraphHandle _h_graph
131 _adj_fn_t _query_fn
132 bint _is_fwd
134 def __init__(self, GraphNode node, bint is_fwd):
135 self._h_node = node._h_node 1pq7gLMNcdjorsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTmik9aebf68Uhn
136 self._h_graph = graph_node_get_graph(node._h_node) 1pq7gLMNcdjorsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTmik9aebf68Uhn
137 self._is_fwd = is_fwd 1pq7gLMNcdjorsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTmik9aebf68Uhn
138 self._query_fn = driver_get_succs if is_fwd else driver_get_preds 1pq7gLMNcdjorsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTmik9aebf68Uhn
140 cdef inline void _resolve_edge(
141 self, GraphNode other,
142 cydriver.CUgraphNode* c_from,
143 cydriver.CUgraphNode* c_to) noexcept:
144 if self._is_fwd: 1gcdjmikaebfhn
145 c_from[0] = as_cu(self._h_node) 1gdjmkabfhn
146 c_to[0] = as_cu(other._h_node) 1gdjmkabfhn
147 else:
148 c_from[0] = as_cu(other._h_node) 1ciebf
149 c_to[0] = as_cu(self._h_node) 1ciebf
151 cdef inline void check_owner_mutable(self) except *:
152 if as_cu(self._h_graph) == NULL: 1gcdjmikaebf68Uhn
153 raise RuntimeError("GraphDefinition is no longer valid")
154 if as_cu(self._h_node) == NULL: 1gcdjmikaebf68Uhn
155 raise RuntimeError("GraphNode has been destroyed") 18
157 cdef inline void check_mutation(self, GraphNode other) except *:
158 self.check_owner_mutable() 1gcdjmikaebf68Uhn
159 GN_check_valid(other) 1gcdjmikaebf68Uhn
160 if other._is_entry: 1gcdjmikaebf6hn
161 raise ValueError("The virtual graph entry node cannot be used in an edge")
162 if as_cu(graph_node_get_graph(other._h_node)) != as_cu(self._h_graph): 1gcdjmikaebf6hn
163 raise ValueError("Graph nodes must belong to the same GraphDefinition") 16
165 cdef list query(self):
166 cdef cydriver.CUgraphNode c_node = as_cu(self._h_node) 17gLMNcdrsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTiaebfUh
167 if c_node == NULL: 17gLMNcdrsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTiaebfUh
168 return [] 17U
169 cdef cydriver.CUgraphNode stack_buf[16]
170 cdef cydriver.CUgraphNode* nodes
171 cdef size_t count = 0 1gLMNcdrsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTiaebfh
172 cdef size_t i
173 with nogil: 1gLMNcdrsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTiaebfh
174 HANDLE_RETURN(self._query_fn(c_node, NULL, &count)) 1gLMNcdrsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTiaebfh
175 if count == 0: 1gLMNcdrsVtuvwWXYZxyzABC01DE2FG3HI4JK5OPQRSTiaebfh
176 return [] 1gVWXYZ01DE2FG3HI4JK5iaebf
177 cdef vector[cydriver.CUgraphNode] nodes_vec
178 if count <= 16: 1gLMNcdrstuvwxyzABCDEFGHIJKOPQRSTaebfh
179 nodes = stack_buf 1gLMNrstuvwxyzABCDEFGHIJKOPQRSTaebfh
180 else:
181 nodes_vec.resize(count) 1cd
182 nodes = nodes_vec.data() 1cd
183 with nogil: 1gLMNcdrstuvwxyzABCDEFGHIJKOPQRSTaebfh
184 HANDLE_RETURN(self._query_fn(c_node, nodes, &count)) 1gLMNcdrstuvwxyzABCDEFGHIJKOPQRSTaebfh
185 return [GraphNode._create(self._h_graph, nodes[i]) 1gLMNcdrstuvwxyzABCDEFGHIJKOPQRSTaebfh
186 for i in range(count)] 1gLMNcdrstuvwxyzABCDEFGHIJKOPQRSTaebfh
188 cdef bint contains(self, GraphNode other):
189 cdef cydriver.CUgraphNode c_node = as_cu(self._h_node) 1pqgcdjomikaebfhn
190 cdef cydriver.CUgraphNode target = as_cu(other._h_node) 1pqgcdjomikaebfhn
191 if c_node == NULL or target == NULL: 1pqgcdjomikaebfhn
192 return False 1h
193 cdef cydriver.CUgraphNode stack_buf[16]
194 cdef cydriver.CUgraphNode* nodes
195 cdef size_t count = 0 1pqgcdjomikaebfhn
196 cdef size_t i
197 with nogil: 1pqgcdjomikaebfhn
198 HANDLE_RETURN(self._query_fn(c_node, NULL, &count)) 1pqgcdjomikaebfhn
199 if count == 0: 1pqgcdjomikaebfhn
200 return False 1gcdjmikaebfhn
201 cdef vector[cydriver.CUgraphNode] nodes_vec
202 if count <= 16: 1pqcdokaebfh
203 nodes = stack_buf 1pqokaebfh
204 else:
205 nodes_vec.resize(count) 1cd
206 nodes = nodes_vec.data() 1cd
207 with nogil: 1pqcdokaebfh
208 HANDLE_RETURN(self._query_fn(c_node, nodes, &count)) 1pqcdokaebfh
209 for i in range(count): 1pqcdokaebfh
210 if nodes[i] == target: 1pqcdokaebfh
211 return True 1pqcdokae
212 return False 1oaebfh
214 cdef Py_ssize_t count(self):
215 cdef cydriver.CUgraphNode c_node = as_cu(self._h_node) 17cdaebUh
216 if c_node == NULL: 17cdaebUh
217 return 0 17U
218 cdef size_t n = 0 1cdaebh
219 with nogil: 1cdaebh
220 HANDLE_RETURN(self._query_fn(c_node, NULL, &n)) 1cdaebh
221 return <Py_ssize_t>n 1cdaebh
223 cdef void add_edge(self, GraphNode other):
224 cdef cydriver.CUgraphNode c_from, c_to
225 self._resolve_edge(other, &c_from, &c_to) 1jmkaefhn
226 with nogil: 1jmkaefhn
227 HANDLE_RETURN(driver_add_edges(as_cu(self._h_graph), &c_from, &c_to, 1)) 1jmkaefhn
229 cdef void add_edges(self, list nodes):
230 cdef size_t n = len(nodes) 1gcdjiab
231 cdef vector[cydriver.CUgraphNode] from_vec
232 cdef vector[cydriver.CUgraphNode] to_vec
233 from_vec.resize(n) 1gcdjiab
234 to_vec.resize(n) 1gcdjiab
235 cdef size_t i
236 for i in range(n): 1gcdjiab
237 self._resolve_edge(<GraphNode>nodes[i], &from_vec[i], &to_vec[i]) 1gcdjiab
238 with nogil: 1gcdjiab
239 HANDLE_RETURN(driver_add_edges( 1gcdjiab
240 as_cu(self._h_graph), from_vec.data(), to_vec.data(), n))
242 cdef void remove_edge(self, GraphNode other):
243 cdef cydriver.CUgraphNode c_from, c_to
244 self._resolve_edge(other, &c_from, &c_to) 1kae
245 with nogil: 1kae
246 HANDLE_RETURN(driver_remove_edges(as_cu(self._h_graph), &c_from, &c_to, 1)) 1kae
248 cdef void remove_edges(self, list nodes):
249 cdef size_t n = len(nodes) 1cdabf
250 cdef vector[cydriver.CUgraphNode] from_vec
251 cdef vector[cydriver.CUgraphNode] to_vec
252 from_vec.resize(n) 1cdabf
253 to_vec.resize(n) 1cdabf
254 cdef size_t i
255 for i in range(n): 1cdabf
256 self._resolve_edge(<GraphNode>nodes[i], &from_vec[i], &to_vec[i]) 1cdabf
257 with nogil: 1cdabf
258 HANDLE_RETURN(driver_remove_edges( 1cdabf
259 as_cu(self._h_graph), from_vec.data(), to_vec.data(), n))
262# ---- driver wrappers: absorb CUDA version differences ----
264cdef inline cydriver.CUresult driver_get_preds(
265 cydriver.CUgraphNode node, cydriver.CUgraphNode* out,
266 size_t* count) noexcept nogil:
267 IF CUDA_CORE_BUILD_MAJOR >= 13:
268 return cydriver.cuGraphNodeGetDependencies(node, out, NULL, count) 1pqgLMNcorsVtuvwWXYZxyzABC01DE2FG3OPQRSTiebf
269 ELSE:
270 return cydriver.cuGraphNodeGetDependencies(node, out, count)
273cdef inline cydriver.CUresult driver_get_succs(
274 cydriver.CUgraphNode node, cydriver.CUgraphNode* out,
275 size_t* count) noexcept nogil:
276 IF CUDA_CORE_BUILD_MAJOR >= 13:
277 return cydriver.cuGraphNodeGetDependentNodes(node, out, NULL, count) 1gdjrstuvwxyzABCHI4JK5mkaebfhn
278 ELSE:
279 return cydriver.cuGraphNodeGetDependentNodes(node, out, count)
282cdef inline cydriver.CUresult driver_add_edges(
283 cydriver.CUgraph graph, cydriver.CUgraphNode* from_arr,
284 cydriver.CUgraphNode* to_arr, size_t count) noexcept nogil:
285 IF CUDA_CORE_BUILD_MAJOR >= 13:
286 return cydriver.cuGraphAddDependencies( 1gcdjmikaebfhn
287 graph, from_arr, to_arr, NULL, count)
288 ELSE:
289 return cydriver.cuGraphAddDependencies(
290 graph, from_arr, to_arr, count)
293cdef inline cydriver.CUresult driver_remove_edges(
294 cydriver.CUgraph graph, cydriver.CUgraphNode* from_arr,
295 cydriver.CUgraphNode* to_arr, size_t count) noexcept nogil:
296 IF CUDA_CORE_BUILD_MAJOR >= 13:
297 return cydriver.cuGraphRemoveDependencies( 1cdkaebf
298 graph, from_arr, to_arr, NULL, count)
299 ELSE:
300 return cydriver.cuGraphRemoveDependencies(
301 graph, from_arr, to_arr, count)