KaMPIng 0.1.0
(Near) zero-overhead C++ MPI bindings.
Loading...
Searching...
No Matches
exscan.hpp
1// This file is part of KaMPIng.
2//
3// Copyright 2022-2024 The KaMPIng Authors
4//
5// KaMPIng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
7// version. KaMPIng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
8// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
9// for more details.
10//
11// You should have received a copy of the GNU Lesser General Public License along with KaMPIng. If not, see
12// <https://www.gnu.org/licenses/>.
13
14#pragma once
15
16#include <kassert/kassert.hpp>
17#include <mpi.h>
18
19#include "kamping/assertion_levels.hpp"
21#include "kamping/collectives/collectives_helpers.hpp"
22#include "kamping/comm_helper/is_same_on_all_ranks.hpp"
23#include "kamping/communicator.hpp"
31#include "kamping/result.hpp"
32
33/// @addtogroup kamping_collectives
34/// @{
35
36/// @brief Wrapper for \c MPI_Exscan.
37///
38/// \c exscan() wraps \c MPI_Exscan, which is used to perform an exclusive prefix reduction on data distributed across
39/// the calling processes. \c exscan() returns in the \c recv_buf of the process with rank \f$i > 0\f$, the
40/// reduction (calculated according to the function \c op) of the values in the \c send_bufs of processes with ranks
41/// \f$0, \ldots, i - 1\f$ (i.e. excluding i as opposed to \c scan()). The value of the \c recv_buf on rank 0 is set to
42/// the value of \c values_on_rank_0 if provided. If \c values_on_rank_0 is not provided and \c op is a built-in
43/// operation on the data-type used, the value on rank 0 is set to the identity of that operation. If the operation is
44/// not built-in on the data-type used and no \c values_on_rank_0() is provided, the contents of \c recv_buf on rank
45/// 0 are undefined.
46///
47/// The following parameters are required:
48/// - \ref kamping::send_buf() containing the data for which to perform the exclusive scan. This buffer has to be the
49/// same size at each rank.
50/// - \ref kamping::op() the operation to apply to the input.
51///
52/// The following parameters are optional:
53/// - \ref kamping::recv_buf() containing a buffer for the output. A buffer size of at least `send_recv_count` elements
54/// is required.
55///
56/// - \ref kamping::send_recv_count() containing the number of elements to be processed in this operation. This
57/// parameter has to be the same at each rank. If omitted, the size of the send buffer will be used as send_recv_count.
58///
59/// - \ref kamping::send_recv_type() specifying the \c MPI datatype to use as data type in this operation. If omitted,
60/// the \c MPI datatype is derived automatically based on send_buf's underlying \c value_type. If the type is provided
61/// explicitly, the compatibility of the type and operation has to be ensured by the user.
62///
63/// - \ref kamping::values_on_rank_0() containing the value(s) that is/are returned in the \c recv_buf of rank 0. \c
64/// values_on_rank_0 must be a container of the same size as \c recv_buf or a single value (which will be reused for
65/// all elements of the \c recv_buf).
66///
67///
68/// In-place exscan is supported by providing `send_recv_buf()` instead of `send_buf()` and `recv_buf()`. For details
69/// on the in-place version, see \ref Communicator::exscan_inplace().
70///
71/// @tparam Args Automatically deduced template parameters.
72/// @param args All required and any number of the optional parameters described above.
73/// @return Result object wrapping the output parameters to be returned by value.
74///
75/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
76/// <hr>
77/// \include{doc} docs/resize_policy.dox
78template <
79 template <typename...>
80 typename DefaultContainerType,
81 template <typename, template <typename...> typename>
82 typename... Plugins>
83template <typename... Args>
85 using namespace kamping::internal;
87 if constexpr (inplace) {
88 return exscan_inplace(std::forward<Args>(args)...);
89 } else {
91 Args,
94 );
95
96 // Get the send buffer and deduce the send and recv value types.
97 auto const&& send_buf = select_parameter_type<ParameterType::send_buf>(args...).construct_buffer_or_rebind();
98 using send_value_type = typename std::remove_reference_t<decltype(send_buf)>::value_type;
99 KASSERT(
100 is_same_on_all_ranks(send_buf.size()),
101 "The send buffer has to be the same size on all ranks.",
103 );
104
105 // Deduce the recv buffer type and get (if provided) the recv buffer or allocate one (if not provided).
106 using default_recv_value_type = std::remove_const_t<send_value_type>;
109 auto&& recv_buf =
112
113 // Get the send_recv_type.
115 [[maybe_unused]] constexpr bool send_recv_type_is_in_param = !has_to_be_computed<decltype(send_recv_type)>;
116
117 // Get the send_recv count
121 default_send_recv_count_type>(std::tuple(), args...)
122 .construct_buffer_or_rebind();
123
125 if constexpr (do_compute_send_recv_count) {
126 send_recv_count.underlying() = asserting_cast<int>(send_buf.size());
127 }
128
129 KASSERT(
130 is_same_on_all_ranks(send_recv_count.get_single_element()),
131 "The send_recv_count has to be the same on all ranks.",
133 );
134
135 // Get the operation used for the reduction. The signature of the provided function is checked while building.
138
139 // Resize the recv buffer to the same size as the send buffer; get the pointer needed for the MPI call.
140 auto compute_required_recv_buf_size = [&]() {
141 return asserting_cast<size_t>(send_recv_count.get_single_element());
142 };
143 recv_buf.resize_if_requested(compute_required_recv_buf_size);
144 KASSERT(
145 // if the send_recv type is user provided, kamping cannot make any assumptions about the required size of
146 // the recv buffer
148 "Recv buffer is not large enough to hold all received elements.",
150 );
151
152 // Perform the MPI_Allreduce call and return.
153 [[maybe_unused]] int err = MPI_Exscan(
154 send_buf.data(), // sendbuf
155 recv_buf.data(), // recvbuf,
156 send_recv_count.get_single_element(), // count
157 send_recv_type.get_single_element(), // datatype,
158 operation.op(), // op
159 mpi_communicator() // communicator
160 );
161 this->mpi_error_hook(err, "MPI_Exscan");
162
163 // MPI_Exscan leaves the recv_buf on rank 0 in an undefined state. We set it to the value provided via
164 // values_on_rank_0() if given. If values_on_rank_0() is not given and the operation is a built-in operation on
165 // a
166 // built-in data-type, we set the value on rank 0 to the identity of that operation on that datatype (e.g. 0 for
167 // addition on integers).
168 if (rank() == 0) {
169 constexpr bool has_values_on_rank_0_param = has_parameter_type<ParameterType::values_on_rank_0, Args...>();
170 // We decided not to enforce having to provide values_on_rank_0() for a operation for which we cannot
171 // auto-deduce the identity, as this would introduce a parameter which is required in some situtations in
172 // KaMPIng, but never in MPI.
173 if constexpr (has_values_on_rank_0_param) {
174 auto const& values_on_rank_0_param =
175 select_parameter_type<ParameterType::values_on_rank_0>(args...).construct_buffer_or_rebind();
176 KASSERT(
177 // if the send_recv type is user provided, kamping cannot make any assumptions about the required
178 // size of the recv buffer
180 || values_on_rank_0_param.size() == asserting_cast<size_t>(send_recv_count.get_single_element())),
181 "on_rank_0 has to either be of size 1 or of the same size as the recv_buf.",
183 );
184 if (values_on_rank_0_param.size() == 1) {
185 std::fill_n(
186 recv_buf.data(),
187 asserting_cast<size_t>(send_recv_count.get_single_element()),
189 );
190 } else {
191 std::copy_n(values_on_rank_0_param.data(), values_on_rank_0_param.size(), recv_buf.data());
192 }
193 } else if constexpr (operation.is_builtin) {
194 std::fill_n(
195 recv_buf.data(),
196 asserting_cast<size_t>(send_recv_count.get_single_element()),
197 operation.identity()
198 );
199 }
200 }
201
202 return make_mpi_result<std::tuple<Args...>>(
203 std::move(recv_buf),
204 std::move(send_recv_count),
205 std::move(send_recv_type)
206 );
207 }
208}
209
210/// @brief Wrapper for the in-place version of \ref Communicator::exscan().
211///
212/// This variant must be called collectively by all ranks in the communicator. It is semantically equivalent to \ref
213/// Communicator::exscan(), but the input buffer is used as the output buffer. This means that the input buffer is
214/// overwritten with the result of the exscan.
215///
216/// The following parameters are required:
217/// - \ref kamping::send_recv_buf() containing the data for which to perform the exclusive scan and will store the
218/// result of the scan.
219///
220/// - \ref kamping::op() wrapping the operation to apply to the input. If \ref kamping::send_recv_type() is provided
221/// explicitly, the compatibility of the type and operation has to be ensured by the user.
222///
223/// The following parameters are optional:
224/// - \ref kamping::send_recv_count() containing the number of elements to be processed in this operation. This
225/// parameter has to be the same at each rank. If omitted, the size of the send buffer will be used as send_recv_count.
226///
227/// - \ref kamping::send_recv_type() specifying the \c MPI datatype to use as data type in this operation. If omitted,
228/// the \c MPI datatype is derived automatically based on send_recv_buf's underlying \c value_type.
229///
230/// - \ref kamping::values_on_rank_0() containing the value(s) that is/are written to the output on rank 0. \c
231/// values_on_rank_0 must be a container of the same size as \c send_recv_buf or a single value (which will be reused
232/// for all elements of the \c recv_buf).
233///
234/// @tparam Args Automatically deduced template parameters.
235/// @param args All required and any number of the optional parameters described above.
236/// @return Result object wrapping the output parameters to be returned by value.
237///
238/// @see \ref docs/parameter_handling.md for general information about parameter handling in KaMPIng.
239/// <hr>
240/// \include{doc} docs/resize_policy.dox
241template <
242 template <typename...>
243 typename DefaultContainerType,
244 template <typename, template <typename...> typename>
245 typename... Plugins>
246template <typename... Args>
248 using namespace kamping::internal;
250 Args,
253 );
254
255 // get the send recv buffer and deduce the send and recv value types.
256 auto&& send_recv_buf = select_parameter_type<ParameterType::send_recv_buf>(args...).construct_buffer_or_rebind();
257 using value_type = typename std::remove_reference_t<decltype(send_recv_buf)>::value_type;
258
259 // get the send_recv_type
261 [[maybe_unused]] constexpr bool type_is_in_param = !has_to_be_computed<decltype(type)>;
262
263 // get the send_recv count
265 auto&& count =
266 internal::select_parameter_type_or_default<internal::ParameterType::send_recv_count, default_count_type>(
267 std::tuple(),
268 args...
269 )
270 .construct_buffer_or_rebind();
271
272 constexpr bool do_compute_count = internal::has_to_be_computed<decltype(count)>;
273 if constexpr (do_compute_count) {
274 count.underlying() = asserting_cast<int>(send_recv_buf.size());
275 }
276
277 KASSERT(
278 is_same_on_all_ranks(count.get_single_element()),
279 "The send_recv_count has to be the same on all ranks.",
281 );
282
283 // get the operation used for the reduction. The signature of the provided function is checked while building.
286
287 auto compute_required_recv_buf_size = [&]() {
288 return asserting_cast<size_t>(count.get_single_element());
289 };
291 KASSERT(
292 // if the send_recv type is user provided, kamping cannot make any assumptions about the required size of
293 // the buffer
295 "Send/Recv buffer is not large enough to hold all received elements.",
297 );
298
299 // Perform the MPI_Exscan call and return.
300 [[maybe_unused]] int err = MPI_Exscan(
301 MPI_IN_PLACE, // sendbuf
302 send_recv_buf.data(), // recvbuf,
303 count.get_single_element(), // count
304 type.get_single_element(), // datatype,
305 operation.op(), // op
306 mpi_communicator() // communicator
307 );
308 this->mpi_error_hook(err, "MPI_Exscan");
309
310 // MPI_Exscan leaves the recv_buf on rank 0 in an undefined state. We set it to the value provided via
311 // values_on_rank_0() if given. If values_on_rank_0() is not given and the operation is a built-in operation on
312 // a
313 // built-in data-type, we set the value on rank 0 to the identity of that operation on that datatype (e.g. 0 for
314 // addition on integers).
315 if (rank() == 0) {
316 constexpr bool has_values_on_rank_0_param = has_parameter_type<ParameterType::values_on_rank_0, Args...>();
317 // We decided not to enforce having to provide values_on_rank_0() for a operation for which we cannot
318 // auto-deduce the identity, as this would introduce a parameter which is required in some situtations in
319 // KaMPIng, but never in MPI.
320 if constexpr (has_values_on_rank_0_param) {
321 auto const& values_on_rank_0_param =
322 select_parameter_type<ParameterType::values_on_rank_0>(args...).construct_buffer_or_rebind();
323 KASSERT(
324 // if the send_recv type is user provided, kamping cannot make any assumptions about the required
325 // size of the recv buffer
327 || values_on_rank_0_param.size() == asserting_cast<size_t>(count.get_single_element())),
328 "on_rank_0 has to either be of size 1 or of the same size as the recv_buf.",
330 );
331 if (values_on_rank_0_param.size() == 1) {
332 std::fill_n(
333 send_recv_buf.data(),
334 asserting_cast<size_t>(count.get_single_element()),
336 );
337 } else {
338 std::copy_n(values_on_rank_0_param.data(), values_on_rank_0_param.size(), send_recv_buf.data());
339 }
340 } else if constexpr (operation.is_builtin) {
341 std::fill_n(send_recv_buf.data(), asserting_cast<size_t>(count.get_single_element()), operation.identity());
342 }
343 }
344
345 return make_mpi_result<std::tuple<Args...>>(std::move(send_recv_buf), std::move(count), std::move(type));
346}
347
348/// @brief Wrapper for \c MPI_exscan for single elements.
349///
350/// This is functionally equivalent to \c exscan() but provided for uniformity with other operations (e.g. \c
351/// bcast_single()). \c exscan_single() wraps \c MPI_Exscan, which is used to perform an exclusive prefix reduction on
352/// data distributed across the calling processes. \c exscan_single() returns on the process with
353/// rank \f$i > 0\f$, the reduction (calculated according to the function \c op) of the values in the \c send_bufs of
354/// processes with ranks \f$0, \ldots, i - 1\f$ (i.e. excluding i as opposed to \c scan()). The result
355/// on rank 0 is set to the value of \c values_on_rank_0 if provided. If \c values_on_rank_0 is not provided and \c op
356/// is a built-in operation on the data-type used, the value on rank 0 is set to the identity of that operation. If the
357/// operation is not built-in on the data-type used and no \c values_on_rank_0() is provided, the result on rank 0 is
358/// undefined.
359///
360/// The following parameters are required:
361/// - \ref kamping::send_buf() containing the data for which to perform the exclusive scan. This buffer has to be a
362/// single element on each rank.
363/// - \ref kamping::op() the operation to apply to the input.
364///
365/// The following parameters are optional:
366/// - \ref kamping::values_on_rank_0() containing the single value that is returned in the \c recv_buf of rank 0.///
367///
368/// @tparam Args Automatically deduced template parameters.
369/// @param args All required and any number of the optional buffers described above.
370/// @return The single element result of the exclusive scan.
371template <
372 template <typename...>
373 typename DefaultContainerType,
374 template <typename, template <typename...> typename>
375 typename... Plugins>
376template <typename... Args>
378 //! If you expand this function to not being only a simple wrapper around exscan, you have to write more unit
379 //! tests!
380
381 using namespace kamping::internal;
382
383 // The send and recv buffers are always of the same size in exscan, thus, there is no additional exchange of
384 // recv_counts.
386 Args,
389 );
390
391 using send_buf_type = buffer_type_with_requested_parameter_type<ParameterType::send_buf, Args...>;
392 static_assert(
393 send_buf_type::is_single_element,
394 "The underlying container has to be a single element \"container\""
395 );
396 using value_type = typename send_buf_type::value_type;
397 return this->exscan(recv_buf(alloc_new<value_type>), std::forward<Args>(args)...);
398}
399/// @}
Helper functions that make casts safer.
STL-compatible allocator for requesting memory using the builtin MPI allocator.
Definition allocator.hpp:32
T value_type
The value type.
Definition allocator.hpp:53
Code for error handling.
constexpr int light
Assertion level for lightweight assertions.
Definition assertion_levels.hpp:13
constexpr int light_communication
Assertions that perform lightweight communication.
Definition assertion_levels.hpp:25
auto exscan_inplace(Args... args) const
Wrapper for the in-place version of Communicator::exscan().
Definition exscan.hpp:247
auto exscan_single(Args... args) const
Wrapper for MPI_exscan for single elements.
Definition exscan.hpp:377
auto exscan(Args... args) const
Wrapper for MPI_Exscan.
Definition exscan.hpp:84
static constexpr auto alloc_new
Convenience wrapper for creating library allocated containers. See AllocNewT for details.
Definition data_buffer.hpp:170
auto values_on_rank_0(Container &&container)
Passes a container containing the value(s) to return on the first rank to kamping::Communicator::exsc...
Definition named_parameters.hpp:1167
internal::OperationBuilder< Op, Commutative > op(Op &&op, Commutative commute=ops::internal::undefined_commutative_tag{})
Passes a reduction operation to ther underlying call. Accepts function objects, lambdas,...
Definition named_parameters.hpp:1155
auto recv_buf(Container &&container)
Passes a container, into which the received elements will be written, to the underlying call....
Definition named_parameters.hpp:859
auto send_buf(internal::ignore_t< Data > ignore)
Generates a dummy send buf that wraps a nullptr.
Definition named_parameters.hpp:51
auto send_recv_buf(Data &&data)
Passes a container/single value as a send or receive buffer to the underlying MPI call.
Definition named_parameters.hpp:137
auto send_recv_count(int count)
Passes count as send/recv count to the underlying call.
Definition named_parameters.hpp:529
auto send_recv_type(MPI_Datatype send_recv_type)
Passes send_recv_type as send/recv type to the underlying call. (This parameter is in MPI routines su...
Definition named_parameters.hpp:1282
auto send_recv_count_out()
Indicates to deduce the send/recv count and return it to the caller as part of the underlying call's ...
Definition named_parameters.hpp:555
decltype(auto) select_parameter_type_or_default(std::tuple< DefaultArguments... > default_arguments, Args &... args)
Checks if parameter with requested parameter type exists, if not constructs a default value.
Definition named_parameter_selection.hpp:239
std:: tuple_element_t< find_pos< std::integral_constant< ParameterType, parameter_type >, 0, Args... >(), std::tuple< Args... > > buffer_type_with_requested_parameter_type
Type of Buffer with requested.
Definition named_parameter_selection.hpp:176
constexpr bool has_parameter_type()
Checks if parameter with requested parameter type exists.
Definition named_parameter_selection.hpp:186
Utility that maps C++ types to types that can be understood by MPI.
Template magic to check named parameters passed to wrappers at compile time.
#define KAMPING_REQUIRED_PARAMETERS(...)
Wrapper to pass (possibly empty) list of parameter type names as required parameters to KAMPING_CHECK...
Definition named_parameter_check.hpp:52
#define KAMPING_OPTIONAL_PARAMETERS(...)
Wrapper to pass (possibly empty) list of parameter type names as optional parameters to KAMPING_CHECK...
Definition named_parameter_check.hpp:58
#define KAMPING_CHECK_PARAMETERS(args, required, optional)
Assertion macro that checks if passed parameters are correct, i.e., all parameter types are unique,...
Definition named_parameter_check.hpp:80
Template magic to implement named parameters in cpp.
File containing the parameter types used by the KaMPIng library.
Factory methods for buffer wrappers.
Internal namespace marking the code that is not user-facing.
Definition collectives_helpers.hpp:20
static constexpr bool has_to_be_computed
Checks if the buffer has to be computed by kamping, i.e. if it is an output parameter or the buffer h...
Definition named_parameter_check.hpp:398
Some functions and types simplifying/enabling the development of wrapped MPI calls in KaMPIng.