Uma função element_wise_add para Boost.MultiArray em C ++

Oct 30 2020

Esta é uma pergunta de acompanhamento para A Summation Function For Boost.MultiArray em C ++ . Além da operação de soma de todos os elementos, estou tentando me concentrar na operação do elemento sábio aqui. A ideia principal desta questão é implementar uma element_wise_addfunção para Boost.MultiArray. O objetivo desta element_wise_addfunção é realizar a operação de adição elemento a elemento em dois boost::multi_arrays. A função element_wise_addtem dois parâmetros de entrada input1e input2para a operação adicionar elemento a elemento e o tipo de retorno é o resultado elemento a elemento.

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;
}

O is_summableconceito utilizado :

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

O teste desta element_wise_addfunção é o seguinte.

// 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;

Todas as sugestões são bem-vindas.

  • Para qual pergunta é um seguimento?

    Uma função de soma para Boost.MultiArray em C ++

  • Que mudanças foram feitas no código desde a última pergunta?

    A questão anterior é a implementação de uma função de soma para Boost.MultiArray e a ideia principal desta questão é implementar uma element_wise_addfunção para Boost.MultiArray.

  • Por que uma nova revisão está sendo solicitada?

    O uso semelhante de três tipos sobrecarregar função para boost::multi_array, boost::detail::multi_array::sub_arraye boost::detail::multi_array::const_sub_arrayaparece novamente. Eu sei que talvez isso não seja uma boa ideia. No entanto, não há maneira melhor surgindo em minha mente. Além disso, o tratamento de exceções para a situação "a forma é diferente" não é perfeito. No caso de boost::detail::multi_array::sub_arraye boost::detail::multi_array::const_sub_array, não tenho certeza de qual é a coisa apropriada para retornar. Eu já tentei algo std::optionalassim:

    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;
    }
    

    E isto:

    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;
    }
    

    No entanto, isso é tão complexo de usar (o uso de .value()ou .value_or()função é necessário para acessar o conteúdo na std::optional<>estrutura) e um pouco horrível, eu acho. Se houver alguma sugestão ou possível melhoria, diga-me!

Respostas

2 G.Sliepen Oct 30 2020 at 17:17

Considere transformar isso em um operator+()

Visto que a adição de elementos é uma operação bastante comum e natural (por exemplo, o STL suporta isso para std::valarray), pode ser mais intuitivo sobrecarregar em operator+()vez de criar uma função element_wise_add(). Consulte esta questão para uma possível implementação que também se estende facilmente a outras operadoras.

Outra vantagem de tornar este um operator+()é que ele boost::multi_arrayse satisfaz is_summable, portanto, sem adicionar suporte explícito para contêineres recursivos, o seguinte funcionaria:

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

Tratamento de dimensões de matriz incompatíveis

De fato, eu não usaria std::optionalpara sinalizar erros em operações matemáticas. Eu vejo duas maneiras:

  1. Certifique-se de que as dimensões do array retornado são os máximos das dimensões dos dois arrays de entrada. Portanto, se eu adicionar {{1}, {2}}ao {{3, 5}}resultado, será {{4, 5}, {2, 0}}.

  2. Jogue um std::logic_error, supondo que é um erro de programação adicionar dois erros incompatíveis.

Evitando repetição

Você está basicamente escrevendo a mesma coisa três vezes, a única variação é se as entradas são boost::multi_arrays, sub_arrays ou const_sub_arrays regulares . Para evitar isso, você deseja criar um modelo para os tipos de entrada e, para garantir que corresponda apenas boost::multi_arrayaos tipos relacionados, basta escrever um conceito para isso. Novamente, você pode usar expressões que já usa para isso:

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

Isso testará se o tipo de xtem uma shape()função de membro e se um boost::multi_arraypode ser criado por cópia a partir dele. Depois é só escrever:

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;
}

A desvantagem aqui é que todo o input1array é copiado outputsem um bom motivo, exceto que não vejo como construir um novo com multi_arraymais eficiência sem entrar no tipo de inferno que vem com boost::multi_array. Talvez isso possa ser terceirizado para outra função de modelo.

Você também notará que mudei a maneira como o tipo de ié determinado: eu uso decltype(), mas como isso mantém o const-ness, tenho que jogá-lo fora. Existem várias maneiras de fazer isso, usei o +truque unário aqui.

Lidando com mais combinações

E se eu quiser adicionar um sub_arraya um regular multi_arraydo mesmo tamanho? Com sua abordagem, você teria que lidar com todas as combinações possíveis separadamente, mas com o exemplo de função única que mostrei, você pode simplesmente escrever:

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

No entanto, também considere que você pode querer adicionar uma matriz de duplos a uma matriz de inteiros. Isso poderia funcionar, se você também escrever:

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;
}

Embora para torná-lo realmente útil, você deseja que o tipo de outputtenha um tipo de valor que corresponda ao resultado da adição dos tipos de valor dos dois arrays de entrada.