Coverage for cuda/core/graph/_adjacency_set_proxy.pyx: 76.05%
167 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 01:23 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-16 01:23 +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._rt 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, GraphNode node not None: GraphNode, bint is_fwd) -> None:
33 self._core = _AdjacencySetCore(node, is_fwd) 1pq!gLMNcdkorsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWmjl$aebf9#Xhin
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): 1pqgcdkomjlaebfhin
44 return False 1i
45 return (<_AdjacencySetCore>self._core).contains(<GraphNode>x) 1pqgcdkomjlaebfhin
47 def __iter__(self) -> Iterator[GraphNode]:
48 return iter((<_AdjacencySetCore>self._core).query()) 1!gLMNcdrsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWaebXhi
50 def __len__(self) -> int:
51 return (<_AdjacencySetCore>self._core).count() 1!cdaebXhi
53 def add(self, GraphNode value not None: GraphNode) -> None:
54 if not isinstance(value, GraphNode): 1kmlaef9#Xhin
55 raise TypeError(
56 f"expected GraphNode, got {type(value).__name__}")
57 (<_AdjacencySetCore>self._core).check_mutation(value) 1kmlaef9#Xhin
58 if value in self: 1kmlaefhin
59 return 1a
60 (<_AdjacencySetCore>self._core).add_edge(<GraphNode>value) 1kmlaefhin
62 def discard(self, value: object) -> None:
63 # Non-GraphNode values are a no-op, matching MutableSet semantics.
64 (<_AdjacencySetCore>self._core).check_owner_mutable() 1laehi
65 if value not in self: 1laehi
66 return 1ahi
67 (<_AdjacencySetCore>self._core).check_mutation(value) 1lae
68 (<_AdjacencySetCore>self._core).remove_edge(<GraphNode>value) 1lae
70 # --- override for bulk efficiency ---
72 def clear(self) -> None:
73 """Remove all edges in a single driver call."""
74 (<_AdjacencySetCore>self._core).check_owner_mutable() 1gcdjabf#
75 members = (<_AdjacencySetCore>self._core).query() 1gcdjabf
76 if members: 1gcdjabf
77 (<_AdjacencySetCore>self._core).remove_edges(members) 1cdabf
79 def __isub__(self, it: Set[Any]) -> "AdjacencySetProxy":
80 """Remove edges to all nodes in *it* in a single driver call."""
81 (<_AdjacencySetCore>self._core).check_owner_mutable() 1a
82 if it is self: 1a
83 self.clear()
84 else:
85 to_remove = [v for v in it if isinstance(v, GraphNode) and v in self] 1a
86 if to_remove: 1a
87 (<_AdjacencySetCore>self._core).remove_edges(to_remove) 1a
88 return self 1a
90 def update(self, *others) -> None:
91 """Add edges to multiple nodes at once."""
92 (<_AdjacencySetCore>self._core).check_owner_mutable() 1gcdkjab
93 nodes = [] 1gcdkjab
94 for other in others: 1gcdkjab
95 if isinstance(other, GraphNode): 1gcdkjab
96 nodes.append(other)
97 else:
98 for n in other: 1gcdkjab
99 if not isinstance(n, GraphNode): 1gcdkjab
100 raise TypeError(
101 f"expected GraphNode, got {type(n).__name__}")
102 nodes.append(n) 1gcdkjab
103 for n in nodes: 1gcdkjab
104 (<_AdjacencySetCore>self._core).check_mutation(n) 1gcdkjab
105 if not nodes: 1gcdkjab
106 return 1b
107 new = [n for n in nodes if n not in self] 1gcdkjab
108 if new: 1gcdkjab
109 (<_AdjacencySetCore>self._core).add_edges(new) 1gcdkjab
111 def __ior__(self, it: Set[Any]) -> "AdjacencySetProxy": # type: ignore[misc]
112 """Add edges to all nodes in *it* in a single driver call."""
113 self.update(it) 1a
114 return self 1a
116 def __repr__(self) -> str:
117 return "{" + ", ".join(repr(n) for n in self) + "}" 1a
120# ---- cdef core holding a function pointer ------------------------------------
122# Signature shared by driver_get_preds and driver_get_succs.
123ctypedef cydriver.CUresult (*_adj_fn_t)(
124 cydriver.CUgraphNode, cydriver.CUgraphNode*, size_t*) noexcept nogil
127cdef class _AdjacencySetCore:
128 """Cythonized core implementing AdjacencySetProxy"""
129 cdef:
130 GraphNodeHandle _h_node
131 GraphHandle _h_graph
132 _adj_fn_t _query_fn
133 bint _is_fwd
135 def __init__(self, GraphNode node, bint is_fwd):
136 self._h_node = node._h_node 1pq!gLMNcdkorsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWmjl$aebf9#Xhin
137 self._h_graph = graph_node_get_graph(node._h_node) 1pq!gLMNcdkorsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWmjl$aebf9#Xhin
138 self._is_fwd = is_fwd 1pq!gLMNcdkorsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWmjl$aebf9#Xhin
139 self._query_fn = driver_get_succs if is_fwd else driver_get_preds 1pq!gLMNcdkorsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWmjl$aebf9#Xhin
141 cdef inline void _resolve_edge(
142 self, GraphNode other,
143 cydriver.CUgraphNode* c_from,
144 cydriver.CUgraphNode* c_to) noexcept:
145 if self._is_fwd: 1gcdkmjlaebfhin
146 c_from[0] = as_cu(self._h_node) 1gdkmlabfhin
147 c_to[0] = as_cu(other._h_node) 1gdkmlabfhin
148 else:
149 c_from[0] = as_cu(other._h_node) 1cjebf
150 c_to[0] = as_cu(self._h_node) 1cjebf
152 cdef inline void check_owner_mutable(self) except *:
153 if as_cu(self._h_graph) == NULL: 1gcdkmjlaebf9#Xhin
154 raise RuntimeError("GraphDefinition is no longer valid")
155 if as_cu(self._h_node) == NULL: 1gcdkmjlaebf9#Xhin
156 raise RuntimeError("GraphNode has been destroyed") 1#
158 cdef inline void check_mutation(self, GraphNode other) except *:
159 self.check_owner_mutable() 1gcdkmjlaebf9#Xhin
160 GN_check_valid(other) 1gcdkmjlaebf9#Xhin
161 if other._is_entry: 1gcdkmjlaebf9hin
162 raise ValueError("The virtual graph entry node cannot be used in an edge")
163 if as_cu(graph_node_get_graph(other._h_node)) != as_cu(self._h_graph): 1gcdkmjlaebf9hin
164 raise ValueError("Graph nodes must belong to the same GraphDefinition") 19
166 cdef list query(self):
167 cdef cydriver.CUgraphNode c_node = as_cu(self._h_node) 1!gLMNcdrsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWjaebfXhi
168 if c_node == NULL: 1!gLMNcdrsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWjaebfXhi
169 return [] 1!X
170 cdef cydriver.CUgraphNode stack_buf[16]
171 cdef cydriver.CUgraphNode* nodes
172 cdef size_t count = 0 1gLMNcdrsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWjaebfhi
173 cdef size_t i
174 with nogil: 1gLMNcdrsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWjaebfhi
175 HANDLE_RETURN(self._query_fn(c_node, NULL, &count)) 1gLMNcdrsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWjaebfhi
176 if count == 0: 1gLMNcdrsYtuvwZ012xyzABC34DE5FG6HI7JK8OPQRSTUVWjaebfhi
177 return [] 1gYZ01234DE5FG6HI7JK8jaebf
178 cdef vector[cydriver.CUgraphNode] nodes_vec
179 if count <= 16: 1gLMNcdrstuvwxyzABCDEFGHIJKOPQRSTUVWaebfhi
180 nodes = stack_buf 1gLMNrstuvwxyzABCDEFGHIJKOPQRSTUVWaebfhi
181 else:
182 nodes_vec.resize(count) 1cd
183 nodes = nodes_vec.data() 1cd
184 with nogil: 1gLMNcdrstuvwxyzABCDEFGHIJKOPQRSTUVWaebfhi
185 HANDLE_RETURN(self._query_fn(c_node, nodes, &count)) 1gLMNcdrstuvwxyzABCDEFGHIJKOPQRSTUVWaebfhi
186 return [GraphNode._create(self._h_graph, nodes[i]) 1gLMNcdrstuvwxyzABCDEFGHIJKOPQRSTUVWaebfhi
187 for i in range(count)] 1gLMNcdrstuvwxyzABCDEFGHIJKOPQRSTUVWaebfhi
189 cdef bint contains(self, GraphNode other):
190 cdef cydriver.CUgraphNode c_node = as_cu(self._h_node) 1pqgcdkomjlaebfhin
191 cdef cydriver.CUgraphNode target = as_cu(other._h_node) 1pqgcdkomjlaebfhin
192 if c_node == NULL or target == NULL: 1pqgcdkomjlaebfhin
193 return False 1h
194 cdef cydriver.CUgraphNode stack_buf[16]
195 cdef cydriver.CUgraphNode* nodes
196 cdef size_t count = 0 1pqgcdkomjlaebfhin
197 cdef size_t i
198 with nogil: 1pqgcdkomjlaebfhin
199 HANDLE_RETURN(self._query_fn(c_node, NULL, &count)) 1pqgcdkomjlaebfhin
200 if count == 0: 1pqgcdkomjlaebfhin
201 return False 1gcdkmjlaebfhin
202 cdef vector[cydriver.CUgraphNode] nodes_vec
203 if count <= 16: 1pqcdolaebfh
204 nodes = stack_buf 1pqolaebfh
205 else:
206 nodes_vec.resize(count) 1cd
207 nodes = nodes_vec.data() 1cd
208 with nogil: 1pqcdolaebfh
209 HANDLE_RETURN(self._query_fn(c_node, nodes, &count)) 1pqcdolaebfh
210 for i in range(count): 1pqcdolaebfh
211 if nodes[i] == target: 1pqcdolaebfh
212 return True 1pqcdolae
213 return False 1oaebfh
215 cdef Py_ssize_t count(self):
216 cdef cydriver.CUgraphNode c_node = as_cu(self._h_node) 1!cdaebXhi
217 if c_node == NULL: 1!cdaebXhi
218 return 0 1!X
219 cdef size_t n = 0 1cdaebhi
220 with nogil: 1cdaebhi
221 HANDLE_RETURN(self._query_fn(c_node, NULL, &n)) 1cdaebhi
222 return <Py_ssize_t>n 1cdaebhi
224 cdef void add_edge(self, GraphNode other):
225 cdef cydriver.CUgraphNode c_from, c_to
226 self._resolve_edge(other, &c_from, &c_to) 1kmlaefhin
227 with nogil: 1kmlaefhin
228 HANDLE_RETURN(driver_add_edges(as_cu(self._h_graph), &c_from, &c_to, 1)) 1kmlaefhin
230 cdef void add_edges(self, list nodes):
231 cdef size_t n = len(nodes) 1gcdkjab
232 cdef vector[cydriver.CUgraphNode] from_vec
233 cdef vector[cydriver.CUgraphNode] to_vec
234 from_vec.resize(n) 1gcdkjab
235 to_vec.resize(n) 1gcdkjab
236 cdef size_t i
237 for i in range(n): 1gcdkjab
238 self._resolve_edge(<GraphNode>nodes[i], &from_vec[i], &to_vec[i]) 1gcdkjab
239 with nogil: 1gcdkjab
240 HANDLE_RETURN(driver_add_edges( 1gcdkjab
241 as_cu(self._h_graph), from_vec.data(), to_vec.data(), n))
243 cdef void remove_edge(self, GraphNode other):
244 cdef cydriver.CUgraphNode c_from, c_to
245 self._resolve_edge(other, &c_from, &c_to) 1lae
246 with nogil: 1lae
247 HANDLE_RETURN(driver_remove_edges(as_cu(self._h_graph), &c_from, &c_to, 1)) 1lae
249 cdef void remove_edges(self, list nodes):
250 cdef size_t n = len(nodes) 1cdabf
251 cdef vector[cydriver.CUgraphNode] from_vec
252 cdef vector[cydriver.CUgraphNode] to_vec
253 from_vec.resize(n) 1cdabf
254 to_vec.resize(n) 1cdabf
255 cdef size_t i
256 for i in range(n): 1cdabf
257 self._resolve_edge(<GraphNode>nodes[i], &from_vec[i], &to_vec[i]) 1cdabf
258 with nogil: 1cdabf
259 HANDLE_RETURN(driver_remove_edges( 1cdabf
260 as_cu(self._h_graph), from_vec.data(), to_vec.data(), n))
263# ---- driver wrappers: absorb CUDA version differences ----
265cdef inline cydriver.CUresult driver_get_preds(
266 cydriver.CUgraphNode node, cydriver.CUgraphNode* out,
267 size_t* count) noexcept nogil:
268 IF CUDA_CORE_BUILD_MAJOR >= 13:
269 return cydriver.cuGraphNodeGetDependencies(node, out, NULL, count) 1pqgLMNcorsYtuvwZ012xyzABC34DE5FG6OPQRSTUVWjebf
270 ELSE:
271 return cydriver.cuGraphNodeGetDependencies(node, out, count)
274cdef inline cydriver.CUresult driver_get_succs(
275 cydriver.CUgraphNode node, cydriver.CUgraphNode* out,
276 size_t* count) noexcept nogil:
277 IF CUDA_CORE_BUILD_MAJOR >= 13:
278 return cydriver.cuGraphNodeGetDependentNodes(node, out, NULL, count) 1gdkrstuvwxyzABCHI7JK8mlaebfhin
279 ELSE:
280 return cydriver.cuGraphNodeGetDependentNodes(node, out, count)
283cdef inline cydriver.CUresult driver_add_edges(
284 cydriver.CUgraph graph, cydriver.CUgraphNode* from_arr,
285 cydriver.CUgraphNode* to_arr, size_t count) noexcept nogil:
286 IF CUDA_CORE_BUILD_MAJOR >= 13:
287 return cydriver.cuGraphAddDependencies( 1gcdkmjlaebfhin
288 graph, from_arr, to_arr, NULL, count)
289 ELSE:
290 return cydriver.cuGraphAddDependencies(
291 graph, from_arr, to_arr, count)
294cdef inline cydriver.CUresult driver_remove_edges(
295 cydriver.CUgraph graph, cydriver.CUgraphNode* from_arr,
296 cydriver.CUgraphNode* to_arr, size_t count) noexcept nogil:
297 IF CUDA_CORE_BUILD_MAJOR >= 13:
298 return cydriver.cuGraphRemoveDependencies( 1cdlaebf
299 graph, from_arr, to_arr, NULL, count)
300 ELSE:
301 return cydriver.cuGraphRemoveDependencies(
302 graph, from_arr, to_arr, count)