Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add .bcast() #192

Merged
merged 70 commits into from
Jun 24, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
4f8b20d
Rework the Span<> class, add SingleElementModifyableBuffer, fix resiz…
Mar 25, 2022
69f7a9c
Add the collective operation broadcast (bcast).
Mar 25, 2022
8f32da0
Merge branch 'main' into feature-bcast
Mar 30, 2022
9b82c17
Move a const to the east side
Mar 31, 2022
2f83c1a
Clean up code
Apr 4, 2022
4e9d8bc
Let bcast()'s test run on a single core.
Apr 4, 2022
8cd3f4a
Add @todo's to bcast()
Apr 4, 2022
a7ff782
Merge branch 'main' into feature-bcast
Apr 4, 2022
c68cb6f
Re-add the inclusion of communicator.hpp into gather.hpp
Apr 4, 2022
0e429af
Re-order MPI-using unit tests
Apr 4, 2022
4d2df90
Adjust bcast() to use the updated MPIResult()
Apr 4, 2022
d68d556
Apply the new .clang-format to bcast() files.
Apr 4, 2022
dc3a542
Remove recursive include
Apr 4, 2022
22d3ccc
Add two useful overloads for Communicator::is_root()
Apr 4, 2022
cc282c0
Restructure KASSERTs in bcast() to avoid the shortcomings of missing …
Apr 4, 2022
a0dcd36
Add two useful overloads for Communicator::is_root()
Apr 4, 2022
837d7b2
Add unit tests for Communicator::is_root()
Apr 4, 2022
d62a532
Merge branch 'feature-communicator-is-root-overloads' into feature-bcast
Apr 4, 2022
972239f
Merge branch 'main' into feature-bcast
Apr 4, 2022
7445366
Merge branch 'main' into feature-bcast
lukashuebner May 12, 2022
6dae35b
Apply clang-format changes
lukashuebner May 12, 2022
64d87e4
Merge branch 'feature-bcast' of github.com:KIT-ITI10/kamping into fea…
lukashuebner May 12, 2022
7db0be9
squash this
lukashuebner May 16, 2022
0b1f05e
sqash this in rebase
lukashuebner May 17, 2022
75f12ca
Add MPICommpincator::is_same_on_all_pes(...) helper
lukashuebner May 17, 2022
649b3df
Merge branch 'feature-is_same_on_all_pes' into feature-bcast
lukashuebner May 17, 2022
73ef1f3
Take parameters by value
lukashuebner May 31, 2022
942b936
Merge branch 'feature-bcast' of github.com:KIT-ITI10/kamping into fea…
lukashuebner Jun 14, 2022
e451486
Merge branch 'main' into feature-bcast
lukashuebner Jun 14, 2022
462c8da
Add bcast to the Communicator
lukashuebner Jun 14, 2022
712339e
Update documentation on Communicator::bcast
lukashuebner Jun 14, 2022
4245e55
Add a parameter factory for send_recv_count
lukashuebner Jun 14, 2022
55d3465
Rewrite Communicator::bcast
lukashuebner Jun 14, 2022
1b1f566
Rewrite the tests for Communicator::bcast
lukashuebner Jun 14, 2022
8697408
Apply clang-format
lukashuebner Jun 14, 2022
0509ad2
Revert extern/gtest-mpi-listener/gtest-mpi-listener.hpp
lukashuebner Jun 14, 2022
ba1623e
Remove obsolete comments
lukashuebner Jun 14, 2022
1808c4a
Add two additional assertions in Communicator::bcast(...)
lukashuebner Jun 14, 2022
070ced0
Merge branch 'main' into feature-bcast
lukashuebner Jun 15, 2022
2c10f8b
fixup
lukashuebner Jun 15, 2022
d0ace1a
fixup
lukashuebner Jun 15, 2022
b5178f2
Remove duplicate tests for Communicator::is_same_on_all_ranks
lukashuebner Jun 15, 2022
63ef42d
Update comment
lukashuebner Jun 15, 2022
2a94723
blub
lukashuebner Jun 15, 2022
50ef63d
Add missing include to bcast.hpp
lukashuebner Jun 15, 2022
e9ca0cb
Revert send_recv_count's type in bcast() to size_t
lukashuebner Jun 15, 2022
3b6fa05
Update Communicator::bcast()'s documentation
lukashuebner Jun 15, 2022
9988bee
Remove superflous comments and rename variable for clarity in bcast()
lukashuebner Jun 15, 2022
f91e88b
Minor fixes requested by reviewer.
lukashuebner Jun 15, 2022
256280f
Merge branch 'main' into feature-bcast
lukashuebner Jun 15, 2022
29d6960
Use the new DataBuffer.get_single_element()
lukashuebner Jun 15, 2022
7e9ad69
Comments and additional assertions
lukashuebner Jun 21, 2022
47509a9
Resize send_recv_buf also on root rank
lukashuebner Jun 21, 2022
7971b24
Add test for bcast, testing a message of size 0 (should work)
lukashuebner Jun 21, 2022
4673779
Rename send_recv_count to recv_count in bcast()
lukashuebner Jun 21, 2022
5783cbe
Fix a bug where user provded recv_counts are ignored in bcast
lukashuebner Jun 21, 2022
9e11330
Add test for bcast: recv count and vector size differ
lukashuebner Jun 21, 2022
145f007
Add check that recv_count == 1 if a single element buffer is passed a…
lukashuebner Jun 21, 2022
362abd0
Enable recv_count_out in bcast
lukashuebner Jun 21, 2022
5f29ed8
Add test for partially transferred containers for bcast()
lukashuebner Jun 21, 2022
024fe11
Remove unneeded send_recv_count parameter.
lukashuebner Jun 21, 2022
140adfd
Remove unneeded send_recv_count parameter type.
lukashuebner Jun 21, 2022
afd4166
Incorporate feedback from Demian
lukashuebner Jun 22, 2022
fc546ee
Clean up
lukashuebner Jun 23, 2022
5baa5c4
Merge branch 'main' into feature-bcast
lukashuebner Jun 24, 2022
edacedd
Add some KASSERT_FAILS tests to Communicator::bcast(...)
lukashuebner Jun 24, 2022
0b99b71
Mark variable as [[maybe_unused]] in bcast test.
lukashuebner Jun 24, 2022
8eec8d0
Run clang-format
lukashuebner Jun 24, 2022
bd75106
.
lukashuebner Jun 24, 2022
2d4ab53
Reformulate comment
lukashuebner Jun 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions include/kamping/collectives/alltoall.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,34 +59,39 @@ class Alltoall : public CRTPHelper<Communicator, Alltoall> {
internal::has_parameter_type<internal::ParameterType::send_buf, Args...>(),
"Missing required parameter send_buf.");

