Skip to main content

mpi/
environment.rs

1//! Process-wide MPI environment: initialization, finalization and the
2//! [`Universe`] handle. Mirrors `mpi::environment` in rsmpi.
3
4use crate::topology::SystemCommunicator;
5use crate::transport;
6use crate::MpiError;
7
8/// The level of thread support, matching the four MPI constants
9/// (`MPI_THREAD_SINGLE`, …, `MPI_THREAD_MULTIPLE`).
10///
11/// This implementation is thread-safe throughout and always provides
12/// [`Threading::Multiple`]; the requested level is recorded for compatibility.
13#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
14pub enum Threading {
15    /// Only one thread will execute (`MPI_THREAD_SINGLE`).
16    Single,
17    /// The process may be multithreaded, but only the main thread makes MPI
18    /// calls (`MPI_THREAD_FUNNELED`).
19    Funneled,
20    /// Multiple threads may make MPI calls, but not concurrently
21    /// (`MPI_THREAD_SERIALIZED`).
22    Serialized,
23    /// Multiple threads may call MPI concurrently (`MPI_THREAD_MULTIPLE`).
24    Multiple,
25}
26
27impl Threading {
28    /// The maximum thread-support level this library provides.
29    pub const fn max() -> Threading {
30        Threading::Multiple
31    }
32}
33
34/// Represents the initialized MPI environment.
35///
36/// The environment is finalized when the `Universe` is dropped, mirroring
37/// rsmpi where `Universe` owns the lifetime of `MPI_Init` / `MPI_Finalize`.
38pub struct Universe {
39    threading: Threading,
40}
41
42impl Universe {
43    /// The `MPI_COMM_WORLD` communicator containing every process in the job.
44    pub fn world(&self) -> SystemCommunicator {
45        SystemCommunicator::world()
46    }
47
48    /// The thread-support level actually provided.
49    pub fn threading_support(&self) -> Threading {
50        self.threading
51    }
52
53    /// The size of the universe (number of process slots), if known. Mirrors
54    /// rsmpi's `Universe::size`; here it is the world size.
55    pub fn size(&self) -> Option<usize> {
56        Some(transport::runtime().size as usize)
57    }
58
59    /// Attach a buffer of `size` bytes for use by buffered sends
60    /// (`MPI_Buffer_attach`).
61    pub fn buffer_attach(&self, size: usize) {
62        transport::runtime().buffer_attach(size);
63    }
64
65    /// The current buffered-send buffer size (`MPI_Buffer_attach` accounting).
66    pub fn buffer_size(&self) -> usize {
67        transport::runtime().buffer_size()
68    }
69
70    /// Set the buffered-send buffer size, mirroring rsmpi 0.8.x's
71    /// `Universe::set_buffer_size`.
72    pub fn set_buffer_size(&mut self, size: usize) {
73        transport::runtime().set_buffer_size(size);
74    }
75
76    /// Detach the buffered-send buffer, returning its size
77    /// (`MPI_Buffer_detach`).
78    pub fn detach_buffer(&self) -> usize {
79        transport::runtime().buffer_detach()
80    }
81}
82
83impl Drop for Universe {
84    fn drop(&mut self) {
85        // `MPI_Finalize` is collective: synchronize so that every rank has
86        // reached finalization before any tears down. Because the transport's
87        // reader threads drain all incoming messages before a rank can pass the
88        // barrier, this also guarantees messages sent before finalize are
89        // delivered (no silent loss on exit).
90        use crate::collective::CommunicatorCollectives;
91        use crate::topology::Communicator;
92        let world = SystemCommunicator::world();
93        if world.size() > 1 {
94            world.barrier();
95        }
96    }
97}
98
99/// Monotonic time in seconds since an arbitrary fixed point (`MPI_Wtime`).
100///
101/// Uses a monotonic clock so successive readings never go backwards, which is
102/// what timing/benchmarking code requires.
103pub fn time() -> f64 {
104    static START: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
105    START
106        .get_or_init(std::time::Instant::now)
107        .elapsed()
108        .as_secs_f64()
109}
110
111/// The resolution of [`time`] in seconds (`MPI_Wtick`).
112pub fn time_resolution() -> f64 {
113    1e-9
114}
115
116/// The thread-support level provided by the running environment
117/// (`MPI_Query_thread`).
118pub fn threading_support() -> Threading {
119    transport::runtime().threading
120}
121
122/// A name identifying the calling processor (`MPI_Get_processor_name`).
123pub fn processor_name() -> Result<String, MpiError> {
124    let host = std::env::var("HOSTNAME")
125        .ok()
126        .or_else(|| std::env::var("HOST").ok())
127        .unwrap_or_else(|| "localhost".to_string());
128    Ok(host.to_string())
129}
130
131/// A human-readable description of the MPI library (`MPI_Get_library_version`).
132pub fn library_version() -> Result<String, MpiError> {
133    Ok(format!(
134        "pure-rust-mpi {} (rsmpi-compatible, no C library)",
135        env!("CARGO_PKG_VERSION")
136    ))
137}
138
139/// The supported MPI standard version as `(major, minor)` (`MPI_Get_version`).
140pub fn version() -> (i32, i32) {
141    (3, 1)
142}
143
144/// Initialize the MPI environment with the default (maximal) thread support,
145/// returning the [`Universe`] handle. Equivalent to rsmpi's
146/// [`crate::initialize`].
147///
148/// Returns `None` if MPI has already been initialized in this process.
149pub fn initialize() -> Option<Universe> {
150    initialize_with_threading(Threading::Multiple).map(|(u, _)| u)
151}
152
153/// Initialize the MPI environment requesting a given [`Threading`] level.
154/// Returns the [`Universe`] and the level actually provided, mirroring
155/// rsmpi's [`crate::initialize_with_threading`].
156pub fn initialize_with_threading(requested: Threading) -> Option<(Universe, Threading)> {
157    match transport::init(requested) {
158        Ok(()) => {
159            let provided = transport::runtime().threading;
160            Some((
161                Universe {
162                    threading: provided,
163                },
164                provided,
165            ))
166        }
167        Err(MpiError::AlreadyInitialized) => None,
168        Err(e) => {
169            // Bootstrap failures are fatal, like a failed `MPI_Init`.
170            panic!("mpi::initialize failed: {e}");
171        }
172    }
173}