Search code examples
c++boostmpiboost-multi-arrayboost-mpi

How to send subarray of 2d Boost.MultiArray via Boost.MPI?


I need to send reference to a subarray of 2d of Boost.MultiArray with Boost.MPI. Now I have the following code:

matrix_type::array_view<2>::type
                    current_process_batch = matrix[boost::indices[range(bias, finish_line)][range(0, width)]];
world.send(rank, BEGIN_TAG, current_process_batch);

But also I have following error trying to execute it:

/usr/local/include/boost/serialization/access.hpp:116:11: error: no member named 'serialize' in 'boost::detail::multi_array::multi_array_view<double, 2>'
        t.serialize(ar, file_version);

Can anyone help me to send it to another process?


Solution

  • You need to implement serialization.

    Here's a general multi_array<T. Dims> serializer:

    namespace boost::serialization {
        template <typename Ar, typename T, size_t Dims>
            void save(Ar& ar, boost::multi_array<T, Dims> const& ma, unsigned /*version*/) {
                std::array<int, Dims> shape;
                std::copy_n(ma.shape(), Dims, shape.begin());
                ar & make_nvp("shape", shape)
                   & make_nvp("data", make_array(ma.data(), ma.num_elements()));
            }
    
        template <typename Ar, typename T, size_t Dims>
            void load(Ar& ar, boost::multi_array<T, Dims>& ma, unsigned /*version*/) {
                std::array<int, Dims> shape;
                ar & make_nvp("shape", shape);
    
                ma.resize(shape);
                ar & make_nvp("data", make_array(const_cast<T*>(ma.data()), ma.num_elements()));
            }
    
        template <typename Ar, typename T, size_t Dims>
            void serialize(Ar& ar, boost::multi_array<T, Dims>& ma, unsigned version) {
                split_free(ar, ma, version);
            }
    }
    

    See it Live On Coliru

    auto make_multi_array() {
        boost::multi_array<int, 2> ma(boost::extents[7][4]);
        std::iota(ma.data(), ma.data() + ma.num_elements(), 10);
        return ma;
    }
    
    int main() {
        auto const original = make_multi_array();
    
        {
            std::ofstream os("array.txt");
            boost::archive::text_oarchive oa(os);
    
            oa << original;
        }
    
        std::cout << std::ifstream("array.txt").rdbuf() << "\n";
    
        {
            std::ifstream is("array.txt");
            boost::archive::text_iarchive ia(is);
    
            boost::multi_array<int, 2> restored;
            ia >> restored;
    
            assert(restored == original);
        }
    }
    

    Prints

    22 serialization::archive 17 0 0 0 0 2 7 4 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
    

    UPDATE: VIEWS

    Mmm. Already accepted? Noticing late that I didn't actually read your question that well. You wanted to serialize a view. You can't, really. You can't "deserialize a view".

    The most you can do is deserialize INTO a view.

    Note also that

    • serializing the whole array could be (much) more efficient if the size difference isn't huge

    • the view types are not public interface for the library, i.e. it may break in future versions of Boost. If in doubt, serialize the arrays themselves

    Here's some more machinery:

    • adds support for detail::multi_array::multi_array_view<...> in the sense that it can deserialize INTO an existing view of matching shape only

    • adds support for detail::multi_array::const_multi_array_view<...> only for serializing. Said view can be deserialized into a non-const view only, for obvious reasons.

    • extends the demo with a sub-view that patches the initial array after serializing, so we can verify that reading the patch into an equivalent view of the restored array has the same effect as on the original (after checking that the patch created the expected mismatch 20 != 999).

    Live On Coliru

    #include <boost/archive/text_oarchive.hpp>
    #include <boost/archive/text_iarchive.hpp>
    #include <boost/serialization/array.hpp>
    #include <boost/serialization/nvp.hpp>
    #include <boost/serialization/array_wrapper.hpp>
    #include <boost/multi_array.hpp>
    #include <iostream>
    #include <fstream>
    
    namespace boost::serialization {
        template <typename Ar, typename T, size_t Dims>
            void save(Ar& ar, boost::detail::multi_array::const_multi_array_view<T, Dims> const& ma, unsigned /*version*/) {
                std::array<int, Dims> shape;
                std::copy_n(ma.shape(), Dims, shape.begin());
                ar & make_nvp("shape", shape);
    
                for (auto i = 0; i < shape[0]; ++i) {
                    for (auto j = 0; j < shape[1]; ++j) {
                        ar & make_nvp("item", ma[i][j]);
                    }
                }
            }
    
        template <typename Ar, typename T, size_t Dims>
            void load(Ar&, boost::detail::multi_array::const_multi_array_view<T, Dims>&, unsigned) {
                static_assert(not Ar::is_loading::value, "const_multi_array_view immutable");
            }
    
        template <typename Ar, typename T, size_t Dims>
            void serialize(Ar& ar, boost::detail::multi_array::const_multi_array_view<T, Dims>& ma, unsigned version) {
                split_free(ar, ma, version);
            }
    
        template <typename Ar, typename T, size_t Dims>
            void save(Ar& ar, boost::detail::multi_array::multi_array_view<T, Dims> const& ma, unsigned /*version*/) {
                std::array<int, Dims> shape;
                std::copy_n(ma.shape(), Dims, shape.begin());
                ar & make_nvp("shape", shape);
    
                for (auto i = 0; i < shape[0]; ++i) {
                    for (auto j = 0; j < shape[1]; ++j) {
                        ar & make_nvp("item", ma[i][j]);
                    }
                }
            }
    
        template <typename Ar, typename T, size_t Dims>
            void load(Ar& ar, boost::detail::multi_array::multi_array_view<T, Dims>& ma, unsigned /*version*/) {
                std::array<int, Dims> shape;
                ar & make_nvp("shape", shape);
    
                if (!std::equal(begin(shape), end(shape), ma.shape()))
                    throw std::logic_error("multi_array_view shape mismatch");
    
                for (auto i = 0; i < shape[0]; ++i) {
                    for (auto j = 0; j < shape[1]; ++j) {
                        ar & make_nvp("item", ma[i][j]);
                    }
                }
            }
    
        template <typename Ar, typename T, size_t Dims>
            void serialize(Ar& ar, boost::detail::multi_array::multi_array_view<T, Dims>& ma, unsigned version) {
                split_free(ar, ma, version);
            }
    
        template <typename Ar, typename T, size_t Dims>
            void save(Ar& ar, boost::multi_array<T, Dims> const& ma, unsigned /*version*/) {
                std::array<int, Dims> shape;
                std::copy_n(ma.shape(), Dims, shape.begin());
                ar & make_nvp("shape", shape)
                   & make_nvp("data", make_array(ma.data(), ma.num_elements()));
            }
    
        template <typename Ar, typename T, size_t Dims>
            void load(Ar& ar, boost::multi_array<T, Dims>& ma, unsigned /*version*/) {
                std::array<int, Dims> shape;
                ar & make_nvp("shape", shape);
    
                ma.resize(shape);
                ar & make_nvp("data", make_array(const_cast<T*>(ma.data()), ma.num_elements()));
            }
    
        template <typename Ar, typename T, size_t Dims>
            void serialize(Ar& ar, boost::multi_array<T, Dims>& ma, unsigned version) {
                split_free(ar, ma, version);
            }
    }
    
    auto make_multi_array() {
        boost::multi_array<int, 2> ma(boost::extents[7][4]);
        std::iota(ma.data(), ma.data() + ma.num_elements(), 10);
        return ma;
    }
    
    int main() {
        using range = boost::multi_array_types::index_range;
        auto original = make_multi_array();
        auto const SLICE = boost::indices[range(1,3)][range(1,3)];
    
        {
            std::ofstream os("array.txt");
            boost::archive::text_oarchive oa(os);
    
            // only serialize initial array:
            oa << original;
    
            // modify through a sub-view
            auto sub = original[SLICE];
            original[2][2] = 999;
    
            // serialize the patch
            oa << sub;
    
            auto const& const_original = original;
            auto const_sub = const_original[SLICE];
            oa << const_sub; // compiles
        }
    
        std::cout << std::ifstream("array.txt").rdbuf() << "\n";
    
        {
            std::ifstream is("array.txt");
            boost::archive::text_iarchive ia(is);
    
            // only deserialize initial array:
            boost::multi_array<int, 2> restored;
            ia >> restored;
    
            assert(restored != original); // entry overwritten with 999
    
            assert(restored.num_elements() == original.num_elements());
            auto [o_2_2, r_2_2] = std::mismatch(
                    original.data(), original.data()+original.num_elements(),
                    restored.data());
    
            assert(*o_2_2 == 999);
            assert(*r_2_2 == 20);
    
            // now patch in the sub array at the same sub-view:
            auto sub = restored[SLICE];
            ia >> sub;
    
            // now everything is equal
            assert(restored == original); // now matches!
            assert(*r_2_2 == 999);
    
            auto const& const_restored = restored;
            auto const_sub = const_restored[SLICE];
            //ia >> const_sub; // WON'T COMPILE
        }
    }