TRIQS/nda 1.3.0
Multi-dimensional array library for C++
Loading...
Searching...
No Matches
reduce.hpp
Go to the documentation of this file.
1// Copyright (c) 2020-2023 Simons Foundation
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0.txt
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// Authors: Olivier Parcollet, Nils Wentzell
16
17/**
18 * @file
19 * @brief Provides an MPI reduce function for nda::Array types.
20 */
21
22#pragma once
23
24#include "../basic_functions.hpp"
25#include "../concepts.hpp"
26#include "../exceptions.hpp"
27#include "../map.hpp"
28#include "../traits.hpp"
29
30#include <mpi/mpi.hpp>
31
32#include <cstdlib>
33#include <type_traits>
34#include <utility>
35
36/**
37 * @ingroup av_mpi
38 * @brief Specialization of the `mpi::lazy` class for nda::Array types and the `mpi::tag::reduce` tag.
39 *
40 * @details An object of this class is returned when reducing nda::Array objects across multiple MPI processes.
41 *
42 * It models an nda::ArrayInitializer, that means it can be used to initialize and assign to nda::basic_array and
43 * nda::basic_array_view objects. The target array will have the same shape as the input arrays.
44 *
45 * See nda::mpi_reduce for an example.
46 *
47 * @tparam A nda::Array type to be reduced.
48 */
49template <nda::Array A>
50struct mpi::lazy<mpi::tag::reduce, A> {
51 /// Value type of the array/view.
52 using value_type = typename std::decay_t<A>::value_type;
53
54 /// Const view type of the array/view stored in the lazy object.
55 using const_view_type = decltype(std::declval<const A>()());
56
57 /// View of the array/view to be reduced.
58 const_view_type rhs;
59
60 /// MPI communicator.
61 mpi::communicator comm;
62
63 /// MPI root process.
64 const int root{0}; // NOLINT (const is fine here)
65
66 /// Should all processes receive the result.
67 const bool all{false}; // NOLINT (const is fine here)
68
69 /// MPI reduction operation.
70 const MPI_Op op{MPI_SUM}; // NOLINT (const is fine here)
71
72 /**
73 * @brief Compute the shape of the target array.
74 * @note It is assumed that the shape of the input array is the same for all MPI processes.
75 * @return Shape of the input array.
76 */
77 [[nodiscard]] auto shape() const { return rhs.shape(); }
78
79 /**
80 * @brief Execute the lazy MPI operation and write the result to a target array/view.
81 *
82 * @details If the target array/view is the same as the input array/view, i.e. if their data pointers are the same,
83 * the reduction is performed in-place.
84 *
85 * @tparam T nda::Array type of the target array/view.
86 * @param target Target array/view.
87 */
88 template <nda::Array T>
89 void invoke(T &&target) const { // NOLINT (temporary views are allowed here)
90 // check if the arrays can be used in the MPI call
91 if (not target.is_contiguous()) NDA_RUNTIME_ERROR << "Error in MPI reduce for nda::Array: Target array needs to be contiguous";
92 static_assert(std::decay_t<A>::layout_t::stride_order_encoded == std::decay_t<T>::layout_t::stride_order_encoded,
93 "Error in MPI reduce for nda::Array: Incompatible stride orders");
94
95 // special case for non-mpi runs
96 if (not mpi::has_env) {
97 target = rhs;
98 return;
99 }
100
101 // perform the reduction
102 if constexpr (not mpi::has_mpi_type<value_type>) {
103 // if the value type cannot be reduced directly, we call mpi::reduce for each element
104 target = nda::map([this](auto const &x) { return mpi::reduce(x, this->comm, this->root, this->all, this->op); })(rhs);
105 } else {
106 // value type has a corresponding MPI type
107 bool in_place = (target.data() == rhs.data());
108 if (in_place) {
109 if (rhs.size() != target.size())
110 NDA_RUNTIME_ERROR << "Error in MPI reduce for nda::Array: In-place reduction requires arrays of the same size";
111 } else {
112 if ((comm.rank() == root) || all) nda::resize_or_check_if_view(target, shape());
113 if (std::abs(target.data() - rhs.data()) < rhs.size()) NDA_RUNTIME_ERROR << "Error in MPI reduce for nda::Array: Overlapping arrays";
114 }
115
116 void *target_ptr = (void *)target.data();
117 void *rhs_ptr = (void *)rhs.data();
118 auto count = rhs.size();
119 auto mpi_value_type = mpi::mpi_type<value_type>::get();
120 if (!all) {
121 if (in_place)
122 MPI_Reduce((comm.rank() == root ? MPI_IN_PLACE : rhs_ptr), rhs_ptr, count, mpi_value_type, op, root, comm.get());
123 else
124 MPI_Reduce(rhs_ptr, target_ptr, count, mpi_value_type, op, root, comm.get());
125 } else {
126 if (in_place)
127 MPI_Allreduce(MPI_IN_PLACE, rhs_ptr, count, mpi_value_type, op, comm.get());
128 else
129 MPI_Allreduce(rhs_ptr, target_ptr, count, mpi_value_type, op, comm.get());
130 }
131 }
132 }
133};
134
135namespace nda {
136
137 /**
138 * @ingroup av_mpi
139 * @brief Implementation of an MPI reduce for nda::basic_array or nda::basic_array_view types.
140 *
141 * @details Since the returned `mpi::lazy` object models an nda::ArrayInitializer, it can be used to initialize/assign
142 * to nda::basic_array and nda::basic_array_view objects:
143 *
144 * @code{.cpp}
145 * // create an array on all processes
146 * nda::array<int, 2> arr(3, 4);
147 *
148 * // ...
149 * // fill array on each process
150 * // ...
151 *
152 * // reduce the array to the root process
153 * nda::array<int, 2> res = mpi::reduce(arr);
154 * @endcode
155 *
156 * @tparam A nda::basic_array or nda::basic_array_view type.
157 * @param a Array or view to be gathered.
158 * @param comm `mpi::communicator` object.
159 * @param root Rank of the root process.
160 * @param all Should all processes receive the result of the gather.
161 * @param op MPI reduction operation.
162 * @return An `mpi::lazy` object modelling an nda::ArrayInitializer.
163 */
164 template <typename A>
165 ArrayInitializer<std::remove_reference_t<A>> auto mpi_reduce(A &&a, mpi::communicator comm = {}, int root = 0, bool all = false,
166 MPI_Op op = MPI_SUM)
167 requires(is_regular_or_view_v<A>)
168 {
169 if (not a.is_contiguous()) NDA_RUNTIME_ERROR << "Error in MPI reduce for nda::Array: Array needs to be contiguous";
170 return mpi::lazy<mpi::tag::reduce, A>{std::forward<A>(a), comm, root, all, op};
171 }
172
173} // namespace nda
#define NDA_RUNTIME_ERROR
ArrayInitializer< std::remove_reference_t< A > > auto mpi_reduce(A &&a, mpi::communicator comm={}, int root=0, bool all=false, MPI_Op op=MPI_SUM)
Implementation of an MPI reduce for nda::basic_array or nda::basic_array_view types.
Definition reduce.hpp:165
const_view_type rhs
View of the array/view to be reduced.
Definition reduce.hpp:58
auto shape() const
Compute the shape of the target array.
Definition reduce.hpp:77
mpi::communicator comm
MPI communicator.
Definition reduce.hpp:61
const bool all
Should all processes receive the result.
Definition reduce.hpp:67
void invoke(T &&target) const
Execute the lazy MPI operation and write the result to a target array/view.
Definition reduce.hpp:89
const MPI_Op op
MPI reduction operation.
Definition reduce.hpp:70
const int root
MPI root process.
Definition reduce.hpp:64