Una función element_wise_add para Boost.MultiArray en C ++

Oct 30 2020

Esta es una pregunta de seguimiento para una función de suma para Boost.MultiArray en C ++ . Además de la operación de suma de todos los elementos, estoy tratando de centrarme en la operación de elementos aquí. La idea principal de esta pregunta es implementar una element_wise_addfunción para Boost.MultiArray. El propósito de esta element_wise_addfunción es realizar una operación de suma de elementos en dos boost::multi_arrays. La función element_wise_addtiene dos parámetros de entrada input1y input2para la operación de adición de elementos y el tipo de retorno es el resultado de elementos.

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

El is_summableconcepto utilizado :

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

La prueba de esta element_wise_addfunción es la siguiente.

// 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 las sugerencias son bienvenidas.

  • ¿De qué pregunta es un seguimiento?

    Una función de suma para Boost.MultiArray en C ++

  • ¿Qué cambios se han realizado en el código desde la última pregunta?

    La pregunta anterior es la implementación de una función de suma para Boost.MultiArray y la idea principal de esta pregunta es implementar una element_wise_addfunción para Boost.MultiArray.

  • ¿Por qué se solicita una nueva revisión?

    El uso similar de los tres tipos de función de sobrecarga boost::multi_array, boost::detail::multi_array::sub_arrayy boost::detail::multi_array::const_sub_arrayaparece de nuevo. Sé que tal vez esto no sea una buena idea. Sin embargo, no se me ocurre ninguna forma mejor. Además, el manejo de excepciones para la situación de "la forma es diferente" no es perfecto. En el caso de boost::detail::multi_array::sub_arrayy boost::detail::multi_array::const_sub_array, no estoy seguro de qué es lo apropiado para devolver. Alguna vez he probado algo std::optionalcomo esto:

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

    Y esto:

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

    Sin embargo, esto es tan complejo de usar (el uso de .value()o la .value_or()función es necesario para acceder al contenido en la std::optional<>estructura) y creo que un poco horrible. Si hay alguna sugerencia o posible mejora, ¡dímelo!

Respuestas

2 G.Sliepen Oct 30 2020 at 17:17

Considere convertir esto en un operator+()

Dado que la adición de elementos es una operación bastante común y natural (por ejemplo, el STL admite esto std::valarray), podría ser más intuitivo sobrecargar en operator+()lugar de crear una función element_wise_add(). Vea esta pregunta para una posible implementación que también se extiende fácilmente a otros operadores.

Otra ventaja de hacer esto operator+()es que se boost::multi_arraysatisface por sí mismo is_summable, por lo que sin agregar soporte explícito para contenedores recursivos, lo siguiente funcionaría:

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

Manejo de dimensiones de matriz no coincidentes

De hecho, no lo usaría std::optionalpara señalar errores para operaciones matemáticas. Veo dos formas:

  1. Asegúrese de que las dimensiones de la matriz devuelta sean los máximos de las dimensiones de las dos matrices de entrada. Así que si le agrego {{1}, {2}}al {{3, 5}}resultado será {{4, 5}, {2, 0}}.

  2. Lanzar un std::logic_error, asumiendo que es un error de programación agregar dos errores no coincidentes.

Evitando la repetición

Básicamente, está escribiendo lo mismo tres veces, la única variación es si las entradas son regulares boost::multi_arrays, sub_arrays o const_sub_arrays. Para evitar esto, desea hacer que los tipos de entrada tengan una plantilla, y para asegurarse de que solo coincidan boost::multi_arraycon los tipos relacionados, simplemente escriba un concepto para eso. Nuevamente, puede usar expresiones que ya usa para esto:

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

Esto probará que el tipo de xtiene una shape()función miembro y que boost::multi_arrayse puede copiar a partir de ella. Entonces solo escribe:

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

El inconveniente aquí es que toda la input1matriz se copia outputsin una buena razón, excepto que no veo cómo construir una nueva de manera multi_arraymás eficiente sin entrar en el infierno de tipos que viene con boost::multi_array. Quizás esto pueda subcontratarse a otra función de plantilla.

También notará que cambié la forma en que ise determina el tipo de : yo uso decltype(), pero como eso mantiene el const-ness, tengo que descartarlo. Hay varias formas de hacerlo, utilicé el +truco unario aquí.

Manejo de más combinaciones

¿Qué pasa si quiero agregar una sub_arraya una regular multi_arraydel mismo tamaño? Con su enfoque, tendría que manejar todas las combinaciones posibles por separado, pero con el ejemplo de función única que mostré, puede simplemente escribir:

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

Sin embargo, también considere que es posible que desee agregar una matriz de dobles a una matriz de enteros. Eso podría funcionar, si también escribe:

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

Aunque para que sea realmente útil, desea que el tipo de outputtenga un tipo de valor que coincida con el resultado de sumar los tipos de valor de las dos matrices de entrada.