Функция element_wise_add для Boost.MultiArray в C ++

Oct 30 2020

Это дополнительный вопрос для функции суммирования для Boost.MultiArray в C ++ . Помимо операции суммирования всех элементов, я пытаюсь сосредоточиться здесь на поэлементной операции. Основная идея этого вопроса - реализовать element_wise_addфункцию для Boost.MultiArray. Цель этой element_wise_addфункции - выполнить поэлементное сложение двух boost::multi_arrays. Функция element_wise_addимеет два входных параметра input1и input2для операции поэлементного добавления, а возвращаемый тип - это поэлементный результат.

template<class T> requires is_summable<T>
auto element_wise_add(const T& input1, const T& input2)
{
    return input1 + input2;
}

//  Deal with the two input case
template<class T, std::size_t Dims> requires is_summable<T>
auto element_wise_add(const boost::detail::multi_array::const_sub_array<T, Dims>& input1, const boost::detail::multi_array::const_sub_array<T, Dims>& input2)
{
    boost::multi_array<T, Dims> output(reinterpret_cast<boost::array<size_t, Dims> const&>(*input1.shape()));
    for (typename boost::detail::multi_array::const_sub_array<T, Dims>::index i = 0; i < input1.shape()[0]; i++)
    {
        output[i] = element_wise_add(input1[i], input2[i]);
    }
    return output;
}

//  Deal with the two input case
template<class T, std::size_t Dims> requires is_summable<T>
auto element_wise_add(const boost::detail::multi_array::sub_array<T, Dims>& input1, const boost::detail::multi_array::sub_array<T, Dims>& input2)
{
    boost::multi_array<T, Dims> output(reinterpret_cast<boost::array<size_t, Dims> const&>(*input1.shape()));
    for (typename boost::detail::multi_array::sub_array<T, Dims>::index i = 0; i < input1.shape()[0]; i++)
    {
        output[i] = element_wise_add(input1[i], input2[i]);
    }
    return output;
}

//  Deal with the two input case
template<class T, std::size_t Dims> requires is_summable<T>
auto element_wise_add(const boost::multi_array<T, Dims>& input1, const boost::multi_array<T, Dims>& input2)
{
    if (*input1.shape() != *input2.shape())         //  if shape is different
    {
        return input1;                              //  unable to perform element-wise add operation
    }
    boost::multi_array<T, Dims> output(reinterpret_cast<boost::array<size_t, Dims> const&>(*input1.shape()));
    for (typename boost::multi_array<T, Dims>::index i = 0; i < input1.shape()[0]; i++)
    {
        output[i] = element_wise_add(input1[i], input2[i]);
    }
    return output;
}

Используемая is_summableконцепция:

template<typename T>
concept is_summable = requires(T x) { x + x; };

Проверка этой element_wise_addфункции выглядит следующим образом.

// Create a 3D array that is 3 x 4 x 2
typedef boost::multi_array<double, 3> array_type;
typedef array_type::index index;
array_type A(boost::extents[3][4][2]);

// Assign values to the elements
int values = 0;
for (index i = 0; i != 3; ++i)
    for (index j = 0; j != 4; ++j)
        for (index k = 0; k != 2; ++k)
            A[i][j][k] = values++;

for (index i = 0; i != 3; ++i)
    for (index j = 0; j != 4; ++j)
        for (index k = 0; k != 2; ++k)
            std::cout << A[i][j][k] << std::endl;

auto DoubleA = element_wise_add(A, A);

for (index i = 0; i != 3; ++i)
    for (index j = 0; j != 4; ++j)
        for (index k = 0; k != 2; ++k)
            std::cout << DoubleA[i][j][k] << std::endl;

Все предложения приветствуются.

  • На какой вопрос это продолжение?

    Функция суммирования для Boost.MultiArray в C ++

  • Какие изменения были внесены в код с момента последнего вопроса?

    Предыдущий вопрос - это реализация функции суммирования для Boost.MultiArray, и основная идея этого вопроса - реализовать element_wise_addфункцию для Boost.MultiArray.

  • Почему запрашивается новый обзор?

    Подобное использование трех типов перегрузить функцию boost::multi_array, boost::detail::multi_array::sub_arrayи boost::detail::multi_array::const_sub_arrayпоявляется снова. Я знаю, может, это плохая идея. Однако я не могу придумать лучшего способа. Более того, обработка исключений для ситуации «форма отличается» не идеальна. В случае с boost::detail::multi_array::sub_arrayи boost::detail::multi_array::const_sub_arrayя не уверен, что нужно вернуть обратно. Я когда-либо пробовал что-то std::optionalподобное:

    template<class T, std::size_t Dims> requires is_summable<T>
    auto element_wise_add(const boost::detail::multi_array::const_sub_array<T, Dims>& input1, const boost::detail::multi_array::const_sub_array<T, Dims>& input2)
    {
        std::optional<boost::detail::multi_array::const_sub_array<T, Dims>> final_output;
        if (*input1.shape() != *input2.shape())         //  if shape is different
        {
            final_output = std::nullopt;
        }
        else
        {
            boost::multi_array<T, Dims> output(reinterpret_cast<boost::array<size_t, Dims> const&>(*input1.shape()));
            for (typename boost::detail::multi_array::const_sub_array<T, Dims>::index i = 0; i < input1.shape()[0]; i++)
            {
                output[i] = element_wise_add(input1[i], input2[i]);
            }
            final_output = output;
        }
        return final_output;
    }
    

    И это:

    template<class T, std::size_t Dims> requires is_summable<T>
    auto element_wise_add(const boost::detail::multi_array::sub_array<T, Dims>& input1, const boost::detail::multi_array::sub_array<T, Dims>& input2)
    {
        std::optional<boost::detail::multi_array::sub_array<T, Dims>> final_output;
        if (*input1.shape() != *input2.shape())         //  if shape is different
        {
            final_output = std::nullopt;
        }
        else
        {
            boost::multi_array<T, Dims> output(reinterpret_cast<boost::array<size_t, Dims> const&>(*input1.shape()));
            for (typename boost::detail::multi_array::sub_array<T, Dims>::index i = 0; i < input1.shape()[0]; i++)
            {
                output[i] = element_wise_add(input1[i], input2[i]);
            }
            final_output = output;
        }
        return final_output;
    }
    

    Однако это настолько сложно в использовании ( для доступа к содержимому в структуре требуется использование функции .value()or ) и, на мой взгляд, немного ужасно. Если есть предложения или возможные улучшения, сообщите мне!.value_or()std::optional<>

