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}