use num_complex::Complex64;
#[cfg(feature = "distributed-mpi")]
use crate::error::{PrismError, Result};
#[cfg(feature = "distributed-mpi")]
use mpi::environment::Threading;
pub trait RankComm: std::fmt::Debug + Send + Sync {
fn rank(&self) -> usize;
fn size(&self) -> usize;
fn allgather_c64(&self, local: &[Complex64]) -> Vec<Complex64>;
fn allgather_f64(&self, local: &[f64]) -> Vec<f64>;
fn allgatherv_u64(&self, local: &[u64], counts: &[usize]) -> Vec<u64>;
fn allgather_u64(&self, local: &[u64]) -> Vec<u64> {
self.allgatherv_u64(local, &vec![local.len(); self.size()])
}
fn allreduce_sum_f64(&self, value: f64) -> f64;
fn sendrecv_c64(&self, partner: usize, send: &[Complex64], recv: &mut [Complex64]);
fn barrier(&self);
}
#[derive(Debug, Default, Clone, Copy)]
pub struct SerialComm;
impl RankComm for SerialComm {
#[inline]
fn rank(&self) -> usize {
0
}
#[inline]
fn size(&self) -> usize {
1
}
#[inline]
fn allgather_c64(&self, local: &[Complex64]) -> Vec<Complex64> {
local.to_vec()
}
#[inline]
fn allgather_f64(&self, local: &[f64]) -> Vec<f64> {
local.to_vec()
}
#[inline]
fn allgatherv_u64(&self, local: &[u64], _counts: &[usize]) -> Vec<u64> {
local.to_vec()
}
#[inline]
fn allreduce_sum_f64(&self, value: f64) -> f64 {
value
}
#[inline]
fn sendrecv_c64(&self, _partner: usize, send: &[Complex64], recv: &mut [Complex64]) {
debug_assert_eq!(send.len(), recv.len());
recv.copy_from_slice(send);
}
#[inline]
fn barrier(&self) {}
}
#[cfg(feature = "distributed-mpi")]
const _: () = assert!(std::mem::size_of::<Complex64>() == 2 * std::mem::size_of::<f64>());
#[cfg(feature = "distributed-mpi")]
#[inline]
fn as_f64(slice: &[Complex64]) -> &[f64] {
unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const f64, slice.len() * 2) }
}
#[cfg(feature = "distributed-mpi")]
#[inline]
fn as_f64_mut(slice: &mut [Complex64]) -> &mut [f64] {
unsafe { std::slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut f64, slice.len() * 2) }
}
#[cfg(feature = "distributed-mpi")]
const REQUIRED_THREADING: Threading = Threading::Funneled;
#[cfg(feature = "distributed-mpi")]
fn check_threading(provided: Threading) -> Result<()> {
if provided >= REQUIRED_THREADING {
return Ok(());
}
Err(PrismError::IncompatibleBackend {
backend: "distributed".into(),
reason: format!(
"MPI thread level {provided:?} is below {REQUIRED_THREADING:?}, the minimum for \
running Rayon workers beside MPI"
),
})
}
#[cfg(feature = "distributed-mpi")]
pub struct MpiComm {
_universe: Option<mpi::environment::Universe>,
world: mpi::topology::SimpleCommunicator,
rank: usize,
size: usize,
}
#[cfg(feature = "distributed-mpi")]
impl std::fmt::Debug for MpiComm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MpiComm")
.field("rank", &self.rank)
.field("size", &self.size)
.finish()
}
}
#[cfg(feature = "distributed-mpi")]
impl MpiComm {
pub fn world() -> Result<Self> {
let (universe, provided) = mpi::environment::initialize_with_threading(REQUIRED_THREADING)
.ok_or_else(|| PrismError::IncompatibleBackend {
backend: "distributed".into(),
reason: "MPI is already initialized; attach to it with `MpiComm::attach_world`"
.into(),
})?;
check_threading(provided)?;
let world = universe.world();
Ok(Self::from_parts(Some(universe), world))
}
pub fn attach_world() -> Result<Option<Self>> {
if !mpi::environment::is_initialized() {
return Ok(None);
}
check_threading(mpi::environment::threading_support())?;
Ok(Some(Self::from_parts(
None,
mpi::topology::SimpleCommunicator::world(),
)))
}
fn from_parts(
universe: Option<mpi::environment::Universe>,
world: mpi::topology::SimpleCommunicator,
) -> Self {
use mpi::traits::Communicator;
let rank = world.rank() as usize;
let size = world.size() as usize;
Self {
_universe: universe,
world,
rank,
size,
}
}
}
#[cfg(feature = "distributed-mpi")]
impl RankComm for MpiComm {
fn rank(&self) -> usize {
self.rank
}
fn size(&self) -> usize {
self.size
}
fn allgather_c64(&self, local: &[Complex64]) -> Vec<Complex64> {
use mpi::traits::CommunicatorCollectives;
let mut out = vec![Complex64::new(0.0, 0.0); local.len() * self.size];
self.world
.all_gather_into(as_f64(local), as_f64_mut(&mut out));
out
}
fn allgather_f64(&self, local: &[f64]) -> Vec<f64> {
use mpi::traits::CommunicatorCollectives;
let mut out = vec![0.0_f64; local.len() * self.size];
self.world.all_gather_into(local, &mut out);
out
}
fn allgatherv_u64(&self, local: &[u64], counts: &[usize]) -> Vec<u64> {
use mpi::datatype::PartitionMut;
use mpi::traits::CommunicatorCollectives;
debug_assert_eq!(counts[self.rank], local.len());
let counts: Vec<mpi::Count> = counts.iter().map(|&c| c as mpi::Count).collect();
let mut displs = Vec::with_capacity(counts.len());
let mut total: mpi::Count = 0;
for &c in &counts {
displs.push(total);
total += c;
}
let mut out = vec![0_u64; total as usize];
self.world
.all_gather_varcount_into(local, &mut PartitionMut::new(&mut out[..], counts, displs));
out
}
fn allreduce_sum_f64(&self, value: f64) -> f64 {
use mpi::traits::CommunicatorCollectives;
let mut out = 0.0_f64;
self.world
.all_reduce_into(&value, &mut out, mpi::collective::SystemOperation::sum());
out
}
fn sendrecv_c64(&self, partner: usize, send: &[Complex64], recv: &mut [Complex64]) {
use mpi::point_to_point as p2p;
use mpi::traits::Communicator;
debug_assert_eq!(send.len(), recv.len());
let peer = self.world.process_at_rank(partner as i32);
p2p::send_receive_into(as_f64(send), &peer, as_f64_mut(recv), &peer);
}
fn barrier(&self) {
use mpi::traits::CommunicatorCollectives;
self.world.barrier();
}
}
#[cfg(all(test, feature = "distributed-mpi"))]
mod threading_tests {
use super::*;
#[test]
fn thread_level_check_rejects_only_single() {
assert!(Threading::Single < Threading::Funneled);
assert!(Threading::Funneled < Threading::Serialized);
assert!(Threading::Serialized < Threading::Multiple);
assert!(check_threading(Threading::Single).is_err());
for level in [
Threading::Funneled,
Threading::Serialized,
Threading::Multiple,
] {
assert!(check_threading(level).is_ok(), "{level:?}");
}
}
}