diff --git a/DataFormats/Portable/interface/PortableCollectionCommon.h b/DataFormats/Portable/interface/PortableCollectionCommon.h index ccdf19b84e2fb..44ffc2f27da67 100644 --- a/DataFormats/Portable/interface/PortableCollectionCommon.h +++ b/DataFormats/Portable/interface/PortableCollectionCommon.h @@ -12,7 +12,8 @@ namespace portablecollection { - template + template + requires requires { Descriptor::num_cols; } void deepCopy(TQueue& queue, Descriptor& dest, ConstDescriptor const& src) { if constexpr (I < ConstDescriptor::num_cols) { assert(std::get(dest.buff).size_bytes() == std::get(src.buff).size_bytes()); @@ -24,6 +25,16 @@ namespace portablecollection { } } + // Helper function implementing the recursive deep copy for blocks + template + requires requires { Descriptor::blocksNumber; } + void deepCopy(TQueue& queue, Descriptor& dest, ConstDescriptor const& src) { + if constexpr (I < ConstDescriptor::blocksNumber) { + deepCopy(queue, std::get(dest.buff), std::get(src.buff)); + deepCopy(queue, dest, src); + } + } + template constexpr int size_cast(Int input) { if ((std::is_signed_v && input < 0) || input > std::numeric_limits::max()) { diff --git a/DataFormats/Portable/interface/PortableDeviceCollection.h b/DataFormats/Portable/interface/PortableDeviceCollection.h index 76c1c556bb11b..a0a62d632eada 100644 --- a/DataFormats/Portable/interface/PortableDeviceCollection.h +++ b/DataFormats/Portable/interface/PortableDeviceCollection.h @@ -127,14 +127,12 @@ class PortableDeviceCollection { alpaka::memset(std::forward(queue), *buffer_, 0x00); } - // Copy column by column heterogeneously for device to host/device data transfer. - // TODO: implement heterogeneous deepCopy for SoA blocks + // Copy column by column or block by block heterogeneously for device to host/device data transfer. template - requires(alpaka::isQueue && (!requires { Layout::blocksNumber; })) void deepCopy(TQueue& queue, ConstView const& view) { ConstDescriptor desc{view}; Descriptor desc_{view_}; - portablecollection::deepCopy<0>(queue, desc_, desc); + portablecollection::deepCopy(queue, desc_, desc); } // Either Layout::size_type for normal layouts or std::array for SoABlocks layouts diff --git a/DataFormats/Portable/interface/PortableHostCollection.h b/DataFormats/Portable/interface/PortableHostCollection.h index 52c7c1720b660..4034c0c315f8f 100644 --- a/DataFormats/Portable/interface/PortableHostCollection.h +++ b/DataFormats/Portable/interface/PortableHostCollection.h @@ -168,14 +168,12 @@ class PortableHostCollection { // The view must point to data in host memory. void deepCopy(ConstView const& view) { layout_.deepCopy(view); } - // Copy column by column heterogeneously for device to host data transfer. - // TODO: implement heterogeneous deepCopy for SoA blocks + // Copy column by column or block by block heterogeneously for device to host data transfer. template - requires(alpaka::isQueue && (!requires { Layout::blocksNumber; })) void deepCopy(TQueue& queue, ConstView const& view) { ConstDescriptor desc{view}; Descriptor desc_{view_}; - portablecollection::deepCopy<0>(queue, desc_, desc); + portablecollection::deepCopy(queue, desc_, desc); } // Either Layout::size_type for normal layouts or std::array for SoABlocks layouts diff --git a/DataFormats/Portable/test/BuildFile.xml b/DataFormats/Portable/test/BuildFile.xml index 00f59583c6f49..6d66e2d85d56c 100644 --- a/DataFormats/Portable/test/BuildFile.xml +++ b/DataFormats/Portable/test/BuildFile.xml @@ -40,3 +40,20 @@ + + + + + + + + + + + + + + + + + diff --git a/DataFormats/Portable/test/alpaka/test_catch2_heterogeneousDeepCopy_SoABlocks.dev.cc b/DataFormats/Portable/test/alpaka/test_catch2_heterogeneousDeepCopy_SoABlocks.dev.cc new file mode 100644 index 0000000000000..691ec2529a6d6 --- /dev/null +++ b/DataFormats/Portable/test/alpaka/test_catch2_heterogeneousDeepCopy_SoABlocks.dev.cc @@ -0,0 +1,303 @@ +#include +#include + +#include + +#define CATCH_CONFIG_MAIN +#include + +#include "DataFormats/SoATemplate/interface/SoABlocks.h" +#include "DataFormats/Portable/interface/PortableCollection.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/AlpakaInterface/interface/memory.h" +#include "HeterogeneousCore/AlpakaInterface/interface/workdivision.h" + +#include + +using namespace ALPAKA_ACCELERATOR_NAMESPACE; + +GENERATE_SOA_LAYOUT(SoALayout1, SOA_COLUMN(int, column), SOA_EIGEN_COLUMN(Eigen::Vector3d, vector), SOA_SCALAR(int, id)) +GENERATE_SOA_LAYOUT(SoALayout2, SOA_COLUMN(int, column), SOA_EIGEN_COLUMN(Eigen::Vector3d, vector), SOA_SCALAR(int, id)) +GENERATE_SOA_LAYOUT(SoALayout3, SOA_COLUMN(int, column), SOA_EIGEN_COLUMN(Eigen::Vector3d, vector), SOA_SCALAR(int, id)) +GENERATE_SOA_LAYOUT(SoALayout4, SOA_COLUMN(int, column), SOA_EIGEN_COLUMN(Eigen::Vector3d, vector), SOA_SCALAR(int, id)) + +GENERATE_SOA_BLOCKS(FirstBlocksTemplate, SOA_BLOCK(first, SoALayout1), SOA_BLOCK(second, SoALayout2)) +GENERATE_SOA_BLOCKS(SecondBlocksTemplate, SOA_BLOCK(first, SoALayout3), SOA_BLOCK(second, SoALayout4)) + +GENERATE_SOA_BLOCKS(NestedBlocksTemplate, + SOA_BLOCK(firstBlocks, FirstBlocksTemplate), + SOA_BLOCK(secondBlocks, SecondBlocksTemplate), + SOA_BLOCK(firstLayout, SoALayout1), + SOA_BLOCK(secondLayout, SoALayout4)) + +GENERATE_SOA_BLOCKS(GenericBlocksTemplate, SOA_BLOCK(blocks, FirstBlocksTemplate), SOA_BLOCK(layout, SoALayout4)) + +using NestedBlocksSoA = NestedBlocksTemplate<>; +using NestedBlocksView = NestedBlocksSoA::View; +using NestedBlocksConstView = NestedBlocksSoA::ConstView; + +using GenericSoA = GenericBlocksTemplate<>; +using GenericSoAView = GenericSoA::View; +using GenericSoAConstView = GenericSoA::ConstView; + +// Fill SoAs +struct FillSoA { + ALPAKA_FN_ACC void operator()(Acc1D const& acc, NestedBlocksView view) const { + if (cms::alpakatools::once_per_grid(acc)) { + view.firstBlocks().first().id() = 21; + view.firstBlocks().second().id() = 22; + view.secondBlocks().first().id() = 42; + view.secondBlocks().second().id() = 43; + view.firstLayout().id() = 333; + view.secondLayout().id() = 666; + } + + for (auto i : cms::alpakatools::uniform_elements(acc, view.metadata().size()[0])) { + view.firstBlocks().first()[i].column() = static_cast(i); + view.firstBlocks().first()[i].vector() = Eigen::Vector3d(i, i + 1, i + 2); + } + + for (auto i : cms::alpakatools::uniform_elements(acc, view.metadata().size()[1])) { + view.firstBlocks().second()[i].column() = static_cast(i); + view.firstBlocks().second()[i].vector() = Eigen::Vector3d(i, i + 1, i + 2); + } + + for (auto i : cms::alpakatools::uniform_elements(acc, view.metadata().size()[2])) { + view.secondBlocks().first()[i].column() = static_cast(i); + view.secondBlocks().first()[i].vector() = Eigen::Vector3d(i, i + 1, i + 2); + } + + for (auto i : cms::alpakatools::uniform_elements(acc, view.metadata().size()[3])) { + view.secondBlocks().second()[i].column() = static_cast(i); + view.secondBlocks().second()[i].vector() = Eigen::Vector3d(i, i + 1, i + 2); + } + + for (auto i : cms::alpakatools::uniform_elements(acc, view.metadata().size()[4])) { + view.firstLayout()[i].column() = static_cast(i); + view.firstLayout()[i].vector() = Eigen::Vector3d(i, i + 1, i + 2); + } + + for (auto i : cms::alpakatools::uniform_elements(acc, view.metadata().size()[5])) { + view.secondLayout()[i].column() = static_cast(i); + view.secondLayout()[i].vector() = Eigen::Vector3d(i, i + 1, i + 2); + } + } +}; + +void check(NestedBlocksConstView nestedBlocksConstView, GenericSoAConstView genericSoABlocksView) { + REQUIRE(nestedBlocksConstView.metadata().size()[0] == genericSoABlocksView.metadata().size()[0]); + REQUIRE(nestedBlocksConstView.metadata().size()[1] == genericSoABlocksView.metadata().size()[1]); + REQUIRE(nestedBlocksConstView.metadata().size()[5] == genericSoABlocksView.metadata().size()[2]); + // Verify data + for (NestedBlocksSoA::size_type i = 0; i < genericSoABlocksView.metadata().size()[0]; ++i) { + auto nestedFirst = nestedBlocksConstView.firstBlocks().first()[i]; + auto first = genericSoABlocksView.blocks().first()[i]; + REQUIRE(first.column() == nestedFirst.column()); + REQUIRE(first.vector() == nestedFirst.vector()); + } + + for (NestedBlocksSoA::size_type i = 0; i < genericSoABlocksView.metadata().size()[1]; ++i) { + auto nestedSecond = nestedBlocksConstView.firstBlocks().second()[i]; + auto second = genericSoABlocksView.blocks().second()[i]; + REQUIRE(second.column() == nestedSecond.column()); + REQUIRE(second.vector() == nestedSecond.vector()); + } + + for (NestedBlocksSoA::size_type i = 0; i < genericSoABlocksView.metadata().size()[2]; ++i) { + auto nested = nestedBlocksConstView.secondLayout()[i]; + auto generic = genericSoABlocksView.layout()[i]; + REQUIRE(generic.column() == nested.column()); + REQUIRE(generic.vector() == nested.vector()); + } + + REQUIRE(nestedBlocksConstView.firstBlocks().first().id() == genericSoABlocksView.blocks().first().id()); + REQUIRE(nestedBlocksConstView.firstBlocks().second().id() == genericSoABlocksView.blocks().second().id()); + REQUIRE(nestedBlocksConstView.secondLayout().id() == genericSoABlocksView.layout().id()); +} + +TEST_CASE("Heterogeneous Deep Copy SoABlocks") { + auto const& devices = cms::alpakatools::devices(); + if (devices.empty()) { + std::cout << "No devices available for the " << EDM_STRINGIZE(ALPAKA_ACCELERATOR_NAMESPACE) + << " backend, skipping.\n"; + return; + } + + for (auto const& device : devices) { + std::cout << "Running on " << alpaka::getName(device) << std::endl; + Queue queue(device); + + std::array sizes = {21, 45, 137, 43, 222, 177}; + + PortableCollection nestedBlocksCollection(queue, sizes); + NestedBlocksView nestedBlocksView = nestedBlocksCollection.view(); + NestedBlocksConstView nestedBlocksConstView = nestedBlocksCollection.const_view(); + + // fill up + auto blockSize = 64; + NestedBlocksSoA::size_type largestSize = *std::max_element(sizes.begin(), sizes.end()); + auto numberOfBlocks = cms::alpakatools::divide_up_by(largestSize, blockSize); + const auto workDiv = cms::alpakatools::make_workdiv(numberOfBlocks, blockSize); + + alpaka::exec(queue, workDiv, FillSoA{}, nestedBlocksView); + alpaka::wait(queue); + + PortableHostCollection h_nestedBlocksCollection(queue, sizes); + alpaka::memcpy(queue, h_nestedBlocksCollection.buffer(), nestedBlocksCollection.buffer()); + + SECTION("Deep copy the View host to host and device to device") { + GenericSoAView genericView{nestedBlocksView.firstBlocks(), nestedBlocksView.secondLayout()}; + + // Verify metadata + REQUIRE(genericView.metadata().size()[0] == sizes[0]); + REQUIRE(genericView.blocks().first().metadata().size() == sizes[0]); + REQUIRE(genericView.metadata().size()[1] == sizes[1]); + REQUIRE(genericView.blocks().second().metadata().size() == sizes[1]); + REQUIRE(genericView.metadata().size()[2] == sizes[5]); + REQUIRE(genericView.layout().metadata().size() == sizes[5]); + + // Check for equality of memory addresses + REQUIRE(genericView.blocks().first().metadata().addressOf_column() == + nestedBlocksView.firstBlocks().first().metadata().addressOf_column()); + REQUIRE(genericView.blocks().first().metadata().addressOf_vector() == + nestedBlocksView.firstBlocks().first().metadata().addressOf_vector()); + REQUIRE(genericView.blocks().second().metadata().addressOf_column() == + nestedBlocksView.firstBlocks().second().metadata().addressOf_column()); + REQUIRE(genericView.blocks().second().metadata().addressOf_vector() == + nestedBlocksView.firstBlocks().second().metadata().addressOf_vector()); + REQUIRE(genericView.layout().metadata().addressOf_column() == + nestedBlocksView.secondLayout().metadata().addressOf_column()); + REQUIRE(genericView.layout().metadata().addressOf_vector() == + nestedBlocksView.secondLayout().metadata().addressOf_vector()); + + // PortableCollection that will host the aggregated columns + PortableCollection blocksCollection(queue, sizes[0], sizes[1], sizes[5]); + blocksCollection.deepCopy(queue, genericView); + + GenericSoAView copiedBlocksView = blocksCollection.view(); + REQUIRE(copiedBlocksView.blocks().first().metadata().addressOf_column() != + nestedBlocksView.firstBlocks().first().metadata().addressOf_column()); + REQUIRE(copiedBlocksView.blocks().first().metadata().addressOf_vector() != + nestedBlocksView.firstBlocks().first().metadata().addressOf_vector()); + REQUIRE(copiedBlocksView.blocks().second().metadata().addressOf_column() != + nestedBlocksView.firstBlocks().second().metadata().addressOf_column()); + REQUIRE(copiedBlocksView.blocks().second().metadata().addressOf_vector() != + nestedBlocksView.firstBlocks().second().metadata().addressOf_vector()); + REQUIRE(copiedBlocksView.layout().metadata().addressOf_column() != + nestedBlocksView.secondLayout().metadata().addressOf_column()); + REQUIRE(copiedBlocksView.layout().metadata().addressOf_vector() != + nestedBlocksView.secondLayout().metadata().addressOf_vector()); + + PortableHostCollection outputHost(cms::alpakatools::host(), sizes[0], sizes[1], sizes[5]); + alpaka::memcpy(queue, outputHost.buffer(), blocksCollection.buffer()); + alpaka::wait(queue); + + check(h_nestedBlocksCollection.const_view(), outputHost.const_view()); + } + + SECTION("Deep copy the ConstView host to host and device to device") { + GenericSoAConstView genericConstView{nestedBlocksConstView.firstBlocks(), nestedBlocksConstView.secondLayout()}; + + // Verify metadata + REQUIRE(genericConstView.metadata().size()[0] == sizes[0]); + REQUIRE(genericConstView.blocks().first().metadata().size() == sizes[0]); + REQUIRE(genericConstView.metadata().size()[1] == sizes[1]); + REQUIRE(genericConstView.blocks().second().metadata().size() == sizes[1]); + REQUIRE(genericConstView.metadata().size()[2] == sizes[5]); + REQUIRE(genericConstView.layout().metadata().size() == sizes[5]); + + // Check for equality of memory addresses + REQUIRE(genericConstView.blocks().first().metadata().addressOf_column() == + nestedBlocksConstView.firstBlocks().first().metadata().addressOf_column()); + REQUIRE(genericConstView.blocks().first().metadata().addressOf_vector() == + nestedBlocksConstView.firstBlocks().first().metadata().addressOf_vector()); + REQUIRE(genericConstView.blocks().second().metadata().addressOf_column() == + nestedBlocksConstView.firstBlocks().second().metadata().addressOf_column()); + REQUIRE(genericConstView.blocks().second().metadata().addressOf_vector() == + nestedBlocksConstView.firstBlocks().second().metadata().addressOf_vector()); + REQUIRE(genericConstView.layout().metadata().addressOf_column() == + nestedBlocksConstView.secondLayout().metadata().addressOf_column()); + REQUIRE(genericConstView.layout().metadata().addressOf_vector() == + nestedBlocksConstView.secondLayout().metadata().addressOf_vector()); + + // PortableCollection that will host the aggregated columns + PortableCollection genericCollection(queue, sizes[0], sizes[1], sizes[5]); + genericCollection.deepCopy(queue, genericConstView); + + GenericSoAConstView copiedGenericConstView = genericCollection.const_view(); + REQUIRE(copiedGenericConstView.blocks().first().metadata().addressOf_column() != + nestedBlocksConstView.firstBlocks().first().metadata().addressOf_column()); + REQUIRE(copiedGenericConstView.blocks().first().metadata().addressOf_vector() != + nestedBlocksConstView.firstBlocks().first().metadata().addressOf_vector()); + REQUIRE(copiedGenericConstView.blocks().second().metadata().addressOf_column() != + nestedBlocksConstView.firstBlocks().second().metadata().addressOf_column()); + REQUIRE(copiedGenericConstView.blocks().second().metadata().addressOf_vector() != + nestedBlocksConstView.firstBlocks().second().metadata().addressOf_vector()); + REQUIRE(copiedGenericConstView.layout().metadata().addressOf_column() != + nestedBlocksConstView.secondLayout().metadata().addressOf_column()); + REQUIRE(copiedGenericConstView.layout().metadata().addressOf_vector() != + nestedBlocksConstView.secondLayout().metadata().addressOf_vector()); + + PortableHostCollection outputHost(cms::alpakatools::host(), sizes[0], sizes[1], sizes[5]); + alpaka::memcpy(queue, outputHost.buffer(), genericCollection.buffer()); + alpaka::wait(queue); + + check(h_nestedBlocksCollection.const_view(), outputHost.const_view()); + } + + SECTION("Deep copy the ConstView device to host") { + GenericSoAConstView genericConstView{nestedBlocksConstView.firstBlocks(), nestedBlocksConstView.secondLayout()}; + + // PortableCollection that will host the aggregated columns + PortableHostCollection genericCollection(queue, sizes[0], sizes[1], sizes[5]); + genericCollection.deepCopy(queue, genericConstView); + alpaka::wait(queue); + + GenericSoAConstView copiedGenericConstView = genericCollection.const_view(); + REQUIRE(copiedGenericConstView.blocks().first().metadata().addressOf_column() != + nestedBlocksConstView.firstBlocks().first().metadata().addressOf_column()); + REQUIRE(copiedGenericConstView.blocks().first().metadata().addressOf_vector() != + nestedBlocksConstView.firstBlocks().first().metadata().addressOf_vector()); + REQUIRE(copiedGenericConstView.blocks().second().metadata().addressOf_column() != + nestedBlocksConstView.firstBlocks().second().metadata().addressOf_column()); + REQUIRE(copiedGenericConstView.blocks().second().metadata().addressOf_vector() != + nestedBlocksConstView.firstBlocks().second().metadata().addressOf_vector()); + REQUIRE(copiedGenericConstView.layout().metadata().addressOf_column() != + nestedBlocksConstView.secondLayout().metadata().addressOf_column()); + REQUIRE(copiedGenericConstView.layout().metadata().addressOf_vector() != + nestedBlocksConstView.secondLayout().metadata().addressOf_vector()); + + check(h_nestedBlocksCollection.const_view(), genericCollection.const_view()); + } + + SECTION("Deep copy the ConstView host to device") { + GenericSoAConstView genericConstView{h_nestedBlocksCollection.const_view().firstBlocks(), + h_nestedBlocksCollection.const_view().secondLayout()}; + + // PortableCollection that will host the aggregated columns + PortableCollection genericCollection(queue, sizes[0], sizes[1], sizes[5]); + genericCollection.deepCopy(queue, genericConstView); + alpaka::wait(queue); + + GenericSoAConstView copiedGenericConstView = genericCollection.const_view(); + REQUIRE(copiedGenericConstView.blocks().first().metadata().addressOf_column() != + h_nestedBlocksCollection.const_view().firstBlocks().first().metadata().addressOf_column()); + REQUIRE(copiedGenericConstView.blocks().first().metadata().addressOf_vector() != + h_nestedBlocksCollection.const_view().firstBlocks().first().metadata().addressOf_vector()); + REQUIRE(copiedGenericConstView.blocks().second().metadata().addressOf_column() != + h_nestedBlocksCollection.const_view().firstBlocks().second().metadata().addressOf_column()); + REQUIRE(copiedGenericConstView.blocks().second().metadata().addressOf_vector() != + h_nestedBlocksCollection.const_view().firstBlocks().second().metadata().addressOf_vector()); + REQUIRE(copiedGenericConstView.layout().metadata().addressOf_column() != + h_nestedBlocksCollection.const_view().secondLayout().metadata().addressOf_column()); + REQUIRE(copiedGenericConstView.layout().metadata().addressOf_vector() != + h_nestedBlocksCollection.const_view().secondLayout().metadata().addressOf_vector()); + + PortableHostCollection outputHost(cms::alpakatools::host(), sizes[0], sizes[1], sizes[5]); + alpaka::memcpy(queue, outputHost.buffer(), genericCollection.buffer()); + alpaka::wait(queue); + + check(h_nestedBlocksCollection.const_view(), outputHost.const_view()); + } + } +} diff --git a/DataFormats/Portable/test/alpaka/test_catch2_nestedSoABlocks.dev.cc b/DataFormats/Portable/test/alpaka/test_catch2_nestedSoABlocks.dev.cc new file mode 100644 index 0000000000000..5ff0610a51457 --- /dev/null +++ b/DataFormats/Portable/test/alpaka/test_catch2_nestedSoABlocks.dev.cc @@ -0,0 +1,163 @@ +#include +#include + +#include + +#define CATCH_CONFIG_MAIN +#include + +#include "DataFormats/SoATemplate/interface/SoABlocks.h" +#include "DataFormats/Portable/interface/PortableCollection.h" +#include "HeterogeneousCore/AlpakaInterface/interface/config.h" +#include "HeterogeneousCore/AlpakaInterface/interface/memory.h" +#include "HeterogeneousCore/AlpakaInterface/interface/workdivision.h" + +#include + +// TODO remove +#include + +using namespace ALPAKA_ACCELERATOR_NAMESPACE; +using namespace Catch::Matchers; + +GENERATE_SOA_LAYOUT(SoALayout1, SOA_COLUMN(int, column), SOA_EIGEN_COLUMN(Eigen::Vector3d, vector), SOA_SCALAR(int, id)) + +GENERATE_SOA_LAYOUT(SoALayout2, SOA_COLUMN(int, column), SOA_EIGEN_COLUMN(Eigen::Vector3d, vector), SOA_SCALAR(int, id)) + +GENERATE_SOA_LAYOUT(SoALayout3, SOA_COLUMN(int, column), SOA_EIGEN_COLUMN(Eigen::Vector3d, vector), SOA_SCALAR(int, id)) + +GENERATE_SOA_LAYOUT(SoALayout4, SOA_COLUMN(int, column), SOA_EIGEN_COLUMN(Eigen::Vector3d, vector), SOA_SCALAR(int, id)) + +GENERATE_SOA_BLOCKS(BlocksTemplate, SOA_BLOCK(first, SoALayout1), SOA_BLOCK(second, SoALayout2)) + +GENERATE_SOA_BLOCKS(SingleNestedBlocksTemplate, SOA_BLOCK(blocks, BlocksTemplate), SOA_BLOCK(soa, SoALayout3)) + +GENERATE_SOA_BLOCKS(DoubleNestedBlocksTemplate, + SOA_BLOCK(blocks, SingleNestedBlocksTemplate), + SOA_BLOCK(soa, SoALayout4)) + +using BlockSoA = DoubleNestedBlocksTemplate<>; +using View = BlockSoA::View; +using ConstView = BlockSoA::ConstView; + +// Fill SoAs +struct FillSoAs { + ALPAKA_FN_ACC void operator()(Acc1D const& acc, View view) const { + // Fill elements of SoALayout1 + if (cms::alpakatools::once_per_grid(acc)) { + view.blocks().blocks().first().id() = view.metadata().size()[0]; + } + for (auto i : cms::alpakatools::uniform_elements(acc, view.metadata().size()[0])) { + auto element = view.blocks().blocks().first()[i]; + element.column() = static_cast(i); + element.vector() = Eigen::Vector3d(i, i + 1, i + 2); + } + + // Fill elements of SoALayout2 + if (cms::alpakatools::once_per_grid(acc)) { + view.blocks().blocks().second().id() = view.metadata().size()[1]; + } + for (auto i : cms::alpakatools::uniform_elements(acc, view.metadata().size()[1])) { + auto element = view.blocks().blocks().second()[i]; + element.column() = static_cast(i * 10); + element.vector() = Eigen::Vector3d(i * 10, i * 10 + 1, i * 10 + 2); + } + + // Fill elements of SoALayout3 + if (cms::alpakatools::once_per_grid(acc)) { + view.blocks().soa().id() = view.metadata().size()[2]; + } + for (auto i : cms::alpakatools::uniform_elements(acc, view.metadata().size()[2])) { + auto element = view.blocks().soa()[i]; + element.column() = static_cast(i * 100); + element.vector() = Eigen::Vector3d(i * 100, i * 100 + 1, i * 100 + 2); + } + + // Fill elements of SoALayout4 + if (cms::alpakatools::once_per_grid(acc)) { + view.soa().id() = view.metadata().size()[3]; + } + for (auto i : cms::alpakatools::uniform_elements(acc, view.metadata().size()[3])) { + auto element = view.soa()[i]; + element.column() = static_cast(i * 6654); + element.vector() = Eigen::Vector3d(i * 6654, i * 6654 + 1, i * 6654 + 2); + } + } +}; + +void checkNestedSoABlocks(const ConstView& view) { + REQUIRE(view.blocks().blocks().first().id() == view.metadata().size()[0]); + for (BlockSoA::size_type i = 0; i < view.metadata().size()[0]; ++i) { + const auto& element = view.blocks().blocks().first()[i]; + REQUIRE(element.column() == static_cast(i)); + REQUIRE(element.vector().isApprox(Eigen::Vector3d(i, i + 1, i + 2))); + } + + REQUIRE(view.blocks().blocks().second().id() == view.metadata().size()[1]); + for (BlockSoA::size_type i = 0; i < view.metadata().size()[1]; ++i) { + const auto& element = view.blocks().blocks().second()[i]; + REQUIRE(element.column() == static_cast(i * 10)); + REQUIRE(element.vector().isApprox(Eigen::Vector3d(i * 10, i * 10 + 1, i * 10 + 2))); + } + + REQUIRE(view.blocks().soa().id() == view.metadata().size()[2]); + for (BlockSoA::size_type i = 0; i < view.metadata().size()[2]; ++i) { + const auto& element = view.blocks().soa()[i]; + REQUIRE(element.column() == static_cast(i * 100)); + REQUIRE(element.vector().isApprox(Eigen::Vector3d(i * 100, i * 100 + 1, i * 100 + 2))); + } + + REQUIRE(view.soa().id() == view.metadata().size()[3]); + for (BlockSoA::size_type i = 0; i < view.metadata().size()[3]; ++i) { + const auto& element = view.soa()[i]; + REQUIRE(element.column() == static_cast(i * 6654)); + REQUIRE(element.vector().isApprox(Eigen::Vector3d(i * 6654, i * 6654 + 1, i * 6654 + 2))); + } +} + +TEST_CASE("NestedSoABlocks minimal test") { + auto const& devices = cms::alpakatools::devices(); + if (devices.empty()) { + std::cout << "No devices available for the " << EDM_STRINGIZE(ALPAKA_ACCELERATOR_NAMESPACE) + << " backend, skipping.\n"; + return; + } + + for (auto const& device : devices) { + std::cout << "Running on " << alpaka::getName(device) << std::endl; + Queue queue(device); + + std::array sizes = {2, 1189, 33, 3333}; + const std::size_t N = *std::max_element(sizes.begin(), sizes.end()); + + PortableCollection nestedBlocksCollection(queue, sizes); + View view = nestedBlocksCollection.view(); + + // check that the sizes in the View are correctly propagated from the SoABlocks + REQUIRE(view.metadata().size()[0] == sizes[0]); + REQUIRE(view.blocks().blocks().first().metadata().size() == sizes[0]); + REQUIRE(view.metadata().size()[1] == sizes[1]); + REQUIRE(view.blocks().blocks().second().metadata().size() == sizes[1]); + REQUIRE(view.metadata().size()[2] == sizes[2]); + REQUIRE(view.blocks().soa().metadata().size() == sizes[2]); + REQUIRE(view.metadata().size()[3] == sizes[3]); + REQUIRE(view.soa().metadata().size() == sizes[3]); + + // Work division + const std::size_t blockSize = 256; + const std::size_t nBlocks = cms::alpakatools::divide_up_by(N, blockSize); + const auto workDiv = cms::alpakatools::make_workdiv(nBlocks, blockSize); + + // Fill: all layouts + alpaka::exec(queue, workDiv, FillSoAs{}, view); + alpaka::wait(queue); + + // Check results on host + PortableHostCollection nestedBlocksHostCollection(cms::alpakatools::host(), sizes); + alpaka::memcpy(queue, nestedBlocksHostCollection.buffer(), nestedBlocksCollection.buffer()); + alpaka::wait(queue); + + ConstView constHostView = nestedBlocksHostCollection.const_view(); + checkNestedSoABlocks(constHostView); + } +} diff --git a/DataFormats/SoATemplate/interface/SoABlocks.h b/DataFormats/SoATemplate/interface/SoABlocks.h index 9cbacf23d19fa..b23737641bc74 100644 --- a/DataFormats/SoATemplate/interface/SoABlocks.h +++ b/DataFormats/SoATemplate/interface/SoABlocks.h @@ -107,7 +107,15 @@ /* * Initialize the array of sizes for the View of an SoA by blocks */ -#define _DECLARE_CONST_VIEW_SIZES_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) (BOOST_PP_CAT(NAME, View_).metadata().size()) +#define _DECLARE_CONST_VIEW_SIZES_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + if constexpr (requires { LayoutFor::blocksNumber; }) { \ + const auto sizes = BOOST_PP_CAT(NAME, View_).metadata().size(); \ + for (size_type i = 0; i < LayoutFor::blocksNumber; ++i) { \ + sizes_[idx++] = sizes[i]; \ + } \ + } else { \ + sizes_[idx++] = BOOST_PP_CAT(NAME, View_).metadata().size(); \ + } #define _DECLARE_CONST_VIEW_SIZES(R, DATA, NAME) \ BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ @@ -125,6 +133,18 @@ BOOST_PP_EMPTY(), \ BOOST_PP_EXPAND(_DECLARE_MEMBERS_CONST_VIEW_BLOCKS_IMPL NAME)) +/** + * Declare the const_cast version of the blocks + * This is used to convert a ConstView into a View + */ +#define _DECLARE_CONST_CAST_VIEWS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + (LayoutFor::const_cast_View(view.NAME())) + +#define _DECLARE_CONST_CAST_VIEWS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_CONST_CAST_VIEWS_IMPL NAME)) + /* * Declare accessors for the Layout of each block */ @@ -139,22 +159,68 @@ /* * Computation of the size for each block */ -#define _ACCUMULATE_SOA_BLOCKS_SIZE_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ - _soa_impl_ret += LayoutFor::computeDataSize(sizes[index]); \ - index++; +#define _ACCUMULATE_SOA_BLOCKS_SIZE_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + _soa_impl_ret += LayoutFor::computeDataSize( \ + cms::soa::detail::extractSegment, blocksNumber>(sizes, index)); \ + index += cms::soa::detail::nBlocks>(); #define _ACCUMULATE_SOA_BLOCKS_SIZE(R, DATA, NAME) \ BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ BOOST_PP_EMPTY(), \ BOOST_PP_EXPAND(_ACCUMULATE_SOA_BLOCKS_SIZE_IMPL NAME)) +/* + * Assignment of spans to each block + */ +#define _ASSIGN_SPANS_TO_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + (typename LayoutFor::Descriptor(view.NAME())) + +#define _ASSIGN_SPANS_TO_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_ASSIGN_SPANS_TO_BLOCKS_IMPL NAME)) + +/* + * Assignment of const spans to each block + */ +#define _ASSIGN_CONST_SPANS_TO_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + (typename LayoutFor::ConstDescriptor(view.NAME())) + +#define _ASSIGN_CONST_SPANS_TO_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_ASSIGN_CONST_SPANS_TO_BLOCKS_IMPL NAME)) + +/* + * Declaration of the descriptor by composition of descriptors of each block + */ +#define _DECLARE_DESCRIPTOR_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) (typename LayoutFor::Descriptor) + +#define _DECLARE_DESCRIPTOR_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_DESCRIPTOR_BLOCKS_IMPL NAME)) + +/* + * Declaration of the const descriptor by composition of descriptors of each block + */ +#define _DECLARE_CONST_DESCRIPTOR_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + (typename LayoutFor::ConstDescriptor) + +#define _DECLARE_CONST_DESCRIPTOR_BLOCKS(R, DATA, NAME) \ + BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ + BOOST_PP_EMPTY(), \ + BOOST_PP_EXPAND(_DECLARE_CONST_DESCRIPTOR_BLOCKS_IMPL NAME)) + /* * Computation of the block location in the memory layout (at SoA by blocks construction time) */ -#define _DECLARE_MEMBER_CONSTRUCTION_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ - BOOST_PP_CAT(NAME, _) = LayoutFor(mem + offset, sizes_[index]); \ - offset += LayoutFor::computeDataSize(sizes_[index]); \ - index++; +#define _DECLARE_MEMBER_CONSTRUCTION_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + BOOST_PP_CAT(NAME, _) = LayoutFor( \ + mem + offset, cms::soa::detail::extractSegment, blocksNumber>(sizes_, index)); \ + offset += LayoutFor::computeDataSize( \ + cms::soa::detail::extractSegment, blocksNumber>(sizes_, index)); \ + index += cms::soa::detail::nBlocks>(); #define _DECLARE_MEMBER_CONSTRUCTION_BLOCKS(R, DATA, NAME) \ BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ @@ -174,7 +240,8 @@ /* * Computate number of blocks */ -#define _COUNT_SOA_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) soa_blocks_count += 1; +#define _COUNT_SOA_BLOCKS_IMPL(VALUE_TYPE, NAME, LAYOUT_NAME) \ + soa_blocks_count += cms::soa::detail::nBlocks>(); #define _COUNT_SOA_BLOCKS(R, DATA, NAME) \ BOOST_PP_IF(BOOST_PP_GREATER(BOOST_PP_TUPLE_ELEM(0, NAME), _VALUE_TYPE_BLOCK), \ @@ -392,10 +459,10 @@ sizes_{blocks.sizes_} {} \ \ /* Constructor relying on user provided const views for each block */ \ - SOA_HOST_ONLY ConstViewTemplateFreeParams( \ + SOA_HOST_DEVICE ConstViewTemplateFreeParams( \ _ITERATE_ON_ALL_COMMA(_DECLARE_CONST_VIEW_CONSTRUCTOR_BLOCKS, ~, __VA_ARGS__)) \ - : _ITERATE_ON_ALL_COMMA(_INITIALIZE_MEMBER_CONST_VIEW_BLOCKS, ~, __VA_ARGS__), \ - sizes_{{_ITERATE_ON_ALL_COMMA(_DECLARE_CONST_VIEW_SIZES, ~, __VA_ARGS__)}} {} \ + : _ITERATE_ON_ALL_COMMA(_INITIALIZE_MEMBER_CONST_VIEW_BLOCKS, ~, __VA_ARGS__){ \ + std::size_t idx = 0; _ITERATE_ON_ALL(_DECLARE_CONST_VIEW_SIZES, ~, __VA_ARGS__)} \ \ /* Accessors for the const views for each block */ \ _ITERATE_ON_ALL(_DECLARE_ACCESSORS_CONST_VIEW_BLOCKS, ~, __VA_ARGS__) \ @@ -476,7 +543,7 @@ : base_type{blocks} {} \ \ /* Constructor relying on user provided views for each block */ \ - SOA_HOST_ONLY ViewTemplateFreeParams(_ITERATE_ON_ALL_COMMA(_DECLARE_VIEW_CONSTRUCTOR_BLOCKS, ~, __VA_ARGS__)) : \ + SOA_HOST_DEVICE ViewTemplateFreeParams(_ITERATE_ON_ALL_COMMA(_DECLARE_VIEW_CONSTRUCTOR_BLOCKS, ~, __VA_ARGS__)) :\ base_type{_ITERATE_ON_ALL_COMMA(_INITIALIZE_MEMBER_VIEW_BLOCKS, ~, __VA_ARGS__)} {} \ \ /* Accessors for the views for each block */ \ @@ -488,9 +555,27 @@ using ViewTemplate = ViewTemplateFreeParams; \ using View = ViewTemplate; \ \ - /* TODO: implement Descriptor and ConstDescriptor for Blocks to enable heterogeneous deepCopy */ \ - struct Descriptor; \ - struct ConstDescriptor; \ + struct Descriptor { \ + Descriptor() = default; \ + \ + explicit Descriptor(View view) \ + : buff(std::make_tuple(_ITERATE_ON_ALL_COMMA(_ASSIGN_SPANS_TO_BLOCKS, ~, __VA_ARGS__))) {} \ + \ + static constexpr size_type blocksNumber = std::tuple_size>::value; \ + std::tuple< _ITERATE_ON_ALL_COMMA(_DECLARE_DESCRIPTOR_BLOCKS, ~, __VA_ARGS__)> buff; \ + }; \ + \ + struct ConstDescriptor { \ + ConstDescriptor() = default; \ + \ + explicit ConstDescriptor(ConstView const view) \ + : buff(std::make_tuple(_ITERATE_ON_ALL_COMMA(_ASSIGN_CONST_SPANS_TO_BLOCKS, ~, __VA_ARGS__))) {} \ + \ + static constexpr size_type blocksNumber = std::tuple_size>::value; \ + std::tuple< _ITERATE_ON_ALL_COMMA(_DECLARE_CONST_DESCRIPTOR_BLOCKS, ~, __VA_ARGS__)> buff; \ + }; \ \ /* Trivial constuctor */ \ CLASS() \ @@ -516,6 +601,14 @@ return *this; \ } \ \ + /* Helper to implement View as derived from ConstView in SoABlocks implementation */ \ + template \ + SOA_HOST_DEVICE SOA_INLINE static ViewTemplate const_cast_View( \ + ConstViewTemplate const& view) { \ + return ViewTemplate{ \ + _ITERATE_ON_ALL_COMMA(_DECLARE_CONST_CAST_VIEWS, ~, __VA_ARGS__)}; \ + } \ + \ /* \ * Method for copying the data from a generic ConstView by blocks to a memory blob. \ * Host-only data can be handled by this method. \ diff --git a/DataFormats/SoATemplate/interface/SoACommon.h b/DataFormats/SoATemplate/interface/SoACommon.h index d6a82fc8c0195..8a805679c0975 100644 --- a/DataFormats/SoATemplate/interface/SoACommon.h +++ b/DataFormats/SoATemplate/interface/SoACommon.h @@ -5,6 +5,7 @@ * Definitions of SoA common parameters for SoA class generators */ +#include #include #include #include @@ -15,6 +16,7 @@ #include #include #include +#include #include @@ -1093,6 +1095,32 @@ namespace cms::soa::detail { T::ColsAtCompileTime / sizeof(typename T::Scalar)); } + // Helper function for extracting the number of blocks of a layout. Falls back to 1 if the layout does not define a static member blocksNumber. + template + constexpr size_type nBlocks() { + if constexpr (requires { T::blocksNumber; }) + return T::blocksNumber; + else + return static_cast(1); + } + + // Case 1: type has blocksNumber → returns a sub-array + template + requires requires { T::blocksNumber; } + [[nodiscard]] constexpr std::array extractSegment(const std::array& sizes, + size_type offset) { + return [&](std::index_sequence) { + return std::array{sizes[offset + I]...}; + }(std::make_index_sequence{}); + } + + // Case 2: fallback (single block) → returns a scalar + template + requires(!requires { T::blocksNumber; }) + [[nodiscard]] constexpr size_type extractSegment(const std::array& sizes, size_type offset) { + return sizes[offset]; + } + } // namespace cms::soa::detail #endif // DataFormats_SoATemplate_interface_SoACommon_h diff --git a/DataFormats/SoATemplate/interface/SoALayout.h b/DataFormats/SoATemplate/interface/SoALayout.h index 6b64b93dd4930..d4d250dd8b47e 100644 --- a/DataFormats/SoATemplate/interface/SoALayout.h +++ b/DataFormats/SoATemplate/interface/SoALayout.h @@ -1700,7 +1700,7 @@ _SWITCH_ON_TYPE(VALUE_TYPE, struct ConstDescriptor { \ ConstDescriptor() = default; \ \ - explicit ConstDescriptor(ConstView const& view) \ + explicit ConstDescriptor(ConstView const view) \ : buff{ _ITERATE_ON_ALL_COMMA(_ASSIGN_SPAN_TO_COLUMNS, ~, __VA_ARGS__)}, \ parameterTypes{ _ITERATE_ON_ALL_COMMA(_ASSIGN_PARAMETER_TO_COLUMNS, ~, __VA_ARGS__)} {} \ \ @@ -1716,7 +1716,7 @@ _SWITCH_ON_TYPE(VALUE_TYPE, struct Descriptor { \ Descriptor() = default; \ \ - explicit Descriptor(View& view) \ + explicit Descriptor(View view) \ : buff{ _ITERATE_ON_ALL_COMMA(_ASSIGN_SPAN_TO_COLUMNS, ~, __VA_ARGS__)}, \ parameterTypes{ _ITERATE_ON_ALL_COMMA(_ASSIGN_PARAMETER_TO_COLUMNS, ~, __VA_ARGS__)} {} \ \ diff --git a/DataFormats/SoATemplate/test/BuildFile.xml b/DataFormats/SoATemplate/test/BuildFile.xml index 3a4afced0dd34..937591a711519 100644 --- a/DataFormats/SoATemplate/test/BuildFile.xml +++ b/DataFormats/SoATemplate/test/BuildFile.xml @@ -62,6 +62,13 @@ + + + + + + + diff --git a/DataFormats/SoATemplate/test/SoABlocks_t.cc b/DataFormats/SoATemplate/test/SoABlocks_t.cc index b2caee477386d..69984bd2b0017 100644 --- a/DataFormats/SoATemplate/test/SoABlocks_t.cc +++ b/DataFormats/SoATemplate/test/SoABlocks_t.cc @@ -6,6 +6,8 @@ #include "DataFormats/SoATemplate/interface/SoABlocks.h" +using namespace Catch::Matchers; + // This file tests the main properties of SoABlocks GENERATE_SOA_LAYOUT(SoAPositionTemplate, @@ -22,15 +24,24 @@ GENERATE_SOA_LAYOUT(SoAPCATemplate, GENERATE_SOA_LAYOUT(SoATemplate, SOA_SCALAR(int, id), SOA_SCALAR(int, type), SOA_SCALAR(float, energy)) +GENERATE_SOA_LAYOUT( + SimpleLayoutTemplate, SOA_COLUMN(float, x), SOA_COLUMN(float, y), SOA_COLUMN(float, z), SOA_COLUMN(float, t)) + GENERATE_SOA_BLOCKS(SoABlocksTemplate, SOA_BLOCK(position, SoAPositionTemplate), SOA_BLOCK(pca, SoAPCATemplate), SOA_BLOCK(scalars, SoATemplate)) +GENERATE_SOA_BLOCKS(NestedBlocksTemplate, SOA_BLOCK(blocks, SoABlocksTemplate), SOA_BLOCK(simple, SimpleLayoutTemplate)) + using SoABlocks = SoABlocksTemplate<>; using SoABlocksView = SoABlocks::View; using SoABlocksConstView = SoABlocks::ConstView; +using NestedBlocks = NestedBlocksTemplate<>; +using NestedBlocksView = NestedBlocks::View; +using NestedBlocksConstView = NestedBlocks::ConstView; + TEST_CASE("SoABlocks") { // Create a SoABlocks instance with three blocks of different sizes std::array sizes{{10, 20, 1}}; @@ -234,4 +245,66 @@ TEST_CASE("SoABlocks") { REQUIRE(noRestrictBlockConstView.scalars().restrictQualify == cms::soa::RestrictQualify::disabled); REQUIRE(noRestrictBlockConstView.scalars().rangeChecking == cms::soa::RangeChecking::Default); } + + SECTION("Check extended blocks layout") { + std::array sizes{{11, 12, 13, 14}}; + const std::size_t blocksExtendedBufferSize = NestedBlocks::computeDataSize(sizes); + + std::unique_ptr buffer{ + reinterpret_cast(aligned_alloc(NestedBlocks::alignment, blocksExtendedBufferSize)), std::free}; + + NestedBlocks NestedBlocksSoA(buffer.get(), sizes); + NestedBlocksView nestedBlocksView{NestedBlocksSoA}; + NestedBlocksConstView nestedBlocksConstView{NestedBlocksSoA}; + + nestedBlocksView.blocks().position().detectorType() = 1; + for (int i = 0; i < nestedBlocksView.metadata().size()[0]; ++i) { + nestedBlocksView.blocks().position()[i] = {0.1f, 0.2f, 0.3f}; + } + + for (int i = 0; i < nestedBlocksView.metadata().size()[1]; ++i) { + nestedBlocksView.blocks().pca()[i].vector_1() = 0.0f; + nestedBlocksView.blocks().pca()[i].vector_2() = 0.0f; + nestedBlocksView.blocks().pca()[i].vector_3() = 1.0f; + nestedBlocksView.blocks().pca()[i].candidateDirection() = Eigen::Vector3d(1.0, 0.0, 0.0); + } + nestedBlocksView.blocks().scalars().id() = 42; + nestedBlocksView.blocks().scalars().type() = 1; + nestedBlocksView.blocks().scalars().energy() = 100.0f; + + for (int i = 0; i < nestedBlocksView.metadata().size()[3]; ++i) { + nestedBlocksView.simple()[i] = {2.1f, 2.2f, 2.3f, 2.4f}; + } + + REQUIRE(NestedBlocksSoA.blocks().position().metadata().size() == 11); + REQUIRE(NestedBlocksSoA.blocks().pca().metadata().size() == 12); + REQUIRE(NestedBlocksSoA.blocks().scalars().metadata().size() == 13); + REQUIRE(NestedBlocksSoA.simple().metadata().size() == 14); + + REQUIRE(nestedBlocksConstView.blocks().position().detectorType() == 1); + for (int i = 0; i < nestedBlocksConstView.metadata().size()[0]; ++i) { + REQUIRE_THAT(nestedBlocksConstView.blocks().position()[i].x(), WithinRel(0.1f)); + REQUIRE_THAT(nestedBlocksConstView.blocks().position()[i].y(), WithinRel(0.2f)); + REQUIRE_THAT(nestedBlocksConstView.blocks().position()[i].z(), WithinRel(0.3f)); + } + + for (int i = 0; i < nestedBlocksConstView.metadata().size()[1]; ++i) { + REQUIRE_THAT(nestedBlocksConstView.blocks().pca()[i].vector_1(), WithinRel(0.0f)); + REQUIRE_THAT(nestedBlocksConstView.blocks().pca()[i].vector_2(), WithinRel(0.0f)); + REQUIRE_THAT(nestedBlocksConstView.blocks().pca()[i].vector_3(), WithinRel(1.0f)); + REQUIRE_THAT(nestedBlocksConstView.blocks().pca()[i].candidateDirection()[0], WithinRel(1.0)); + REQUIRE_THAT(nestedBlocksConstView.blocks().pca()[i].candidateDirection()[1], WithinRel(0.0)); + REQUIRE_THAT(nestedBlocksConstView.blocks().pca()[i].candidateDirection()[2], WithinRel(0.0)); + } + REQUIRE(nestedBlocksConstView.blocks().scalars().id() == 42); + REQUIRE(nestedBlocksConstView.blocks().scalars().type() == 1); + REQUIRE_THAT(nestedBlocksConstView.blocks().scalars().energy(), WithinRel(100.0f)); + + for (int i = 0; i < nestedBlocksConstView.metadata().size()[3]; ++i) { + REQUIRE_THAT(nestedBlocksConstView.simple()[i].x(), WithinRel(2.1f)); + REQUIRE_THAT(nestedBlocksConstView.simple()[i].y(), WithinRel(2.2f)); + REQUIRE_THAT(nestedBlocksConstView.simple()[i].z(), WithinRel(2.3f)); + REQUIRE_THAT(nestedBlocksConstView.simple()[i].t(), WithinRel(2.4f)); + } + } } diff --git a/DataFormats/SoATemplate/test/SoAGenericNestedBlocksView_t.cc b/DataFormats/SoATemplate/test/SoAGenericNestedBlocksView_t.cc new file mode 100644 index 0000000000000..4acf7115ecec5 --- /dev/null +++ b/DataFormats/SoATemplate/test/SoAGenericNestedBlocksView_t.cc @@ -0,0 +1,224 @@ +#include +#include + +#define CATCH_CONFIG_MAIN +#include + +#include "DataFormats/SoATemplate/interface/SoABlocks.h" + +// Similar test to SoAGenericBlocksView but with nested SoABlocks + +GENERATE_SOA_LAYOUT(SoALayout1, SOA_COLUMN(int, column), SOA_EIGEN_COLUMN(Eigen::Vector3d, vector), SOA_SCALAR(int, id)) + +GENERATE_SOA_LAYOUT(SoALayout2, SOA_COLUMN(int, column), SOA_EIGEN_COLUMN(Eigen::Vector3d, vector), SOA_SCALAR(int, id)) + +GENERATE_SOA_LAYOUT(SoALayout3, SOA_COLUMN(int, column), SOA_EIGEN_COLUMN(Eigen::Vector3d, vector), SOA_SCALAR(int, id)) + +GENERATE_SOA_BLOCKS(BlocksTemplate, SOA_BLOCK(first, SoALayout1), SOA_BLOCK(second, SoALayout2)) + +GENERATE_SOA_BLOCKS(NestedBlocksTemplate, SOA_BLOCK(blocks, BlocksTemplate), SOA_BLOCK(soa, SoALayout3)) + +using BlocksSoA = BlocksTemplate<>; +using BlocksView = BlocksSoA::View; +using BlocksConstView = BlocksSoA::ConstView; + +using NestedBlocksSoA = NestedBlocksTemplate<>; +using NestedBlocksView = NestedBlocksSoA::View; +using NestedBlocksConstView = NestedBlocksSoA::ConstView; + +TEST_CASE("SoAGenericNestedBlocksView") { + // different number of elements for the SoAs + std::array sizes = {10, 20, 30}; + + // buffer sizes + const auto bufferSize = NestedBlocksSoA::computeDataSize(sizes); + + // memory buffer for the SoA of positions + std::unique_ptr buffer{ + reinterpret_cast(aligned_alloc(NestedBlocksSoA::alignment, bufferSize)), std::free}; + + // SoA Layouts + NestedBlocksSoA nestedBlocks{buffer.get(), sizes}; + NestedBlocksView nestedBlocksView{nestedBlocks}; + NestedBlocksConstView nestedBlocksConstView{nestedBlocks}; + + // fill up + nestedBlocksView.blocks().first().id() = 21; + for (NestedBlocksSoA::size_type i = 0; i < sizes[0]; i++) { + nestedBlocksView.blocks().first()[i].column() = static_cast(i); + nestedBlocksView.blocks().first()[i].vector() = Eigen::Vector3d(i, i + 1, i + 2); + } + + nestedBlocksView.blocks().second().id() = 42; + for (NestedBlocksSoA::size_type i = 0; i < sizes[1]; i++) { + nestedBlocksView.blocks().second()[i].column() = static_cast(i); + nestedBlocksView.blocks().second()[i].vector() = Eigen::Vector3d(i, i + 1, i + 2); + } + + nestedBlocksView.soa().id() = 666; + for (NestedBlocksSoA::size_type i = 0; i < sizes[2]; i++) { + nestedBlocksView.soa()[i].column() = static_cast(i); + nestedBlocksView.soa()[i].vector() = Eigen::Vector3d(i, i + 1, i + 2); + } + + SECTION("GenericBlocks View from nested blocks") { + // building the SoABlocks View, there is no need for runtime check for the size since they are different + BlocksView blocksView{nestedBlocksView.blocks().first(), nestedBlocksView.blocks().second()}; + + // Verify metadata + REQUIRE(blocksView.metadata().size()[0] == sizes[0]); + REQUIRE(blocksView.first().metadata().size() == sizes[0]); + REQUIRE(blocksView.metadata().size()[1] == sizes[1]); + REQUIRE(blocksView.second().metadata().size() == sizes[1]); + + // Check for equality of memory addresses + REQUIRE(blocksView.first().metadata().addressOf_column() == + nestedBlocksView.blocks().first().metadata().addressOf_column()); + REQUIRE(blocksView.first().metadata().addressOf_vector() == + nestedBlocksView.blocks().first().metadata().addressOf_vector()); + REQUIRE(blocksView.second().metadata().addressOf_column() == + nestedBlocksView.blocks().second().metadata().addressOf_column()); + REQUIRE(blocksView.second().metadata().addressOf_vector() == + nestedBlocksView.blocks().second().metadata().addressOf_vector()); + + // Verify data + for (NestedBlocksSoA::size_type i = 0; i < sizes[0]; ++i) { + auto nestedFirst = nestedBlocksView.blocks().first()[i]; + auto first = blocksView.first()[i]; + REQUIRE(first.column() == nestedFirst.column()); + REQUIRE(first.vector() == nestedFirst.vector()); + } + + for (NestedBlocksSoA::size_type i = 0; i < sizes[1]; ++i) { + auto nestedSecond = nestedBlocksView.blocks().second()[i]; + auto second = blocksView.second()[i]; + REQUIRE(second.column() == nestedSecond.column()); + REQUIRE(second.vector() == nestedSecond.vector()); + } + + REQUIRE(nestedBlocksView.blocks().first().id() == blocksView.first().id()); + REQUIRE(nestedBlocksView.blocks().second().id() == blocksView.second().id()); + } + + SECTION("GenericBlocks ConstView from const nested blocks") { + // building the SoABlocks ConstView, there is no need for runtime check for the size since they are different + BlocksConstView blocksConstView{nestedBlocksConstView.blocks().first(), nestedBlocksConstView.blocks().second()}; + + // Verify metadata + REQUIRE(blocksConstView.metadata().size()[0] == sizes[0]); + REQUIRE(blocksConstView.first().metadata().size() == sizes[0]); + REQUIRE(blocksConstView.metadata().size()[1] == sizes[1]); + REQUIRE(blocksConstView.second().metadata().size() == sizes[1]); + + // Check for equality of memory addresses + REQUIRE(blocksConstView.first().metadata().addressOf_column() == + nestedBlocksConstView.blocks().first().metadata().addressOf_column()); + REQUIRE(blocksConstView.first().metadata().addressOf_vector() == + nestedBlocksConstView.blocks().first().metadata().addressOf_vector()); + REQUIRE(blocksConstView.second().metadata().addressOf_column() == + nestedBlocksConstView.blocks().second().metadata().addressOf_column()); + REQUIRE(blocksConstView.second().metadata().addressOf_vector() == + nestedBlocksConstView.blocks().second().metadata().addressOf_vector()); + + // Verify data + for (NestedBlocksSoA::size_type i = 0; i < sizes[0]; ++i) { + auto nestedFirst = nestedBlocksConstView.blocks().first()[i]; + auto first = blocksConstView.first()[i]; + REQUIRE(first.column() == nestedFirst.column()); + REQUIRE(first.vector() == nestedFirst.vector()); + } + + for (NestedBlocksSoA::size_type i = 0; i < sizes[1]; ++i) { + auto nestedSecond = nestedBlocksConstView.blocks().second()[i]; + auto second = blocksConstView.second()[i]; + REQUIRE(second.column() == nestedSecond.column()); + REQUIRE(second.vector() == nestedSecond.vector()); + } + + REQUIRE(nestedBlocksConstView.blocks().first().id() == blocksConstView.first().id()); + REQUIRE(nestedBlocksConstView.blocks().second().id() == blocksConstView.second().id()); + } + + SECTION("GenericBlocks ConstView from nested blocks") { + // building the SoABlocks ConstView, there is no need for runtime check for the size since they are different + BlocksConstView blocksConstView{nestedBlocksView.blocks().first(), nestedBlocksView.blocks().second()}; + + // Verify metadata + REQUIRE(blocksConstView.metadata().size()[0] == sizes[0]); + REQUIRE(blocksConstView.first().metadata().size() == sizes[0]); + REQUIRE(blocksConstView.metadata().size()[1] == sizes[1]); + REQUIRE(blocksConstView.second().metadata().size() == sizes[1]); + + // Check for equality of memory addresses + REQUIRE(blocksConstView.first().metadata().addressOf_column() == + nestedBlocksConstView.blocks().first().metadata().addressOf_column()); + REQUIRE(blocksConstView.first().metadata().addressOf_vector() == + nestedBlocksConstView.blocks().first().metadata().addressOf_vector()); + REQUIRE(blocksConstView.second().metadata().addressOf_column() == + nestedBlocksConstView.blocks().second().metadata().addressOf_column()); + REQUIRE(blocksConstView.second().metadata().addressOf_vector() == + nestedBlocksConstView.blocks().second().metadata().addressOf_vector()); + + // Verify data + for (NestedBlocksSoA::size_type i = 0; i < sizes[0]; ++i) { + auto nestedFirst = nestedBlocksConstView.blocks().first()[i]; + auto first = blocksConstView.first()[i]; + REQUIRE(first.column() == nestedFirst.column()); + REQUIRE(first.vector() == nestedFirst.vector()); + } + + for (NestedBlocksSoA::size_type i = 0; i < sizes[1]; ++i) { + auto nestedSecond = nestedBlocksConstView.blocks().second()[i]; + auto second = blocksConstView.second()[i]; + REQUIRE(second.column() == nestedSecond.column()); + REQUIRE(second.vector() == nestedSecond.vector()); + } + + REQUIRE(nestedBlocksConstView.blocks().first().id() == blocksConstView.first().id()); + REQUIRE(nestedBlocksConstView.blocks().second().id() == blocksConstView.second().id()); + } + + SECTION("Deep copy the nested blocks to a normal blocks layout") { + // building the SoABlocks View, there is no need for runtime check for the size since they are different + BlocksView genericBlocksView{nestedBlocksView.blocks().first(), nestedBlocksView.blocks().second()}; + + // Instantiate a SoABlocks + std::array size = {sizes[0], sizes[1]}; + const std::size_t blocksBufferSize = BlocksSoA::computeDataSize(size); + std::unique_ptr bufferBlocks{ + reinterpret_cast(aligned_alloc(BlocksSoA::alignment, blocksBufferSize)), std::free}; + + BlocksSoA genericBlocks{bufferBlocks.get(), size}; + + genericBlocks.deepCopy(genericBlocksView); + + BlocksView genericSoABlocksView{genericBlocks}; + // Check for inequality of memory addresses + REQUIRE(genericSoABlocksView.first().metadata().addressOf_column() != + nestedBlocksView.blocks().first().metadata().addressOf_column()); + REQUIRE(genericSoABlocksView.first().metadata().addressOf_vector() != + nestedBlocksView.blocks().first().metadata().addressOf_vector()); + REQUIRE(genericSoABlocksView.second().metadata().addressOf_column() != + nestedBlocksView.blocks().second().metadata().addressOf_column()); + REQUIRE(genericSoABlocksView.second().metadata().addressOf_vector() != + nestedBlocksView.blocks().second().metadata().addressOf_vector()); + + // Verify data + for (NestedBlocksSoA::size_type i = 0; i < sizes[0]; ++i) { + auto nestedFirst = nestedBlocksConstView.blocks().first()[i]; + auto first = genericSoABlocksView.first()[i]; + REQUIRE(first.column() == nestedFirst.column()); + REQUIRE(first.vector() == nestedFirst.vector()); + } + + for (NestedBlocksSoA::size_type i = 0; i < sizes[1]; ++i) { + auto nestedSecond = nestedBlocksConstView.blocks().second()[i]; + auto second = genericSoABlocksView.second()[i]; + REQUIRE(second.column() == nestedSecond.column()); + REQUIRE(second.vector() == nestedSecond.vector()); + } + + REQUIRE(nestedBlocksConstView.blocks().first().id() == genericSoABlocksView.first().id()); + REQUIRE(nestedBlocksConstView.blocks().second().id() == genericSoABlocksView.second().id()); + } +}