auto& send_buf_param = internal::select_parameter_type<internal::ParameterType::send_buf>(args...);
auto send_buf = send_buf_param.get();
using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
MPI_Datatype mpi_send_type = mpi_datatype<send_value_type>();
const auto& send_buf = internal::select_parameter_type<internal::ParameterType::send_buf>(args...).get();
lukashuebner marked this conversation as resolved.
Show resolved Hide resolved
using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
using default_recv_value_type = std::remove_const_t<send_value_type>;
MPI_Datatype mpi_send_type = mpi_datatype<send_value_type>();

using default_recv_buf_type = decltype(kamping::recv_buf(NewContainer<std::vector<send_value_type>>{}));
using default_recv_buf_type = decltype(kamping::recv_buf(NewContainer<std::vector<default_recv_value_type>>{}));
auto&& recv_buf =
internal::select_parameter_type_or_default<internal::ParameterType::recv_buf, default_recv_buf_type>(
std::tuple(), args...);
using recv_value_type = typename std::remove_reference_t<decltype(recv_buf)>::value_type;
MPI_Datatype mpi_recv_type = mpi_datatype<recv_value_type>();

static_assert(
std::is_same_v<std::remove_const_t<send_value_type>, recv_value_type>,
"Types of send and receive buffers do not match.");
static_assert(!std::is_const_v<recv_value_type>, "The receive buffer must not have a const value_type.");
KASSERT(
mpi_send_type == mpi_recv_type, "The MPI receive type does not match the MPI send type.", assert::light);