Ответы

2 G.Sliepen Oct 30 2020 at 17:17

Подумайте о том, чтобы превратить это в operator+()

Поскольку поэлементное добавление является довольно распространенной и естественной операцией (например, STL поддерживает это для std::valarray), может быть более интуитивно понятнее перегрузка operator+()вместо создания функции element_wise_add(). См. Этот вопрос для возможной реализации, которая также легко распространяется на других операторов.

Еще одним преимуществом создания this operator+()является то, что он boost::multi_arrayудовлетворяет саму себя is_summable, поэтому без добавления явной поддержки рекурсивных контейнеров будет работать следующее:

boost::multi_array<boost::multi_array<double, 2>, 3> array1, array2;
auto array3 = array1 + array2;

Обработка несовпадающих размеров массива

Я бы действительно не std::optionalстал сигнализировать об ошибках математических операций. Я вижу два пути:

  1. Убедитесь, что размеры возвращаемого массива являются максимальными размерами двух входных массивов. Так что, если добавить {{1}, {2}}в {{3, 5}}результат будет {{4, 5}, {2, 0}}.

  2. Бросьте std::logic_error, в предположении, что это ошибка программирования, чтобы добавить две несовпадающие ошибки.

Избегайте повторения

Вы в основном пишете одно и то же трижды, единственное отличие состоит в том, являются ли входные данные обычными boost::multi_arrays, sub_arrays или const_sub_arrays. Чтобы избежать этого, вы хотите сделать входные типы шаблонными, а чтобы гарантировать, что он соответствует только boost::multi_arrayсвязанным типам, просто напишите концепцию для этого. Опять же, вы можете использовать выражения, которые вы уже используете для этого:

template<T>
concept is_multi_array = requires(T x) {
        x.shape();
        boost::multi_array(x);
};

Это проверит, что тип xимеет функцию- shape()член и boost::multi_arrayможет быть скопирован из него. Тогда просто напишите:

template<class T> requires is_multi_array<T>
auto element_wise_add(const T& input1, const T& input2)
{
    if (*input1.shape() != *input2.shape())
    {
        throw std::logic_error("array shape mismatch");
    }

    boost::multi_array output(input1);

    for (decltype(+input1.shape()[0]) i = 0; i < input1.shape()[0]; i++)
    {
        output[i] = element_wise_add(input1[i], input2[i]);
    }

    return output;
}

Недостатком здесь является то, что весь input1массив копируется в него outputбез уважительной причины, за исключением того, что я не вижу, как построить новый multi_arrayболее эффективно, не углубляясь в ад типов, который приходит с ним boost::multi_array. Возможно, это можно передать другой функции шаблона.

Вы также заметите, что я изменил способ определения типа i: я использую decltype(), но поскольку это сохраняет const-ness, я должен отбросить его. Есть разные способы сделать это, здесь я использовал унарный +прием.

Обработка большего количества комбинаций

Что делать, если я хочу добавить sub_arrayк обычному стилю multi_arrayтакого же размера? При вашем подходе вам придется обрабатывать все возможные комбинации по отдельности, но в показанном мной примере одной функции вы можете просто написать:

template<class T1, class T2> requires (is_multi_array<T1> && is_multi_array<T2>)
auto element_wise_add(const T1& input1, const T2& input2)
{
    ...
}

Однако также учтите, что вы можете добавить массив двойных чисел к массиву целых чисел. Это можно заставить работать, если вы также напишете:

template<typename T1, typename T2>
concept is_summable = requires(T1 x, T2 y) { x + y; };

template<class T1, class T2> requires is_summable<T1, T2>
auto element_wise_add(const T1& input1, const T2& input2)
{
    return input1 + input2;
}

Хотя для того, чтобы сделать его по-настоящему полезным, вы хотите, чтобы тип outputимел тип значения, который соответствует результату добавления типов значений двух входных массивов.