0
\$\begingroup\$

This is a follow-up question for A get_extents helper function for Boost.MultiArray in C++. In order to retrieve, manipulate and calculate size information in each dimension from Boost.MultiArray in some better ways. Besides the get_extents helper function, I am attempting to implement extents_to_array and array_to_extents helper functions to convert boost::extents into std::array<std::size_t, NumDims>, and vice versa.

The experimental implementation

The experimental implementation of extents_to_array and array_to_extents helper functions are as below.

template<std::size_t NumDims>
constexpr auto extents_to_array(const boost::detail::multi_array::extent_gen<NumDims>& input)
{
    std::array<std::size_t, NumDims> output;
    for (size_t rank_index = 0; rank_index < NumDims; rank_index++)
    {
        output[rank_index] = filled_multi_array(input, 0.0).shape()[rank_index];
    }
    return output;
}

template<std::size_t NumDims, std::size_t RankIndex = NumDims - 1>
constexpr auto array_to_extents(std::array<std::size_t, NumDims> new_size)
{
    if constexpr (RankIndex == 0)
    {
        return boost::extents[new_size[0]];
    }
    else
    {
        return get_extents(filled_multi_array(array_to_extents<NumDims, RankIndex - 1>(new_size), 0.0))[new_size[RankIndex]];
    }
}

template<class T, std::size_t NumDims>
constexpr auto print_size(const boost::multi_array<T, NumDims>& input)
{
    auto input_size = extents_to_array(get_extents(input));
    //  Checking shape
    for (size_t rank_index = 0; rank_index < NumDims; rank_index++)
    {
        std::cout << "input_size[" << rank_index << "]:" << input_size[rank_index] << std::endl;
    }
    return;
}

Test case and Full Testing Code

#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <complex>
#include <concepts>
#include <deque>
#include <execution>
#include <exception>
#include <functional>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <mutex>
#include <numeric>
#include <optional>
#include <ranges>
#include <stdexcept>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>

#define USE_BOOST_MULTIDIMENSIONAL_ARRAY
#ifdef USE_BOOST_MULTIDIMENSIONAL_ARRAY
#include <boost/multi_array.hpp>
#include <boost/multi_array/algorithm.hpp>
#include <boost/multi_array/base.hpp>
#include <boost/multi_array/collection_concept.hpp>
#endif

template<typename T>
concept is_inserterable = requires(T x)
{
    std::inserter(x, std::ranges::end(x));
};

template<typename T>
concept is_minusable = requires(T x) { x - x; };

template<typename T1, typename T2>
concept is_minusable2 = requires(T1 x1, T2 x2) { x1 - x2; };


#ifdef USE_BOOST_MULTIDIMENSIONAL_ARRAY
template<typename T>
concept is_multi_array = requires(T x)
{
    x.num_dimensions();
    x.shape();
    boost::multi_array(x);
};

//  Add operator
template<is_multi_array T1, is_multi_array T2>
auto operator+(const T1& input1, const T2& input2)
{
    if (input1.num_dimensions() != input2.num_dimensions())     //  Dimensions are different, unable to perform element-wise add operation
    {
        throw std::logic_error("Array dimensions are different");
    }
    if (*input1.shape() != *input2.shape())                     //  Shapes are different, unable to perform element-wise add operation
    {
        throw std::logic_error("Array shapes are different");
    }
    boost::multi_array output(input1);
    for (decltype(+input1.shape()[0]) i{}; i != input1.shape()[0]; ++i)
    {
        output[i] = input1[i] + input2[i];
    }
    return output;
}

//  Minus operator
template<is_multi_array T1, is_multi_array T2>
auto operator-(const T1& input1, const T2& input2)
{
    if (input1.num_dimensions() != input2.num_dimensions())     //  Dimensions are different, unable to perform element-wise add operation
    {
        throw std::logic_error("Array dimensions are different");
    }
    if (*input1.shape() != *input2.shape())                     //  Shapes are different, unable to perform element-wise add operation
    {
        throw std::logic_error("Array shapes are different");
    }
    boost::multi_array output(input1);
    for (decltype(+input1.shape()[0]) i{}; i != input1.shape()[0]; ++i)
    {
        output[i] = input1[i] - input2[i];
    }
    return output;
}


//  Element Increment Operator
//  Define nonmember prefix increment operator
template<is_multi_array T>
auto operator++(T& input)
{
    input = recursive_transform(input, [](auto& x) {return x + 1; });
    return input;
}