// Get the send and receive counts
int send_count = throwing_cast<int>(send_buf.size / asserting_cast<size_t>(this->underlying().size()));
KASSERT(mpi_send_type == mpi_recv_type, "The specified receive type does not match the send type.");
int send_count = throwing_cast<int>(send_buf.size() / asserting_cast<size_t>(this->underlying().size()));

size_t recv_buf_size = send_buf.size;
size_t recv_buf_size = send_buf.size();
int recv_count = throwing_cast<int>(recv_buf_size / asserting_cast<size_t>(this->underlying().size()));
KASSERT(send_count == recv_count, assert::light);
recv_buf.resize(recv_buf_size);

// These KASSERTs are required to avoid a false warning from g++ in release mode
auto* send_buf_ptr = send_buf.ptr;
KASSERT(send_buf_ptr != nullptr);
auto* recv_buf_ptr = recv_buf.get_ptr(recv_buf_size);
KASSERT(recv_buf_ptr != nullptr);
KASSERT(send_buf.data() != nullptr, assert::light);
KASSERT(recv_buf.data() != nullptr, assert::light);

[[maybe_unused]] int err = MPI_Alltoall(
send_buf.ptr, send_count, mpi_send_type, recv_buf.get_ptr(recv_buf_size), recv_count, mpi_recv_type,
send_buf.data(), send_count, mpi_send_type, recv_buf.data(), recv_count, mpi_recv_type,
this->underlying().mpi_communicator());

THROW_IF_MPI_ERROR(err, MPI_Alltoall);
Expand Down
137 changes: 137 additions & 0 deletions include/kamping/collectives/bcast.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// This file is part of KaMPI.ng.
//
// Copyright 2022 The KaMPI.ng Authors
//
// KaMPI.ng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
// version. KaMPI.ng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
// for more details.
//
// You should have received a copy of the GNU Lesser General Public License along with KaMPI.ng. If not, see
// <https://www.gnu.org/licenses/>.

#pragma once

#include "kamping/checking_casts.hpp"
#include "kamping/mpi_datatype.hpp"
#include "kamping/mpi_function_wrapper_helpers.hpp"
#include "kamping/named_parameter_selection.hpp"
#include "kamping/parameter_factories.hpp"
#include "kamping/parameter_objects.hpp"
#include "kamping/parameter_type_definitions.hpp"
#include <mpi.h>

