.. _program_listing_file_src_pcms_utility_arrays.h: Program Listing for File arrays.h ================================= |exhale_lsh| :ref:`Return to documentation for file ` (``src/pcms/utility/arrays.h``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp #ifndef PCMS_COUPLING_ARRAYS_H #define PCMS_COUPLING_ARRAYS_H #include "mdspan/mdspan.hpp" #include "pcms/utility/types.h" #include "pcms/utility/memory_spaces.h" #include namespace pcms { namespace detail { template struct memory_space_accessor : public Kokkos::default_accessor { using memory_space = MemorySpace; // Default constructor memory_space_accessor() = default; // Converting constructor to allow adding const qualification template >> constexpr memory_space_accessor( const memory_space_accessor&) noexcept { } }; template > struct HasValueType : std::false_type {}; template struct HasValueType> : std::true_type {}; template > struct memory_space_selector { using type = HostMemorySpace; }; template struct memory_space_selector> { using type = typename T::memory_space; }; template using memory_space_selector_t = typename memory_space_selector::type; template struct arr_trait; template struct arr_trait { using type = typename T::value_type; }; template struct arr_trait { using type = T; }; template struct arr_trait> { using type = T; }; template struct arr_trait> { using type = T; }; template using element_type_t = typename arr_trait::value>::type; // Layout selector: detects layout from source type template > struct layout_selector { using type = Kokkos::layout_right; // Default for host arrays and std::array }; // Specialization for types with array_layout (Kokkos Views) template struct layout_selector> { using type = std::conditional_t< std::is_same_v, Kokkos::layout_left, Kokkos::layout_right>; }; template using layout_selector_t = typename layout_selector::type; // Default layout selector for memory spaces // GPU memory spaces typically use LayoutLeft for better coalescing template > struct default_layout_for_memory_space { using type = Kokkos::layout_right; // Default for host }; #ifdef PCMS_HAS_DISTINCT_DEVICE_MEMORY_SPACE // Specialization for device memory space - use LayoutLeft for GPU template <> struct default_layout_for_memory_space { using type = Kokkos::layout_left; }; #endif template using default_layout_for_memory_space_t = typename default_layout_for_memory_space::type; } // namespace detail template auto make_mdspan(const ContainerType& /* unused */) -> Kokkos::mdspan { static_assert(detail::dependent_always_false::type, "creating mdspan is not implemented for type"); } // TODO make_mdspan template , typename IndexType = LO> using View = Kokkos::mdspan, LayoutPolicy, detail::memory_space_accessor< std::remove_reference_t, MemorySpace>>; template > using Rank1View = View<1, ElementType, MemorySpace, LayoutPolicy>; template > using Rank2View = View<2, ElementType, MemorySpace, LayoutPolicy>; template > using Rank3View = View<3, ElementType, MemorySpace, LayoutPolicy>; template > using Rank4View = View<4, ElementType, MemorySpace, LayoutPolicy>; template using GlobalIDView = View<1, const GO, MemorySpace, Kokkos::layout_right, GO>; // default implementation of make_array_view template , typename ElementType = detail::element_type_t> auto make_array_view(const T& array) -> Rank1View { using std::data; using std::size; return Rank1View{data(array), size(array)}; } template , typename ElementType = detail::element_type_t> auto make_array_view(T& array) -> Rank1View { using std::data; using std::size; return Rank1View{data(array), size(array)}; } template , typename ElementType = detail::element_type_t> auto make_const_array_view(T& array) -> Rank1View { using std::data; using std::size; return Rank1View{data(array), size(array)}; } // Helper functions to create mdspan views from Kokkos Views with layout // detection template auto MakeRank1View(Kokkos::View& view) { using KokkosView = Kokkos::View; using MemorySpace = typename KokkosView::memory_space; using LayoutPolicy = detail::layout_selector_t; return Rank1View(view.data(), view.extent(0)); } template auto MakeRank1View(const Kokkos::View& view) { using KokkosView = Kokkos::View; using MemorySpace = typename KokkosView::memory_space; using LayoutPolicy = detail::layout_selector_t; return Rank1View(view.data(), view.extent(0)); } template auto MakeRank2View(Kokkos::View& view) { using KokkosView = Kokkos::View; using MemorySpace = typename KokkosView::memory_space; using LayoutPolicy = detail::layout_selector_t; return Rank2View(view.data(), view.extent(0), view.extent(1)); } template auto MakeRank2View(const Kokkos::View& view) { using KokkosView = Kokkos::View; using MemorySpace = typename KokkosView::memory_space; using LayoutPolicy = detail::layout_selector_t; return Rank2View( view.data(), view.extent(0), view.extent(1)); } template auto MakeConstRank2View(const Kokkos::View& view) { using KokkosView = Kokkos::View; using MemorySpace = typename KokkosView::memory_space; using LayoutPolicy = detail::layout_selector_t; return Rank2View( view.data(), view.extent(0), view.extent(1)); } template auto MakeConstRank2View(Omega_h::Read array, int dim) { // Cannonical usage of Omega_h arrays is a layout_right 1D array return Rank2View( array.data(), array.size() / dim, dim); } // Flatten a contiguous (exhaustive) 2D [dof][comp] view into a 1D view over the // same storage. Intended for boundary code (serialization, element-wise // comparison) that wants a flat [num_dof_holder * num_components] view rather // than indexing pointers directly. template Rank1View FlattenToRank1View( Rank2View view) { return Rank1View(view.data_handle(), static_cast(view.size())); } // utility function to deep copy between layout incompatible views // layout imcompatible view can't be deep_copied directly between different // memory spaces, the workaround is provided from // https://kokkos.org/kokkos-core-wiki/API/core/view/deep_copy.html#how-to-get-layout-incompatible-views-copied template auto DeepCopyMismatchLayouts(DestView& dest, const SrcView& src) { static_assert(Kokkos::is_view::value && Kokkos::is_view::value, "Both arguments must be Kokkos::View types"); using DestMemSpace = typename DestView::memory_space; using SrcMemSpace = typename SrcView::memory_space; if constexpr (std::is_same_v && !std::is_same_v) { // Device to Host: mirror the device view to host auto src_tmp = Kokkos::create_mirror_view_and_copy(HostMemorySpace(), src); Kokkos::deep_copy(dest, src_tmp); } else if constexpr (!std::is_same_v && std::is_same_v) { // Host to Device: create mirror on device from host view auto dest_tmp = Kokkos::create_mirror_view(dest); Kokkos::deep_copy(dest_tmp, src); Kokkos::deep_copy(dest, dest_tmp); } else { // Same memory space Kokkos::deep_copy(dest, src); } } template void ConvertMismatchLayoutView2D(DestView& dest, const SrcView& src) { Kokkos::parallel_for( "ConvertMismatchLayoutView2D", Kokkos::RangePolicy(0, src.extent(0)), KOKKOS_LAMBDA(int i) { for (int j = 0; j < src.extent(1); ++j) { dest(i, j) = src(i, j); } }); } // utility function to fill a view with sequentially increasing values template void iota_view(Kokkos::View view, T start = 0) { Kokkos::parallel_for( "iota_view", view.extent(0), KOKKOS_LAMBDA(LO i) { view[i] = start + i; }); } // utility function to copy device Rank1View to device Kokkos View template void CopyDeviceRank1ViewToDeviceView(Kokkos::View dest, Rank1View src) { if (dest.extent(0) != src.size()) { throw pcms_error("CopyDeviceRank1ViewToDeviceView: size mismatch"); } Kokkos::parallel_for( "CopyDeviceRank1ViewToDeviceView", dest.extent(0), KOKKOS_LAMBDA(LO i) { dest(i) = src(i); }); } // utility function to copy device Rank1View to host Kokkos View template void CopyDeviceRank1ViewToHostView(Kokkos::View dest, Rank1View src) { if (dest.extent(0) != src.size()) { throw pcms_error("CopyDeviceRank1ViewToHostView: size mismatch"); } Kokkos::View src_tmp( "CopyDeviceRank1ViewToHostView_tmp", src.size()); Kokkos::parallel_for( "CopyDeviceRank1ViewToHostView", src.size(), KOKKOS_LAMBDA(LO i) { src_tmp(i) = src(i); }); Kokkos::deep_copy(dest, src_tmp); } template void CopyHostRank1ViewToDeviceView(Kokkos::View dest, Rank1View src) { if (dest.extent(0) != src.size()) { throw pcms_error("CopyHostRank1ViewToDeviceView: size mismatch"); } Kokkos::View src_tmp("CopyHostRank1ViewToDeviceView_tmp", src.size()); for (size_t i = 0; i < src.size(); ++i) { src_tmp(i) = src(i); } Kokkos::deep_copy(dest, src_tmp); } // Copy a [dof][comp] device Rank2View into a flat node-major device View. // Uses the 2D indexing src(i, c) so the result is correct regardless of the // source view's layout (unlike flattening through data_handle(), which assumes // node-major storage and silently reorders wide/multi-component data on GPU). template void CopyDeviceRank2ViewToDeviceView(Kokkos::View dest, Rank2View src) { const size_t num_dof = src.extent(0); const size_t num_comp = src.extent(1); if (dest.extent(0) != num_dof * num_comp) { throw pcms_error("CopyDeviceRank2ViewToDeviceView: size mismatch"); } Kokkos::parallel_for( "CopyDeviceRank2ViewToDeviceView", num_dof, KOKKOS_LAMBDA(LO i) { for (size_t c = 0; c < num_comp; ++c) { dest(i * num_comp + c) = src(i, c); } }); } // Rank-2 destination overload, so callers can migrate their internal storage // from a flat View to a View without changing the call site. template void CopyDeviceRank2ViewToDeviceView(Kokkos::View dest, Rank2View src) { const size_t num_dof = src.extent(0); const size_t num_comp = src.extent(1); if (dest.extent(0) != num_dof || dest.extent(1) != num_comp) { throw pcms_error("CopyDeviceRank2ViewToDeviceView: size mismatch"); } Kokkos::parallel_for( "CopyDeviceRank2ViewToDeviceView", num_dof, KOKKOS_LAMBDA(LO i) { for (size_t c = 0; c < num_comp; ++c) { dest(i, c) = src(i, c); } }); } // Host analogue: copy a [dof][comp] host Rank2View into a flat node-major // device View. The host Rank2View is layout_right (contiguous node-major), so // we alias its storage with an unmanaged view and deep_copy straight to device. template void CopyHostRank2ViewToDeviceView(Kokkos::View dest, Rank2View src) { const size_t num_dof = src.extent(0); const size_t num_comp = src.extent(1); if (dest.extent(0) != num_dof * num_comp) { throw pcms_error("CopyHostRank2ViewToDeviceView: size mismatch"); } Kokkos::View> src_view(src.data_handle(), num_dof * num_comp); Kokkos::deep_copy(dest, src_view); } // Rank-2 destination overload for host-to-device copies. The device View // may use a different layout than the host source, so stage through a mirror // that matches the destination layout. template void CopyHostRank2ViewToDeviceView(Kokkos::View dest, Rank2View src) { const size_t num_dof = src.extent(0); const size_t num_comp = src.extent(1); if (dest.extent(0) != num_dof || dest.extent(1) != num_comp) { throw pcms_error("CopyHostRank2ViewToDeviceView: size mismatch"); } auto src_mirror = Kokkos::create_mirror_view(dest); for (size_t i = 0; i < num_dof; ++i) { for (size_t c = 0; c < num_comp; ++c) { src_mirror(i, c) = src(i, c); } } Kokkos::deep_copy(dest, src_mirror); } // Element-wise copy between device Rank2Views. Indexing through (i, c) makes // the copy correct whatever layouts the two views carry, unlike a flat copy // through data_handle(), which assumes both element sequences agree. template void CopyDeviceRank2ViewToRank2View(Rank2View dest, Rank2View src) { const size_t num_dof = src.extent(0); const size_t num_comp = src.extent(1); if (dest.extent(0) != num_dof || dest.extent(1) != num_comp) { throw pcms_error("CopyDeviceRank2ViewToRank2View: extent mismatch"); } Kokkos::parallel_for( "CopyDeviceRank2ViewToRank2View", Kokkos::RangePolicy( 0, static_cast(num_dof)), KOKKOS_LAMBDA(LO i) { for (size_t c = 0; c < num_comp; ++c) { dest(i, c) = src(i, c); } }); } // utility function to copy from Rank1View to Rank1View template void CopyRank1ViewToHost(Rank1View dest, Rank1View src) { if (dest.size() != src.size()) { throw pcms_error("CopyRank1ViewToHost: size mismatch"); } Kokkos::View src_tmp("CopyRank1ViewToHost_tmp", src.size()); Kokkos::parallel_for( "CopyRank1ViewToHost", src.size(), KOKKOS_LAMBDA(LO i) { src_tmp(i) = src(i); }); Kokkos::View src_tmp_h("CopyRank1ViewToHost_tmp_h", src.size()); Kokkos::deep_copy(src_tmp_h, src_tmp); for (size_t i = 0; i < src.size(); ++i) { dest(i) = src_tmp_h(i); } } template void CopyRank1ViewToDevice(Rank1View dest, Rank1View src) { if (dest.size() != src.size()) { throw pcms_error("CopyRank1ViewToDevice: size mismatch"); } Kokkos::View src_tmp("CopyRank1ViewToDevice_tmp", src.size()); for (size_t i = 0; i < src.size(); ++i) { src_tmp(i) = src(i); } Kokkos::View src_tmp_d("CopyRank1ViewToDevice_tmp_d", src.size()); Kokkos::deep_copy(src_tmp_d, src_tmp); Kokkos::parallel_for( "CopyRank1ViewToDevice", src.size(), KOKKOS_LAMBDA(LO i) { dest(i) = src_tmp_d(i); }); } } // namespace pcms #endif // PCMS_COUPLING_ARRAYS_H