use std::ops::AddAssign;
use num_traits::Float;
use rayon::prelude::*;
use crate::PARALLEL_CODE_THRESHOLD;
use super::morton::{Dim, Morton, quantize};
const SENTINEL: u32 = u32::MAX;
pub(crate) const STACK_CAP: usize = 256;
#[derive(Debug)]
struct Node<T, const D: usize> {
center_of_mass: [T; D],
count: u32,
first_child: u32,
child_count: u8,
level: u8,
}
#[derive(Debug)]
pub(crate) struct Arena<T, const D: usize> {
nodes: Vec<Node<T, D>>,
ranges: Vec<(u32, u32)>,
sorted: Vec<(u64, u32)>,
level_half_width_sq: Vec<T>,
}
fn bounding_box<T, const D: usize>(y: &[T]) -> ([T; D], [T; D])
where
T: Float + Send + Sync,
{
y.par_chunks_exact(D)
.with_min_len(PARALLEL_CODE_THRESHOLD)
.fold(
|| ([T::max_value(); D], [-T::max_value(); D]),
|(mut min, mut max), point| {
for axis in 0..D {
min[axis] = min[axis].min(point[axis]);
max[axis] = max[axis].max(point[axis]);
}
(min, max)
},
)
.reduce(
|| ([T::max_value(); D], [-T::max_value(); D]),
|(mut min_a, mut max_a), (min_b, max_b)| {
for axis in 0..D {
min_a[axis] = min_a[axis].min(min_b[axis]);
max_a[axis] = max_a[axis].max(max_b[axis]);
}
(min_a, max_a)
},
)
}
impl<T, const D: usize> Arena<T, D>
where
T: Float + Send + Sync + AddAssign,
{
pub(crate) fn empty() -> Self {
Self {
nodes: Vec::new(),
ranges: Vec::new(),
sorted: Vec::new(),
level_half_width_sq: Vec::new(),
}
}
pub(crate) fn new(y: &[T], n_samples: usize) -> Self
where
Dim<D>: Morton<D>,
{
let mut arena = Self::empty();
arena.rebuild(y, n_samples);
arena
}
pub(crate) fn rebuild(&mut self, y: &[T], n_samples: usize)
where
Dim<D>: Morton<D>,
{
let bits = <Dim<D> as Morton<D>>::BITS;
self.nodes.clear();
self.ranges.clear();
self.sorted.clear();
if n_samples == 0 {
self.level_half_width_sq.clear();
return;
}
let (min, max) = bounding_box::<T, D>(y);
let mut extent = [T::zero(); D];
for axis in 0..D {
extent[axis] = max[axis] - min[axis];
}
let scale = T::from(1u64 << bits).unwrap();
let max_bucket = ((1u64 << bits) - 1) as u32;
let mut inv_scale = [T::zero(); D];
for axis in 0..D {
inv_scale[axis] = if extent[axis] > T::zero() {
scale / extent[axis]
} else {
T::zero()
};
}
let max_extent = extent.iter().copied().fold(T::zero(), T::max);
self.level_half_width_sq.clear();
for level in 0..=bits {
let half_width = max_extent / T::from(1u64 << (level + 1)).unwrap();
self.level_half_width_sq.push(half_width * half_width);
}
self.sorted.par_extend(
(0..n_samples)
.into_par_iter()
.with_min_len(PARALLEL_CODE_THRESHOLD)
.map(|i| {
let point = &y[i * D..i * D + D];
let code = <Dim<D> as Morton<D>>::encode(quantize::<T, D>(
point, &min, &inv_scale, max_bucket,
));
(code, i as u32)
}),
);
self.sorted.par_sort_unstable_by_key(|&(code, _)| code);
let sorted = &self.sorted;
let nodes = &mut self.nodes;
let ranges = &mut self.ranges;
nodes.reserve(2 * n_samples - 1);
ranges.reserve(2 * n_samples - 1);
nodes.push(Node {
center_of_mass: [T::zero(); D],
count: n_samples as u32,
first_child: SENTINEL,
child_count: 0,
level: bits as u8,
});
ranges.push((0, n_samples as u32));
let mask = (1u64 << D) - 1;
let mut node = 0usize;
while node < nodes.len() {
let (start, end) = ranges[node];
if end - start <= 1 || sorted[start as usize].0 == sorted[(end - 1) as usize].0 {
node += 1;
continue;
}
let xor = sorted[start as usize].0 ^ sorted[(end - 1) as usize].0;
let highest_diff = 63 - xor.leading_zeros();
let level = (bits - 1) - highest_diff / D as u32;
let shift = D as u32 * (bits - 1 - level);
nodes[node].level = level as u8;
let first_child = nodes.len() as u32;
let mut child_count: u8 = 0;
let mut child_start = start;
while child_start < end {
let group = (sorted[child_start as usize].0 >> shift) & mask;
let mut child_end = child_start + 1;
while child_end < end && (sorted[child_end as usize].0 >> shift) & mask == group {
child_end += 1;
}
nodes.push(Node {
center_of_mass: [T::zero(); D],
count: (child_end - child_start),
first_child: SENTINEL,
child_count: 0,
level: bits as u8,
});
ranges.push((child_start, child_end));
child_count += 1;
child_start = child_end;
}
debug_assert!(child_count as usize <= <Dim<D> as Morton<D>>::CHILDREN);
nodes[node].first_child = first_child;
nodes[node].child_count = child_count;
node += 1;
}
nodes
.par_iter_mut()
.zip(ranges.par_iter())
.with_min_len(PARALLEL_CODE_THRESHOLD)
.for_each(|(node, &(start, end))| {
if node.first_child == SENTINEL {
let mut center = [T::zero(); D];
for slot in start..end {
let index = sorted[slot as usize].1 as usize;
let point = &y[index * D..index * D + D];
center
.iter_mut()
.zip(point.iter())
.for_each(|(ci, pi)| *ci += *pi);
}
let inverse = T::from(node.count).unwrap().recip();
center
.iter_mut()
.for_each(|value| *value = *value * inverse);
node.center_of_mass = center;
}
});
for node in (0..nodes.len()).rev() {
if nodes[node].first_child == SENTINEL {
continue;
}
let first_child = nodes[node].first_child as usize;
let child_count = nodes[node].child_count as usize;
let total = T::from(nodes[node].count).unwrap();
let mut center = [T::zero(); D];
for child in &nodes[first_child..first_child + child_count] {
let weight = T::from(child.count).unwrap();
center
.iter_mut()
.zip(child.center_of_mass.iter())
.for_each(|(value, &component)| *value += component * weight);
}
let inverse = total.recip();
center
.iter_mut()
.for_each(|value| *value = *value * inverse);
nodes[node].center_of_mass = center;
}
debug_assert_eq!(
nodes
.iter()
.filter(|node| node.first_child == SENTINEL)
.map(|node| node.count as u64)
.sum::<u64>() as usize,
n_samples,
"arena lost or invented points"
);
debug_assert!(check_coms_within_cells::<T, D>(
nodes, ranges, sorted, &min, &extent, bits
));
}
pub(crate) fn compute_non_edge_forces(
&self,
index: usize,
theta_sq: T,
y: &[T],
negative_forces_row: &mut [T; D],
q_sum: &mut T,
stack: &mut [u32],
) {
if self.nodes.is_empty() {
return;
}
let query = &y[index * D..index * D + D];
let mut top = 0usize;
stack[top] = 0;
top += 1;
while top > 0 {
top -= 1;
let node = &self.nodes[stack[top] as usize];
let mut displacement = [T::zero(); D];
let mut distance = T::zero();
for axis in 0..D {
let delta = query[axis] - node.center_of_mass[axis];
displacement[axis] = delta;
distance += delta * delta;
}
if node.first_child == SENTINEL {
if distance == T::zero() {
continue;
}
} else if self.level_half_width_sq[node.level as usize] >= theta_sq * distance {
for child in 0..node.child_count as u32 {
stack[top] = node.first_child + child;
top += 1;
}
continue;
}
let inverse = (T::one() + distance).recip();
let mut magnitude = T::from(node.count).unwrap() * inverse;
*q_sum += magnitude;
magnitude = magnitude * inverse;
for axis in 0..D {
negative_forces_row[axis] += magnitude * displacement[axis];
}
}
}
}
pub(crate) fn compute_edge_forces<T, const D: usize>(
index: usize,
y: &[T],
p_rows: &[usize],
p_columns: &[u32],
p_values: &[T],
positive_forces_row: &mut [T],
) where
T: Float + AddAssign,
{
let sample = &y[index * D..index * D + D];
for entry in p_rows[index]..p_rows[index + 1] {
let other = p_columns[entry] as usize;
let other_sample = &y[other * D..other * D + D];
let mut displacement = [T::zero(); D];
let mut distance = T::zero();
for axis in 0..D {
let delta = sample[axis] - other_sample[axis];
displacement[axis] = delta;
distance += delta * delta;
}
let factor = p_values[entry] / (distance + T::one());
for axis in 0..D {
positive_forces_row[axis] += factor * displacement[axis];
}
}
}
fn check_coms_within_cells<T, const D: usize>(
nodes: &[Node<T, D>],
ranges: &[(u32, u32)],
sorted: &[(u64, u32)],
min: &[T; D],
extent: &[T; D],
bits: u32,
) -> bool
where
T: Float,
Dim<D>: Morton<D>,
{
let slack_fraction = T::from(1e-3).unwrap();
nodes.iter().zip(ranges.iter()).all(|(node, &(start, _))| {
let level = node.level as u32;
let coords = <Dim<D> as Morton<D>>::decode(sorted[start as usize].0);
let cells = T::from(1u64 << level).unwrap();
(0..D).all(|axis| {
let width = extent[axis] / cells;
let cell_index = T::from((coords[axis] as u64) >> (bits - level)).unwrap();
let low = min[axis] + cell_index * width;
let high = low + width;
let magnitude = low.abs().max(high.abs());
let slack = slack_fraction * (extent[axis] + magnitude) + T::min_positive_value();
node.center_of_mass[axis] >= low - slack && node.center_of_mass[axis] <= high + slack
})
})
}
#[cfg(test)]
impl<T, const D: usize> Arena<T, D> {
pub(crate) fn root_count(&self) -> usize {
self.nodes.first().map_or(0, |node| node.count as usize)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn lcg_cloud(n: usize, dim: usize, mut state: u64) -> Vec<f32> {
let mut data = Vec::with_capacity(n * dim);
for _ in 0..n * dim {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
data.push((state >> 40) as f32 / (1u64 << 24) as f32);
}
data
}
fn mean<const D: usize>(y: &[f32], n: usize) -> [f32; D] {
let mut sum = [0.0f32; D];
for point in y.chunks_exact(D) {
for axis in 0..D {
sum[axis] += point[axis];
}
}
sum.iter_mut().for_each(|value| *value /= n as f32);
sum
}
#[test]
fn build_conserves_points_and_keeps_coms_in_cells_2d() {
const N: usize = 2_000;
let mut data = lcg_cloud(N, 2, 17);
for value in data.iter_mut() {
*value += 100.0;
}
let arena = Arena::<f32, 2>::new(&data, N);
assert_eq!(arena.root_count(), N);
}
#[test]
fn build_conserves_points_3d() {
const N: usize = 1_500;
let data = lcg_cloud(N, 3, 23);
let arena = Arena::<f32, 3>::new(&data, N);
assert_eq!(arena.root_count(), N);
}
#[test]
fn root_center_of_mass_equals_the_mean() {
const N: usize = 1_000;
let data = lcg_cloud(N, 2, 5);
let arena = Arena::<f32, 2>::new(&data, N);
let expected = mean::<2>(&data, N);
let root = &arena.nodes[0];
assert!((root.center_of_mass[0] - expected[0]).abs() < 1e-3);
assert!((root.center_of_mass[1] - expected[1]).abs() < 1e-3);
}
#[test]
fn duplicate_points_collapse_to_one_leaf() {
const N: usize = 500;
let data = vec![3.5f32; N * 2];
let arena = Arena::<f32, 2>::new(&data, N);
assert_eq!(arena.root_count(), N);
assert_eq!(arena.nodes.len(), 1);
assert_eq!(arena.nodes[0].first_child, SENTINEL);
assert_eq!(arena.nodes[0].count as usize, N);
}
#[test]
fn single_point_builds_a_leaf_root() {
let data = [2.0f32, -1.0];
let arena = Arena::<f32, 2>::new(&data, 1);
assert_eq!(arena.root_count(), 1);
assert_eq!(arena.nodes.len(), 1);
assert_eq!(arena.nodes[0].center_of_mass, [2.0, -1.0]);
}
#[test]
fn empty_input_builds_empty_arena() {
let arena = Arena::<f32, 2>::new(&[], 0);
assert_eq!(arena.root_count(), 0);
let mut forces = [0.0f32; 2];
let mut q_sum = 0.0f32;
let mut stack = [0u32; STACK_CAP];
arena.compute_non_edge_forces(0, 0.25, &[], &mut forces, &mut q_sum, &mut stack);
assert_eq!(forces, [0.0, 0.0]);
assert_eq!(q_sum, 0.0);
}
}