Skip to main content

spatialrust_search/
kdtree.rs

1use std::cmp::Ordering;
2
3use spatialrust_core::{HasPositions3, PointCloud, SpatialResult};
4
5use crate::{
6    chunked::{ChunkQueryRange, ChunkedNearestNeighborIndex, ChunkedRadiusSearchIndex},
7    NearestNeighborIndex, Neighbor, RadiusSearchIndex, SpatialIndex,
8};
9
10const LEAF_SIZE: usize = 16;
11const SEARCH_STACK_SIZE: usize = 64;
12const AXIS_LEAF: u8 = u8::MAX;
13const INVALID_NODE: u32 = u32::MAX;
14
15/// Cache-friendly KD-tree for 3D point clouds.
16#[derive(Clone, Debug)]
17pub struct KdTree {
18    x: Vec<f32>,
19    y: Vec<f32>,
20    z: Vec<f32>,
21    points_order: Vec<u32>,
22    nodes: Vec<KdNode>,
23    root: u32,
24}
25
26#[derive(Clone, Copy, Debug)]
27struct KdNode {
28    split: f32,
29    left: u32,
30    right: u32,
31    start: u32,
32    end: u32,
33    axis: u8,
34}
35
36impl KdTree {
37    /// Builds a KD-tree from coordinate slices.
38    #[must_use]
39    pub fn from_slices(x: &[f32], y: &[f32], z: &[f32]) -> Self {
40        assert_eq!(x.len(), y.len());
41        assert_eq!(x.len(), z.len());
42
43        let len = x.len();
44        let mut points_order: Vec<u32> = (0..len as u32).collect();
45        let mut nodes = Vec::with_capacity(if len == 0 { 0 } else { len.div_ceil(LEAF_SIZE) * 2 });
46
47        let root = if len == 0 {
48            INVALID_NODE
49        } else {
50            build_node(x, y, z, &mut points_order, 0, len, &mut nodes)
51        };
52
53        Self { x: x.to_vec(), y: y.to_vec(), z: z.to_vec(), points_order, nodes, root }
54    }
55
56    /// Builds a KD-tree from any point cloud with XYZ positions.
57    pub fn from_point_cloud(cloud: &PointCloud) -> SpatialResult<Self> {
58        let (x, y, z) = cloud.positions3()?;
59        Ok(Self::from_slices(x, y, z))
60    }
61
62    fn point(&self, point_index: u32) -> (f32, f32, f32) {
63        let idx = point_index as usize;
64        (self.x[idx], self.y[idx], self.z[idx])
65    }
66
67    fn ordered_point(&self, order_index: u32) -> (u32, f32, f32, f32) {
68        let point_index = self.points_order[order_index as usize];
69        let (x, y, z) = self.point(point_index);
70        (point_index, x, y, z)
71    }
72
73    fn nearest_k_recursive(
74        &self,
75        node: u32,
76        qx: f32,
77        qy: f32,
78        qz: f32,
79        k: usize,
80        best: &mut KnnAccumulator,
81    ) {
82        if node == INVALID_NODE {
83            return;
84        }
85
86        let node_data = self.nodes[node as usize];
87        if node_data.axis == AXIS_LEAF {
88            let start = node_data.start as usize;
89            let end = node_data.end as usize;
90            for order_index in start..end {
91                let (index, px, py, pz) = self.ordered_point(order_index as u32);
92                best.insert(
93                    k,
94                    Neighbor {
95                        index: index as usize,
96                        distance_squared: squared_distance(px, py, pz, qx, qy, qz),
97                    },
98                );
99            }
100            return;
101        }
102
103        let diff = match node_data.axis {
104            0 => qx - node_data.split,
105            1 => qy - node_data.split,
106            _ => qz - node_data.split,
107        };
108
109        let (near, far) = if diff <= 0.0 {
110            (node_data.left, node_data.right)
111        } else {
112            (node_data.right, node_data.left)
113        };
114
115        self.nearest_k_recursive(near, qx, qy, qz, k, best);
116
117        let worst = best.prune_distance(k);
118        if diff * diff < worst || best.len() < k {
119            self.nearest_k_recursive(far, qx, qy, qz, k, best);
120        }
121    }
122
123    fn radius_recursive(
124        &self,
125        node: u32,
126        qx: f32,
127        qy: f32,
128        qz: f32,
129        radius_sq: f32,
130        out: &mut Vec<Neighbor>,
131    ) {
132        if node == INVALID_NODE {
133            return;
134        }
135
136        let node_data = self.nodes[node as usize];
137        if node_data.axis == AXIS_LEAF {
138            let start = node_data.start as usize;
139            let end = node_data.end as usize;
140            for order_index in start..end {
141                let (index, px, py, pz) = self.ordered_point(order_index as u32);
142                let distance_squared = squared_distance(px, py, pz, qx, qy, qz);
143                if distance_squared <= radius_sq {
144                    out.push(Neighbor { index: index as usize, distance_squared });
145                }
146            }
147            return;
148        }
149
150        let diff = match node_data.axis {
151            0 => qx - node_data.split,
152            1 => qy - node_data.split,
153            _ => qz - node_data.split,
154        };
155
156        let (near, far) = if diff <= 0.0 {
157            (node_data.left, node_data.right)
158        } else {
159            (node_data.right, node_data.left)
160        };
161
162        self.radius_recursive(near, qx, qy, qz, radius_sq, out);
163        if diff * diff <= radius_sq {
164            self.radius_recursive(far, qx, qy, qz, radius_sq, out);
165        }
166    }
167
168    /// Returns whether at least `target` points lie within `radius` of the
169    /// query, stopping as soon as the threshold is reached. Unlike
170    /// [`radius_search`](RadiusSearchIndex::radius_search) this allocates nothing
171    /// and early-exits, which is much faster for density tests (outlier removal).
172    #[must_use]
173    pub fn radius_reaches(&self, x: f32, y: f32, z: f32, radius: f32, target: usize) -> bool {
174        if target == 0 {
175            return true;
176        }
177        if self.is_empty() || radius < 0.0 {
178            return false;
179        }
180        self.radius_count_iterative(x, y, z, radius * radius, target)
181    }
182
183    fn radius_count_iterative(
184        &self,
185        qx: f32,
186        qy: f32,
187        qz: f32,
188        radius_sq: f32,
189        target: usize,
190    ) -> bool {
191        let mut count = 0usize;
192        let mut stack = [INVALID_NODE; SEARCH_STACK_SIZE];
193        let mut stack_len = 0usize;
194        let mut node = self.root;
195
196        loop {
197            while node != INVALID_NODE {
198                let node_data = self.nodes[node as usize];
199                if node_data.axis == AXIS_LEAF {
200                    let start = node_data.start as usize;
201                    let end = node_data.end as usize;
202                    for order_index in start..end {
203                        let (_, px, py, pz) = self.ordered_point(order_index as u32);
204                        if squared_distance(px, py, pz, qx, qy, qz) <= radius_sq {
205                            count += 1;
206                            if count >= target {
207                                return true;
208                            }
209                        }
210                    }
211                    break;
212                }
213
214                let diff = match node_data.axis {
215                    0 => qx - node_data.split,
216                    1 => qy - node_data.split,
217                    _ => qz - node_data.split,
218                };
219                let (near, far) = if diff <= 0.0 {
220                    (node_data.left, node_data.right)
221                } else {
222                    (node_data.right, node_data.left)
223                };
224
225                if diff * diff <= radius_sq {
226                    if stack_len < stack.len() {
227                        stack[stack_len] = far;
228                        stack_len += 1;
229                    } else if self
230                        .radius_count_recursive(far, qx, qy, qz, radius_sq, target, &mut count)
231                    {
232                        return true;
233                    }
234                }
235                node = near;
236            }
237
238            if stack_len == 0 {
239                return false;
240            }
241            stack_len -= 1;
242            node = stack[stack_len];
243        }
244    }
245
246    /// Accumulates points within `radius_sq` into `count`; returns `true` as soon
247    /// as `count` reaches `target` so the search can short-circuit.
248    fn radius_count_recursive(
249        &self,
250        node: u32,
251        qx: f32,
252        qy: f32,
253        qz: f32,
254        radius_sq: f32,
255        target: usize,
256        count: &mut usize,
257    ) -> bool {
258        if node == INVALID_NODE {
259            return false;
260        }
261
262        let node_data = self.nodes[node as usize];
263        if node_data.axis == AXIS_LEAF {
264            let start = node_data.start as usize;
265            let end = node_data.end as usize;
266            for order_index in start..end {
267                let (_, px, py, pz) = self.ordered_point(order_index as u32);
268                if squared_distance(px, py, pz, qx, qy, qz) <= radius_sq {
269                    *count += 1;
270                    if *count >= target {
271                        return true;
272                    }
273                }
274            }
275            return false;
276        }
277
278        let diff = match node_data.axis {
279            0 => qx - node_data.split,
280            1 => qy - node_data.split,
281            _ => qz - node_data.split,
282        };
283        let (near, far) = if diff <= 0.0 {
284            (node_data.left, node_data.right)
285        } else {
286            (node_data.right, node_data.left)
287        };
288
289        if self.radius_count_recursive(near, qx, qy, qz, radius_sq, target, count) {
290            return true;
291        }
292        if diff * diff <= radius_sq {
293            return self.radius_count_recursive(far, qx, qy, qz, radius_sq, target, count);
294        }
295        false
296    }
297}
298
299impl SpatialIndex for KdTree {
300    fn len(&self) -> usize {
301        self.x.len()
302    }
303}
304
305impl NearestNeighborIndex for KdTree {
306    fn nearest_one(&self, x: f32, y: f32, z: f32) -> Option<Neighbor> {
307        self.nearest_k(x, y, z, 1).into_iter().next()
308    }
309
310    fn nearest_k(&self, x: f32, y: f32, z: f32, k: usize) -> Vec<Neighbor> {
311        let mut best = Vec::with_capacity(k.min(self.len()));
312        self.nearest_k_into(x, y, z, k, &mut best);
313        best
314    }
315}
316
317impl KdTree {
318    /// Finds up to `k` nearest neighbors sorted by ascending distance, reusing
319    /// the caller-provided output buffer.
320    pub fn nearest_k_into(&self, x: f32, y: f32, z: f32, k: usize, out: &mut Vec<Neighbor>) {
321        self.nearest_k_unsorted_into(x, y, z, k, out);
322        out.sort_by(|a, b| {
323            a.distance_squared.partial_cmp(&b.distance_squared).unwrap_or(Ordering::Equal)
324        });
325    }
326
327    /// Finds up to `k` nearest neighbors without sorting the result, reusing the
328    /// caller-provided output buffer. This is faster for callers that only need
329    /// the neighbor set, such as covariance and mean-distance calculations.
330    pub fn nearest_k_unsorted_into(
331        &self,
332        x: f32,
333        y: f32,
334        z: f32,
335        k: usize,
336        out: &mut Vec<Neighbor>,
337    ) {
338        out.clear();
339        if self.is_empty() || k == 0 {
340            return;
341        }
342
343        out.reserve(k.min(self.len()));
344        let mut best = KnnAccumulator::new(out);
345        self.nearest_k_recursive(self.root, x, y, z, k, &mut best);
346    }
347}
348
349impl RadiusSearchIndex for KdTree {
350    fn radius_search(&self, x: f32, y: f32, z: f32, radius: f32) -> Vec<Neighbor> {
351        if self.is_empty() || radius < 0.0 {
352            return Vec::new();
353        }
354
355        let radius_sq = radius * radius;
356        let mut out = Vec::new();
357        self.radius_recursive(self.root, x, y, z, radius_sq, &mut out);
358        // Intentionally unsorted: callers count or iterate neighbors, and
359        // sorting every query dominates radius search on dense clouds.
360        out
361    }
362}
363
364impl ChunkedRadiusSearchIndex for KdTree {
365    fn radius_search_chunk_into(
366        &self,
367        x: &[f32],
368        y: &[f32],
369        z: &[f32],
370        chunk: ChunkQueryRange,
371        radius: f32,
372        out: &mut Vec<(usize, Neighbor)>,
373    ) {
374        if self.is_empty() || radius < 0.0 || chunk.is_empty() {
375            return;
376        }
377
378        let radius_sq = radius * radius;
379        let mut scratch = Vec::new();
380        for index in chunk {
381            scratch.clear();
382            self.radius_recursive(self.root, x[index], y[index], z[index], radius_sq, &mut scratch);
383            for neighbor in scratch.drain(..) {
384                out.push((index, neighbor));
385            }
386        }
387    }
388}
389
390impl ChunkedNearestNeighborIndex for KdTree {
391    fn nearest_k_chunk_into(
392        &self,
393        x: &[f32],
394        y: &[f32],
395        z: &[f32],
396        chunk: ChunkQueryRange,
397        k: usize,
398        out: &mut Vec<(usize, Neighbor)>,
399    ) {
400        if chunk.is_empty() || k == 0 {
401            return;
402        }
403
404        let mut scratch = Vec::new();
405        for index in chunk {
406            scratch.clear();
407            self.nearest_k_unsorted_into(x[index], y[index], z[index], k, &mut scratch);
408            for neighbor in scratch.drain(..) {
409                out.push((index, neighbor));
410            }
411        }
412    }
413}
414
415#[allow(clippy::too_many_arguments)]
416fn build_node(
417    x: &[f32],
418    y: &[f32],
419    z: &[f32],
420    points_order: &mut [u32],
421    start: usize,
422    end: usize,
423    nodes: &mut Vec<KdNode>,
424) -> u32 {
425    let node_index = nodes.len() as u32;
426    nodes.push(KdNode {
427        split: 0.0,
428        left: INVALID_NODE,
429        right: INVALID_NODE,
430        start: start as u32,
431        end: end as u32,
432        axis: 0,
433    });
434
435    let count = end - start;
436    if count <= LEAF_SIZE {
437        nodes[node_index as usize].axis = AXIS_LEAF;
438        return node_index;
439    }
440
441    let axis = select_axis(x, y, z, points_order, start, end);
442    let mid = start + count / 2;
443    select_nth_by_axis(x, y, z, points_order, start, end, axis, mid);
444
445    let split_point = points_order[mid];
446    let split_value = coordinate(x, y, z, split_point, axis);
447    nodes[node_index as usize].axis = axis;
448    nodes[node_index as usize].split = split_value;
449
450    let left = build_node(x, y, z, points_order, start, mid, nodes);
451    let right = build_node(x, y, z, points_order, mid, end, nodes);
452
453    nodes[node_index as usize].left = left;
454    nodes[node_index as usize].right = right;
455    node_index
456}
457
458fn select_axis(
459    x: &[f32],
460    y: &[f32],
461    z: &[f32],
462    points_order: &[u32],
463    start: usize,
464    end: usize,
465) -> u8 {
466    let mut min = [f32::INFINITY; 3];
467    let mut max = [f32::NEG_INFINITY; 3];
468    for &point_index in &points_order[start..end] {
469        min[0] = min[0].min(x[point_index as usize]);
470        min[1] = min[1].min(y[point_index as usize]);
471        min[2] = min[2].min(z[point_index as usize]);
472        max[0] = max[0].max(x[point_index as usize]);
473        max[1] = max[1].max(y[point_index as usize]);
474        max[2] = max[2].max(z[point_index as usize]);
475    }
476
477    let mut best_axis = 0_u8;
478    let mut best_extent = max[0] - min[0];
479    for axis in 1_u8..3 {
480        let extent = max[axis as usize] - min[axis as usize];
481        if extent > best_extent {
482            best_extent = extent;
483            best_axis = axis;
484        }
485    }
486    best_axis
487}
488
489fn select_nth_by_axis(
490    x: &[f32],
491    y: &[f32],
492    z: &[f32],
493    points_order: &mut [u32],
494    start: usize,
495    end: usize,
496    axis: u8,
497    nth: usize,
498) {
499    let mut left = start;
500    let mut right = end;
501    while left < right {
502        let pivot = partition_by_axis(x, y, z, points_order, left, right, axis);
503        match nth.cmp(&pivot) {
504            Ordering::Less => right = pivot,
505            Ordering::Greater => left = pivot + 1,
506            Ordering::Equal => break,
507        }
508    }
509}
510
511fn partition_by_axis(
512    x: &[f32],
513    y: &[f32],
514    z: &[f32],
515    points_order: &mut [u32],
516    start: usize,
517    end: usize,
518    axis: u8,
519) -> usize {
520    let pivot_index = (start + end) / 2;
521    points_order.swap(start, pivot_index);
522    let pivot_point = points_order[start];
523    let pivot_value = coordinate(x, y, z, pivot_point, axis);
524
525    let mut store = start + 1;
526    for i in (start + 1)..end {
527        if coordinate(x, y, z, points_order[i], axis) < pivot_value {
528            points_order.swap(i, store);
529            store += 1;
530        }
531    }
532    points_order.swap(start, store - 1);
533    store - 1
534}
535
536fn coordinate(x: &[f32], y: &[f32], z: &[f32], point_index: u32, axis: u8) -> f32 {
537    match axis {
538        0 => x[point_index as usize],
539        1 => y[point_index as usize],
540        _ => z[point_index as usize],
541    }
542}
543
544fn squared_distance(px: f32, py: f32, pz: f32, qx: f32, qy: f32, qz: f32) -> f32 {
545    let dx = px - qx;
546    let dy = py - qy;
547    let dz = pz - qz;
548    dx * dx + dy * dy + dz * dz
549}
550
551#[derive(Debug)]
552struct KnnAccumulator<'a> {
553    neighbors: &'a mut Vec<Neighbor>,
554    worst_index: usize,
555    worst_distance_squared: f32,
556}
557
558impl<'a> KnnAccumulator<'a> {
559    fn new(neighbors: &'a mut Vec<Neighbor>) -> Self {
560        Self { neighbors, worst_index: 0, worst_distance_squared: 0.0 }
561    }
562
563    fn len(&self) -> usize {
564        self.neighbors.len()
565    }
566
567    fn prune_distance(&self, k: usize) -> f32 {
568        if self.neighbors.len() < k {
569            f32::INFINITY
570        } else {
571            self.worst_distance_squared
572        }
573    }
574
575    fn insert(&mut self, k: usize, candidate: Neighbor) {
576        if k == 0 {
577            return;
578        }
579
580        if self.neighbors.len() < k {
581            let distance_squared = candidate.distance_squared;
582            self.neighbors.push(candidate);
583            if self.neighbors.len() == 1 || distance_squared > self.worst_distance_squared {
584                self.worst_index = self.neighbors.len() - 1;
585                self.worst_distance_squared = distance_squared;
586            }
587            return;
588        }
589
590        if candidate.distance_squared >= self.worst_distance_squared {
591            return;
592        }
593
594        self.neighbors[self.worst_index] = candidate;
595        self.refresh_worst();
596    }
597
598    fn refresh_worst(&mut self) {
599        let mut worst_index = 0usize;
600        let mut worst_distance_squared = self.neighbors[0].distance_squared;
601        for (index, neighbor) in self.neighbors.iter().enumerate().skip(1) {
602            if neighbor.distance_squared > worst_distance_squared {
603                worst_index = index;
604                worst_distance_squared = neighbor.distance_squared;
605            }
606        }
607        self.worst_index = worst_index;
608        self.worst_distance_squared = worst_distance_squared;
609    }
610}
611
612#[cfg(test)]
613mod tests {
614    use super::KdTree;
615    use crate::{
616        brute::{brute_force_knn, brute_force_radius, BruteForceIndex},
617        NearestNeighborIndex, RadiusSearchIndex,
618    };
619    use spatialrust_core::{PointCloudBuilder, StandardSchemas};
620
621    use crate::SpatialIndex;
622
623    fn sample_cloud() -> (Vec<f32>, Vec<f32>, Vec<f32>) {
624        (
625            vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
626            vec![0.0, 0.0, 0.0, 1.0, 2.0, 0.0],
627            vec![0.0, 0.0, 0.0, 0.0, 0.0, 5.0],
628        )
629    }
630
631    #[test]
632    fn nearest_one_matches_brute_force() {
633        let (x, y, z) = sample_cloud();
634        let tree = KdTree::from_slices(&x, &y, &z);
635        let brute = BruteForceIndex::from_slices(&x, &y, &z);
636
637        let query = (2.1_f32, 0.0, 0.0);
638        assert_eq!(
639            tree.nearest_one(query.0, query.1, query.2),
640            brute.nearest_one(query.0, query.1, query.2)
641        );
642    }
643
644    #[test]
645    fn nearest_k_matches_brute_force() {
646        let (x, y, z) = sample_cloud();
647        let tree = KdTree::from_slices(&x, &y, &z);
648        let expected = brute_force_knn(&x, &y, &z, 1.0, 0.0, 0.0, 3);
649        let actual = tree.nearest_k(1.0, 0.0, 0.0, 3);
650        assert_eq!(actual, expected);
651    }
652
653    #[test]
654    fn radius_search_matches_brute_force() {
655        let (x, y, z) = sample_cloud();
656        let tree = KdTree::from_slices(&x, &y, &z);
657        let mut expected = brute_force_radius(&x, &y, &z, 2.0, 0.0, 0.0, 1.5);
658        let mut actual = tree.radius_search(2.0, 0.0, 0.0, 1.5);
659        // `radius_search` is unsorted, so compare as sets ordered by index.
660        expected.sort_by_key(|n| n.index);
661        actual.sort_by_key(|n| n.index);
662        assert_eq!(actual, expected);
663    }
664
665    #[test]
666    fn radius_reaches_matches_radius_search_count() {
667        let (x, y, z) = sample_cloud();
668        let tree = KdTree::from_slices(&x, &y, &z);
669        let count = tree.radius_search(2.0, 0.0, 0.0, 1.5).len();
670        // True for any target up to the real count, false beyond it.
671        assert!(tree.radius_reaches(2.0, 0.0, 0.0, 1.5, count));
672        assert!(!tree.radius_reaches(2.0, 0.0, 0.0, 1.5, count + 1));
673        assert!(tree.radius_reaches(2.0, 0.0, 0.0, 1.5, 0));
674    }
675
676    #[test]
677    fn radius_reaches_matches_brute_force_on_many_queries() {
678        let mut state = 0x8765_4321_u32;
679        let mut next = || {
680            state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
681            (state as f32 / u32::MAX as f32) * 20.0 - 10.0
682        };
683
684        let mut x = Vec::new();
685        let mut y = Vec::new();
686        let mut z = Vec::new();
687        for _ in 0..257 {
688            x.push(next());
689            y.push(next());
690            z.push(next());
691        }
692
693        let tree = KdTree::from_slices(&x, &y, &z);
694        for radius in [0.5_f32, 2.0, 5.0] {
695            for _ in 0..32 {
696                let qx = next();
697                let qy = next();
698                let qz = next();
699                let count = brute_force_radius(&x, &y, &z, qx, qy, qz, radius).len();
700                assert!(tree.radius_reaches(qx, qy, qz, radius, 0));
701                if count > 0 {
702                    assert!(tree.radius_reaches(qx, qy, qz, radius, count));
703                }
704                assert!(!tree.radius_reaches(qx, qy, qz, radius, count + 1));
705            }
706        }
707    }
708
709    #[test]
710    fn nearest_k_matches_brute_force_on_many_queries() {
711        let mut state = 0x1234_5678_u32;
712        let mut next = || {
713            state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
714            (state as f32 / u32::MAX as f32) * 20.0 - 10.0
715        };
716
717        let mut x = Vec::new();
718        let mut y = Vec::new();
719        let mut z = Vec::new();
720        for _ in 0..257 {
721            x.push(next());
722            y.push(next());
723            z.push(next());
724        }
725
726        let tree = KdTree::from_slices(&x, &y, &z);
727        for k in [1_usize, 2, 5, 10, 33] {
728            for _ in 0..64 {
729                let qx = next();
730                let qy = next();
731                let qz = next();
732                let actual = tree.nearest_k(qx, qy, qz, k);
733                let expected = brute_force_knn(&x, &y, &z, qx, qy, qz, k);
734                assert_eq!(actual, expected, "k={k}, query=({qx}, {qy}, {qz})");
735            }
736        }
737    }
738
739    #[test]
740    fn builds_from_point_cloud() {
741        let mut builder = PointCloudBuilder::new(StandardSchemas::point_xyz());
742        builder.push_point([0.0, 0.0, 0.0]).unwrap();
743        builder.push_point([1.0, 0.0, 0.0]).unwrap();
744        let cloud = builder.build().unwrap();
745        let tree = KdTree::from_point_cloud(&cloud).unwrap();
746        assert_eq!(tree.len(), 2);
747        let nearest = tree.nearest_one(0.9, 0.0, 0.0).unwrap();
748        assert_eq!(nearest.index, 1);
749    }
750
751    #[test]
752    fn degenerate_points_return_valid_neighbor() {
753        let x = vec![1.0, 1.0, 1.0];
754        let y = vec![2.0, 2.0, 2.0];
755        let z = vec![3.0, 3.0, 3.0];
756        let tree = KdTree::from_slices(&x, &y, &z);
757        let neighbor = tree.nearest_one(1.0, 2.0, 2.0).unwrap();
758        assert_eq!(neighbor.distance_squared, 1.0);
759    }
760}