@@ -162,8 +162,8 @@ namespace
162162}
163163
164164template <class T , class Allocator = std::allocator<T> >
165- void make_PODVector ( py::module &m, std::string typestr, std::string allocstr)
166- {
165+ py::class_<PODVector<T, Allocator> >
166+ make_PODVector (py:: module &m, std::string typestr, std::string allocstr) {
167167 using namespace amrex ;
168168
169169 using PODVector_type = PODVector<T, Allocator>;
@@ -176,7 +176,7 @@ void make_PODVector(py::module &m, std::string typestr, std::string allocstr)
176176 .append (allocstr)
177177 .append (" ' allocation." );
178178
179- py::class_<PODVector_type>(m, podv_name.c_str (), podv_doc.c_str ())
179+ auto cl = py::class_<PODVector_type>(m, podv_name.c_str (), podv_doc.c_str ())
180180 .def (" __repr__" ,
181181 [typestr](PODVector_type const & pv) {
182182 std::stringstream s, rs;
@@ -224,15 +224,6 @@ void make_PODVector(py::module &m, std::string typestr, std::string allocstr)
224224 py::arg (" strategy" ) = GrowthStrategy::Poisson
225225 )
226226 .def (" shrink_to_fit" , &PODVector_type::shrink_to_fit)
227- .def (" to_host" , [](PODVector_type const & pv) {
228- PODVector<T, amrex::PinnedArenaAllocator<T>> h_data (pv.size ());
229- amrex::Gpu::copyAsync (amrex::Gpu::deviceToHost,
230- pv.begin (), pv.end (),
231- h_data.begin ()
232- );
233- Gpu::streamSynchronize ();
234- return h_data;
235- }, py::return_value_policy::move)
236227
237228 // front
238229 // back
@@ -290,21 +281,102 @@ PODVector
290281 A new PODVector with a copy of the data.
291282)" )
292283 ;
284+
285+ return cl;
286+ }
287+
288+ /* * Bind the host/device copy helpers ``to_host`` and ``to_device`` on a
289+ * single PODVector class.
290+ *
291+ * Both return a different PODVector allocator type than ``cl`` (``to_host`` a
292+ * pinned vector, ``to_device`` a ``Gpu::DeviceVector`` = ``arena`` on GPU,
293+ * ``std`` on CPU). Binding them only after every allocator class exists lets
294+ * pybind11 stubgen resolve those return types to registered Python types.
295+ */
296+ template <class T , class Allocator >
297+ void bind_host_device (py::class_<PODVector<T, Allocator> > & cl)
298+ {
299+ using namespace amrex ;
300+
301+ cl.def (" to_host" ,
302+ [](PODVector<T, Allocator> const & src) {
303+ PODVector<T, amrex::PinnedArenaAllocator<T>> h_data (src.size ());
304+ amrex::Gpu::copyAsync (amrex::Gpu::deviceToHost,
305+ src.begin (), src.end (),
306+ h_data.begin ()
307+ );
308+ Gpu::streamSynchronize ();
309+ return h_data;
310+ },
311+ py::return_value_policy::move,
312+ " Copy this vector into a new pinned (host) PODVector. Mirrors to_device()." )
313+
314+ .def (" to_device" ,
315+ [](PODVector<T, Allocator> const & src) -> amrex::Gpu::DeviceVector<T> {
316+ amrex::Gpu::DeviceVector<T> dst (src.size ());
317+ if (src.empty ()) {
318+ return dst;
319+ }
320+ #ifdef AMREX_USE_GPU
321+ bool const src_host = is_host_accessible (src);
322+ bool const dst_host = is_host_accessible (dst);
323+ if (src_host && !dst_host) {
324+ Gpu::copyAsync (Gpu::hostToDevice, src.begin (), src.end (), dst.begin ());
325+ Gpu::streamSynchronize ();
326+ return dst;
327+ } else if (!src_host && dst_host) {
328+ Gpu::copyAsync (Gpu::deviceToHost, src.begin (), src.end (), dst.begin ());
329+ Gpu::streamSynchronize ();
330+ return dst;
331+ } else if (!src_host && !dst_host) {
332+ Gpu::copyAsync (Gpu::deviceToDevice, src.begin (), src.end (), dst.begin ());
333+ Gpu::streamSynchronize ();
334+ return dst;
335+ }
336+ #endif
337+ std::copy (src.begin (), src.end (), dst.begin ());
338+ return dst;
339+ },
340+ py::return_value_policy::move,
341+ " Copy this vector into a new amrex Gpu::DeviceVector (the arena "
342+ " allocator on GPU, std on CPU), transferring across memory spaces "
343+ " as needed. Mirrors to_host()." );
344+ }
345+
346+ /* * Bind ``to_host``/``to_device`` on each of the given PODVector classes. */
347+ template <class ... PODVectorClass>
348+ void add_host_device (PODVectorClass &... cls)
349+ {
350+ (bind_host_device (cls), ...);
293351}
294352
295353template <class T >
296354void make_PODVector (py::module &m, std::string typestr)
297355{
298356 // see Src/Base/AMReX_GpuContainers.H
299- make_PODVector<T, amrex::PinnedArenaAllocator<T>> (m, typestr, " pinned" );
300- make_PODVector<T, amrex::ArenaAllocator<T>> (m, typestr, " arena" );
301- make_PODVector<T, std::allocator<T>> (m, typestr, " std" );
357+ auto pv_pinned = make_PODVector<T, amrex::PinnedArenaAllocator<T>> (m, typestr, " pinned" );
358+ auto pv_arena = make_PODVector<T, amrex::ArenaAllocator<T>> (m, typestr, " arena" );
359+ auto pv_std = make_PODVector<T, std::allocator<T>> (m, typestr, " std" );
360+ #ifdef AMREX_USE_GPU
361+ auto pv_device = make_PODVector<T, amrex::DeviceArenaAllocator<T>> (m, typestr, " device" );
362+ auto pv_managed = make_PODVector<T, amrex::ManagedArenaAllocator<T>> (m, typestr, " managed" );
363+ auto pv_async = make_PODVector<T, amrex::AsyncArenaAllocator<T>> (m, typestr, " async" );
364+ #endif
365+ auto pv_polymorphic = make_PODVector<T, amrex::PolymorphicArenaAllocator<T>> (m, typestr, " polymorphic" );
366+
367+ // bind to_host/to_device now that every PODVector allocator class is
368+ // registered, so their PODVector return types resolve to known Python types
369+ add_host_device (
370+ pv_pinned,
371+ pv_arena,
372+ pv_std,
302373#ifdef AMREX_USE_GPU
303- make_PODVector<T, amrex::DeviceArenaAllocator<T>> (m, typestr, " device " );
304- make_PODVector<T, amrex::ManagedArenaAllocator<T>> (m, typestr, " managed " );
305- make_PODVector<T, amrex::AsyncArenaAllocator<T>> (m, typestr, " async " );
374+ pv_device,
375+ pv_managed,
376+ pv_async,
306377#endif
307- make_PODVector<T, amrex::PolymorphicArenaAllocator<T>> (m, typestr, " polymorphic" );
378+ pv_polymorphic
379+ );
308380
309381 // Implement AMReX_GpuContainers.H
310382 // Alias matching Gpu::DeviceVector<T> etc. — resolves per platform:
0 commit comments