molframe_spatial/queries/
knn.rs1use super::indices::IndexWorkspace;
4use crate::brute::distance_squared;
5use crate::{
6 KdPeriodicOptions, KdTree, KnnOptions, KnnResult, NO_NEIGHBOR, PeriodicBox, SpatialBackend,
7 SpatialError,
8};
9use molframe_core::{AtomSelection, ExecutionContext};
10
11pub fn knn_within(
30 positions: &[[f32; 3]],
31 query: &AtomSelection,
32 targets: &AtomSelection,
33 k: usize,
34 options: KnnOptions,
35 backend: SpatialBackend,
36 periodic: Option<&PeriodicBox>,
37 context: &ExecutionContext,
38) -> Result<KnnResult, SpatialError> {
39 let indices = IndexWorkspace::new(query, targets, positions.len(), context)?;
40 if backend == SpatialBackend::BruteForce {
41 return brute_knn(
42 positions,
43 indices.left(),
44 indices.right(),
45 k,
46 options,
47 periodic,
48 );
49 }
50 KdTree::build_in(
51 positions,
52 indices.right(),
53 periodic.copied(),
54 KdPeriodicOptions::default(),
55 context,
56 )?
57 .knn(indices.left(), k, options, context)
58}
59
60fn brute_knn(
61 positions: &[[f32; 3]],
62 query: &[u32],
63 targets: &[u32],
64 k: usize,
65 options: KnnOptions,
66 periodic: Option<&PeriodicBox>,
67) -> Result<KnnResult, SpatialError> {
68 if let Some(distance) = options.max_distance
69 && !(distance.is_finite() && distance >= 0.0)
70 {
71 return Err(SpatialError::InvalidCutoff);
72 }
73 let limit = options.max_distance.map_or(f32::INFINITY, |d| d * d);
74 let mut indices = Vec::with_capacity(query.len() * k);
75 let mut distances = Vec::with_capacity(query.len() * k);
76 let finite = |point: [f32; 3]| point.iter().all(|value| value.is_finite());
77 let mut row: Vec<(f32, u32)> = Vec::new();
78 for &atom in query {
79 let point = positions
80 .get(atom as usize)
81 .copied()
82 .ok_or(SpatialError::AtomOutOfBounds(atom))?;
83 row.clear();
84 if finite(point) {
85 for &target in targets {
86 let other = positions
87 .get(target as usize)
88 .copied()
89 .ok_or(SpatialError::AtomOutOfBounds(target))?;
90 if (options.exclude_self && target == atom) || !finite(other) {
91 continue;
92 }
93 let squared = distance_squared(point, other, periodic);
94 if squared <= limit {
95 row.push((squared, target));
96 }
97 }
98 }
99 row.sort_unstable_by(|a, b| a.0.total_cmp(&b.0).then(a.1.cmp(&b.1)));
100 for slot in 0..k {
101 let (id, squared) = row
102 .get(slot)
103 .map_or((NO_NEIGHBOR, f32::INFINITY), |&(d, id)| (id, d));
104 indices.push(id);
105 distances.push(squared);
106 }
107 }
108 Ok(KnnResult {
109 k,
110 indices,
111 distances_squared: distances,
112 })
113}