//  Define nonmember postfix increment operator
template<is_multi_array T>
auto operator++(T& input, int i)
{
    auto output = input;
    input = recursive_transform(input, [](auto& x) {return x + 1; });
    return output;
}

//  Element Decrement Operator
//  Define nonmember prefix decrement operator
template<is_multi_array T>
auto operator--(T& input)
{
    input = recursive_transform(input, [](auto& x) {return x - 1; });
    return input;
}

//  Define nonmember postfix decrement operator
template<is_multi_array T>
auto operator--(T& input, int i)
{
    auto output = input;
    input = recursive_transform(input, [](auto& x) {return x - 1; });
    return output;
}


template<typename T>
concept is_multiplicable = requires(T x)
{
    x * x;
};

//  Multiplication
template<is_multiplicable T1, is_multiplicable T2>
constexpr auto element_wise_multiplication(const T1& input1, const T2& input2)
{
    return input1 * input2;
}

template<is_multi_array T1, is_multi_array T2>
constexpr auto element_wise_multiplication(const T1& input1, const T2& input2)
{
    if (input1.num_dimensions() != input2.num_dimensions())     //  Dimensions are different, unable to perform element-wise add operation
    {
        throw std::logic_error("Array dimensions are different");
    }
    if (*input1.shape() != *input2.shape())                     //  Shapes are different, unable to perform element-wise add operation
    {
        throw std::logic_error("Array shapes are different");
    }
    boost::multi_array output(input1);
    for (decltype(+input1.shape()[0]) i{}; i != input1.shape()[0]; ++i)
    {
        output[i] = element_wise_multiplication(input1[i], input2[i]);
    }
    return output;
}

template<typename T>
concept is_divisible = requires(T x)
{
    x / x;
};

//  Division
template<is_divisible T1, is_divisible T2>
constexpr auto element_wise_division(const T1& input1, const T2& input2)
{
    if (input2 == 0)
    {
        throw std::logic_error("Divide by zero exception"); //  Handle the case of dividing by zero exception
    }
    return input1 / input2;
}

template<is_multi_array T1, is_multi_array T2>
constexpr auto element_wise_division(const T1& input1, const T2& input2)
{
    if (input1.num_dimensions() != input2.num_dimensions())     //  Dimensions are different, unable to perform element-wise add operation
    {
        throw std::logic_error("Array dimensions are different");
    }
    if (*input1.shape() != *input2.shape())                     //  Shapes are different, unable to perform element-wise add operation
    {
        throw std::logic_error("Array shapes are different");
    }
    boost::multi_array output(input1);
    for (decltype(+input1.shape()[0]) i{}; i != input1.shape()[0]; ++i)
    {
        output[i] = element_wise_division(input1[i], input2[i]);
    }
    return output;
}

//  ones function
//  Reference: https://stackoverflow.com/q/46595857/6667035
template<class T, std::size_t NumDims>
constexpr auto ones(boost::detail::multi_array::extent_gen<NumDims> size)
{
    boost::multi_array<T, NumDims> output(size);
    return recursive_transform(output, [](auto& x) { return 1; });
}

template<class T, std::size_t NumDims>
constexpr auto filled_multi_array(boost::detail::multi_array::extent_gen<NumDims> size, const T& value)
{
    boost::multi_array<T, NumDims> output(size);
    std::fill_n(output.data(), output.num_elements(), value);
    return output;
}

template<class T, std::size_t NumDims>
auto get_extents(const boost::multi_array<T, NumDims>& input) {
    boost::detail::multi_array::extent_gen<NumDims> output;
    std::vector<std::size_t> shape_list;
    for (size_t i = 0; i < NumDims; i++)
    {
        shape_list.push_back(input.shape()[i]);
    }
    std::copy_n(input.shape(), NumDims, output.ranges_.begin());
    return output;
}

template<std::size_t NumDims>
constexpr auto extents_to_array(const boost::detail::multi_array::extent_gen<NumDims>& input)
{
    std::array<std::size_t, NumDims> output;
    for (size_t rank_index = 0; rank_index < NumDims; rank_index++)
    {
        output[rank_index] = filled_multi_array(input, 0.0).shape()[rank_index];
    }
    return output;
}

template<std::size_t NumDims, std::size_t RankIndex = NumDims - 1>
constexpr auto array_to_extents(std::array<std::size_t, NumDims> new_size)
{
    if constexpr (RankIndex == 0)
    {
        return boost::extents[new_size[0]];
    }
    else
    {
        return get_extents(filled_multi_array(array_to_extents<NumDims, RankIndex - 1>(new_size), 0.0))[new_size[RankIndex]];
    }
}

