use super::indices::IndexWorkspace;
use crate::brute::distance_squared;
use crate::{
KdPeriodicOptions, KdTree, KnnOptions, KnnResult, NO_NEIGHBOR, PeriodicBox, SpatialBackend,
SpatialError,
};
use molframe_core::{AtomSelection, ExecutionContext};
pub fn knn_within(
positions: &[[f32; 3]],
query: &AtomSelection,
targets: &AtomSelection,
k: usize,
options: KnnOptions,
backend: SpatialBackend,
periodic: Option<&PeriodicBox>,
context: &ExecutionContext,
) -> Result<KnnResult, SpatialError> {
let indices = IndexWorkspace::new(query, targets, positions.len(), context)?;
if backend == SpatialBackend::BruteForce {
return brute_knn(
positions,
indices.left(),
indices.right(),
k,
options,
periodic,
);
}
KdTree::build_in(
positions,
indices.right(),
periodic.copied(),
KdPeriodicOptions::default(),
context,
)?
.knn(indices.left(), k, options, context)
}
fn brute_knn(
positions: &[[f32; 3]],
query: &[u32],
targets: &[u32],
k: usize,
options: KnnOptions,
periodic: Option<&PeriodicBox>,
) -> Result<KnnResult, SpatialError> {
if let Some(distance) = options.max_distance
&& !(distance.is_finite() && distance >= 0.0)
{
return Err(SpatialError::InvalidCutoff);
}
let limit = options.max_distance.map_or(f32::INFINITY, |d| d * d);
let mut indices = Vec::with_capacity(query.len() * k);
let mut distances = Vec::with_capacity(query.len() * k);
let finite = |point: [f32; 3]| point.iter().all(|value| value.is_finite());
let mut row: Vec<(f32, u32)> = Vec::new();
for &atom in query {
let point = positions
.get(atom as usize)
.copied()
.ok_or(SpatialError::AtomOutOfBounds(atom))?;
row.clear();
if finite(point) {
for &target in targets {
let other = positions
.get(target as usize)
.copied()
.ok_or(SpatialError::AtomOutOfBounds(target))?;
if (options.exclude_self && target == atom) || !finite(other) {
continue;
}
let squared = distance_squared(point, other, periodic);
if squared <= limit {
row.push((squared, target));
}
}
}
row.sort_unstable_by(|a, b| a.0.total_cmp(&b.0).then(a.1.cmp(&b.1)));
for slot in 0..k {
let (id, squared) = row
.get(slot)
.map_or((NO_NEIGHBOR, f32::INFINITY), |&(d, id)| (id, d));
indices.push(id);
distances.push(squared);
}
}
Ok(KnnResult {
k,
indices,
distances_squared: distances,
})
}