@@ -98,6 +98,130 @@ def podvector_to_xp(self, copy=False):
9898 return self .to_cupy (copy ) if amr .Config .have_gpu else self .to_numpy (copy )
9999
100100
101+ def _is_host_accessible (cls ):
102+ """Check if a PODVector type's allocator provides host-accessible memory.
103+
104+ On CPU builds all allocators are host-accessible. On GPU builds only
105+ ``std``, ``pinned`` and ``managed`` allocators live in host memory;
106+ the rest (``arena``, ``device``, ``async``, ...) are device-only.
107+ """
108+ import inspect
109+
110+ amr = inspect .getmodule (cls )
111+ if not amr .Config .have_gpu :
112+ return True
113+ suffix = cls .__name__ .rsplit ("_" , 1 )[- 1 ]
114+ return suffix in ("std" , "pinned" , "managed" )
115+
116+
117+ def podvector_from_numpy (cls , arr ):
118+ """
119+ Create a new PODVector from a NumPy array (or array-like).
120+
121+ Always copies the data into a newly allocated PODVector.
122+ Only works for allocator types with host-accessible memory (e.g.,
123+ ``std``, ``pinned``). For device-only allocators, use
124+ :meth:`from_cupy` or :meth:`from_xp` instead.
125+
126+ Parameters
127+ ----------
128+ cls : type
129+ The PODVector type to construct.
130+ arr : array_like
131+ Input data, convertible to a NumPy array.
132+
133+ Returns
134+ -------
135+ PODVector
136+ A new PODVector with a copy of the data.
137+
138+ Raises
139+ ------
140+ TypeError
141+ If the allocator is not host-accessible.
142+ """
143+ import numpy as np
144+
145+ n = len (arr )
146+ if n == 0 :
147+ return cls ()
148+
149+ if not _is_host_accessible (cls ):
150+ raise TypeError (
151+ f"{ cls .__name__ } is not host-accessible. "
152+ "Use from_cupy() or from_xp() instead."
153+ )
154+
155+ pv = cls (n )
156+ np .array (pv , copy = False )[:] = arr
157+ return pv
158+
159+
160+ def podvector_from_cupy (cls , arr ):
161+ """
162+ Create a new PODVector from a CuPy array (or array-like).
163+
164+ Always copies the data into a newly allocated PODVector.
165+ Works for every allocator type: for host-only allocators the
166+ data is staged to the host through NumPy automatically.
167+
168+ Parameters
169+ ----------
170+ cls : type
171+ The PODVector type to construct.
172+ arr : array_like
173+ Input data, convertible to a CuPy array.
174+
175+ Returns
176+ -------
177+ PODVector
178+ A new PODVector with a copy of the data.
179+ """
180+ import cupy as cp
181+
182+ n = len (arr )
183+ if n == 0 :
184+ return cls ()
185+ pv = cls (n )
186+ if _is_host_accessible (cls ):
187+ import numpy as np
188+
189+ np .array (pv , copy = False )[:] = cp .asnumpy (arr )
190+ else :
191+ cp .asarray (pv )[:] = arr
192+ return pv
193+
194+
195+ def podvector_from_xp (cls , arr ):
196+ """
197+ Create a new PODVector from a NumPy or CuPy array,
198+ depending on amr.Config.have_gpu .
199+
200+ Always copies the data into a newly allocated PODVector.
201+ Unlike :meth:`to_xp`, a zero-copy view is not possible here because
202+ PODVector always owns its memory through its allocator.
203+
204+ This function is similar to CuPy's xp naming suggestion for CPU/GPU agnostic code:
205+ https://docs.cupy.dev/en/stable/user_guide/basic.html#how-to-write-cpu-gpu-agnostic-code
206+
207+ Parameters
208+ ----------
209+ cls : type
210+ The PODVector type to construct.
211+ arr : array_like
212+ Input data (NumPy or CuPy array).
213+
214+ Returns
215+ -------
216+ PODVector
217+ A new PODVector with a copy of the data.
218+ """
219+ import inspect
220+
221+ amr = inspect .getmodule (cls )
222+ return cls .from_cupy (arr ) if amr .Config .have_gpu else cls .from_numpy (arr )
223+
224+
101225def register_PODVector_extension (amr ):
102226 """PODVector helper methods"""
103227 import inspect
@@ -112,6 +236,12 @@ def register_PODVector_extension(amr):
112236 and member .__name__ .startswith ("PODVector_" )
113237 ),
114238 ):
239+ # instance methods: PODVector -> array
115240 POD_type .to_numpy = podvector_to_numpy
116241 POD_type .to_cupy = podvector_to_cupy
117242 POD_type .to_xp = podvector_to_xp
243+
244+ # class methods: array -> PODVector
245+ POD_type .from_numpy = classmethod (podvector_from_numpy )
246+ POD_type .from_cupy = classmethod (podvector_from_cupy )
247+ POD_type .from_xp = classmethod (podvector_from_xp )
0 commit comments