template<class T, std::size_t NumDims>
constexpr auto print_size(const boost::multi_array<T, NumDims>& input)
{
    auto input_size = extents_to_array(get_extents(input));
    //  Checking shape
    for (size_t rank_index = 0; rank_index < NumDims; rank_index++)
    {
        std::cout << "input_size[" << rank_index << "]:" << input_size[rank_index] << std::endl;
    }
    return;
}
#endif

template<std::size_t dim, class T>
constexpr auto n_dim_vector_generator(T input, std::size_t times)
{
    if constexpr (dim == 0)
    {
        return input;
    }
    else
    {
        auto element = n_dim_vector_generator<dim - 1>(input, times);
        std::vector<decltype(element)> output(times, element);
        return output;
    }
}

template<std::size_t dim, std::size_t times, class T>
constexpr auto n_dim_array_generator(T input)
{
    if constexpr (dim == 0)
    {
        return input;
    }
    else
    {
        auto element = n_dim_array_generator<dim - 1, times>(input);
        std::array<decltype(element), times> output;
        std::fill(std::ranges::begin(output), std::ranges::end(output), element);
        return output;
    }
}

template<std::size_t dim, class T>
constexpr auto n_dim_deque_generator(T input, std::size_t times)
{
    if constexpr (dim == 0)
    {
        return input;
    }
    else
    {
        auto element = n_dim_deque_generator<dim - 1>(input, times);
        std::deque<decltype(element)> output(times, element);
        return output;
    }
}

template<std::size_t dim, class T>
constexpr auto n_dim_list_generator(T input, std::size_t times)
{
    if constexpr (dim == 0)
    {
        return input;
    }
    else
    {
        auto element = n_dim_list_generator<dim - 1>(input, times);
        std::list<decltype(element)> output(times, element);
        return output;
    }
}

template<std::size_t dim, template<class...> class Container = std::vector, class T>
constexpr auto n_dim_container_generator(T input, std::size_t times)
{
    if constexpr (dim == 0)
    {
        return input;
    }
    else
    {
        return Container(times, n_dim_container_generator<dim - 1, Container, T>(input, times));
    }
}

int main()
{
    //  Create one multidimensional std::array with 7 elements, the value of each element is set to 30 in type `std::size_t`.
    auto size_info = n_dim_array_generator<1, 7>(static_cast<std::size_t>(30));
    
    //  Update some values in array.
    size_info[0] = 10;
    size_info[1] = 5;
    size_info[2] = 2;
    

    //  array_to_extents(size_info)
    //  Construct a new Boost.MultiArray with a given one multidimensional std::array
    //  The dimension of this new Boost.MultiArray is determined from the size of the given one multidimensional std::array
    //  The sizes of each dimension are determined from the values in the given one multidimensional std::array
    print_size(filled_multi_array(array_to_extents(size_info), 0.0));
    
    return 0;
}

The output of the testing code above:

input_size[0]:10
input_size[1]:5
input_size[2]:2
input_size[3]:30
input_size[4]:30
input_size[5]:30
input_size[6]:30

A Godbolt link is here.

All suggestions are welcome.

The summary information:

  • Which question it is a follow-up to?

    A get_extents helper function for Boost.MultiArray in C++

  • What changes has been made in the code since last question?

    extents_to_array and array_to_extents are implemented here.

  • Why a new review is being asked for?

    If there is any possible improvement, please let me know.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Is that creating a temporary multi-dimensional array just to get the extents? It looks like the test code creates a multi-array of doubles with 30 * 30 * 30 * 30 * 2 * 5 * 10 elements... if my math is right that's a ~618MB memory allocation to copy 7 numbers. D:

If we're already using the boost::detail namespace, we may as well just copy what boost does to get the array shape (const_multi_array_ref::init_from_extent_gen):

template<std::size_t NumDims>
constexpr auto extents_to_array(const boost::detail::multi_array::extent_gen<NumDims>& input)
{
    std::array<std::size_t, NumDims> output;
    std::transform(input.ranges_.begin(), input.ranges_.end(), output.begin(),
        [] (auto r) { return r.size(); });

    return output;
}

And the other direction would look something like this:

template<std::size_t NumDims>
constexpr auto array_to_extents(std::array<std::size_t, NumDims> input)
{
    boost::detail::multi_array::extent_gen<NumDims> output;

    std::transform(input.begin(), input.end(), output.ranges_.begin(),
        [] (auto s) { return boost::detail::multi_array::extent_range<boost::detail::multi_array::index, boost::detail::multi_array::size_type>(s); });

    return output;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.