KaMPIng 0.1.1
Flexible and (near) zero-overhead C++ bindings for MPI
Loading...
Searching...
No Matches
distributed_graph_communicator.hpp
1// This file is part of KaMPIng.
2//
3// Copyright 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 <kamping/communicator.hpp>
17#include <kamping/span.hpp>
18#include <kamping/topology_communicator.hpp>
19
20namespace kamping {
21
22/// @brief Local view of a a distributed communication graph from the perspective of the current rank. Each vertex is a
23/// rank and the edges define possible communication links between the vertices. This view provides access to the
24/// (potentially weighted) in- and outgoing edges which are represented as a sequence of neighboring ranks. Note that
25/// MPI allows this to be a multi-graph.
26/// process' rank.
28public:
29 using SpanType = kamping::Span<int const>; ///< type to be used for views on ranks and weights
30
31 /// @brief Constructs a view of an unweighted communication graph.
32 ///
33 /// @tparam ContiguousRange Type of the input range for in and out ranks.
34 /// @param in_ranks Neighboring in ranks, i.e., ranks i for which there is an edge (i,own_rank).
35 /// @param out_ranks Neighboring out ranks, i.e., ranks i for which there is an edge (own_rank, i).
36 template <typename ContiguousRange>
38 : _in_ranks{in_ranks.data(), in_ranks.size()},
39 _out_ranks{out_ranks.data(), out_ranks.size()} {}
40 /// @brief Constructs a view of an unweighted communication graph.
41 ///
42 /// @tparam ContiguousRange Type of the input range for in and out ranks.
43 /// @param in_ranks Neighboring in ranks, i.e., ranks i for which there is an edge (i,own_rank).
44 /// @param out_ranks Neighboring out ranks, i.e., ranks i for which there is an edge (own_rank, i).
45 /// @param in_weights Weights associcated to neighboring in ranks.
46 /// @param out_weights Weights associcated to neighboring out ranks.
47 template <typename ContiguousRange>
53 )
54 : _in_ranks{in_ranks.data(), in_ranks.size()},
55 _out_ranks{out_ranks.data(), out_ranks.size()},
56 _in_weights{SpanType{in_weights.data(), in_weights.size()}},
57 _out_weights{SpanType{out_weights.data(), out_weights.size()}} {}
58
59 /// @brief Returns in degree of the rank, i.e. the number of in-going edges/communication links towards the rank.
60 /// @return In degree of the rank.
61 size_t in_degree() const {
62 return _in_ranks.size();
63 }
64
65 /// @brief Returns in degree of the rank, i.e. the number of in-going edges/communication links towards the rank.
66 /// @return Signed in degree of the rank.
67 int in_degree_signed() const {
69 }
70
71 /// @brief Returns out degree of the rank, i.e. the number of out-going edges/communication links starting at the
72 /// rank.
73 /// @return Out degree of the rank.
74 size_t out_degree() const {
75 return _out_ranks.size();
76 }
77
78 /// @brief Returns out degree of the rank, i.e. the number of out-going edges/communication links starting at the
79 /// rank.
80 /// @return Signed out degree of the rank.
81 int out_degree_signed() const {
83 }
84
85 /// @brief Returns whether the communication graph is weighted or not.
86 bool is_weighted() const {
87 return _in_weights.has_value();
88 }
89
90 /// @brief Returns view on the in-going edges.
92 return _in_ranks;
93 }
94
95 /// @brief Returns view on the out-going edges.
97 return _out_ranks;
98 }
99
100 /// @brief Returns view on the in-going edge weights if present.
101 std::optional<SpanType> in_weights() const {
102 return _in_weights;
103 }
104
105 /// @brief Returns view on the out-going edge weights if present.
106 std::optional<SpanType> out_weights() const {
107 return _out_weights;
108 }
109
110 /// @brief Creates a distributed graph communicator based on the view of the given communication graph using \c
111 /// MPI_Dist_graph_create_adjacent.
112 ///
113 /// @param comm MPI comm on which the graph topology will be applied.
114 /// @return MPi comm with associated graph topology.
116 int const* in_weights = is_weighted() ? _in_weights.value().data() : MPI_UNWEIGHTED;
117 int const* out_weights = is_weighted() ? _out_weights.value().data() : MPI_UNWEIGHTED;
118
120
122 comm,
124 in_ranks().data(),
127 out_ranks().data(),
130 false, // do not reorder ranks
132 );
133 return mpi_graph_comm;
134 }
135
136private:
137 SpanType _in_ranks; ///< View on in-going edges.
138 SpanType _out_ranks; ///< View on out-going edges.
139 std::optional<SpanType> _in_weights; ///< View on in-going edge weights.
140 std::optional<SpanType> _out_weights; ///< View on out-going edge weights.
141};
142
143namespace internal {
144/// @brief Returns whether a given range of neighbors is weighted or not at compile time, i.e., whether the neighborhood
145/// only consists of ranks or of (rank, weight) pairs
146/// @tparam NeighborhoodRange Range type to be checked.
147template <typename NeighborhoodRange>
149 using NeighborType = typename NeighborhoodRange::value_type;
150#ifdef KAMPING_ENABLE_REFLECTION
151 static_assert(
152 kamping::internal::tuple_size<NeighborType> == 1 || kamping::internal::tuple_size<NeighborType> == 2,
153 "Neighbor type has to be a scalar (in the unweighted case) or pair-like (in the weighted case) type"
154 );
155 return kamping::internal::tuple_size<NeighborType> == 2;
156#else
157 // If reflection is not available, we assume that the neighbor type is a pair-like type if it is not integral.
158 // This is the best we can do, because kamping::internal::tuple_size does not work with arbitrary types without
159 // reflection.
160 return !std::is_integral_v<NeighborType>;
161#endif
162}
163} // namespace internal
164
165/// @brief A (vertex-centric) distributed communication graph. Each vertex of the graph corresponds to a rank and each
166/// edge (i,j) connect two ranks i and j which can communicate with each other. The distributed communication graph is
167/// vertex-centric in the sense that on each rank the local graph only contains the corresponding vertex and its in and
168/// out neighborhood. Note that MPI allow multiple edges between the same ranks i and j, i.e. the distributed
169/// communication graph can be a multi-graph.
170template <template <typename...> typename DefaultContainer = std::vector>
172public:
173 /// @brief Default constructor.
175
176 /// @brief Constructs a communication graph based on a range of in-going and out-going edges which might be
177 /// weighted. A unweighted edge is simply an integer, wherea a weighted edge is a pair-like object consisting of two
178 /// integers which can be decomposed by a structured binding via `auto [rank, weight] = weighted_edge;`.
179 ///
180 /// @tparam InNeighborsRange Range type of in-going neighbors.
181 /// @tparam OutNeighborsRange Range type of out-going neighbors.
182 /// @param in_neighbors Range of in-going neighbors.
183 /// @param out_neighbors Range of out-going neighbors.
184 ///
185 template <typename InNeighborsRange, typename OutNeighborsRange>
187 constexpr bool are_in_neighbors_weighted = internal::are_neighborhoods_weighted<InNeighborsRange>();
188 constexpr bool are_out_neighbors_weighted = internal::are_neighborhoods_weighted<OutNeighborsRange>();
189 static_assert(
191 "If weighted neighborhoods are passed, they must be provided for both in and out neighbors!"
192 );
193 auto get_rank = [&](auto const& edge) {
194 if constexpr (are_in_neighbors_weighted) {
195 auto const& [rank, _] = edge;
196 return static_cast<int>(rank);
197 } else {
198 return static_cast<int>(edge);
199 }
200 };
201 _in_ranks.resize(in_neighbors.size());
202 _out_ranks.resize(out_neighbors.size());
203 std::transform(in_neighbors.data(), in_neighbors.data() + in_neighbors.size(), _in_ranks.data(), get_rank);
204 std::transform(out_neighbors.data(), out_neighbors.data() + out_neighbors.size(), _out_ranks.data(), get_rank);
205
206 transform_elems_to_integers(in_neighbors, _in_ranks, get_rank);
207 transform_elems_to_integers(out_neighbors, _out_ranks, get_rank);
208
209 if constexpr (are_in_neighbors_weighted) {
210 auto get_weight = [](auto const& edge) {
211 auto const& [_, weight] = edge;
212 return static_cast<int>(weight);
213 };
214 _in_weights = DefaultContainer<int>(in_neighbors.size());
215 _out_weights = DefaultContainer<int>(out_neighbors.size());
216
217 std::transform(
218 in_neighbors.data(),
219 in_neighbors.data() + in_neighbors.size(),
220 _in_weights.value().data(),
222 );
223 std::transform(
224 out_neighbors.data(),
225 out_neighbors.data() + out_neighbors.size(),
226 _out_weights.value().data(),
228 );
229 }
230 }
231
232 /// @brief Constructs a communication graph based on a range of unweighted in-going and out-going neighbors, i.e.,
233 /// ranks.
235 : _in_ranks{std::move(in_ranks)},
236 _out_ranks{std::move(out_ranks)} {}
237
238 /// @brief Constructs a communication graph based on a range of weighted in-going and out-going neighbors.
239 /// The ownership of the underlying ranks/weights containers is transfered.
241 DefaultContainer<int>&& in_ranks,
242 DefaultContainer<int>&& out_ranks,
243 DefaultContainer<int>&& in_weights,
244 DefaultContainer<int>&& out_weights
245 )
246 : _in_ranks{std::move(in_ranks)},
247 _out_ranks{std::move(out_ranks)},
248 _in_weights{std::move(in_weights)},
249 _out_weights{std::move(out_weights)} {}
250
251 /// @brief Constructs a communication graph based where in and out neighbors are the same, i.e. a symmetric
252 /// neighborhood/graph.
253 template <typename NeighborRange>
256
257 /// @brief Returns a view of the communication graph.
259 if (_in_weights.has_value()) {
260 return CommunicationGraphLocalView(_in_ranks, _out_ranks, _in_weights.value(), _out_weights.value());
261 } else {
262 return CommunicationGraphLocalView(_in_ranks, _out_ranks);
263 }
264 }
265
266 /// @brief In neighborhood collectives the order of sent and received data depends on
267 /// the ordering of the underlying out and in neighbors (note that the MPI standard allows that a neighbor occurs
268 /// multiple times within the neighbors list). For example in \c MPI_Neighbor_alltoall the \c k-th block in the send
269 /// buffer is sent to the \c k-th neighboring process. Hence, to exchange data to rank r via neighborhood
270 /// collectives it might be useful to know the index of rank r within the out neighbors (provided r is a neighbor at
271 /// all). Therefore, this function returns a mapping from the rank of each out neighbor r to its index within the
272 /// out neighbors. If r occurs multiple times, one of these positions is returned in the mapping.
273 ///
274 /// @tparam Map Map type storing the mapping, uses std::unordered_map<size_t, size_t> as default.
275 ///
276 /// @return Returns the mapping.
277 template <typename Map = std::unordered_map<size_t, size_t>>
279 Map mapping;
280 for (size_t i = 0; i < _out_ranks.size(); ++i) {
281 size_t rank = static_cast<size_t>(_out_ranks.data()[i]);
282 mapping.emplace(rank, i);
283 }
284 return mapping;
285 }
286
287private:
288 template <typename ReadFromRange, typename WriteToContainer, typename TransformOp>
289 void transform_elems_to_integers(ReadFromRange const& input, WriteToContainer& output, TransformOp&& transform_op) {
290 output.resize(input.size());
291 std::transform(input.data(), input.data() + static_cast<int>(input.size()), output.data(), transform_op);
292 }
293
294private:
295 DefaultContainer<int> _in_ranks; ///< In-going edges.
296 DefaultContainer<int> _out_ranks; ///< Out-going edges.
297 std::optional<DefaultContainer<int>> _in_weights; ///< Weights of in-going edges if present.
298 std::optional<DefaultContainer<int>> _out_weights; ///< Weights of out-going edges if present.
299};
300
301/// @brief A \ref Communicator which possesses an additional virtual topology and supports neighborhood collectives (on
302/// the topology). The virtual topology is specified via a distributed communication graph (see \ref
303/// DistributedCommunicationGraph).
304/// @tparam DefaultContainerType The default container type to use for containers created by KaMPIng. Defaults to
305/// std::vector.
306/// @tparam Plugins Plugins adding functionality to KaMPIng. Plugins should be classes taking a ``Communicator``
307/// template parameter and can assume that they are castable to `Communicator` from which they can
308/// call any function of `kamping::Communicator`. See `test/plugin_tests.cpp` for examples.
309template <
310 template <typename...> typename DefaultContainerType = std::vector,
311 template <typename, template <typename...> typename>
312 typename... Plugins>
314 : public TopologyCommunicator<DefaultContainerType>,
315 public Plugins<DistributedGraphCommunicator<DefaultContainerType, Plugins...>, DefaultContainerType>... {
316public:
317 /// @brief Type of the default container type to use for containers created inside operations of this
318 /// communicator.
319 /// @tparam Args Arguments to the container type.
320 template <typename... Args>
322
323 /// @brief Construtor based on a given communicator and a view of a communication graph.
324 ///
325 /// @tparam Communicator Type of communicator.
326 /// @param comm Communicator for which a graph topology shall be added.
327 /// @param comm_graph_view View on the communication graph which will be added to the given communicator.
328 template <typename Communicator>
336
337 /// @brief Construtor based on a given communicator and a communication graph.
338 ///
339 /// @tparam Communicator Type of communicator.
340 /// @param comm Communicator for which a graph topology shall be added.
341 /// @param comm_graph Communication graph which will be added to the given communicator.
342 template <typename Communicator>
347
348 /// @brief Returns the communicators underlying communication graph by calling `MPI_Dist_graph_neighbors`.
351 in_ranks.resize(this->in_degree());
353 out_ranks.resize(this->out_degree());
354 DefaultContainerType<int> in_weights;
355 DefaultContainerType<int> out_weights;
356 if (is_weighted()) {
357 in_weights.resize(this->in_degree());
358 out_weights.resize(this->out_degree());
359 }
360
362 this->_comm,
363 this->in_degree_signed(),
364 in_ranks.data(),
365 is_weighted() ? in_weights.data() : MPI_UNWEIGHTED,
366 this->out_degree_signed(),
367 out_ranks.data(),
368 is_weighted() ? out_weights.data() : MPI_UNWEIGHTED
369 );
370 if (is_weighted()) {
372 std::move(in_ranks),
373 std::move(out_ranks),
374 std::move(in_weights),
375 std::move(out_weights)
376 );
377 } else {
378 return DistributedCommunicationGraph<DefaultContainerType>(std::move(in_ranks), std::move(out_ranks));
379 }
380 }
381
382 /// @brief Returns whether the communicator's underlying communication graph is weighted.
383 /// @return True if weighted, false otherwise.
384 bool is_weighted() const {
385 return _is_weighted;
386 }
387
388private:
389 size_t _in_degree; ///< In degree of the rank within the communication graph.
390 size_t _out_degree; ///< Out degree of the rank within the communication graph.
391 bool _is_weighted; ///< Weight status of the communication graph.
392};
393} // namespace kamping
Local view of a a distributed communication graph from the perspective of the current rank....
Definition distributed_graph_communicator.hpp:27
SpanType out_ranks() const
Returns view on the out-going edges.
Definition distributed_graph_communicator.hpp:96
MPI_Comm create_mpi_graph_communicator(MPI_Comm comm) const
Creates a distributed graph communicator based on the view of the given communication graph using MPI...
Definition distributed_graph_communicator.hpp:115
size_t out_degree() const
Returns out degree of the rank, i.e. the number of out-going edges/communication links starting at th...
Definition distributed_graph_communicator.hpp:74
CommunicationGraphLocalView(ContiguousRange const &in_ranks, ContiguousRange const &out_ranks, ContiguousRange const &in_weights, ContiguousRange const &out_weights)
Constructs a view of an unweighted communication graph.
Definition distributed_graph_communicator.hpp:48
CommunicationGraphLocalView(ContiguousRange const &in_ranks, ContiguousRange const &out_ranks)
Constructs a view of an unweighted communication graph.
Definition distributed_graph_communicator.hpp:37
int out_degree_signed() const
Returns out degree of the rank, i.e. the number of out-going edges/communication links starting at th...
Definition distributed_graph_communicator.hpp:81
int in_degree_signed() const
Returns in degree of the rank, i.e. the number of in-going edges/communication links towards the rank...
Definition distributed_graph_communicator.hpp:67
bool is_weighted() const
Returns whether the communication graph is weighted or not.
Definition distributed_graph_communicator.hpp:86
size_t in_degree() const
Returns in degree of the rank, i.e. the number of in-going edges/communication links towards the rank...
Definition distributed_graph_communicator.hpp:61
std::optional< SpanType > out_weights() const
Returns view on the out-going edge weights if present.
Definition distributed_graph_communicator.hpp:106
std::optional< SpanType > in_weights() const
Returns view on the in-going edge weights if present.
Definition distributed_graph_communicator.hpp:101
kamping::Span< int const > SpanType
type to be used for views on ranks and weights
Definition distributed_graph_communicator.hpp:29
SpanType in_ranks() const
Returns view on the in-going edges.
Definition distributed_graph_communicator.hpp:91
Wrapper for MPI communicator providing access to rank() and size() of the communicator....
Definition communicator.hpp:49
MPI_Comm _comm
Corresponding MPI communicator.
Definition communicator.hpp:640
MPI_Comm mpi_communicator() const
MPI communicator corresponding to this communicator.
Definition communicator.hpp:192
A (vertex-centric) distributed communication graph. Each vertex of the graph corresponds to a rank an...
Definition distributed_graph_communicator.hpp:171
DistributedCommunicationGraph(NeighborRange const &neighbors)
Constructs a communication graph based where in and out neighbors are the same, i....
Definition distributed_graph_communicator.hpp:254
DistributedCommunicationGraph(DefaultContainer< int > &&in_ranks, DefaultContainer< int > &&out_ranks, DefaultContainer< int > &&in_weights, DefaultContainer< int > &&out_weights)
Constructs a communication graph based on a range of weighted in-going and out-going neighbors....
Definition distributed_graph_communicator.hpp:240
DistributedCommunicationGraph(InNeighborsRange const &in_neighbors, OutNeighborsRange const &out_neighbors)
Constructs a communication graph based on a range of in-going and out-going edges which might be weig...
Definition distributed_graph_communicator.hpp:186
DistributedCommunicationGraph(DefaultContainer< int > &&in_ranks, DefaultContainer< int > &&out_ranks)
Constructs a communication graph based on a range of unweighted in-going and out-going neighbors,...
Definition distributed_graph_communicator.hpp:234
auto get_rank_to_out_neighbor_idx_mapping()
In neighborhood collectives the order of sent and received data depends on the ordering of the underl...
Definition distributed_graph_communicator.hpp:278
DistributedCommunicationGraph()=default
Default constructor.
CommunicationGraphLocalView get_view() const
Returns a view of the communication graph.
Definition distributed_graph_communicator.hpp:258
A Communicator which possesses an additional virtual topology and supports neighborhood collectives (...
Definition distributed_graph_communicator.hpp:315
DistributedGraphCommunicator(Communicator const &comm, DistributedCommunicationGraph< DefaultContainerType > const &comm_graph)
Construtor based on a given communicator and a communication graph.
Definition distributed_graph_communicator.hpp:343
auto get_communication_graph()
Returns the communicators underlying communication graph by calling MPI_Dist_graph_neighbors.
Definition distributed_graph_communicator.hpp:349
bool is_weighted() const
Returns whether the communicator's underlying communication graph is weighted.
Definition distributed_graph_communicator.hpp:384
DistributedGraphCommunicator(Communicator const &comm, CommunicationGraphLocalView comm_graph_view)
Construtor based on a given communicator and a view of a communication graph.
Definition distributed_graph_communicator.hpp:329
STL-compatible allocator for requesting memory using the builtin MPI allocator.
Definition allocator.hpp:32
A Communicator which possesses an additional virtual topology and supports neighborhood collectives (...
Definition topology_communicator.hpp:37
int out_degree_signed() const
Returns the out degree of the process' rank, i.e. the number of out-going edges/communication links s...
Definition topology_communicator.hpp:67
size_t in_degree() const
Returns the in degree of the process' rank, i.e. the number of in-going edges/communication links tow...
Definition topology_communicator.hpp:47
size_t out_degree() const
Returns the out degree of the process' rank, i.e. the number of out-going edges/communication links s...
Definition topology_communicator.hpp:60
int in_degree_signed() const
Returns the in degree of the process' rank, i.e. the number of in-going edges/communication links tow...
Definition topology_communicator.hpp:53
constexpr bool are_neighborhoods_weighted()
Returns whether a given range of neighbors is weighted or not at compile time, i.e....
Definition distributed_graph_communicator.hpp:148
STL namespace.