1 // Copyright Alain Miniussi 2014.
2 // Distributed under the Boost Software License, Version 1.0.
3 // (See accompanying file LICENSE_1_0.txt or copy at
4 // http://www.boost.org/LICENSE_1_0.txt)
5
6 // Authors: Alain Miniussi
7
8 /** @file cartesian_communicator.hpp
9 *
10 * This header defines facilities to support MPI communicators with
11 * cartesian topologies.
12 * If known at compiled time, the dimension of the implied grid
13 * can be statically enforced, through the templatized communicator
14 * class. Otherwise, a non template, dynamic, base class is provided.
15 *
16 */
17 #ifndef BOOST_MPI_CARTESIAN_COMMUNICATOR_HPP
18 #define BOOST_MPI_CARTESIAN_COMMUNICATOR_HPP
19
20 #include <boost/mpi/communicator.hpp>
21
22 #include <vector>
23 #include <utility>
24 #include <iostream>
25 #include <utility>
26 #if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST)
27 #include <initializer_list>
28 #endif // BOOST_NO_CXX11_HDR_INITIALIZER_LIST
29
30 // Headers required to implement cartesian topologies
31 #include <boost/shared_array.hpp>
32 #include <boost/assert.hpp>
33 #include <boost/foreach.hpp>
34
35 namespace boost { namespace mpi {
36
37 /**
38 * @brief Specify the size and periodicity of the grid in a single dimension.
39 *
40 * POD lightweight object.
41 */
42 struct cartesian_dimension {
43 /** The size of the grid n this dimension. */
44 int size;
45 /** Is the grid periodic in this dimension. */
46 bool periodic;
47
cartesian_dimensionboost::mpi::cartesian_dimension48 cartesian_dimension(int sz = 0, bool p = false) : size(sz), periodic(p) {}
49
50 private:
51 friend class boost::serialization::access;
52 template<class Archive>
serializeboost::mpi::cartesian_dimension53 void serialize(Archive & ar, const unsigned int version)
54 {
55 ar & size & periodic;
56 }
57
58 };
59
60 template <>
61 struct is_mpi_datatype<cartesian_dimension> : mpl::true_ { };
62
63 /**
64 * @brief Test if the dimensions values are identical.
65 */
66 inline
67 bool
operator ==(cartesian_dimension const & d1,cartesian_dimension const & d2)68 operator==(cartesian_dimension const& d1, cartesian_dimension const& d2) {
69 return &d1 == &d2 || (d1.size == d2.size && d1.periodic == d2.periodic);
70 }
71
72 /**
73 * @brief Test if the dimension values are different.
74 */
75 inline
76 bool
operator !=(cartesian_dimension const & d1,cartesian_dimension const & d2)77 operator!=(cartesian_dimension const& d1, cartesian_dimension const& d2) {
78 return !(d1 == d2);
79 }
80
81 /**
82 * @brief Pretty printing of a cartesian dimension (size, periodic)
83 */
84 std::ostream& operator<<(std::ostream& out, cartesian_dimension const& d);
85
86 /**
87 * @brief Describe the topology of a cartesian grid.
88 *
89 * Behave mostly like a sequence of @c cartesian_dimension with the notable
90 * exception that its size is fixed.
91 * This is a lightweight object, so that any constructor that could be considered
92 * missing could be replaced with a function (move constructor provided when supported).
93 */
94 class BOOST_MPI_DECL cartesian_topology
95 : private std::vector<cartesian_dimension> {
96 friend class cartesian_communicator;
97 typedef std::vector<cartesian_dimension> super;
98 public:
99 /**
100 * Retrieve a specific dimension.
101 */
102 using super::operator[];
103 /**
104 * @brief Topology dimentionality.
105 */
106 using super::size;
107 using super::begin;
108 using super::end;
109 using super::swap;
110
111 #if !defined(BOOST_NO_CXX11_DELETED_FUNCTIONS)
112 cartesian_topology() = delete;
113 #endif
114 #if !defined(BOOST_NO_CXX11_DEFAULTED_FUNCTIONS)
115 cartesian_topology(cartesian_topology const&) = default;
116 cartesian_topology& operator=(cartesian_topology const&) = default;
117 // There is apparently no macro for checking the support of move constructor.
118 // Assume that defaulted function is close enough.
119 #if !defined(BOOST_NO_CXX11_DEFAULTED_MOVES)
cartesian_topology(cartesian_topology && other)120 cartesian_topology(cartesian_topology&& other) : super(other) {}
operator =(cartesian_topology && other)121 cartesian_topology& operator=(cartesian_topology&& other) {
122 stl().swap(other.stl());
123 return *this;
124 }
125 #endif
126 ~cartesian_topology() = default;
127 #endif
128 /**
129 * @brief Create a N dimension space.
130 * Each dimension is initialized as non periodic of size 0.
131 */
cartesian_topology(int ndim)132 cartesian_topology(int ndim)
133 : super(ndim) {}
134
135 /**
136 * @brief Use the provided dimensions specification as initial values.
137 */
cartesian_topology(std::vector<cartesian_dimension> const & dims)138 cartesian_topology(std::vector<cartesian_dimension> const& dims)
139 : super(dims) {}
140
141 /**
142 * @brief Use dimensions specification provided in the sequence container as initial values.
143 * #param dims must be a sequence container.
144 */
145 template<class InitArr>
cartesian_topology(InitArr dims)146 explicit cartesian_topology(InitArr dims)
147 : super(0) {
148 BOOST_FOREACH(cartesian_dimension const& d, dims) {
149 push_back(d);
150 }
151 }
152 #if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST)
153 /**
154 * @brief Use dimensions specification provided in the initialization list as initial values.
155 * #param dims can be of the form { dim_1, false}, .... {dim_n, true}
156 */
cartesian_topology(std::initializer_list<cartesian_dimension> dims)157 explicit cartesian_topology(std::initializer_list<cartesian_dimension> dims)
158 : super(dims) {}
159 #endif
160 /**
161 * @brief Use dimensions specification provided in the array.
162 * #param dims can be of the form { dim_1, false}, .... {dim_n, true}
163 */
164 template<int NDIM>
cartesian_topology(cartesian_dimension (& dims)[NDIM])165 explicit cartesian_topology(cartesian_dimension (&dims)[NDIM])
166 : super(dims, dims+NDIM) {}
167
168 /**
169 * @brief Use dimensions specification provided in the input ranges
170 * The ranges do not need to be the same size. If the sizes are different,
171 * the missing values will be complete with zeros of the dim and assumed non periodic.
172 * @param dim_rg the dimensions, values must convert to integers.
173 * @param period_rg the periodicities, values must convert to booleans.
174 * #param dims can be of the form { dim_1, false}, .... {dim_n, true}
175 */
176 template<class DimRg, class PerRg>
cartesian_topology(DimRg const & dim_rg,PerRg const & period_rg)177 cartesian_topology(DimRg const& dim_rg, PerRg const& period_rg)
178 : super(0) {
179 BOOST_FOREACH(int d, dim_rg) {
180 super::push_back(cartesian_dimension(d));
181 }
182 super::iterator it = begin();
183 BOOST_FOREACH(bool p, period_rg) {
184 if (it < end()) {
185 it->periodic = p;
186 } else {
187 push_back(cartesian_dimension(0,p));
188 }
189 ++it;
190 }
191 }
192
193
194 /**
195 * @brief Iterator based initializer.
196 * Will use the first n iterated values.
197 * Both iterators can be single pass.
198 * @param dit dimension iterator, value must convert to integer type.
199 * @param pit periodicity iterator, value must convert to booleans..
200 */
201 template<class DimIter, class PerIter>
cartesian_topology(DimIter dit,PerIter pit,int n)202 cartesian_topology(DimIter dit, PerIter pit, int n)
203 : super(n) {
204 for(int i = 0; i < n; ++i) {
205 (*this)[i] = cartesian_dimension(*dit++, *pit++);
206 }
207 }
208
209 /**
210 * Export as an stl sequence.
211 */
stl()212 std::vector<cartesian_dimension>& stl() { return *this; }
213 /**
214 * Export as an stl sequence.
215 */
stl() const216 std::vector<cartesian_dimension> const& stl() const{ return *this; }
217 /**
218 * Split the topology in two sequences of sizes and periodicities.
219 */
220 void split(std::vector<int>& dims, std::vector<bool>& periodics) const;
221 };
222
223 inline
224 bool
operator ==(cartesian_topology const & t1,cartesian_topology const & t2)225 operator==(cartesian_topology const& t1, cartesian_topology const& t2) {
226 return t1.stl() == t2.stl();
227 }
228
229 inline
230 bool
operator !=(cartesian_topology const & t1,cartesian_topology const & t2)231 operator!=(cartesian_topology const& t1, cartesian_topology const& t2) {
232 return t1.stl() != t2.stl();
233 }
234
235 /**
236 * @brief Pretty printing of a cartesian topology
237 */
238 std::ostream& operator<<(std::ostream& out, cartesian_topology const& t);
239
240 /**
241 * @brief An MPI communicator with a cartesian topology.
242 *
243 * A @c cartesian_communicator is a communicator whose topology is
244 * expressed as a grid. Cartesian communicators have the same
245 * functionality as (intra)communicators, but also allow one to query
246 * the relationships among processes and the properties of the grid.
247 */
248 class BOOST_MPI_DECL cartesian_communicator : public communicator
249 {
250 friend class communicator;
251
252 /**
253 * INTERNAL ONLY
254 *
255 * Construct a cartesian communicator given a shared pointer to the
256 * underlying MPI_Comm (which must have a cartesian topology).
257 * This operation is used for "casting" from a communicator to
258 * a cartesian communicator.
259 */
cartesian_communicator(const shared_ptr<MPI_Comm> & comm_ptr)260 explicit cartesian_communicator(const shared_ptr<MPI_Comm>& comm_ptr)
261 : communicator()
262 {
263 this->comm_ptr = comm_ptr;
264 BOOST_ASSERT(has_cartesian_topology());
265 }
266
267 public:
268 /**
269 * Build a new Boost.MPI cartesian communicator based on the MPI
270 * communicator @p comm with cartesian topology.
271 *
272 * @p comm may be any valid MPI communicator. If @p comm is
273 * MPI_COMM_NULL, an empty communicator (that cannot be used for
274 * communication) is created and the @p kind parameter is
275 * ignored. Otherwise, the @p kind parameter determines how the
276 * Boost.MPI communicator will be related to @p comm:
277 *
278 * - If @p kind is @c comm_duplicate, duplicate @c comm to create
279 * a new communicator. This new communicator will be freed when
280 * the Boost.MPI communicator (and all copies of it) is
281 * destroyed. This option is only permitted if the underlying MPI
282 * implementation supports MPI 2.0; duplication of
283 * intercommunicators is not available in MPI 1.x.
284 *
285 * - If @p kind is @c comm_take_ownership, take ownership of @c
286 * comm. It will be freed automatically when all of the Boost.MPI
287 * communicators go out of scope.
288 *
289 * - If @p kind is @c comm_attach, this Boost.MPI communicator
290 * will reference the existing MPI communicator @p comm but will
291 * not free @p comm when the Boost.MPI communicator goes out of
292 * scope. This option should only be used when the communicator is
293 * managed by the user.
294 */
cartesian_communicator(const MPI_Comm & comm,comm_create_kind kind)295 cartesian_communicator(const MPI_Comm& comm, comm_create_kind kind)
296 : communicator(comm, kind)
297 {
298 BOOST_ASSERT(has_cartesian_topology());
299 }
300
301 /**
302 * Create a new communicator whose topology is described by the
303 * given cartesian. The indices of the vertices in the cartesian will be
304 * assumed to be the ranks of the processes within the
305 * communicator. There may be fewer vertices in the cartesian than
306 * there are processes in the communicator; in this case, the
307 * resulting communicator will be a NULL communicator.
308 *
309 * @param comm The communicator that the new, cartesian communicator
310 * will be based on.
311 *
312 * @param dims the cartesian dimension of the new communicator. The size indicate
313 * the number of dimension. Some dimensions be set to zero, in which case
314 * the corresponding dimension value is left to the system.
315 *
316 * @param reorder Whether MPI is permitted to re-order the process
317 * ranks within the returned communicator, to better optimize
318 * communication. If false, the ranks of each process in the
319 * returned process will match precisely the rank of that process
320 * within the original communicator.
321 */
322 cartesian_communicator(const communicator& comm,
323 const cartesian_topology& dims,
324 bool reorder = false);
325
326 /**
327 * Create a new cartesian communicator whose topology is a subset of
328 * an existing cartesian cimmunicator.
329 * @param comm the original communicator.
330 * @param keep and array containiing the dimension to keep from the existing
331 * communicator.
332 */
333 cartesian_communicator(const cartesian_communicator& comm,
334 const std::vector<int>& keep );
335
336 using communicator::rank;
337
338 /**
339 * Retrive the number of dimension of the underlying toppology.
340 */
341 int ndims() const;
342
343 /**
344 * Return the rank of the process at the given coordinates.
345 * @param coords the coordinates. the size must match the communicator's topology.
346 */
347 int rank(const std::vector<int>& coords) const;
348 /**
349 * Return the rank of the source and target destination process through a shift.
350 * @param dim the dimension in which the shift takes place. 0 <= dim <= ndim().
351 * @param disp the shift displacement, can be positive (upward) or negative (downward).
352 */
353 std::pair<int, int> shifted_ranks(int dim, int disp) const;
354 /**
355 * Provides the coordinates of the process with the given rank.
356 * @param rk the ranks in this communicator.
357 * @returns the coordinates.
358 */
359 std::vector<int> coordinates(int rk) const;
360 /**
361 * Retrieve the topology and coordinates of this process in the grid.
362 *
363 */
364 void topology( cartesian_topology& dims, std::vector<int>& coords ) const;
365 /**
366 * Retrieve the topology of the grid.
367 *
368 */
369 cartesian_topology topology() const;
370 };
371
372 /**
373 * Given en number of processes, and a partially filled sequence
374 * of dimension, try to complete the dimension sequence.
375 * @param nb_proc the numer of mpi processes.fill a sequence of dimension.
376 * @param dims a sequence of positive or null dimensions. Non zero dimension
377 * will be left untouched.
378 */
379 std::vector<int>& cartesian_dimensions(int nb_proc, std::vector<int>& dims);
380
381 } } // end namespace boost::mpi
382
383 #endif // BOOST_MPI_CARTESIAN_COMMUNICATOR_HPP
384