From 5f0542378d977b605c011a67174602602e0c4d4b Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 8 Apr 2026 18:31:11 +0000 Subject: [PATCH] Add IdxRange boundary slicing coverage --- include/alpaka/mem/IdxRange.hpp | 4 +- test/unit/mem/idxRange.cpp | 105 ++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/include/alpaka/mem/IdxRange.hpp b/include/alpaka/mem/IdxRange.hpp index b4d4ffe35..89d134a83 100644 --- a/include/alpaka/mem/IdxRange.hpp +++ b/include/alpaka/mem/IdxRange.hpp @@ -137,8 +137,8 @@ namespace alpaka auto const range, alpaka::BoundaryDirection const& boundaryDir) { - auto m_begin = Vec::fill(0u); - auto m_end = Vec::fill(0u); + auto m_begin = range.m_begin; + auto m_end = range.m_end; for(uint32_t i = 0; i < T_dim; ++i) { switch(boundaryDir.data[i]) diff --git a/test/unit/mem/idxRange.cpp b/test/unit/mem/idxRange.cpp index a9d5e3d4b..0d049390f 100644 --- a/test/unit/mem/idxRange.cpp +++ b/test/unit/mem/idxRange.cpp @@ -6,6 +6,9 @@ #include +#include +#include + TEST_CASE("IdxRange::begin() and end()", "[mem][IdxRange][iterator]") { SECTION("only end extents") @@ -59,3 +62,105 @@ TEST_CASE("IdxRange::begin() and end()", "[mem][IdxRange][iterator]") } } } + +TEST_CASE("IdxRange boundary slicing keeps bounds, emptiness, and stride", "[mem][IdxRange][boundary]") +{ + auto collectVisited = [](T_Range const& range) + { + using Idx = std::decay_t; + std::vector visited; + for(auto const idx : range) + visited.push_back(idx); + return visited; + }; + + SECTION("lower upper and middle slices keep the expected subrange values") + { + // Boundary slices should map directly onto the parent begin/end coordinates in each selected dimension. + auto const parent = alpaka::IdxRange(alpaka::Vec{2, 5, 7}, alpaka::Vec{11, 17, 21}, alpaka::Vec{3, 4, 5}); + auto const lowerHalos = alpaka::Vec{2u, 3u, 4u}; + auto const upperHalos = alpaka::Vec{1u, 5u, 2u}; + using HaloVec = std::remove_cvref_t; + auto const makeBoundary = [&](auto const& data) + { return alpaka::BoundaryDirection<3u, HaloVec, HaloVec>{data, lowerHalos, upperHalos}; }; + + // LOWER should keep the parent origin in selected dimensions while the core stays cropped by both halos. + auto const lower = alpaka::makeDirectionSubRange( + parent, + makeBoundary( + alpaka::Vec{alpaka::BoundaryType::LOWER, alpaka::BoundaryType::MIDDLE, alpaka::BoundaryType::LOWER})); + REQUIRE(lower.m_begin == alpaka::Vec{2, 8, 7}); + REQUIRE(lower.m_end == alpaka::Vec{4, 12, 11}); + REQUIRE(lower.m_stride == parent.m_stride); + REQUIRE(collectVisited(lower) == std::vector{alpaka::Vec{2, 8, 7}}); + + // UPPER should anchor the selected dimensions against the parent tail without disturbing stride. + auto const upper = alpaka::makeDirectionSubRange( + parent, + makeBoundary( + alpaka::Vec{alpaka::BoundaryType::UPPER, alpaka::BoundaryType::UPPER, alpaka::BoundaryType::MIDDLE})); + REQUIRE(upper.m_begin == alpaka::Vec{10, 12, 11}); + REQUIRE(upper.m_end == alpaka::Vec{11, 17, 19}); + REQUIRE(upper.m_stride == parent.m_stride); + REQUIRE( + collectVisited(upper) + == std::vector{ + alpaka::Vec{10, 12, 11}, + alpaka::Vec{10, 12, 16}, + alpaka::Vec{10, 16, 11}, + alpaka::Vec{10, 16, 16}}); + + // MIDDLE should crop by asymmetric halos in every dimension and preserve the sparse parent walk. + auto const middle + = alpaka::makeDirectionSubRange(parent, alpaka::makeCoreBoundaryDirection<3u>(lowerHalos, upperHalos)); + REQUIRE(middle.m_begin == alpaka::Vec{4, 8, 11}); + REQUIRE(middle.m_end == alpaka::Vec{10, 12, 19}); + REQUIRE(middle.m_stride == parent.m_stride); + REQUIRE( + collectVisited(middle) + == std::vector{ + alpaka::Vec{4, 8, 11}, + alpaka::Vec{4, 8, 16}, + alpaka::Vec{7, 8, 11}, + alpaka::Vec{7, 8, 16}}); + } + + SECTION("empty core stays empty when halos consume a dimension") + { + // If halos eat the full span of one dimension, the middle slice must become empty instead of yielding garbage. + auto const parent = alpaka::IdxRange(alpaka::Vec{1, 3, 5}, alpaka::Vec{7, 9, 11}, alpaka::Vec{2, 3, 2}); + auto const middle = alpaka::makeDirectionSubRange( + parent, + alpaka::makeCoreBoundaryDirection<3u>(alpaka::Vec{1u, 2u, 3u}, alpaka::Vec{1u, 4u, 1u})); + + REQUIRE(middle.m_begin == alpaka::Vec{2, 5, 8}); + REQUIRE(middle.m_end == alpaka::Vec{6, 5, 10}); + REQUIRE(middle.m_stride == parent.m_stride); + REQUIRE(middle.begin() == middle.end()); + REQUIRE(collectVisited(middle).empty()); + } + + SECTION("signed ranges keep their signed coordinate type") + { + // Signed parent coordinates must survive boundary slicing so negative starts stay representable. + auto const parent = alpaka::IdxRange(alpaka::Vec{-5, -4}, alpaka::Vec{6, 9}, alpaka::Vec{4, 3}); + auto const lowerHalos = alpaka::Vec{2u, 3u}; + auto const upperHalos = alpaka::Vec{1u, 2u}; + auto const middle + = alpaka::makeDirectionSubRange(parent, alpaka::makeCoreBoundaryDirection<2u>(lowerHalos, upperHalos)); + + static_assert(std::is_same_v); + REQUIRE(middle.m_begin == alpaka::Vec{-3, -1}); + REQUIRE(middle.m_end == alpaka::Vec{5, 7}); + REQUIRE(middle.m_stride == parent.m_stride); + REQUIRE( + collectVisited(middle) + == std::vector{ + alpaka::Vec{-3, -1}, + alpaka::Vec{-3, 2}, + alpaka::Vec{-3, 5}, + alpaka::Vec{1, -1}, + alpaka::Vec{1, 2}, + alpaka::Vec{1, 5}}); + } +}