diff --git a/include/xtensor/core/xmath.hpp b/include/xtensor/core/xmath.hpp index e40536651..aa8a7b222 100644 --- a/include/xtensor/core/xmath.hpp +++ b/include/xtensor/core/xmath.hpp @@ -1785,6 +1785,59 @@ namespace xt * @defgroup red_functions reducing functions */ + namespace detail + { + template + inline auto logical_reduce(F f, bool identity, E&& e, X&& axes, O options) + { + auto functors = make_xreducer_functor(f, xt::const_value(identity)); + if constexpr (xtl::is_integral>::value) + { + return xt::reduce(std::move(functors), std::forward(e), {axes}, options); + } + else + { + return xt::reduce(std::move(functors), std::forward(e), std::forward(axes), options); + } + } + } + + /** + * @ingroup red_functions + * @brief Test whether any element is true along the given axes. + * An empty reduction yields false. With no axes argument, the scalar + * overload returns a bool and stops as soon as the result is known. + */ + template >)> + inline auto any(E&& e, X&& axes, O options = O()) + { + return detail::logical_reduce(detail::logical_or(), false, std::forward(e), std::forward(axes), options); + } + + template + inline auto any(E&& e, const I (&axes)[N], O options = O()) + { + return detail::logical_reduce(detail::logical_or(), false, std::forward(e), axes, options); + } + + /** + * @ingroup red_functions + * @brief Test whether all elements are true along the given axes. + * An empty reduction yields true. With no axes argument, the scalar + * overload returns a bool and stops as soon as the result is known. + */ + template >)> + inline auto all(E&& e, X&& axes, O options = O()) + { + return detail::logical_reduce(detail::logical_and(), true, std::forward(e), std::forward(axes), options); + } + + template + inline auto all(E&& e, const I (&axes)[N], O options = O()) + { + return detail::logical_reduce(detail::logical_and(), true, std::forward(e), axes, options); + } + /** * @ingroup red_functions * @brief Sum of elements over given axes. diff --git a/include/xtensor/reducers/xreducer.hpp b/include/xtensor/reducers/xreducer.hpp index 2c292eb9d..9eeb90477 100644 --- a/include/xtensor/reducers/xreducer.hpp +++ b/include/xtensor/reducers/xreducer.hpp @@ -359,6 +359,16 @@ namespace xt return result; } + // Empty slices have no first element to initialize a partial reduction. + if (e.size() == 0) + { + if (result.size() != 0) + { + result.fill(options_t::has_initial_value ? options.initial_value : init_fct()); + } + return result; + } + std::size_t leading_ax = axes[(e.layout() == layout_type::row_major) ? axes.size() - 1 : 0]; auto strides_finder = e.strides().begin() + static_cast(leading_ax); // The computed strides contain "0" where the shape is 1 -- therefore find the next none-zero number diff --git a/test/test_xmath.cpp b/test/test_xmath.cpp index 982e71a1a..9d30763df 100644 --- a/test/test_xmath.cpp +++ b/test/test_xmath.cpp @@ -27,6 +27,59 @@ namespace xt * Basic operations * ********************/ + TEST(xmath, logical_reduction_axes) + { + xarray a = {{0, 0, 0}, {-1, 0, 2}}; + static_assert(std::is_same_v); + static_assert(std::is_same_v); + auto rows = xt::any(a, -1); + static_assert(std::is_same_v); + EXPECT_EQ(rows.shape(), (shape_type{2})); + EXPECT_FALSE(rows(0)); + EXPECT_TRUE(rows(1)); + EXPECT_TRUE(xt::any(a, {0, 1})()); + EXPECT_FALSE(xt::all(a, {0, 1})()); + + std::vector axes = {0}; + auto kept = xt::all(a + 1, axes, xt::keep_dims | xt::evaluation_strategy::immediate); + EXPECT_EQ(kept.shape(), (shape_type{1, 3})); + EXPECT_FALSE(kept(0, 0)); + EXPECT_TRUE(kept(0, 1)); + EXPECT_TRUE(kept(0, 2)); + + xarray mixed = {{{0, 0}, {1, 2}}, {{0, 3}, {-1, 4}}}; + auto columns = xt::all(mixed, {0, 2}); + EXPECT_EQ(columns.shape(), (shape_type{2})); + EXPECT_FALSE(columns(0)); + EXPECT_TRUE(columns(1)); + + std::vector no_axes; + auto elementwise = xt::any(a, no_axes, xt::evaluation_strategy::immediate); + EXPECT_EQ(elementwise.shape(), a.shape()); + EXPECT_FALSE(elementwise(0, 1)); + EXPECT_TRUE(elementwise(1, 0)); + + const xarray empty = xarray::from_shape({2, 0}); + auto none = xt::any(empty, 1); + auto every = xt::all(empty, 1); + EXPECT_FALSE(none(0)); + EXPECT_FALSE(none(1)); + EXPECT_TRUE(every(0)); + EXPECT_TRUE(every(1)); + + auto empty_rows = xarray::from_shape({0, 3}); + auto by_column = xt::any(empty_rows, 0, xt::evaluation_strategy::immediate); + auto every_column = xt::all(empty_rows, 0, xt::evaluation_strategy::immediate); + EXPECT_EQ(by_column.shape(), (shape_type{3})); + EXPECT_FALSE(xt::any(by_column)); + EXPECT_EQ(every_column.shape(), (shape_type{3})); + EXPECT_TRUE(xt::all(every_column)); + EXPECT_EQ(xt::any(empty_rows, 1, xt::evaluation_strategy::immediate).size(), 0u); + EXPECT_EQ(xt::all(empty_rows, 1, xt::evaluation_strategy::immediate).size(), 0u); + auto seeded = xt::sum(empty_rows, 0, xt::initial(7) | xt::evaluation_strategy::immediate); + EXPECT_TRUE(xt::all(xt::equal(seeded, 7))); + } + TEST(xmath, abs) { shape_type shape = {3, 2};