From b7c72501fb19f65e4b5d8c84e0c359ee97861fcd Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 22 Jan 2026 17:39:15 -0500 Subject: [PATCH 1/4] fix: handle empty blocks in preserve_lobound() If any of the extents are zero (lb == up), do not update the lobound, just no-op. But return Derived& to not break the API. --- src/TiledArray/expressions/blk_tsr_expr.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/TiledArray/expressions/blk_tsr_expr.h b/src/TiledArray/expressions/blk_tsr_expr.h index 661e2ff666..d07fbb3699 100644 --- a/src/TiledArray/expressions/blk_tsr_expr.h +++ b/src/TiledArray/expressions/blk_tsr_expr.h @@ -313,8 +313,13 @@ class BlkTsrExprBase : public Expr { /// Sets result trange lobound such that the tile lobounds are not changed Derived& preserve_lobound() { - return set_trange_lobound( - array_.trange().make_tile_range(lower_bound()).lobound()); + // only set lobound if *all* dimensions have non-zero extents + // so compare lower and upper bounds + if (ranges::equal(lower_bound_, upper_bound_, std::less<>{})) { + return set_trange_lobound( + array_.trange().make_tile_range(lower_bound()).lobound()); + } + return static_cast(*this); } /// @return optional to result trange lobound; if null, the result trange From 567c6178fe7bfd5ab68b4aee8c94209f1c6b82a8 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 22 Jan 2026 17:45:04 -0500 Subject: [PATCH 2/4] fix: check block volume in eval_to before setting values We were already checking the host array volume, also check the block volume to avoid empty blocks. --- src/TiledArray/expressions/expr.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/TiledArray/expressions/expr.h b/src/TiledArray/expressions/expr.h index 3b1e9f43be..2df1709e02 100644 --- a/src/TiledArray/expressions/expr.h +++ b/src/TiledArray/expressions/expr.h @@ -518,9 +518,10 @@ class Expr { // Move the data from dist_eval into the sub-block of result array. // This step may involve communication when the tiles are moved from the // sub-block distribution to the array distribution. - // N.B. handle the corner case of zero-volume host array, then no data needs - // to be moved - if (tsr.array().trange().tiles_range().volume() != 0) { + // N.B. handle the corner cases of zero-volume host array and zero-volume + // block, then no data needs to be moved + if (tsr.array().trange().tiles_range().volume() != 0 && + blk_range.volume() != 0) { // N.B. must deep copy TA_ASSERT(tsr.array().trange().tiles_range().includes(tsr.lower_bound())); // N.B. this expression's range, From d1b966121488895f50c98cac14a824f76a4d1711 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 22 Jan 2026 22:01:58 -0500 Subject: [PATCH 3/4] test: add empty block cases to expression suite Test block expressions involving zero extent/volume blocks --- tests/expressions_impl.h | 47 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/expressions_impl.h b/tests/expressions_impl.h index e7c781ccc6..6544b61e79 100644 --- a/tests/expressions_impl.h +++ b/tests/expressions_impl.h @@ -3075,6 +3075,53 @@ BOOST_FIXTURE_TEST_CASE_TEMPLATE(empty_trange1, F, Fixtures, F) { } } +// corner case: expressions involving empty blocks +BOOST_FIXTURE_TEST_CASE_TEMPLATE(empty_block_expressions, F, Fixtures, F) { + auto& a = F::a; + auto& c = F::c; + + // one of the dimensions has zero extent + const auto lb1 = std::array{3, 2, 0}; + const auto ub1 = std::array{4, 2, 2}; + + const int ntiles = F::ntiles; + const auto lb2 = std::array{ntiles, 0, 0}; + const auto ub2 = std::array{ntiles, ntiles, ntiles}; + + // preserve_lobound should go through without issues + { + std::decay_t result; + BOOST_CHECK_NO_THROW(result("a,b,c") = + a("a,b,c").block(lb1, ub1, preserve_lobound)); + + BOOST_CHECK_EQUAL(result.trange().tiles_range().volume(), 0); + } + { + std::decay_t result; + BOOST_CHECK_NO_THROW(result("a,b,c") = + a("a,b,c").block(lb2, ub2, preserve_lobound)); + BOOST_CHECK_EQUAL(result.trange().tiles_range().volume(), 0); + } + + // assignment of empty (zero volume) block + { + BOOST_CHECK_NO_THROW(c("a,b,c").block(lb1, ub1) = + a("a,b,c").block(lb1, ub1)); + } + { + BOOST_CHECK_NO_THROW(c("a,b,c").block(lb2, ub2) = + a("a,b,c").block(lb2, ub2)); + } + + // assignment from empty array + { + std::decay_t z; + z("a,b,c") = a("a,b,c").block(lb2, ub2); // z is a full empty array + BOOST_CHECK_NO_THROW(c("a,b,c").block(lb2, ub2, preserve_lobound) = + z("a,b,c")); + } +} + BOOST_AUTO_TEST_SUITE_END() #endif // TILEDARRAY_TEST_EXPRESSIONS_IMPL_H From 6126f22823020749ceb2eac8deec4420c43d31b3 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 23 Jan 2026 00:06:06 -0500 Subject: [PATCH 4/4] refactor: preserve_lobound uses all_of to detect zero extents Switches to ranges::all_of from ranges::equal to make things more obvious. Also, need to make a `p` object here, cannot use auto&& [lb, ub] directly as argument. --- src/TiledArray/expressions/blk_tsr_expr.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/TiledArray/expressions/blk_tsr_expr.h b/src/TiledArray/expressions/blk_tsr_expr.h index d07fbb3699..f35863dc81 100644 --- a/src/TiledArray/expressions/blk_tsr_expr.h +++ b/src/TiledArray/expressions/blk_tsr_expr.h @@ -32,6 +32,9 @@ #include #include "blk_tsr_engine.h" +#include +#include + #include namespace TiledArray { @@ -314,8 +317,13 @@ class BlkTsrExprBase : public Expr { /// Sets result trange lobound such that the tile lobounds are not changed Derived& preserve_lobound() { // only set lobound if *all* dimensions have non-zero extents - // so compare lower and upper bounds - if (ranges::equal(lower_bound_, upper_bound_, std::less<>{})) { + const bool empty = ranges::any_of( + ranges::views::zip(lower_bound_, upper_bound_), [](auto&& p) { + auto [lb, ub] = p; + return lb >= ub; + }); + + if (!empty) { return set_trange_lobound( array_.trange().make_tile_range(lower_bound()).lobound()); }