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}