diff --git a/res/cmake/test.cmake b/res/cmake/test.cmake index 1f7331b1d..d497000ec 100644 --- a/res/cmake/test.cmake +++ b/res/cmake/test.cmake @@ -29,6 +29,7 @@ if (test AND ${PHARE_EXEC_LEVEL_MIN} GREATER 0) # 0 = no tests add_subdirectory(tests/core/numerics/faraday) add_subdirectory(tests/core/numerics/ohm) add_subdirectory(tests/core/numerics/ion_updater) + add_subdirectory(tests/core/data/tiles) add_subdirectory(tests/initializer) diff --git a/src/core/data/particles/particle_tile.hpp b/src/core/data/particles/particle_tile.hpp new file mode 100644 index 000000000..8e71e2151 --- /dev/null +++ b/src/core/data/particles/particle_tile.hpp @@ -0,0 +1,50 @@ +#ifndef PHARE_PARTICLE_TILE_HPP +#define PHARE_PARTICLE_TILE_HPP + +#include "core/utilities/box/box.hpp" +#include "core/data/tiles/tile_set.hpp" + +namespace PHARE::core +{ + +template +struct ParticleTile : public Box +{ + // *this: box constructed first + ParticleArray domainParticles_{*this}; + ParticleArray patchGhostParticles_{*this}; + ParticleArray levelGhostParticles_{*this}; + ParticleArray levelGhostParticlesNew_{*this}; + ParticleArray levelGhostParticlesOld_{*this}; +}; + + +template +class ParticleTileSet : public TileSet> +{ +public: + static auto constexpr dimension = ParticleArray::dimension; + + auto select_particles(Box const& from) const + { + ParticleArray selected; + + auto overlaped_tiles = export_overlaped_with(from); + for (auto const& [complete, tile] : overlaped_tiles) + { + if (complete) + { + selected.push_back(tile.domainParticles_); + } + else + { + auto intersection = tile * from; + tile.domainParticles.export_to(*intersection, selected); + } + } + return selected; + } +}; +} // namespace PHARE::core + +#endif diff --git a/src/core/data/tiles/tile_set.hpp b/src/core/data/tiles/tile_set.hpp new file mode 100644 index 000000000..85c1ba7e9 --- /dev/null +++ b/src/core/data/tiles/tile_set.hpp @@ -0,0 +1,236 @@ +#ifndef PHARE_TILE_SET_HPP +#define PHARE_TILE_SET_HPP + +#include "core/utilities/box/box.hpp" +#include "core/utilities/types.hpp" +#include "core/def.hpp" +#include "core/data/ndarray/ndarray_vector.hpp" +#include "core/utilities/span.hpp" + +#include +#include +#include + +namespace PHARE::core +{ + + + +template +class TileSetView +{ +public: + static auto constexpr dimension = Tile::dimension; + + TileSetView(Box const& box, std::array const& tile_size, + std::array const& shape, Tile* tiles, + std::uint32_t tile_nbr, Tile** cells, + std::array const& nbr_cells) + : box_{box} + , tile_size_{tile_size} + , shape_{shape} + , tiles_{tiles, tile_nbr} + , cells_{cells, nbr_cells} + { + } + NO_DISCARD auto export_overlaped_with(Box const& box) const + { + std::vector> overlaped; + for (auto const& tile : tiles_) + { + auto overlap = box * Box{tile.lower, tile.upper}; + if (overlap) + { + auto complete_overlap = (*overlap).size() == tile.size(); + overlaped.emplace_back(complete_overlap, tile); + } + } + return overlaped; + } + NO_DISCARD auto shape() const { return shape_; } + NO_DISCARD auto size() const { return tiles_.size(); } + + NO_DISCARD auto begin() { return tiles_.begin(); } + NO_DISCARD auto begin() const { return tiles_.begin(); } + + NO_DISCARD auto end() { return tiles_.end(); } + NO_DISCARD auto end() const { return tiles_.end(); } + + NO_DISCARD auto& operator[](std::size_t i) { return tiles_[i]; } + NO_DISCARD auto const& operator[](std::size_t i) const { return tiles_[i]; } + + template + NO_DISCARD auto& at(Index... indexes) + { + return cells_(indexes...); + } + +private: + Box const box_; + std::array tile_size_; + std::array shape_; + Span tiles_; + NdArrayView cells_; +}; + + + +template +class TileSet +{ +public: + static auto constexpr dimension = Tile::dimension; + + TileSet(Box const& box, std::array const& tile_size) + : box_{box} + , tile_size_{tile_size} + , shape_{[&]() { + std::array s; + auto bs = box.shape(); + for (auto i = 0u; i < dimension; ++i) + { + auto const div = (bs[i] + tile_size_[i] - 1) / tile_size_[i]; + s[i] = div; + } + return s; + }()} + , tiles_(product(shape_)) + , cells_{box.shape().template toArray()} + { + consistent_tile_size_(); + make_tiles_(); + tag_cells_(); + } + + + NO_DISCARD auto export_overlaped_with(Box const& box) const + { + std::vector> overlaped; + for (auto const& tile : tiles_) + { + auto overlap = box * Box{tile.lower, tile.upper}; + if (overlap) + { + auto complete_overlap = (*overlap).size() == tile.size(); + overlaped.emplace_back(complete_overlap, tile); + } + } + return overlaped; + } + + NO_DISCARD auto shape() const { return shape_; } + NO_DISCARD auto size() const { return tiles_.size(); } + + NO_DISCARD auto begin() { return tiles_.begin(); } + NO_DISCARD auto begin() const { return tiles_.begin(); } + + NO_DISCARD auto end() { return tiles_.end(); } + NO_DISCARD auto end() const { return tiles_.end(); } + + NO_DISCARD auto& operator[](std::size_t i) { return tiles_[i]; } + NO_DISCARD auto const& operator[](std::size_t i) const { return tiles_[i]; } + + template + NO_DISCARD auto& at(Index... indexes) + { + return cells_(indexes...); + } + + + auto make_view() + { + return TileSetView{box_, tile_size_, shape_, tiles_.data(), + tiles_.size(), cells_.data(), cells_.shape()}; + } + +private: + void consistent_tile_size_() const + { + for (auto idim = 0u; idim < dimension; ++idim) + { + if (box_.shape()[idim] < tile_size_[idim]) + { + throw std::runtime_error("tile size larger than box size in dimension " + + std::to_string(idim)); + } + } + } + + void make_tiles_() + { + auto const size_me = [&](auto dim, auto idx) { + if (idx == shape_[dim] - 1) + { + auto const remain = box_.shape()[dim] % tile_size_[dim]; + return (remain == 0) ? tile_size_[dim] : remain; + } + else + return tile_size_[dim]; + }; + + for (auto ix = 0u; ix < shape_[0]; ++ix) + { + if constexpr (dimension == 1) + { + // -1 because upper is included + tiles_[ix].lower[0] = box_.lower[0] + ix * tile_size_[0]; + tiles_[ix].upper[0] = tiles_[ix].lower[0] + size_me(0, ix) - 1; + } + else + { + for (auto iy = 0u; iy < shape_[1]; ++iy) + { + if constexpr (dimension == 2) + { + auto const i = ix * shape_[1] + iy; + tiles_[i].lower[0] = box_.lower[0] + ix * tile_size_[0]; + tiles_[i].upper[0] = tiles_[i].lower[0] + size_me(0, ix) - 1; + tiles_[i].lower[1] = box_.lower[1] + iy * tile_size_[1]; + tiles_[i].upper[1] = tiles_[i].lower[1] + size_me(1, iy) - 1; + } + else + { + for (auto iz = 0u; iz < shape_[2]; ++iz) + { + auto const i = ix * shape_[1] * shape_[2] + shape_[2] * iy + iz; + tiles_[i].lower[0] = box_.lower[0] + ix * tile_size_[0]; + tiles_[i].upper[0] = tiles_[i].lower[0] + size_me(0, ix) - 1; + tiles_[i].lower[1] = box_.lower[1] + iy * tile_size_[1]; + tiles_[i].upper[1] = tiles_[i].lower[1] + size_me(1, iy) - 1; + tiles_[i].lower[2] = box_.lower[2] + iz * tile_size_[2]; + tiles_[i].upper[2] = tiles_[i].lower[2] + size_me(2, iz) - 1; + } + } + } + } + } + } + + + //! store the pointer to the tile associated with each cell + void tag_cells_() + { + for (auto& tile : tiles_) + { + for (auto const& cell : tile) + { + cells_(cell) = &tile; + } + } + } + + + Box box_; + std::array tile_size_; + std::array shape_; + std::vector tiles_; + NdArrayVector cells_; +}; + + + + +} // namespace PHARE::core + + +#endif diff --git a/src/core/utilities/span.hpp b/src/core/utilities/span.hpp index f06e03fdb..44ec4012a 100644 --- a/src/core/utilities/span.hpp +++ b/src/core/utilities/span.hpp @@ -19,8 +19,8 @@ struct Span NO_DISCARD auto& operator[](SIZE i) { return ptr[i]; } NO_DISCARD auto& operator[](SIZE i) const { return ptr[i]; } NO_DISCARD T const* const& data() const { return ptr; } - NO_DISCARD T const* const& begin() const { return ptr; } - NO_DISCARD T* end() const { return ptr + s; } + NO_DISCARD auto begin() const { return ptr; } + NO_DISCARD auto end() const { return ptr + s; } NO_DISCARD SIZE const& size() const { return s; } T const* ptr = nullptr; diff --git a/tests/core/data/particles/CMakeLists.txt b/tests/core/data/particles/CMakeLists.txt index d49ee7a25..ad4abf761 100644 --- a/tests/core/data/particles/CMakeLists.txt +++ b/tests/core/data/particles/CMakeLists.txt @@ -20,3 +20,4 @@ endfunction(_particles_test) _particles_test(test_main.cpp test-particles) _particles_test(test_interop.cpp test-particles-interop) +_particles_test(test_particle_tiles.cpp test-particle-tiles) diff --git a/tests/core/data/particles/test_particle_tiles.cpp b/tests/core/data/particles/test_particle_tiles.cpp new file mode 100644 index 000000000..99032226b --- /dev/null +++ b/tests/core/data/particles/test_particle_tiles.cpp @@ -0,0 +1,40 @@ +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "core/data/particles/particle_array.hpp" +#include "core/data/particles/particle_tile.hpp" +#include "core/data/tiles/tile_set.hpp" + +using namespace PHARE::core; + +template +using ParticleTile_t = ParticleTile>; + +template +using TileSet_t = TileSet>; + +using DimParticleTiles = testing::Types, ParticleTile_t<2>, ParticleTile_t<3>>; + +template +class ParticleTileTest : public ::testing::Test +{ +protected: + ParticleTileTest() {} +}; + +TYPED_TEST_SUITE(ParticleTileTest, DimParticleTiles); + +TYPED_TEST(ParticleTileTest, constructs) +{ + constexpr auto dim = TypeParam::dimension; + Box box{ConstArray(0), ConstArray(50)}; + auto const tile_size = PHARE::core::ConstArray(4); + TileSet particleTileSet{box, tile_size}; +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/tests/core/data/tiles/CMakeLists.txt b/tests/core/data/tiles/CMakeLists.txt new file mode 100644 index 000000000..d2a1e3b14 --- /dev/null +++ b/tests/core/data/tiles/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required (VERSION 3.20.1) + +project(test-tile) + +set(SOURCES test_tile.cpp) + +add_executable(${PROJECT_NAME} ${SOURCES}) + +target_include_directories(${PROJECT_NAME} PRIVATE + ${GTEST_INCLUDE_DIRS} + ) + +target_link_libraries(${PROJECT_NAME} PRIVATE + phare_core + ${GTEST_LIBS}) + +add_no_mpi_phare_test(${PROJECT_NAME} ${CMAKE_CURRENT_BINARY_DIR}) + + diff --git a/tests/core/data/tiles/test_tile.cpp b/tests/core/data/tiles/test_tile.cpp new file mode 100644 index 000000000..688ebf063 --- /dev/null +++ b/tests/core/data/tiles/test_tile.cpp @@ -0,0 +1,202 @@ +#include +#include + +#include "core/utilities/box/box.hpp" +#include "core/utilities/types.hpp" +#include "core/data/tiles/tile_set.hpp" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include + + +using namespace PHARE::core; + + +template +class TileTestBase +{ +public: + static auto constexpr dimension = TileSet::dimension; + + TileTestBase(Box box_, std::array const& tile_size) + : box{box_} + , tileSet{box, tile_size} + { + } + + Box box; + TileSet tileSet; +}; + +template +class TileTestBoxShapeNotMultipleTileSize : public TileTestBase, public ::testing::Test +{ +public: + TileTestBoxShapeNotMultipleTileSize() + : TileTestBase{ + Box{ConstArray(0), + ConstArray(54)}, + ConstArray(4)} + { + } +}; + +template +class TileTestBoxShapeMultipleTileSize : public TileTestBase, public ::testing::Test +{ +public: + TileTestBoxShapeMultipleTileSize() + : TileTestBase{ + Box{ConstArray(0), + ConstArray(47)}, + ConstArray(4)} + { + } +}; + + +template +class TileTest : public ::testing::Test +{ +}; + + +template +class TileMock : public Box +{ +}; + +using DimTiles = testing::Types>, TileSet>, TileSet>>; + +TYPED_TEST_SUITE(TileTestBoxShapeNotMultipleTileSize, DimTiles); +TYPED_TEST_SUITE(TileTestBoxShapeMultipleTileSize, DimTiles); +TYPED_TEST_SUITE(TileTest, DimTiles); + + +TYPED_TEST(TileTestBoxShapeNotMultipleTileSize, expectedNbrOfTilesPerDimToCoverTheBox) +{ + auto const& shape = this->tileSet.shape(); + for (auto i = 0u; i < this->dimension; ++i) + EXPECT_EQ(shape[i], 14); +} + +TYPED_TEST(TileTestBoxShapeMultipleTileSize, cluserSetSizeIsCorrect) +{ + EXPECT_EQ(this->tileSet.size(), std::pow(12, this->dimension)); +} + + +TYPED_TEST(TileTestBoxShapeNotMultipleTileSize, totalTileSetSurfaceIsEqualToBoxSurface) +{ + auto surface = 0.; + for (auto i = 0u; i < this->tileSet.size(); ++i) + { + auto current_surface = 1.; + for (auto d = 0u; d < this->dimension; ++d) + { + auto l = (this->tileSet[i].upper[d] - this->tileSet[i].lower[d] + 1); + current_surface *= l; + } + surface += current_surface; + } + EXPECT_EQ(surface, this->box.size()); +} + + + +TYPED_TEST(TileTestBoxShapeNotMultipleTileSize, tileHasNoOverlapWithOthers) +{ + auto constexpr dim = TypeParam::dimension; + for (auto const& tile : this->tileSet) + { + for (auto const& other : this->tileSet) + { + if (&tile != &other) + { + auto const box1 = Box{tile.lower, tile.upper}; + auto const box2 = Box{other.lower, other.upper}; + auto overlap = box1 * box2; + EXPECT_FALSE(overlap.has_value()); + } + } + } +} + + +TYPED_TEST(TileTestBoxShapeNotMultipleTileSize, retrieveTilesFromBoxOverlap) +{ + auto constexpr dim = TypeParam::dimension; + Box selection_box{ConstArray(11), ConstArray(34)}; + + auto expected_nbr = std::pow(7, this->dimension); + auto overlapeds = this->tileSet.export_overlaped_with(selection_box); + EXPECT_EQ(overlapeds.size(), expected_nbr); + + auto completes = 0.; + auto incompletes = 0.; + for (auto const& overlaped : overlapeds) + { + auto const& [is_complete, tile] = overlaped; + if (is_complete) + ++completes; + else + ++incompletes; + } + EXPECT_EQ(completes, std::pow(5, dim)); + EXPECT_EQ(incompletes, std::pow(7, dim) - std::pow(5, dim)); +} + + +TYPED_TEST(TileTest, cannotCreateTileWithTileSizeBiggerThanBox) +{ + constexpr auto dim = TypeParam::dimension; + Box box{ConstArray(0), ConstArray(5)}; + auto const tile_size = PHARE::core::ConstArray(7); // larger than box shape + EXPECT_THROW(std::make_unique(box, tile_size), std::runtime_error); +} + + +TYPED_TEST(TileTestBoxShapeNotMultipleTileSize, canRetrieveTileFromCell) +{ + auto constexpr dim = TypeParam::dimension; + auto tile = [&]() { + if constexpr (dim == 1) + return this->tileSet.at(13); + else if constexpr (dim == 2) + return this->tileSet.at(13, 13); + else if constexpr (dim == 3) + return this->tileSet.at(13, 13, 13); + }(); + auto const expected_box = Box{ConstArray(12), ConstArray(15)}; + EXPECT_TRUE(*tile == expected_box); +} + + +TYPED_TEST(TileTestBoxShapeNotMultipleTileSize, useTileSetView) +{ + auto view = this->tileSet.make_view(); + auto shape = view.shape(); + for (auto const& tile : view) + { + EXPECT_LE(tile.size(), std::pow(4, this->dimension)); + } +} + + + +TEST(TileSetViewSpan, fromManyPatches) +{ + // +} + + + + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +}