use std::rc::Rc;
use itertools::izip;
use mpi::traits::{Communicator, CommunicatorCollectives, Equivalence};
use super::{
ChunkSizes, GhostCommunicatorBuilder, array_tools::all_to_allv, index_layout::IndexLayout,
};
pub struct DataPermutation<'a, C: Communicator> {
index_layout: Rc<IndexLayout<'a, C>>,
nindices: usize,
my_rank: usize,
custom_local_indices: Vec<usize>,
local_to_custom_map: Vec<usize>,
receive_to_custom_map: Vec<usize>,
ghost_communicator: super::GhostCommunicator<usize>,
chunk_size: usize,
}
impl<'a, C: Communicator> DataPermutation<'a, C> {
pub fn new(
index_layout: Rc<IndexLayout<'a, C>>,
custom_indices: &[usize],
chunk_size: usize,
) -> Self
where
C: CommunicatorCollectives,
{
let comm = index_layout.comm();
let mut max_chunk_size = 0_usize;
comm.all_reduce_into(
&chunk_size,
&mut max_chunk_size,
mpi::collective::SystemOperation::max(),
);
let local_valid = chunk_size > 0
&& chunk_size == max_chunk_size
&& custom_indices
.iter()
.all(|&index| index_layout.rank_from_index(index).is_some());
let mut valid = false;
comm.all_reduce_into(
&local_valid,
&mut valid,
mpi::collective::SystemOperation::logical_and(),
);
assert!(
valid,
"permutation index is outside the global layout or chunk sizes are invalid"
);
let mut owner_counts = vec![0_usize; comm.size() as usize];
let mut owner_grouped_indices = Vec::with_capacity(custom_indices.len());
for (owner, owner_count) in owner_counts.iter_mut().enumerate() {
for &index in custom_indices {
if index_layout.rank_from_index(index) == Some(owner) {
*owner_count += 1;
owner_grouped_indices.push(index);
}
}
}
let (_, owned_requests) = all_to_allv(comm, &owner_counts, &owner_grouped_indices);
let mut multiplicities = vec![0_usize; index_layout.number_of_local_indices()];
let my_rank = comm.rank() as usize;
let mut multiplicity_valid = true;
for index in owned_requests {
if let Some(local_index) = index_layout.global2local(my_rank, index) {
multiplicities[local_index] += 1;
} else {
multiplicity_valid = false;
}
}
multiplicity_valid &= multiplicities.iter().all(|&count| count == 1);
let mut exact_permutation = false;
comm.all_reduce_into(
&multiplicity_valid,
&mut exact_permutation,
mpi::collective::SystemOperation::logical_and(),
);
assert!(
exact_permutation,
"custom indices must contain every global index exactly once"
);
let mut custom_local_indices = Vec::new();
let mut custom_ghost_indices = Vec::new();
let mut ghost_builder = GhostCommunicatorBuilder::new();
let mut local_to_custom_map = Vec::<usize>::new();
let mut ghost_to_custom_map = Vec::<usize>::new();
for (pos, &index) in custom_indices.iter().enumerate() {
let index_rank = index_layout.rank_from_index(index).unwrap();
if index_rank == my_rank {
custom_local_indices.push(index_layout.global2local(my_rank, index).unwrap());
local_to_custom_map.push(pos);
} else {
custom_ghost_indices.push(index);
ghost_builder.add_ghost(index, index_rank);
ghost_to_custom_map.push(pos);
}
}
let ghost_communicator = ghost_builder
.chunk_sizes(ChunkSizes::Uniform(chunk_size))
.build(comm);
let ghost_permutation =
permutation_map(ghost_communicator.receive_indices(), &custom_ghost_indices);
let mut receive_to_custom_map =
Vec::<usize>::with_capacity(ghost_communicator.total_receive_count());
for index in 0..ghost_to_custom_map.len() {
receive_to_custom_map.push(ghost_to_custom_map[ghost_permutation[index]]);
}
Self {
index_layout,
nindices: custom_indices.len(),
my_rank,
custom_local_indices,
local_to_custom_map,
receive_to_custom_map,
ghost_communicator,
chunk_size,
}
}
fn check_buffers(data_len: [usize; 2], expected_len: [usize; 2]) {
assert!(
data_len == expected_len,
"permutation input and output data have lengths {data_len:?}, expected {expected_len:?}"
);
}
pub fn chunk_size(&self) -> usize {
self.chunk_size
}
pub fn forward_permute<T: Equivalence + Copy + Default>(
&self,
data: &[T],
permuted_data: &mut [T],
) {
let chunk_size = self.chunk_size;
let lengths = [data.len(), permuted_data.len()];
let expected_lengths = [
self.index_layout.number_of_local_indices() * chunk_size,
self.nindices * chunk_size,
];
let valid = lengths == expected_lengths;
let send_buffer = self.ghost_communicator.send_buffer_from_fn(|index, chunk| {
if valid {
let local_start_index =
chunk_size * self.index_layout.global2local(self.my_rank, index).unwrap();
chunk.copy_from_slice(&data[local_start_index..local_start_index + chunk_size]);
}
});
let receive_buffer = send_buffer.forward();
Self::check_buffers(lengths, expected_lengths);
for (&pos, &local_index) in izip!(&self.local_to_custom_map, &self.custom_local_indices) {
permuted_data[chunk_size * pos..chunk_size * (1 + pos)]
.copy_from_slice(&data[chunk_size * local_index..chunk_size * (1 + local_index)]);
}
for (&permuted_index, (_, chunk)) in
izip!(&self.receive_to_custom_map, receive_buffer.chunks())
{
permuted_data[chunk_size * permuted_index..chunk_size * (1 + permuted_index)]
.copy_from_slice(chunk);
}
}
pub fn backward_permute<T: Equivalence + Copy + Default>(
&self,
data: &[T],
permuted_data: &mut [T],
) {
let chunk_size = self.chunk_size;
let lengths = [data.len(), permuted_data.len()];
let expected_lengths = [
self.nindices * chunk_size,
self.index_layout.number_of_local_indices() * chunk_size,
];
let valid = lengths == expected_lengths;
let mut receive_buffer = self.ghost_communicator.receive_buffer();
if valid {
for (&custom_index, (_, chunk)) in
izip!(&self.receive_to_custom_map, receive_buffer.chunks_mut())
{
chunk.copy_from_slice(
&data[custom_index * chunk_size..(1 + custom_index) * chunk_size],
);
}
}
let send_buffer = receive_buffer.backward();
Self::check_buffers(lengths, expected_lengths);
for (index, chunk) in send_buffer.chunks() {
let local_start_index =
chunk_size * self.index_layout.global2local(self.my_rank, index).unwrap();
let local_end_index = local_start_index + chunk_size;
permuted_data[local_start_index..local_end_index].copy_from_slice(chunk);
}
for (&pos, &local_index) in izip!(&self.local_to_custom_map, &self.custom_local_indices) {
permuted_data[local_index * chunk_size..(1 + local_index) * chunk_size]
.copy_from_slice(&data[pos * chunk_size..(1 + pos) * chunk_size]);
}
}
}
pub fn permutation_map(original_indices: &[usize], permuted_indices: &[usize]) -> Vec<usize> {
concatenate_permutations(
&invert_permutation(&argsort(original_indices)),
&argsort(permuted_indices),
)
}
pub fn argsort<T: Ord>(data: &[T]) -> Vec<usize> {
let mut indices: Vec<usize> = (0..data.len()).collect();
indices.sort_by(|&a, &b| data[a].cmp(&data[b]));
indices
}
pub fn invert_permutation(perm: &[usize]) -> Vec<usize> {
let mut inverse = vec![0; perm.len()];
for (i, &p) in perm.iter().enumerate() {
inverse[p] = i;
}
inverse
}
pub fn concatenate_permutations(perm1: &[usize], perm2: &[usize]) -> Vec<usize> {
assert_eq!(perm1.len(), perm2.len());
let mut result = vec![0; perm1.len()];
for (i, &p) in perm1.iter().enumerate() {
result[i] = perm2[p];
}
result
}
#[cfg(test)]
mod test {
#[test]
fn test_permutation() {
let original_indices = vec![6, 3, 4, 4, 2];
let permuted_indices = vec![6, 4, 3, 2, 4];
let permutation = super::permutation_map(&original_indices, &permuted_indices);
for index in 0..original_indices.len() {
assert_eq!(
permuted_indices[permutation[index]],
original_indices[index]
);
}
}
}