Funkcja element_wise_add dla Boost.MultiArray w C ++

Oct 30 2020

To jest pytanie uzupełniające do funkcji sumowania funkcji Boost.MultiArray w C ++ . Oprócz sumowania wszystkich elementów staram się skupić tutaj na operacji elementarnej. Główną ideą tego pytania jest zaimplementowanie element_wise_addfunkcji Boost.MultiArray. Celem tej element_wise_addfunkcji jest wykonanie operacji dodawania elementów w ciągu dwóch boost::multi_arraysekund. Funkcja element_wise_addma dwa parametry wejściowe input1i input2dla operacji dodawania według elementów, a typ zwracany to wynik z podziałem na elementy.

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

Zastosowana is_summablekoncepcja:

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

Test tej element_wise_addfunkcji jest następujący.

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

Wszelkie sugestie są mile widziane.

  • Które pytanie jest kontynuacją?

    Funkcja sumowania dla Boost.MultiArray w C ++

  • Jakie zmiany wprowadzono w kodzie od ostatniego pytania?

    Poprzednie pytanie dotyczy implementacji funkcji sumowania dla Boost.MultiArray, a główną ideą tego pytania jest implementacja element_wise_addfunkcji dla Boost.MultiArray.

  • Dlaczego proszono o nową recenzję?

    Podobny korzystanie z trzech typów przeciążyć funkcję boost::multi_array, boost::detail::multi_array::sub_arrayi boost::detail::multi_array::const_sub_arraypojawia się ponownie. Wiem, że może to nie jest dobry pomysł. Jednak nie przychodzi mi do głowy żaden lepszy sposób. Co więcej, obsługa wyjątków dla sytuacji „kształt jest inny” nie jest idealna. W przypadku boost::detail::multi_array::sub_arrayi boost::detail::multi_array::const_sub_arraynie jestem pewien, co należy zwrócić z powrotem. Próbowałem kiedyś czegoś std::optionaltakiego:

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

    I to:

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

    Jest to jednak tak skomplikowane w użyciu (użycie .value()lub .value_or()funkcja jest potrzebne, aby uzyskać dostęp do zawartości w std::optional<>strukturze) i wydaje mi się trochę okropne. Jeśli są jakieś sugestie lub możliwe ulepszenia, proszę, powiedz mi!

Odpowiedzi

2 G.Sliepen Oct 30 2020 at 17:17

Rozważ przekształcenie tego w plik operator+()

Ponieważ dodawanie elementów jest dość powszechną i naturalną operacją (na przykład STL obsługuje to std::valarray), przeciążenie operator+()zamiast tworzenia funkcji może być bardziej intuicyjne element_wise_add(). Zobacz to pytanie, aby zapoznać się z możliwą implementacją, która również łatwo rozszerza to na innych operatorów.

Kolejną zaletą zrobienia tego an operator+()jest to, że boost::multi_arraysam spełnia is_summablewymagania, więc bez dodawania jawnej obsługi kontenerów rekurencyjnych, zadziałałyby następujące działania:

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

Obsługa niezgodnych wymiarów tablicy

Rzeczywiście nie użyłbym std::optionaldo sygnalizowania błędów w operacjach matematycznych. Widzę dwa sposoby:

  1. Upewnij się, że wymiary zwracanej tablicy są maksymalnymi wymiarami dwóch tablic wejściowych. Więc jeśli dodam {{1}, {2}}do {{3, 5}}wyniku będzie {{4, 5}, {2, 0}}.

  2. Wrzuć std::logic_error, zakładając, że dodanie dwóch niedopasowanych błędów jest błędem programowania.

Unikanie powtórzeń

Zasadniczo piszesz to samo trzykrotnie, jedyną zmianą jest to, czy dane wejściowe są regularne boost::multi_arrays, sub_arrays czy const_sub_arrays. Aby tego uniknąć, chcesz utworzyć szablony typów danych wejściowych i upewnić się, że pasują one tylko do boost::multi_arraytypów pokrewnych, po prostu napisz koncepcję tego. Ponownie możesz użyć wyrażeń, których już używasz do tego:

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

Spowoduje to sprawdzenie, czy typ xma shape()funkcję składową i czy boost::multi_arraymożna z niej skopiować element . Następnie po prostu napisz:

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

Wadą jest to, że cała input1tablica jest kopiowana outputbez dobrego powodu, z wyjątkiem tego, że nie widzę, jak skonstruować nową multi_arraybardziej wydajnie bez wchodzenia w piekło typu, które się z tym wiąże boost::multi_array. Być może można to zlecić innej funkcji szablonu.

Zauważysz również, że zmieniłem sposób iokreślania typu dla : używam decltype(), ale ponieważ to zachowuje const-ness, muszę to odrzucić. Istnieją różne sposoby, aby to zrobić, użyłem jednoargumentowy +sztuczki tutaj.

Obsługa większej liczby kombinacji

A co jeśli chcę dodać sub_arraydo regularnego multi_arrayw tym samym rozmiarze? Przy takim podejściu musiałbyś osobno obsłużyć wszystkie możliwe kombinacje, ale w przykładzie pojedynczej funkcji, który pokazałem, możesz po prostu napisać:

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

Jednak weź pod uwagę również, że możesz chcieć dodać tablicę podwójnych do tablicy liczb całkowitych. To mogłoby zadziałać, jeśli napiszesz również:

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

Chociaż, aby był naprawdę użyteczny, chcesz, aby typ outputmiał typ wartości, który pasuje do wyniku dodania typów wartości dwóch tablic wejściowych.