mpi_fork_fnsp/topology/cartesian.rs
1use super::{AsCommunicator, Communicator, IntoTopology, Rank, UserCommunicator};
2use crate::ffi::MPI_Comm;
3use crate::raw::traits::AsRaw;
4use crate::{
5 datatype::traits::Collection, ffi, with_uninitialized, with_uninitialized2, Count, IntArray,
6};
7use conv::ConvUtil;
8use std::mem;
9
10/// Contains arrays describing the layout of the
11/// [`CartesianCommunicator`](struct.CartesianCommunicator.html).
12///
13/// `dims[i]` is the extent of the array in axis `i`, `periods[i]` is `true` if axis `i` is periodic, and
14/// `coords[i]` is the cartesian coordinate for the local rank in axis `i`.
15///
16/// Each array, when received from a method in
17/// [`CartesianCommunicator`](struct.CartesianCommunicator.html), will be of length
18/// [`num_dimensions`](struct.CartesianCommunicator.html#method.num_dimensions).
19#[allow(clippy::module_name_repetitions)]
20pub struct CartesianLayout {
21 /// `dims[i]` is the extent of the array in axis `i`
22 pub dims: Vec<Count>,
23 /// `periods[i]` is `true` if axis `i` is periodic, meaning an element at `dims[i] - 1` in axis `i`
24 /// is neighbors with element 0 in axis `i`
25 pub periods: Vec<bool>,
26 /// `coords[i]` is the cartesian coordinate for the local rank in axis `i`
27 pub coords: Vec<Count>,
28}
29
30/// A `CartesianCommunicator` is an MPI communicator object where ranks are laid out in an
31/// n-dimensional cartesian space. This gives ranks neighbors in each of those dimensions, and MPI
32/// is able to optimize the layout of these ranks to improve physical locality.
33///
34/// # Standard Section(s)
35///
36/// 7
37#[allow(clippy::module_name_repetitions)]
38pub struct CartesianCommunicator(pub(crate) UserCommunicator);
39
40impl CartesianCommunicator {
41 /// Given a valid `MPI_Comm` handle in `raw`, returns a `CartesianCommunicator` value if, and
42 /// only if:
43 /// - The handle is not `MPI_COMM_NULL`
44 /// - The topology of the communicator is `MPI_CART`
45 ///
46 /// Otherwise returns None.
47 ///
48 /// # Parameters
49 /// * `raw` - Handle to a valid `MPI_Comm` object
50 ///
51 /// # Safety
52 /// - `raw` must be a live `MPI_Comm` object.
53 /// - `raw` must not be used after calling `from_raw`.
54 pub unsafe fn from_raw(raw: MPI_Comm) -> Option<CartesianCommunicator> {
55 UserCommunicator::from_raw(raw).and_then(|comm| match comm.into_topology() {
56 IntoTopology::Cartesian(c) => Some(c),
57 incorrect => {
58 // Forget the comm object so it's not dropped
59 mem::forget(incorrect);
60
61 None
62 }
63 })
64 }
65
66 /// Creates a `CartesianCommunicator` from `raw`.
67 ///
68 /// # Parameters
69 /// * `raw` - Handle to a valid `MPI_CART` `MPI_Comm` object
70 ///
71 /// # Safety
72 /// - `raw` must be a live `MPI_Comm object`.
73 /// - `raw` must not be used after calling `from_raw_unchecked`.
74 /// - `raw` must not be `MPI_COMM_NULL`.
75 ///
76 /// # Panics
77 /// raw must not be `RSMPI_COMM_NULL`
78 pub unsafe fn from_raw_unchecked(raw: MPI_Comm) -> CartesianCommunicator {
79 debug_assert_ne!(raw, ffi::RSMPI_COMM_NULL);
80 CartesianCommunicator(UserCommunicator::from_raw_unchecked(raw))
81 }
82
83 /// Returns the number of dimensions that the Cartesian communicator was established over.
84 ///
85 /// # Standard section(s)
86 /// 7.5.5 `MPI_Cartdim_get`
87 pub fn num_dimensions(&self) -> Count {
88 unsafe { with_uninitialized(|count| ffi::MPI_Cartdim_get(self.as_raw(), count)).1 }
89 }
90
91 /// Returns the topological structure of the Cartesian communicator
92 ///
93 /// Prefer [`get_layout_into`](#method.get_layout_into)
94 ///
95 /// # Parameters
96 /// * `dims` - array of spatial extents for the cartesian space
97 /// * `periods` - Must match length of `dims`. `periods[i]` indicates if axis i is periodic.
98 /// i.e. if true, the element at `dims[i] - 1` in axis i is a neighbor of element 0 in axis
99 /// i
100 /// * `coords` - `coords[i]` is the location offset in axis i of this rank
101 ///
102 /// # Standard section(s)
103 /// 7.5.5 `MPI_Cart_get`
104 ///
105 /// # Safety
106 /// Behavior is undefined if `dims`, `periods`, and `coords` are not of length
107 /// [`num_dimensions`](#method.num_dimensions).
108 ///
109 /// # Panics
110 /// Invalid boolean value ({}) from the MPI implementation
111 pub unsafe fn get_layout_into_unchecked(
112 &self,
113 dims: &mut [Count],
114 periods: &mut [bool],
115 coords: &mut [Count],
116 ) {
117 let mut periods_int: IntArray = smallvec::smallvec![0; periods.len()];
118
119 ffi::MPI_Cart_get(
120 self.as_raw(),
121 self.num_dimensions(),
122 dims.as_mut_ptr(),
123 periods_int.as_mut_ptr(),
124 coords.as_mut_ptr(),
125 );
126
127 for (p, pi) in periods.iter_mut().zip(periods_int.iter()) {
128 *p = match pi {
129 0 => false,
130 1 => true,
131 _ => panic!(
132 "Received an invalid boolean value ({}) from the MPI implementation",
133 pi
134 ),
135 }
136 }
137 }
138
139 /// Returns the topological structure of the Cartesian communicator
140 ///
141 /// # Panics
142 /// if `dims`, `periods`, and `coords` are not of length
143 /// [`num_dimensions`](#method.num_dimensions).
144 ///
145 /// # Parameters
146 /// * `dims` - array of spatial extents for the cartesian space
147 /// * `periods` - Must match length of `dims`. `periods[i]` indicates if axis i is periodic.
148 /// i.e. if true, the element at `dims[i] - 1` in axis i is a neighbor of element 0 in axis
149 /// i
150 /// * `coords` - `coords[i]` is the location offset in axis i of this rank
151 ///
152 /// # Standard section(s)
153 /// 7.5.5 `MPI_Cart_get`
154 pub fn get_layout_into(&self, dims: &mut [Count], periods: &mut [bool], coords: &mut [Count]) {
155 assert_eq!(
156 dims.count(),
157 periods.count(),
158 "dims, periods, and coords must be the same length"
159 );
160 assert_eq!(
161 dims.count(),
162 coords.count(),
163 "dims, periods, and coords must be the same length"
164 );
165
166 assert_eq!(
167 self.num_dimensions(),
168 dims.count(),
169 "dims, periods, and coords must be equal in length to num_dimensions()"
170 );
171
172 unsafe { self.get_layout_into_unchecked(dims, periods, coords) }
173 }
174
175 /// Returns the topological structure of the Cartesian communicator
176 ///
177 /// # Standard section(s)
178 /// 7.5.5 [`MPI_Cart_get`]
179 pub fn get_layout(&self) -> CartesianLayout {
180 let num_dims = self
181 .num_dimensions()
182 .value_as()
183 .expect("Received unexpected value from MPI_Cartdim_get");
184
185 let mut layout = CartesianLayout {
186 dims: vec![0; num_dims],
187 periods: vec![false; num_dims],
188 coords: vec![0; num_dims],
189 };
190
191 self.get_layout_into(
192 &mut layout.dims[..],
193 &mut layout.periods[..],
194 &mut layout.coords[..],
195 );
196
197 layout
198 }
199
200 /// Converts a set of cartesian coordinates to its rank in the `CartesianCommunicator`.
201 ///
202 /// Coordinates in periodic axes that are out of range are shifted back into the dimensions of
203 /// the communiactor.
204 ///
205 /// Prefer [`coordinates_to_rank`](#method.coordinates_to_rank)
206 ///
207 /// # Parameters
208 /// * `coords` - `coords[i]` is a location offset in axis i
209 ///
210 /// # Standard section(s)
211 /// 7.5.5 `MPI_Cart_rank`
212 ///
213 /// # Safety
214 /// - Behavior is undefined if `coords` is not of length
215 /// [`num_dimensions`](#method.num_dimensions).
216 /// - Behavior is undefined if any coordinates in non-periodic axes are outside the dimensions
217 // of the communicator.
218 pub unsafe fn coordinates_to_rank_unchecked(&self, coords: &[Count]) -> Rank {
219 with_uninitialized(|rank| ffi::MPI_Cart_rank(self.as_raw(), coords.as_ptr(), rank)).1
220 }
221
222 /// Converts a set of cartesian coordinates to its rank in the `CartesianCommunicator`.
223 ///
224 /// Panics if `coords` is not of length [`num_dimensions`](#method.num_dimensions).
225 ///
226 /// Coordinates in periodic axes that are out of range are shifted back into the dimensions of
227 /// the communiactor.
228 ///
229 /// # Panics
230 /// if any coordinates in non-periodic axes are outside the dimensions of the
231 /// communicator.
232 ///
233 /// # Parameters
234 /// * `coords` - `coords[i]` is a location offset in axis i
235 ///
236 /// # Standard section(s)
237 /// 7.5.5 `MPI_Cart_rank`
238 pub fn coordinates_to_rank(&self, coords: &[Count]) -> Rank {
239 let num_dims: usize = self
240 .num_dimensions()
241 .value_as()
242 .expect("Received unexpected value from MPI_Cartdim_get");
243
244 assert_eq!(
245 num_dims,
246 coords.len(),
247 "The coordinates slice must be the same length as the number of dimension in the \
248 CartesianCommunicator"
249 );
250
251 let layout = self.get_layout();
252
253 for (i, coord) in coords.iter().enumerate() {
254 if !layout.periods[i] {
255 assert!(
256 *coord > 0,
257 "The non-periodic coordinate (coords[{}] = {}) must be greater than 0.",
258 i,
259 *coord
260 );
261 assert!(
262 *coord <= layout.dims[i],
263 "The non-period coordinate (coords[{}] = {}) must be within the bounds of the \
264 CartesianCoordinator (dims[{}] = {})",
265 i,
266 *coord,
267 i,
268 layout.dims[i]
269 );
270 }
271 }
272
273 unsafe { self.coordinates_to_rank_unchecked(coords) }
274 }
275
276 /// Receives into `coords` the cartesian coordinates of `rank`.
277 ///
278 /// Prefer [`rank_to_coordinates_into`](#method.rank_to_coordinates_into)
279 ///
280 /// # Parameters
281 /// * `rank` - A rank in the communicator
282 /// * `coords` - `coords[i]` is the cartesian coordinate of `rank` in axis i
283 ///
284 /// # Standard section(s)
285 /// 7.5.5 `MPI_Cart_coords`
286 ///
287 /// # Safety
288 /// Behavior is undefined if `rank` is not a non-negative value less than
289 /// [`size`](trait.Communicator.html#method.size).
290 pub unsafe fn rank_to_coordinates_into_unchecked(&self, rank: Rank, coords: &mut [Count]) {
291 ffi::MPI_Cart_coords(self.as_raw(), rank, coords.count(), coords.as_mut_ptr());
292 }
293
294 /// Receives into `coords` the cartesian coordinates of `rank`.
295 ///
296 /// # Panics
297 /// if `rank` is not a non-negative value less than
298 /// [`size`](trait.Communicator.html#method.size).
299 ///
300 /// # Parameters
301 /// * `rank` - A rank in the communicator
302 /// * `coords` - `coords[i]` is the cartesian coordinate of `rank` in axis i
303 ///
304 /// # Standard section(s)
305 /// 7.5.5 `MPI_Cart_coords`
306 pub fn rank_to_coordinates_into(&self, rank: Rank, coords: &mut [Count]) {
307 assert!(
308 rank >= 0 && rank < self.size(),
309 "rank ({}) must be in the range [0,{})",
310 rank,
311 self.size()
312 );
313
314 unsafe { self.rank_to_coordinates_into_unchecked(rank, coords) }
315 }
316
317 /// Returns an array of `coords` with the cartesian coordinates of `rank`, where `coords[i]` is
318 /// the cartesian coordinate of `rank` in axis i.
319 ///
320 /// # Panics
321 /// if `rank` is not a non-negative value less than
322 /// [`size`](trait.Communicator.html#method.size).
323 ///
324 /// # Parameters
325 /// * `rank` - A rank in the communicator
326 ///
327 /// # Standard section(s)
328 /// 7.5.5 `MPI_Cart_coords`
329 pub fn rank_to_coordinates(&self, rank: Rank) -> Vec<Count> {
330 let mut coords = vec![
331 0;
332 self.num_dimensions()
333 .value_as()
334 .expect("Received unexpected value from MPI_Cartdim_get")
335 ];
336
337 self.rank_to_coordinates_into(rank, &mut coords[..]);
338
339 coords
340 }
341
342 /// Retrieves targets in `dimension` shifted from the current rank by displacing in the negative
343 /// direction by `displacement` units for the first returned rank and in the positive direction
344 /// for the second returned rank.
345 ///
346 /// Prefer [`shift`](#method.shift)
347 ///
348 /// # Parameters
349 /// * `dimension` - which axis to shift in
350 /// * `displacement` - what offset to shift by in each direction
351 ///
352 /// # Standard section(s)
353 /// 7.5.6 `MPI_Cart_shift`
354 ///
355 /// # Safety
356 /// Behavior is undefined if `dimension` is not of length
357 /// [`num_dimensions`](#method.num_dimensions).
358 pub unsafe fn shift_unchecked(
359 &self,
360 dimension: Count,
361 displacement: Count,
362 ) -> (Option<Rank>, Option<Rank>) {
363 let (_, rank_source, rank_destination) =
364 with_uninitialized2(|rank_source, rank_destination| {
365 ffi::MPI_Cart_shift(
366 self.as_raw(),
367 dimension,
368 displacement,
369 rank_source,
370 rank_destination,
371 )
372 });
373
374 let rank_source = if rank_source == ffi::RSMPI_PROC_NULL {
375 None
376 } else {
377 Some(rank_source)
378 };
379
380 let rank_destination = if rank_destination == ffi::RSMPI_PROC_NULL {
381 None
382 } else {
383 Some(rank_destination)
384 };
385
386 (rank_source, rank_destination)
387 }
388
389 /// Retrieves targets in `dimension` shifted from the current rank by displacing in the negative
390 /// direction by `displacement` units for the first returned rank and in the positive direction
391 /// for the second returned rank.
392 ///
393 /// # Parameters
394 /// * `dimension` - which axis to shift in
395 /// * `displacement` - what offset to shift by in each direction
396 ///
397 /// # Standard section(s)
398 /// 7.5.6 `MPI_Cart_shift`
399 ///
400 /// # Panics
401 /// if `dimension` is not of length [`num_dimensions`](#method.num_dimensions).
402 pub fn shift(&self, dimension: Count, displacement: Count) -> (Option<Rank>, Option<Rank>) {
403 assert!(
404 dimension >= 0,
405 "dimension ({}) cannot be negative",
406 dimension
407 );
408
409 assert!(
410 dimension < self.num_dimensions(),
411 "dimension ({}) is not valid for this communicator (num_dimensions = {})",
412 dimension,
413 self.num_dimensions(),
414 );
415
416 unsafe { self.shift_unchecked(dimension, displacement) }
417 }
418
419 /// Partitions an existing Cartesian communicator into a new Cartesian communicator in a lower
420 /// dimension.
421 ///
422 /// Prefer [`subgroup`](#method.subgroup)
423 ///
424 /// # Parameters
425 /// * `retain` - if `retain[i]` is true, then axis i is retained in the new communicator
426 ///
427 /// # Standard section(s)
428 /// 7.5.7 `MPI_Cart_sub`
429 ///
430 /// # Safety
431 /// Behavior is undefined if `retain` is not of length
432 /// [`num_dimensions`](#method.num_dimensions).
433 pub unsafe fn subgroup_unchecked(&self, retain: &[bool]) -> CartesianCommunicator {
434 let retain_int: IntArray = retain.iter().map(|b| *b as _).collect();
435
436 CartesianCommunicator::from_raw_unchecked(
437 with_uninitialized(|newcomm| {
438 ffi::MPI_Cart_sub(self.as_raw(), retain_int.as_ptr(), newcomm)
439 })
440 .1,
441 )
442 }
443
444 /// Partitions an existing Cartesian communicator into a new Cartesian communicator in a lower
445 /// dimension.
446 ///
447 /// # Panics
448 /// if `retain` is not of length [`num_dimensions`](#method.num_dimensions).
449 ///
450 /// # Parameters
451 /// * `retain` - if `retain[i]` is true, then axis i is retained in the new communicator
452 ///
453 /// # Standard section(s)
454 /// 7.5.7 `MPI_Cart_sub`
455 pub fn subgroup(&self, retain: &[bool]) -> CartesianCommunicator {
456 assert_eq!(
457 self.num_dimensions(),
458 retain.count(),
459 "The length of the retained dimensions array must be equal to the number of dimensions \
460 in the CartesianCommunicator");
461
462 unsafe { self.subgroup_unchecked(retain) }
463 }
464}
465
466impl Communicator for CartesianCommunicator {}
467
468impl AsCommunicator for CartesianCommunicator {
469 type Out = CartesianCommunicator;
470 fn as_communicator(&self) -> &Self::Out {
471 self
472 }
473}
474
475unsafe impl AsRaw for CartesianCommunicator {
476 type Raw = MPI_Comm;
477 fn as_raw(&self) -> Self::Raw {
478 self.0.as_raw()
479 }
480}