use crate::uniform_grid::UniformGrid;
use crate::utils::SendSyncWrapper;
use crate::{new_map, AxisAlignedBoundingBox3d, HashState, Index, MapType, ParallelMapType, Real};
use nalgebra::Vector3;
use rayon::prelude::*;
#[inline(never)]
pub fn search<I: Index, R: Real>(
domain: &AxisAlignedBoundingBox3d<R>,
particle_positions: &[Vector3<R>],
search_radius: R,
enable_multi_threading: bool,
) -> Vec<Vec<usize>> {
let mut particle_neighbor_lists = Vec::new();
if enable_multi_threading {
parallel_search::<I, R>(
domain,
particle_positions,
search_radius,
&mut particle_neighbor_lists,
)
} else {
sequential_search::<I, R>(
domain,
particle_positions,
search_radius,
&mut particle_neighbor_lists,
)
}
particle_neighbor_lists
}
#[inline(never)]
pub fn search_inplace<I: Index, R: Real>(
domain: &AxisAlignedBoundingBox3d<R>,
particle_positions: &[Vector3<R>],
search_radius: R,
enable_multi_threading: bool,
particle_neighbor_lists: &mut Vec<Vec<usize>>,
) {
if enable_multi_threading {
parallel_search::<I, R>(
domain,
particle_positions,
search_radius,
particle_neighbor_lists,
)
} else {
sequential_search::<I, R>(
domain,
particle_positions,
search_radius,
particle_neighbor_lists,
)
}
}
fn init_neighborhood_list(neighborhood_list: &mut Vec<Vec<usize>>, new_len: usize) {
let old_len = neighborhood_list.len();
if old_len != new_len {
for particle_list in neighborhood_list.iter_mut().take(old_len.min(new_len)) {
particle_list.clear();
}
}
neighborhood_list.resize_with(new_len, || Vec::with_capacity(15));
}
fn par_init_neighborhood_list(neighborhood_list: &mut Vec<Vec<usize>>, new_len: usize) {
let old_len = neighborhood_list.len();
if old_len != new_len {
neighborhood_list
.par_iter_mut()
.take(old_len.min(new_len))
.for_each(|particle_list| {
particle_list.clear();
});
}
neighborhood_list.resize_with(new_len, || Vec::with_capacity(15));
}
#[inline(never)]
pub fn sequential_search<I: Index, R: Real>(
domain: &AxisAlignedBoundingBox3d<R>,
particle_positions: &[Vector3<R>],
search_radius: R,
neighborhood_list: &mut Vec<Vec<usize>>,
) {
profile!("neighborhood_search");
assert!(
search_radius > R::zero(),
"Search radius for neighborhood search has to be positive!"
);
assert!(
domain.is_consistent(),
"Domain for neighborhood search has to be consistent!"
);
assert!(
!domain.is_degenerate(),
"Domain for neighborhood search cannot be degenerate!"
);
let search_radius_squared = search_radius * search_radius;
let grid = UniformGrid::from_aabb(&domain, search_radius)
.expect("Failed to construct grid for neighborhood search!");
let particles_per_cell =
sequential_generate_cell_to_particle_map::<I, R>(&grid, particle_positions);
init_neighborhood_list(neighborhood_list, particle_positions.len());
{
profile!("calculate_particle_neighbors_seq");
let mut potential_neighbor_particle_vecs = Vec::new();
for (&flat_cell_index, particles) in &particles_per_cell {
let current_cell = grid.try_unflatten_cell_index(flat_cell_index).unwrap();
potential_neighbor_particle_vecs.clear();
potential_neighbor_particle_vecs.extend(
grid.cells_adjacent_to_cell(¤t_cell)
.chain(std::iter::once(current_cell))
.filter_map(|c| {
let flat_cell_index = grid.flatten_cell_index(&c);
particles_per_cell.get(&flat_cell_index)
}),
);
let potential_neighbor_particle_iter = || {
potential_neighbor_particle_vecs
.iter()
.flat_map(|v| v.iter())
};
for &particle_i in particles {
let pos_i = &particle_positions[particle_i];
let particle_i_neighbors = &mut neighborhood_list[particle_i];
for &particle_j in potential_neighbor_particle_iter() {
if particle_i == particle_j {
continue;
}
let pos_j = &particle_positions[particle_j];
if (pos_j - pos_i).norm_squared() < search_radius_squared {
particle_i_neighbors.push(particle_j);
}
}
}
}
}
}
#[inline(never)]
pub fn parallel_search<I: Index, R: Real>(
domain: &AxisAlignedBoundingBox3d<R>,
particle_positions: &[Vector3<R>],
search_radius: R,
neighborhood_list: &mut Vec<Vec<usize>>,
) {
profile!("par_neighborhood_search");
assert!(
search_radius > R::zero(),
"Search radius for neighborhood search has to be positive!"
);
assert!(
domain.is_consistent(),
"Domain for neighborhood search has to be consistent!"
);
assert!(
!domain.is_degenerate(),
"Domain for neighborhood search cannot be degenerate!"
);
let search_radius_squared = search_radius * search_radius;
let grid = UniformGrid::from_aabb(&domain, search_radius)
.expect("Failed to construct grid for neighborhood search!");
let particles_per_cell_map =
parallel_generate_cell_to_particle_map::<I, R>(&grid, particle_positions).into_read_only();
let particles_per_cell_vec: Vec<(I, Vec<usize>)> = particles_per_cell_map
.iter()
.map(|(&i, v)| (i, v.clone()))
.collect::<Vec<_>>();
let adjacent_cell_particle_vecs = {
profile!("get_cell_neighborhoods_par");
particles_per_cell_vec
.par_iter()
.map(|(flat_cell_index, _)| {
let current_cell = grid.try_unflatten_cell_index(*flat_cell_index).unwrap();
let potential_neighbor_particle_vecs: Vec<&Vec<usize>> = grid
.cells_adjacent_to_cell(¤t_cell)
.filter_map(|c| {
let flat_cell_index = grid.flatten_cell_index(&c);
particles_per_cell_map.get(&flat_cell_index)
})
.collect();
potential_neighbor_particle_vecs
})
.collect::<Vec<_>>()
};
par_init_neighborhood_list(neighborhood_list, particle_positions.len());
let neighborhood_list_mut_ptr = unsafe { SendSyncWrapper::new(neighborhood_list.as_mut_ptr()) };
{
profile!("calculate_particle_neighbors_par");
particles_per_cell_vec.par_iter().enumerate().for_each(
|(cell_k, (_, cell_k_particles))| {
let cell_k_adjacent_particle_vecs = &adjacent_cell_particle_vecs[cell_k];
for (i, &particle_i) in cell_k_particles.iter().enumerate() {
let pos_i = &particle_positions[particle_i];
let particle_i_neighbors =
unsafe { &mut *neighborhood_list_mut_ptr.get().add(particle_i) };
for &adjacent_cell_particles in cell_k_adjacent_particle_vecs.iter() {
for &particle_j in adjacent_cell_particles.iter() {
let pos_j = &particle_positions[particle_j];
if (pos_j - pos_i).norm_squared() < search_radius_squared {
particle_i_neighbors.push(particle_j);
}
}
}
for &particle_j in cell_k_particles.iter().skip(i + 1) {
let pos_j = &particle_positions[particle_j];
if (pos_j - pos_i).norm_squared() < search_radius_squared {
particle_i_neighbors.push(particle_j);
let particle_j_neighbors =
unsafe { &mut *neighborhood_list_mut_ptr.get().add(particle_j) };
particle_j_neighbors.push(particle_i);
}
}
}
},
);
}
}
#[derive(Clone, Debug)]
pub struct NeighborhoodStats {
pub histogram: Vec<usize>,
pub particles_with_neighbors: usize,
pub max_neighbors: usize,
pub avg_neighbors: f64,
}
pub fn compute_neigborhood_stats(neighborhood_list: &Vec<Vec<usize>>) -> NeighborhoodStats {
let mut max_neighbors = 0;
let mut total_neighbors = 0;
let mut particles_with_neighbors = 0;
let mut neighbor_histogram: Vec<usize> = vec![0; 1];
for neighborhood in neighborhood_list.iter() {
if !neighborhood.is_empty() {
if neighbor_histogram.len() < neighborhood.len() + 1 {
neighbor_histogram.resize(neighborhood.len() + 1, 0);
}
neighbor_histogram[neighborhood.len()] += 1;
max_neighbors = max_neighbors.max(neighborhood.len());
total_neighbors += neighborhood.len();
particles_with_neighbors += 1;
} else {
neighbor_histogram[0] += 1;
}
}
let avg_neighbors = total_neighbors as f64 / particles_with_neighbors as f64;
NeighborhoodStats {
histogram: neighbor_histogram,
particles_with_neighbors,
max_neighbors,
avg_neighbors,
}
}
#[inline(never)]
fn sequential_generate_cell_to_particle_map<I: Index, R: Real>(
grid: &UniformGrid<I, R>,
particle_positions: &[Vector3<R>],
) -> MapType<I, Vec<usize>> {
profile!("sequential_generate_cell_to_particle_map");
let mut particles_per_cell = new_map();
let cell_dims = grid.cells_per_dim();
let n_cells = cell_dims[0] * cell_dims[1] * cell_dims[2];
let avg_density = particle_positions.len() / n_cells.to_usize().unwrap_or(1);
for (particle_i, particle) in particle_positions.iter().enumerate() {
let cell_ijk = grid.enclosing_cell(particle);
let cell = grid.get_cell(cell_ijk).unwrap();
let flat_cell_index = grid.flatten_cell_index(&cell);
particles_per_cell
.entry(flat_cell_index)
.or_insert_with(|| Vec::with_capacity(avg_density))
.push(particle_i);
}
particles_per_cell
}
#[inline(never)]
fn parallel_generate_cell_to_particle_map<I: Index, R: Real>(
grid: &UniformGrid<I, R>,
particle_positions: &[Vector3<R>],
) -> ParallelMapType<I, Vec<usize>> {
profile!("parallel_generate_cell_to_particle_map");
let particles_per_cell = ParallelMapType::with_hasher(HashState::default());
particle_positions
.par_iter()
.enumerate()
.for_each(|(particle_i, particle)| {
let cell_ijk = grid.enclosing_cell(particle);
let cell = grid.get_cell(cell_ijk).unwrap();
let flat_cell_index = grid.flatten_cell_index(&cell);
particles_per_cell
.entry(flat_cell_index)
.or_insert_with(Vec::new)
.push(particle_i);
});
particles_per_cell
}