prism_q/distributed/
comm.rs1use num_complex::Complex64;
10
11#[cfg(feature = "distributed-mpi")]
12use crate::error::{PrismError, Result};
13#[cfg(feature = "distributed-mpi")]
14use mpi::environment::Threading;
15
16pub trait RankComm: std::fmt::Debug + Send + Sync {
21 fn rank(&self) -> usize;
23
24 fn size(&self) -> usize;
26
27 fn allgather_c64(&self, local: &[Complex64]) -> Vec<Complex64>;
32
33 fn allgather_f64(&self, local: &[f64]) -> Vec<f64>;
35
36 fn allgatherv_u64(&self, local: &[u64], counts: &[usize]) -> Vec<u64>;
43
44 fn allgather_u64(&self, local: &[u64]) -> Vec<u64> {
46 self.allgatherv_u64(local, &vec![local.len(); self.size()])
47 }
48
49 fn allreduce_sum_f64(&self, value: f64) -> f64;
51
52 fn sendrecv_c64(&self, partner: usize, send: &[Complex64], recv: &mut [Complex64]);
57
58 fn barrier(&self);
60}
61
62#[derive(Debug, Default, Clone, Copy)]
64pub struct SerialComm;
65
66impl RankComm for SerialComm {
67 #[inline]
68 fn rank(&self) -> usize {
69 0
70 }
71
72 #[inline]
73 fn size(&self) -> usize {
74 1
75 }
76
77 #[inline]
78 fn allgather_c64(&self, local: &[Complex64]) -> Vec<Complex64> {
79 local.to_vec()
80 }
81
82 #[inline]
83 fn allgather_f64(&self, local: &[f64]) -> Vec<f64> {
84 local.to_vec()
85 }
86
87 #[inline]
88 fn allgatherv_u64(&self, local: &[u64], _counts: &[usize]) -> Vec<u64> {
89 local.to_vec()
90 }
91
92 #[inline]
93 fn allreduce_sum_f64(&self, value: f64) -> f64 {
94 value
95 }
96
97 #[inline]
98 fn sendrecv_c64(&self, _partner: usize, send: &[Complex64], recv: &mut [Complex64]) {
99 debug_assert_eq!(send.len(), recv.len());
100 recv.copy_from_slice(send);
101 }
102
103 #[inline]
104 fn barrier(&self) {}
105}
106
107#[cfg(feature = "distributed-mpi")]
109const _: () = assert!(std::mem::size_of::<Complex64>() == 2 * std::mem::size_of::<f64>());
110
111#[cfg(feature = "distributed-mpi")]
113#[inline]
114fn as_f64(slice: &[Complex64]) -> &[f64] {
115 unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const f64, slice.len() * 2) }
117}
118
119#[cfg(feature = "distributed-mpi")]
120#[inline]
121fn as_f64_mut(slice: &mut [Complex64]) -> &mut [f64] {
122 unsafe { std::slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut f64, slice.len() * 2) }
124}
125
126#[cfg(feature = "distributed-mpi")]
129const REQUIRED_THREADING: Threading = Threading::Funneled;
130
131#[cfg(feature = "distributed-mpi")]
133fn check_threading(provided: Threading) -> Result<()> {
134 if provided >= REQUIRED_THREADING {
135 return Ok(());
136 }
137 Err(PrismError::IncompatibleBackend {
138 backend: "distributed".into(),
139 reason: format!(
140 "MPI thread level {provided:?} is below {REQUIRED_THREADING:?}, the minimum for \
141 running Rayon workers beside MPI"
142 ),
143 })
144}
145
146#[cfg(feature = "distributed-mpi")]
154pub struct MpiComm {
155 _universe: Option<mpi::environment::Universe>,
159 world: mpi::topology::SimpleCommunicator,
160 rank: usize,
161 size: usize,
162}
163
164#[cfg(feature = "distributed-mpi")]
165impl std::fmt::Debug for MpiComm {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 f.debug_struct("MpiComm")
168 .field("rank", &self.rank)
169 .field("size", &self.size)
170 .finish()
171 }
172}
173
174#[cfg(feature = "distributed-mpi")]
175impl MpiComm {
176 pub fn world() -> Result<Self> {
185 let (universe, provided) = mpi::environment::initialize_with_threading(REQUIRED_THREADING)
186 .ok_or_else(|| PrismError::IncompatibleBackend {
187 backend: "distributed".into(),
188 reason: "MPI is already initialized; attach to it with `MpiComm::attach_world`"
189 .into(),
190 })?;
191 check_threading(provided)?;
192 let world = universe.world();
193 Ok(Self::from_parts(Some(universe), world))
194 }
195
196 pub fn attach_world() -> Result<Option<Self>> {
207 if !mpi::environment::is_initialized() {
208 return Ok(None);
209 }
210 check_threading(mpi::environment::threading_support())?;
211 Ok(Some(Self::from_parts(
212 None,
213 mpi::topology::SimpleCommunicator::world(),
214 )))
215 }
216
217 fn from_parts(
218 universe: Option<mpi::environment::Universe>,
219 world: mpi::topology::SimpleCommunicator,
220 ) -> Self {
221 use mpi::traits::Communicator;
222 let rank = world.rank() as usize;
223 let size = world.size() as usize;
224 Self {
225 _universe: universe,
226 world,
227 rank,
228 size,
229 }
230 }
231}
232
233#[cfg(feature = "distributed-mpi")]
234impl RankComm for MpiComm {
235 fn rank(&self) -> usize {
236 self.rank
237 }
238
239 fn size(&self) -> usize {
240 self.size
241 }
242
243 fn allgather_c64(&self, local: &[Complex64]) -> Vec<Complex64> {
244 use mpi::traits::CommunicatorCollectives;
245 let mut out = vec![Complex64::new(0.0, 0.0); local.len() * self.size];
246 self.world
247 .all_gather_into(as_f64(local), as_f64_mut(&mut out));
248 out
249 }
250
251 fn allgather_f64(&self, local: &[f64]) -> Vec<f64> {
252 use mpi::traits::CommunicatorCollectives;
253 let mut out = vec![0.0_f64; local.len() * self.size];
254 self.world.all_gather_into(local, &mut out);
255 out
256 }
257
258 fn allgatherv_u64(&self, local: &[u64], counts: &[usize]) -> Vec<u64> {
259 use mpi::datatype::PartitionMut;
260 use mpi::traits::CommunicatorCollectives;
261 debug_assert_eq!(counts[self.rank], local.len());
262 let counts: Vec<mpi::Count> = counts.iter().map(|&c| c as mpi::Count).collect();
263 let mut displs = Vec::with_capacity(counts.len());
264 let mut total: mpi::Count = 0;
265 for &c in &counts {
266 displs.push(total);
267 total += c;
268 }
269 let mut out = vec![0_u64; total as usize];
270 self.world
271 .all_gather_varcount_into(local, &mut PartitionMut::new(&mut out[..], counts, displs));
272 out
273 }
274
275 fn allreduce_sum_f64(&self, value: f64) -> f64 {
276 use mpi::traits::CommunicatorCollectives;
277 let mut out = 0.0_f64;
278 self.world
279 .all_reduce_into(&value, &mut out, mpi::collective::SystemOperation::sum());
280 out
281 }
282
283 fn sendrecv_c64(&self, partner: usize, send: &[Complex64], recv: &mut [Complex64]) {
284 use mpi::point_to_point as p2p;
285 use mpi::traits::Communicator;
286 debug_assert_eq!(send.len(), recv.len());
287 let peer = self.world.process_at_rank(partner as i32);
288 p2p::send_receive_into(as_f64(send), &peer, as_f64_mut(recv), &peer);
289 }
290
291 fn barrier(&self) {
292 use mpi::traits::CommunicatorCollectives;
293 self.world.barrier();
294 }
295}
296
297#[cfg(all(test, feature = "distributed-mpi"))]
298mod threading_tests {
299 use super::*;
300
301 #[test]
304 fn thread_level_check_rejects_only_single() {
305 assert!(Threading::Single < Threading::Funneled);
306 assert!(Threading::Funneled < Threading::Serialized);
307 assert!(Threading::Serialized < Threading::Multiple);
308 assert!(check_threading(Threading::Single).is_err());
309 for level in [
310 Threading::Funneled,
311 Threading::Serialized,
312 Threading::Multiple,
313 ] {
314 assert!(check_threading(level).is_ok(), "{level:?}");
315 }
316 }
317}