use std::os::raw::c_void;
use mpi_sys;
use mpi::topology::SimpleCommunicator;
use mpi::traits::{AsRaw, Communicator, CommunicatorCollectives, Equivalence, FromRaw};
use crate::distributed_tools::array_tools::{SPARSE_EXCHANGE_TAG, nbx};
unsafe fn neighbor_alltoallv<T: Equivalence>(
comm: &SimpleCommunicator,
send: &[T],
send_counts: &[i32],
send_displacements: &[i32],
receive: &mut [T],
receive_counts: &[i32],
receive_displacements: &[i32],
) {
unsafe {
mpi_sys::MPI_Neighbor_alltoallv(
send.as_ptr() as *const c_void,
send_counts.as_ptr(),
send_displacements.as_ptr(),
<T as Equivalence>::equivalent_datatype().as_raw(),
receive.as_mut_ptr() as *mut c_void,
receive_counts.as_ptr(),
receive_displacements.as_ptr(),
<T as Equivalence>::equivalent_datatype().as_raw(),
comm.as_raw(),
);
}
}
fn dist_graph_create<C: Communicator>(
comm: &C,
sources: &[i32],
destinations: &[i32],
) -> SimpleCommunicator {
let source_count = i32::try_from(sources.len()).expect("collectively validated neighbours");
let destination_count =
i32::try_from(destinations.len()).expect("collectively validated neighbours");
unsafe {
let mut raw_comm = mpi_sys::RSMPI_COMM_NULL;
mpi_sys::MPI_Dist_graph_create_adjacent(
comm.as_raw(),
source_count,
sources.as_ptr(),
mpi_sys::RSMPI_UNWEIGHTED(),
destination_count,
destinations.as_ptr(),
mpi_sys::RSMPI_UNWEIGHTED(),
mpi_sys::RSMPI_INFO_NULL,
0,
&mut raw_comm,
);
SimpleCommunicator::from_raw(raw_comm)
}
}
fn collective_assert<C: CommunicatorCollectives>(comm: &C, local: bool, message: &str) {
let mut global = false;
comm.all_reduce_into(
&local,
&mut global,
mpi::collective::SystemOperation::logical_and(),
);
assert!(global, "{message}");
}
fn offsets(sizes: &[usize]) -> Option<Vec<usize>> {
let mut offsets = Vec::with_capacity(1 + sizes.len());
let mut total = 0_usize;
offsets.push(total);
for &size in sizes {
total = total.checked_add(size)?;
offsets.push(total);
}
Some(offsets)
}
fn value_counts(index_counts: &[i32], offsets: &[usize]) -> Option<(Vec<i32>, Vec<i32>)> {
i32::try_from(*offsets.last()?).ok()?;
let mut counts = Vec::with_capacity(index_counts.len());
let mut displacements = Vec::with_capacity(index_counts.len());
let mut start = 0_usize;
for &count in index_counts {
let end = start + count as usize;
displacements.push(offsets[start] as i32);
counts.push((offsets[end] - offsets[start]) as i32);
start = end;
}
Some((counts, displacements))
}
pub enum ChunkSizes<'a, I> {
Uniform(usize),
PerIndex(Box<dyn Fn(I) -> usize + 'a>),
}
impl<I> Default for ChunkSizes<'_, I> {
fn default() -> Self {
Self::Uniform(1)
}
}
pub struct GhostCommunicatorBuilder<'a, I> {
ghosts: Vec<(I, usize)>,
chunk_sizes: ChunkSizes<'a, I>,
}
impl<I> Default for GhostCommunicatorBuilder<'_, I> {
fn default() -> Self {
Self {
ghosts: Vec::new(),
chunk_sizes: ChunkSizes::default(),
}
}
}
impl<'a, I: Default + Copy + Equivalence> GhostCommunicatorBuilder<'a, I> {
pub fn new() -> Self {
Self::default()
}
pub fn add_ghost(&mut self, index: I, owner: usize) -> &mut Self {
self.ghosts.push((index, owner));
self
}
pub fn add_ghosts(&mut self, ghosts: impl IntoIterator<Item = (I, usize)>) -> &mut Self {
self.ghosts.extend(ghosts);
self
}
pub fn chunk_sizes(&mut self, chunk_sizes: ChunkSizes<'a, I>) -> &mut Self {
self.chunk_sizes = chunk_sizes;
self
}
pub fn build<C: CommunicatorCollectives>(&self, comm: &C) -> GhostCommunicator<I> {
let size = comm.size() as usize;
let rank = comm.rank() as usize;
let mut ghosts = self.ghosts.clone();
ghosts.sort_by_key(|&(_, owner)| owner);
let receive_indices = ghosts.iter().map(|&(index, _)| index).collect::<Vec<_>>();
let mut in_ranks = Vec::<i32>::new();
let mut neighbor_receive_counts = Vec::<i32>::new();
let mut receive_displacements = Vec::<i32>::new();
let mut valid = !matches!(self.chunk_sizes, ChunkSizes::Uniform(0))
&& ghosts.last().is_none_or(|&(_, owner)| owner < size)
&& i32::try_from(ghosts.len()).is_ok();
if valid {
let mut start = 0;
for run in ghosts.chunk_by(|left, right| left.1 == right.1) {
in_ranks.push(run[0].1 as i32);
neighbor_receive_counts.push(run.len() as i32);
receive_displacements.push(start as i32);
start += run.len();
}
}
let mut own_requests = None;
let mut requests = Vec::new();
for ((&owner, &count), &start) in in_ranks
.iter()
.zip(&neighbor_receive_counts)
.zip(&receive_displacements)
{
let block = &receive_indices[start as usize..(start + count) as usize];
if owner as usize == rank {
own_requests = Some(block.to_vec());
} else {
requests.push((owner as usize, block));
}
}
let mut received = nbx(comm, SPARSE_EXCHANGE_TAG, requests);
if let Some(block) = own_requests {
let position = received.partition_point(|&(source, _)| source < rank);
received.insert(position, (rank, block));
}
let mut out_ranks = Vec::<i32>::with_capacity(received.len());
let mut neighbor_send_counts = Vec::<i32>::with_capacity(received.len());
let mut send_displacements = Vec::<i32>::with_capacity(received.len());
let mut send_indices = Vec::<I>::new();
for (source, block) in received {
out_ranks.push(source as i32);
send_displacements.push(i32::try_from(send_indices.len()).unwrap_or(i32::MAX));
neighbor_send_counts.push(block.len() as i32);
send_indices.extend(block);
}
valid &= i32::try_from(send_indices.len()).is_ok();
collective_assert(comm, valid, "invalid ghost owner or chunk size metadata");
let forward_comm = dist_graph_create(comm, &in_ranks, &out_ranks);
let backward_comm = dist_graph_create(comm, &out_ranks, &in_ranks);
let send_chunk_sizes = match &self.chunk_sizes {
ChunkSizes::Uniform(chunk_size) => vec![*chunk_size; send_indices.len()],
ChunkSizes::PerIndex(chunk_size) => send_indices
.iter()
.map(|&index| chunk_size(index))
.collect(),
};
let mut receive_chunk_sizes = vec![0_usize; receive_indices.len()];
unsafe {
neighbor_alltoallv(
&forward_comm,
&send_chunk_sizes,
&neighbor_send_counts,
&send_displacements,
&mut receive_chunk_sizes,
&neighbor_receive_counts,
&receive_displacements,
);
}
let send_offsets = offsets(&send_chunk_sizes);
let receive_offsets = offsets(&receive_chunk_sizes);
let send_values = send_offsets
.as_deref()
.and_then(|offsets| value_counts(&neighbor_send_counts, offsets));
let receive_values = receive_offsets
.as_deref()
.and_then(|offsets| value_counts(&neighbor_receive_counts, offsets));
let chunks_agree = match self.chunk_sizes {
ChunkSizes::Uniform(chunk_size) => {
receive_chunk_sizes.iter().all(|&size| size == chunk_size)
}
ChunkSizes::PerIndex(_) => true,
};
collective_assert(
comm,
chunks_agree && send_values.is_some() && receive_values.is_some(),
"invalid or inconsistent ghost chunk sizes",
);
let (send_value_counts, send_value_displacements) = send_values.unwrap();
let (receive_value_counts, receive_value_displacements) = receive_values.unwrap();
GhostCommunicator {
out_ranks,
in_ranks,
send_indices,
receive_indices,
send_counts: neighbor_send_counts,
receive_counts: neighbor_receive_counts,
send_chunk_sizes,
receive_chunk_sizes,
send_offsets: send_offsets.unwrap(),
receive_offsets: receive_offsets.unwrap(),
send_value_counts,
receive_value_counts,
send_value_displacements,
receive_value_displacements,
forward_comm,
backward_comm,
}
}
}
pub struct GhostCommunicator<I: Default + Copy + Equivalence> {
out_ranks: Vec<i32>,
in_ranks: Vec<i32>,
send_indices: Vec<I>,
receive_indices: Vec<I>,
send_counts: Vec<i32>,
receive_counts: Vec<i32>,
send_chunk_sizes: Vec<usize>,
receive_chunk_sizes: Vec<usize>,
send_offsets: Vec<usize>,
receive_offsets: Vec<usize>,
send_value_counts: Vec<i32>,
receive_value_counts: Vec<i32>,
send_value_displacements: Vec<i32>,
receive_value_displacements: Vec<i32>,
forward_comm: SimpleCommunicator,
backward_comm: SimpleCommunicator,
}
impl<I: Default + Copy + Equivalence> GhostCommunicator<I> {
pub fn out_ranks(&self) -> &[i32] {
&self.out_ranks
}
pub fn in_ranks(&self) -> &[i32] {
&self.in_ranks
}
pub fn send_indices(&self) -> &[I] {
&self.send_indices
}
pub fn receive_indices(&self) -> &[I] {
&self.receive_indices
}
pub fn send_counts(&self) -> &[i32] {
&self.send_counts
}
pub fn receive_counts(&self) -> &[i32] {
&self.receive_counts
}
pub fn total_send_count(&self) -> usize {
self.send_indices.len()
}
pub fn total_receive_count(&self) -> usize {
self.receive_indices.len()
}
pub fn send_chunk_sizes(&self) -> &[usize] {
&self.send_chunk_sizes
}
pub fn receive_chunk_sizes(&self) -> &[usize] {
&self.receive_chunk_sizes
}
pub fn send_offsets(&self) -> &[usize] {
&self.send_offsets
}
pub fn receive_offsets(&self) -> &[usize] {
&self.receive_offsets
}
pub fn send_buffer_len(&self) -> usize {
*self.send_offsets.last().unwrap()
}
pub fn receive_buffer_len(&self) -> usize {
*self.receive_offsets.last().unwrap()
}
pub fn forward_comm(&self) -> &SimpleCommunicator {
&self.forward_comm
}
pub fn backward_comm(&self) -> &SimpleCommunicator {
&self.backward_comm
}
pub fn send_buffer<T: Equivalence + Copy + Default>(&self) -> GhostSendBuffer<'_, I, T> {
GhostSendBuffer {
ghosts: self,
data: vec![T::default(); self.send_buffer_len()],
}
}
pub fn send_buffer_from_fn<T: Equivalence + Copy + Default>(
&self,
mut fill: impl FnMut(I, &mut [T]),
) -> GhostSendBuffer<'_, I, T> {
let mut buffer = self.send_buffer();
for (index, chunk) in buffer.chunks_mut() {
fill(index, chunk);
}
buffer
}
pub fn receive_buffer<T: Equivalence + Copy + Default>(&self) -> GhostReceiveBuffer<'_, I, T> {
GhostReceiveBuffer {
ghosts: self,
data: vec![T::default(); self.receive_buffer_len()],
}
}
fn exchange<T: Equivalence>(
&self,
direction: Direction,
out_values: &[T],
in_values: &mut [T],
) {
let (comm, out_counts, out_displacements, in_counts, in_displacements) = match direction {
Direction::Forward => (
&self.forward_comm,
&self.send_value_counts,
&self.send_value_displacements,
&self.receive_value_counts,
&self.receive_value_displacements,
),
Direction::Backward => (
&self.backward_comm,
&self.receive_value_counts,
&self.receive_value_displacements,
&self.send_value_counts,
&self.send_value_displacements,
),
};
let (out_len, in_len) = self.buffer_lens(direction);
assert!(
out_values.len() == out_len && in_values.len() == in_len,
"ghost exchange buffers must match the communicator layout"
);
unsafe {
neighbor_alltoallv(
comm,
out_values,
out_counts,
out_displacements,
in_values,
in_counts,
in_displacements,
);
}
}
fn buffer_lens(&self, direction: Direction) -> (usize, usize) {
match direction {
Direction::Forward => (self.send_buffer_len(), self.receive_buffer_len()),
Direction::Backward => (self.receive_buffer_len(), self.send_buffer_len()),
}
}
fn exchange_or_panic<T: Equivalence + Copy + Default>(
&self,
direction: Direction,
out_values: &[T],
in_values: &mut [T],
) {
let (out_len, in_len) = self.buffer_lens(direction);
let (out_actual, in_actual) = (out_values.len(), in_values.len());
let scratch_out;
let out_values = if out_actual == out_len {
out_values
} else {
scratch_out = vec![T::default(); out_len];
&scratch_out
};
if in_actual == in_len {
self.exchange(direction, out_values, in_values);
} else {
let mut scratch_in = vec![T::default(); in_len];
self.exchange(direction, out_values, &mut scratch_in);
}
assert!(
out_actual == out_len && in_actual == in_len,
"ghost exchange buffer mismatch in {direction:?} exchange: outgoing buffer has length {out_actual}, expected {out_len}; incoming buffer has length {in_actual}, expected {in_len}"
);
}
pub fn forward_send_values<T: Equivalence + Copy + Default>(
&self,
out_values: &[T],
in_values: &mut [T],
) {
self.exchange_or_panic(Direction::Forward, out_values, in_values);
}
pub fn backward_send_values<T: Equivalence + Copy + Default>(
&self,
out_values: &[T],
in_values: &mut [T],
) {
self.exchange_or_panic(Direction::Backward, out_values, in_values);
}
}
#[derive(Clone, Copy, Debug)]
enum Direction {
Forward,
Backward,
}
fn chunks<'s, I: Copy, T>(
indices: &'s [I],
offsets: &'s [usize],
data: &'s [T],
) -> impl Iterator<Item = (I, &'s [T])> {
indices
.iter()
.zip(offsets.windows(2))
.map(move |(&index, range)| (index, &data[range[0]..range[1]]))
}
fn chunks_mut<'s, I: Copy, T>(
indices: &'s [I],
sizes: &'s [usize],
mut data: &'s mut [T],
) -> impl Iterator<Item = (I, &'s mut [T])> {
indices.iter().zip(sizes).map(move |(&index, &size)| {
let (chunk, rest) = std::mem::take(&mut data).split_at_mut(size);
data = rest;
(index, chunk)
})
}
pub struct GhostSendBuffer<'g, I: Default + Copy + Equivalence, T> {
ghosts: &'g GhostCommunicator<I>,
data: Vec<T>,
}
impl<'g, I: Default + Copy + Equivalence, T: Equivalence + Copy + Default>
GhostSendBuffer<'g, I, T>
{
pub fn forward(&self) -> GhostReceiveBuffer<'g, I, T> {
let mut received = self.ghosts.receive_buffer();
self.ghosts
.exchange(Direction::Forward, &self.data, &mut received.data);
received
}
pub fn ghosts(&self) -> &'g GhostCommunicator<I> {
self.ghosts
}
pub fn as_slice(&self) -> &[T] {
&self.data
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut self.data
}
pub fn chunk(&self, i: usize) -> &[T] {
let offsets = self.ghosts.send_offsets();
&self.data[offsets[i]..offsets[i + 1]]
}
pub fn chunk_mut(&mut self, i: usize) -> &mut [T] {
let offsets = self.ghosts.send_offsets();
&mut self.data[offsets[i]..offsets[i + 1]]
}
pub fn chunks(&self) -> impl Iterator<Item = (I, &[T])> {
chunks(
self.ghosts.send_indices(),
self.ghosts.send_offsets(),
&self.data,
)
}
pub fn chunks_mut(&mut self) -> impl Iterator<Item = (I, &mut [T])> {
chunks_mut(
self.ghosts.send_indices(),
self.ghosts.send_chunk_sizes(),
&mut self.data,
)
}
pub fn into_vec(self) -> Vec<T> {
self.data
}
}
pub struct GhostReceiveBuffer<'g, I: Default + Copy + Equivalence, T> {
ghosts: &'g GhostCommunicator<I>,
data: Vec<T>,
}
impl<'g, I: Default + Copy + Equivalence, T: Equivalence + Copy + Default>
GhostReceiveBuffer<'g, I, T>
{
pub fn backward(&self) -> GhostSendBuffer<'g, I, T> {
let mut returned = self.ghosts.send_buffer();
self.ghosts
.exchange(Direction::Backward, &self.data, &mut returned.data);
returned
}
pub fn ghosts(&self) -> &'g GhostCommunicator<I> {
self.ghosts
}
pub fn as_slice(&self) -> &[T] {
&self.data
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut self.data
}
pub fn chunk(&self, i: usize) -> &[T] {
let offsets = self.ghosts.receive_offsets();
&self.data[offsets[i]..offsets[i + 1]]
}
pub fn chunk_mut(&mut self, i: usize) -> &mut [T] {
let offsets = self.ghosts.receive_offsets();
&mut self.data[offsets[i]..offsets[i + 1]]
}
pub fn chunks(&self) -> impl Iterator<Item = (I, &[T])> {
chunks(
self.ghosts.receive_indices(),
self.ghosts.receive_offsets(),
&self.data,
)
}
pub fn chunks_mut(&mut self) -> impl Iterator<Item = (I, &mut [T])> {
chunks_mut(
self.ghosts.receive_indices(),
self.ghosts.receive_chunk_sizes(),
&mut self.data,
)
}
pub fn into_vec(self) -> Vec<T> {
self.data
}
}
#[cfg(test)]
mod test {
use super::{offsets, value_counts};
#[test]
fn test_offsets() {
assert_eq!(offsets(&[]), Some(vec![0]));
assert_eq!(offsets(&[2, 0, 3]), Some(vec![0, 2, 2, 5]));
assert_eq!(offsets(&[usize::MAX, 1]), None);
}
#[test]
fn test_value_counts() {
let offsets = [0, 2, 2, 5];
assert_eq!(
value_counts(&[2, 1], &offsets),
Some((vec![2, 3], vec![0, 2]))
);
assert_eq!(value_counts(&[], &[0]), Some((vec![], vec![])));
assert_eq!(
value_counts(&[1, 2], &[0, 0, 4, 4]),
Some((vec![0, 4], vec![0, 0]))
);
let limit = i32::MAX as usize;
assert_eq!(
value_counts(&[1], &[0, limit]),
Some((vec![i32::MAX], vec![0]))
);
assert_eq!(value_counts(&[1], &[0, limit + 1]), None);
}
}