use mpi::{
Tag,
collective::SystemOperation,
topology::SimpleCommunicator,
traits::{Communicator, CommunicatorCollectives, Destination, Equivalence, Source},
};
use crate::distributed_tools::array_tools::{SPARSE_EXCHANGE_TAG, nbx, rotated_position};
const FORWARD_TAG: Tag = 1;
const BACKWARD_TAG: Tag = 2;
type Routes<'s> = (&'s [usize], &'s [usize]);
pub struct SparseRoutes {
comm: SimpleCommunicator,
rank: usize,
destinations: Vec<usize>,
send_counts: Vec<usize>,
sources: Vec<usize>,
receive_counts: Vec<usize>,
max_per_item: usize,
}
impl SparseRoutes {
pub fn new<C: CommunicatorCollectives>(comm: &C, counts: &[usize]) -> Self {
let size = comm.size() as usize;
let rank = comm.rank() as usize;
let comm = comm.duplicate();
let valid =
counts.len() == size && counts.iter().all(|&count| i32::try_from(count).is_ok());
let mut destinations = Vec::new();
let mut send_counts = Vec::new();
if valid {
for (destination, &count) in counts.iter().enumerate() {
if count != 0 {
destinations.push(destination);
send_counts.push(count);
}
}
}
let messages = destinations
.iter()
.filter(|&&destination| destination != rank)
.map(|&destination| (destination, &counts[destination..destination + 1]));
let mut received = nbx(&comm, SPARSE_EXCHANGE_TAG, messages);
if valid && counts[rank] != 0 {
let position = received.partition_point(|&(source, _)| source < rank);
received.insert(position, (rank, vec![counts[rank]]));
}
let (sources, receive_counts): (Vec<usize>, Vec<usize>) = received
.into_iter()
.map(|(source, count)| (source, count[0]))
.unzip();
let largest = send_counts
.iter()
.chain(&receive_counts)
.copied()
.max()
.unwrap_or(0)
.max(1);
let local_max = i32::MAX as usize / largest;
let mine = [i64::from(!valid), -(local_max as i64)];
let mut all = [0_i64; 2];
comm.all_reduce_into(&mine[..], &mut all[..], SystemOperation::max());
assert!(all[0] == 0, "invalid sparse route counts");
Self {
comm,
rank,
destinations,
send_counts,
sources,
receive_counts,
max_per_item: (-all[1]) as usize,
}
}
pub fn comm(&self) -> &SimpleCommunicator {
&self.comm
}
pub fn destinations(&self) -> &[usize] {
&self.destinations
}
pub fn send_counts(&self) -> &[usize] {
&self.send_counts
}
pub fn sources(&self) -> &[usize] {
&self.sources
}
pub fn receive_counts(&self) -> &[usize] {
&self.receive_counts
}
pub fn total_send_count(&self) -> usize {
self.send_counts.iter().sum()
}
pub fn total_receive_count(&self) -> usize {
self.receive_counts.iter().sum()
}
pub fn max_per_item(&self) -> usize {
self.max_per_item
}
pub fn exchange<T: Equivalence + Copy + Default>(
&self,
values: &[T],
per_item: usize,
) -> Vec<T> {
self.check_per_item(per_item);
let mut received = vec![T::default(); self.total_receive_count() * per_item];
self.exchange_into(values, per_item, &mut received);
received
}
pub fn exchange_into<T: Equivalence + Copy + Default>(
&self,
values: &[T],
per_item: usize,
received: &mut [T],
) {
self.route_or_panic(Direction::Forward, values, per_item, received);
}
pub fn exchange_back<T: Equivalence + Copy + Default>(
&self,
values: &[T],
per_item: usize,
) -> Vec<T> {
self.check_per_item(per_item);
let mut returned = vec![T::default(); self.total_send_count() * per_item];
self.exchange_back_into(values, per_item, &mut returned);
returned
}
pub fn exchange_back_into<T: Equivalence + Copy + Default>(
&self,
values: &[T],
per_item: usize,
returned: &mut [T],
) {
self.route_or_panic(Direction::Backward, values, per_item, returned);
}
fn check_per_item(&self, per_item: usize) {
assert!(
per_item <= self.max_per_item,
"{per_item} values per item exceed the {} that fit MPI's i32 counts",
self.max_per_item
);
}
fn routes(&self, direction: Direction) -> (Routes<'_>, Routes<'_>) {
let forward = (&self.destinations[..], &self.send_counts[..]);
let backward = (&self.sources[..], &self.receive_counts[..]);
match direction {
Direction::Forward => (forward, backward),
Direction::Backward => (backward, forward),
}
}
fn route_or_panic<T: Equivalence + Copy + Default>(
&self,
direction: Direction,
values: &[T],
per_item: usize,
received: &mut [T],
) {
self.check_per_item(per_item);
let ((_, out_counts), (_, in_counts)) = self.routes(direction);
let out_len = out_counts.iter().sum::<usize>() * per_item;
let in_len = in_counts.iter().sum::<usize>() * per_item;
let (out_actual, in_actual) = (values.len(), received.len());
let scratch_out;
let values = if out_actual == out_len {
values
} else {
scratch_out = vec![T::default(); out_len];
&scratch_out
};
if in_actual == in_len {
self.route(direction, values, per_item, received);
} else {
let mut scratch_in = vec![T::default(); in_len];
self.route(direction, values, per_item, &mut scratch_in);
}
assert!(
out_actual == out_len && in_actual == in_len,
"sparse route buffer mismatch in {direction:?} exchange: outgoing buffer has length {out_actual}, expected {out_len}; incoming buffer has length {in_actual}, expected {in_len}"
);
}
fn route<T: Equivalence + Copy>(
&self,
direction: Direction,
values: &[T],
per_item: usize,
received: &mut [T],
) {
let ((out_ranks, out_counts), (in_ranks, in_counts)) = self.routes(direction);
let tag = match direction {
Direction::Forward => FORWARD_TAG,
Direction::Backward => BACKWARD_TAG,
};
let size = self.comm.size() as usize;
let order = |peer: usize| rotated_position(peer, self.rank, size);
let mut own_receive = None;
let mut receives = Vec::with_capacity(in_ranks.len());
let mut rest = &mut received[..];
for (&source, &count) in in_ranks.iter().zip(in_counts) {
let (block, tail) = std::mem::take(&mut rest).split_at_mut(count * per_item);
rest = tail;
if source == self.rank {
own_receive = Some(block);
} else if !block.is_empty() {
receives.push((source, block));
}
}
let mut own_send = None;
let mut sends = Vec::with_capacity(out_ranks.len());
let mut start = 0;
for (&destination, &count) in out_ranks.iter().zip(out_counts) {
let block = &values[start..start + count * per_item];
start += count * per_item;
if destination == self.rank {
own_send = Some(block);
} else if !block.is_empty() {
sends.push((destination, block));
}
}
receives.sort_by_key(|&(source, _)| order(source));
sends.sort_by_key(|&(destination, _)| order(destination));
mpi::request::scope(|scope| {
let mut requests = Vec::with_capacity(receives.len() + sends.len());
for (source, block) in receives {
requests.push(
self.comm
.process_at_rank(source as i32)
.immediate_receive_into_with_tag(scope, block, tag),
);
}
for (destination, block) in sends {
requests.push(
self.comm
.process_at_rank(destination as i32)
.immediate_send_with_tag(scope, block, tag),
);
}
if let (Some(own_receive), Some(own_send)) = (own_receive, own_send) {
own_receive.copy_from_slice(own_send);
}
for request in requests {
request.wait_without_status();
}
});
}
}
#[derive(Clone, Copy, Debug)]
enum Direction {
Forward,
Backward,
}