How-To Guides

How to Fill Elements of an Array with a Constant Value?

Use a standard fill algorithm, std::ranges::fill for example. If you have an array:

#include<ranges>
...
  multi::array<double, 2> arr({4, 4});
  ...
  std::ranges::fill(arr.elements(), 1);
...

or a subarray:

...
  std::ranges::fill(arr({1, 3}, {1, 3}).elements(), 2);

If you don’t have access to the ranges library (C++20) or if you want to control parallelism use std::fill:

#include<algorithm>
#include<execution>
...
  std::fill(std::execution::par, arr.elements().begin(), arr.elements().end(), 1);
...

If the data is in the GPU, you can use Thrust algorithms (which detect the memory space automatically, and are parallel by default):

#include <multi/adaptors/thrust.hpp>
#include <thrust/fill.h>
...
  thrust::fill(arr.elements().begin(), arr.elements().end(), 1);
...

or more explicitly as,

#include <thrust/execution_policy.h>
...
  thrust::fill(thrust::device, arr.elements().begin(), arr.elements().end(), 1);
...

To create a new array with a constant value, it is easier to directly construct it:

  multi::array<double, 2> arr({4, 4}, 1);  // generate a 4x4 array; all its elements equal to 1

How to Reduce the Rows of an Array?

Each element of a 2D array is a row (a 1D subarray), so a reduction of every row is a transform over the rows, where each row is reduced by a standard reduction algorithm, std::reduce for example. The result is a 1D array with one element per row:

#include<algorithm>
#include<numeric>
...
  multi::array<double, 2> arr = {
    {1.0, 2.0, 3.0},
    {4.0, 5.0, 6.0},
  };

  multi::array<double, 1> row_sums(arr.size());

  std::transform(
    arr.begin(), arr.end(), row_sums.begin(),
    [](auto const& row) { return std::reduce(row.begin(), row.end()); }
  );
  // row_sums is {6.0, 15.0}
...

Other reductions follow the same pattern, for example the maximum of each row:

  std::transform(
    arr.begin(), arr.end(), row_max.begin(),
    [](auto const& row) { return *std::max_element(row.begin(), row.end()); }
  );

To reduce the columns instead, reduce the rows of the transposed view, which doesn’t copy any data:

  multi::array<double, 1> col_sums(arr.size(1));

  std::transform(
    (~arr).begin(), (~arr).end(), col_sums.begin(),
    [](auto const& col) { return std::reduce(col.begin(), col.end()); }
  );
  // col_sums is {5.0, 7.0, 9.0}

To partially control parallelism, use an execution policy on the either the outer or the inner algorithm, since each row is independent:

#include<execution>
...
  std::transform(
    std::execution::par, arr.begin(), arr.end(), row_sums.begin(),
    [](auto const& row) { return std::reduce(row.begin(), row.end()); }
  );

Be aware that following this pattern limits full parallelization and doesn’t take into account memory access patterns. Other more advanced algorithms such as Reduce-by-key (e.g. implemented as thurst::reduce_by_key) can take advantage of two-dimensionally parallelism.

How to Serialize and Deserialize an Array?

To save an array, write it to an output archive; to load it, read it into an array (of the same dimensionality and element type) from an input archive. Loading resizes the destination array to the saved extents.

Binary archives are the fastest and most compact:

#include<multi/array.hpp>

#include<boost/archive/binary_iarchive.hpp>
#include<boost/archive/binary_oarchive.hpp>

#include<fstream>
...
  multi::array<double, 2> const A({3, 4}, 1.5);

  {
    std::ofstream ofs("file.bin", std::ios::binary);
    boost::archive::binary_oarchive oarchive(ofs);
    oarchive << A;
  }

  multi::array<double, 2> B;
  {
    std::ifstream ifs("file.bin", std::ios::binary);
    boost::archive::binary_iarchive iarchive(ifs);
    iarchive >> B;
  }

  assert(A == B);

See the Serialization section for more details and a performance comparison between archive formats.