namespace kamping::internal {

/// @brief CRTP mixin class for \c MPI_Bcast.
///
/// This class is only to be used as a super class of kamping::Communicator
template <typename Communicator>
class Bcast : public CRTPHelper<Communicator, Bcast> {
public:
/// @brief Wrapper for \c MPI_Bcast
///
/// This wrapper for \c MPI_Bcast sends data from the root to all other ranks.
/// The following buffers are required:
/// - \ref kamping::send_buf() containing the data that is sent to the other ranks.
/// The following parameters are optional:
/// - \ref kamping::root() specifying an alternative root. If not present, the default root of the \c Communicator
/// is used, see root().
/// - \ref kamping::recv_buf() containing a buffer for the output. Afterwards, at all other ranks, this buffer will
/// contain the data from the root send buffer.
/// @todo Describe what happens at the root
/// @tparam Args Automatically deducted template parameters.
/// @param args All required and any number of the optional buffers described above.
/// @return Result type wrapping the output buffer if not specified as input parameter.
template <typename... Args>
auto bcast(Args&&... args) {
static_assert(
all_parameters_are_rvalues<Args...>,
"All parameters have to be passed in as rvalue references, meaning that you must not hold a variable "
"returned by the named parameter helper functions like recv_buf().");

// Check and get all parameters.

// The parameter send_recv_buf() is required on all processes.
static_assert(
internal::has_parameter_type<internal::ParameterType::send_recv_buf, Args...>(),
"Missing required parameter send_recv_buf.");
lukashuebner marked this conversation as resolved.
Show resolved Hide resolved

const auto& send_recv_buf =
internal::select_parameter_type<internal::ParameterType::send_recv_buf>(args...).get();
using value_type = typename std::remove_reference_t<decltype(send_recv_buf)>::value_type;

auto&& root = internal::select_parameter_type_or_default<internal::ParameterType::root, internal::Root>(
std::tuple(this->underlying().root()), args...);

auto mpi_value_type = mpi_datatype<value_type>();

// Conduct some validity check on the parmeters.
KASSERT(this->underlying().is_valid_rank(root.rank()), "Invalid rank as root.", assert::light);

KASSERT(
this->underlying().rank() != root.rank() || send_recv_buf.size() > 0,
"The send_recv_buf() on root is empty.", assert::light);

KASSERT(
recv_buf_large_enough_on_all_processes(send_recv_buf, root.rank()),
"The receive buffer is too small on at least one rank.", assert::light_communication);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we want an execution path where kamping resizes the buffer so that it is large enough?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What should the behavior be in the following cases:

  • send_recv_buf.size() == 1 on root and == 0 on all other ranks, send_counts not specified: Assume a default size of 1? Broadcast the size? -> doubling the number of broadcasts might be unexpected here; depend on if the ominous SingleElementBuffer is specified?
  • send_counts explicitly specified: resize the buffer on all but the root process? On all including the root process? What, if the send_recv_buf() on root is const?
  • send_counts not specified and not case 1: Broadcast the size of the send buffer, resize the receive buffers, broadcast the data?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @DanielSeemaier does something like this in #170. Bcast and Scatter should probably use the same mechanisms.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Postponed, see #170 .

We could do that in a future PR when we see what's best (in particular this might change with our ongoing discussion on reducing syntax overhead anyways)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm I still don't really like the way it's done here. We never expected the size of the receive buffers to be set by the user and I would like to keep it that way.

Copy link
Member

@Hespian Hespian May 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would force the user to write different code for root and non-root ranks. Probably not ideal

What would you think about send_recv_buf being optional?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Year, I don't like splitting up the send_recv_buf into a send_buf and recv_buf, too. But iirc someone argued sternly for it, as it'll unify the interface.

Copy link
Member Author

@lukashuebner lukashuebner May 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this then? :-)

if I'm the root:
    assert, that I have a send_recv_buf
    if I have no recv_count specified:
       // assume, that the other ranks don't know how much data to receive
       broadcast the amount of data to receive
    else:
        // assume that all other ranks also know about their recv_count
        Check, that the recv_counts are the same on all ranks
    broadcast the data
else: // if I'm not the root:
    if I do not have a send_recv_buf, allocate one
    if I have a recv_count:
        resize my send_recv_buf
        assert, that all ranks have the same recv_count (eq. to the one provided to the root)
    else:
        receive the broadcast of the amount of data to receive
        resize my send_recv_buf
    receive the broadcast of the data

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than that resizing the send_recv_buf should happen after receiving the recv_count, I think that should work fine. Not sure I remember the part of the discussion about a unified interface. @kurpicz ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I looked at the old protocols and we already voted in favor of "(Bcast) SendRecv-Buffer auf allen ranks oder wenn SendRecv-Buffer nicht gegeben ist, ist man kein Root und man bekommt das Recv Result zurück."


// Perform the broadcast.
// int size = 0;
// void* buffer = nullptr;
// if constexpr (internal:: ))

// The error code is unused if KTHROW is removed at compile time.
// KASSERT(size != 0, assert::light);
// KASSERT(buffer != nullptr, assert::light);
[[maybe_unused]] int err = MPI_Bcast(
send_recv_buf.data(), // buffer*
asserting_cast<int>(send_recv_buf.size()), // count
mpi_value_type, // datatype
root.rank(), // root
this->underlying().mpi_communicator() // MPI_Comm comm
);
THROW_IF_MPI_ERROR(err, MPI_Bcast);

return MPIResult(
std::move(send_recv_buf), internal::BufferCategoryNotUsed{}, internal::BufferCategoryNotUsed{},
internal::BufferCategoryNotUsed{});
}

protected:
Bcast() {}

private:
/// @brief Checks if the receive buffer is large enough to receive all elements on all ranks.
///
/// Broadcasts the size of the send buffer (which is equal to the recv_buf) from the root rank,
/// performs local comparison and collects the result using an allreduce.
/// @param send_recv_buf The send buffer on root, the receive buffer on all other ranks.
/// @param root The rank of the root process.
template <typename RecvBuf>
bool recv_buf_large_enough_on_all_processes(RecvBuf const& send_recv_buf, int const root) const {
uint64_t size = send_recv_buf.size();
MPI_Bcast(
&size, // src/dest buffer
1, // size
mpi_datatype<decltype(size)>(), // datatype
root, // root
this->underlying().mpi_communicator() // communicator
);
bool const local_buffer_large_enough = size <= send_recv_buf.size();
bool every_buffer_large_enough;
MPI_Allreduce(
&local_buffer_large_enough, // src buffer
&every_buffer_large_enough, // dest buffer
1, // count
mpi_datatype<bool>(), // datatype
MPI_LAND, // operation
this->underlying().mpi_communicator() // communicator
);
return every_buffer_large_enough;
}
lukashuebner marked this conversation as resolved.
Show resolved Hide resolved
}; // class Bcast

} // namespace kamping::internal
14 changes: 8 additions & 6 deletions include/kamping/collectives/reduce.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ class Reduce : public CRTPHelper<Communicator, Reduce> {
auto&& root = internal::select_parameter_type_or_default<internal::ParameterType::root, internal::Root>(
std::tuple(this->underlying().root()), args...);

auto& send_buf_param = internal::select_parameter_type<internal::ParameterType::send_buf>(args...);
auto send_buf = send_buf_param.get();
const auto& send_buf = internal::select_parameter_type<internal::ParameterType::send_buf>(args...).get();
using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
using default_recv_value_type = std::remove_const_t<send_value_type>;

using default_recv_buf_type = decltype(kamping::recv_buf(NewContainer<std::vector<send_value_type>>{}));
using default_recv_buf_type = decltype(kamping::recv_buf(NewContainer<std::vector<default_recv_value_type>>{}));
auto&& recv_buf =
internal::select_parameter_type_or_default<internal::ParameterType::recv_buf, default_recv_buf_type>(
std::tuple(), args...);
Expand All @@ -81,17 +81,19 @@ class Reduce : public CRTPHelper<Communicator, Reduce> {

// Check parameters
static_assert(
std::is_same_v<send_value_type, recv_value_type>, "Types of send and receive buffers do not match.");
std::is_same_v<std::remove_const_t<send_value_type>, recv_value_type>,
"Types of send and receive buffers do not match.");
MPI_Datatype type = mpi_datatype<send_value_type>();

KASSERT(this->underlying().is_valid_rank(root.rank()), "The provided root rank is invalid.");

send_value_type* recv_buf_ptr = nullptr;
if (this->underlying().rank() == root.rank()) {
recv_buf_ptr = recv_buf.get_ptr(send_buf.size);
recv_buf.resize(send_buf.size());
recv_buf_ptr = recv_buf.data();
}
[[maybe_unused]] int err = MPI_Reduce(
send_buf.ptr, recv_buf_ptr, asserting_cast<int>(send_buf.size), type, operation.op(), root.rank(),
send_buf.data(), recv_buf_ptr, asserting_cast<int>(send_buf.size()), type, operation.op(), root.rank(),
this->underlying().mpi_communicator());

THROW_IF_MPI_ERROR(err, MPI_Reduce);
Expand Down
5 changes: 4 additions & 1 deletion include/kamping/communicator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@

#include "error_handling.hpp"
#include "kamping/collectives/alltoall.hpp"
#include "kamping/collectives/bcast.hpp"
#include "kamping/collectives/reduce.hpp"
#include "kamping/kassert.hpp"

namespace kamping {

/// @brief Wrapper for MPI communicator providing access to \ref rank() and \ref size() of the communicator. The \ref
/// Communicator is also access point to all MPI communications provided by KaMPI.ng.
class Communicator : public internal::Alltoall<Communicator>, public internal::Reduce<Communicator> {
class Communicator : public internal::Alltoall<Communicator>,
public internal::Reduce<Communicator>,
public internal::Bcast<Communicator> {
public:
/// @brief Default constructor not specifying any MPI communicator and using \c MPI_COMM_WORLD by default.
Communicator() : Communicator(MPI_COMM_WORLD) {}
Expand Down
44 changes: 41 additions & 3 deletions include/kamping/parameter_factories.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,44 @@ auto send_buf(const Data& data) {
}
}


/// @brief Generates a buffer wrapper encapsulating a buffer used for sending or receiving based on this processes rank
/// and the root() of the operation.
///
/// For example when used as parameter to \c bcast, all processes provide this buffer; on the root process it
/// acts as the send buffer, on all other processes as the receive buffer.
///
/// If the underlying container provides \c data(), it is assumed that it is a container and all elements in the
/// container are considered for the operation. In this case, the container has to provide a \c size() member functions
/// and expose the contained \c value_type. If no \c data() member function exists, a single element is wrapped in the
/// send_recv buffer. For receiving, the buffer is automatically resized to the correct size and thus has to provide a
/// \c resize() method.
///
/// @tparam Data Data type representing the element(s) to send/receive.
/// @param data Data (either a container which contains the elements or the element directly) to send or the buffer to
/// receive to.
/// @return Object referring to the storage containing the data elements to send / the received elements.
template <typename Data>
auto send_recv_buf(Data& data) {
if constexpr (internal::has_data_member_v<Data>) {
return internal::UserAllocatedContainerBasedBuffer<Data, internal::ParameterType::send_recv_buf>(data);
} else {
return internal::SingleElementModifyableBuffer<Data, internal::ParameterType::send_recv_buf>(data);
}
}

/// @brief Generates buffer wrapper based on a container for the receive buffer, i.e. the underlying storage
/// will contained the received elements when the \c MPI call has been completed.
/// The storage is allocated by the library and encapsulated in a container of type Container.
/// The underlying container must provide a \c data(), \c resize() and \c size() member function and expose the
/// contained \c value_type
/// @tparam Container Container type which contains the received elements.
/// @return Object referring to the storage containing the send displacements.
template <typename Container>
auto send_recv_buf(NewContainer<Container>&&) {
return internal::LibAllocatedContainerBasedBuffer<Container, internal::ParameterType::send_recv_buf>();
}

/// @brief Generates buffer wrapper based on a container for the send counts, i.e. the underlying storage must contain
/// the send counts to each relevant PE.
///
Expand Down Expand Up @@ -167,13 +205,13 @@ auto recv_counts_out(Container& container) {
return internal::UserAllocatedContainerBasedBuffer<Container, internal::ParameterType::recv_counts>(container);
}

///@brief Generates buffer wrapper based on a container for the receive counts, i.e. the underlying storage
/// @brief Generates buffer wrapper based on a container for the receive counts, i.e. the underlying storage
/// will contained the receive counts when the \c MPI call has been completed.
/// The storage is allocated by the library and encapsulated in a container of type Container.
/// The underlying container must provide a \c data(), \c resize() and \c size() member function and expose the
/// contained \c value_type
///@tparam Container Container type which contains the send displacements.
///@return Object referring to the storage containing the receive counts.
/// @tparam Container Container type which contains the send displacements.
/// @return Object referring to the storage containing the receive counts.
template <typename Container>
auto recv_counts_out(NewContainer<Container>&&) {
return internal::LibAllocatedContainerBasedBuffer<Container, internal::ParameterType::recv_counts>();
Expand Down
Loading