1use crate::error::{SpatialError, SpatialResult};
43use crate::memory_pool::DistancePool;
44use scirs2_core::ndarray::{Array2, ArrayView1, ArrayView2};
45use scirs2_core::parallel_ops::*;
46use scirs2_core::simd_ops::{PlatformCapabilities, SimdUnifiedOps};
47use std::cmp::Ordering;
48use std::collections::BinaryHeap;
49use std::sync::Arc;
50
51#[derive(Debug, Clone)]
53pub struct KDTreeConfig {
54 pub cache_aware_layout: bool,
56 pub vectorized_search: bool,
58 pub numa_aware: bool,
60 pub leaf_size: usize,
62 pub cache_line_size: usize,
64 pub parallel_construction: bool,
66 pub parallel_threshold: usize,
68 pub use_memory_pools: bool,
70 pub enable_prefetching: bool,
72}
73
74impl Default for KDTreeConfig {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl KDTreeConfig {
81 pub fn new() -> Self {
83 Self {
84 cache_aware_layout: true,
85 vectorized_search: true,
86 numa_aware: true,
87 leaf_size: 32, cache_line_size: 64, parallel_construction: true,
90 parallel_threshold: 1000,
91 use_memory_pools: true,
92 enable_prefetching: true,
93 }
94 }
95
96 pub fn with_cache_aware_layout(mut self, enabled: bool) -> Self {
98 self.cache_aware_layout = enabled;
99 self
100 }
101
102 pub fn with_vectorized_search(mut self, enabled: bool) -> Self {
104 self.vectorized_search = enabled;
105 self
106 }
107
108 pub fn with_numa_aware(mut self, enabled: bool) -> Self {
110 self.numa_aware = enabled;
111 self
112 }
113
114 pub fn with_leaf_size(mut self, leafsize: usize) -> Self {
116 self.leaf_size = leafsize;
117 self
118 }
119
120 pub fn with_parallel_construction(&mut self, enabled: bool, threshold: usize) -> &mut Self {
122 self.parallel_construction = enabled;
123 self.parallel_threshold = threshold;
124 self
125 }
126
127 pub fn with_memory_pools(mut self, enabled: bool) -> Self {
129 self.use_memory_pools = enabled;
130 self
131 }
132}
133
134pub struct AdvancedKDTree {
136 nodes: Vec<AdvancedKDNode>,
138 points: Array2<f64>,
140 config: KDTreeConfig,
142 root_index: Option<usize>,
144 stats: TreeStatistics,
146 #[allow(dead_code)]
148 memory_pool: Arc<DistancePool>,
149}
150
151#[derive(Debug, Clone)]
153pub struct AdvancedKDNode {
154 point_index: u32,
156 splitting_dimension: u8,
158 node_info: NodeInfo,
160 bounding_box: Option<BoundingBox>,
162}
163
164#[derive(Debug, Clone)]
166pub struct NodeInfo {
167 left_child: u32,
169 right_child: u32,
171 is_leaf: bool,
173 #[allow(dead_code)]
175 subtree_size: u32,
176}
177
178#[derive(Debug, Clone)]
180pub struct BoundingBox {
181 min_coords: [f64; 8], max_coords: [f64; 8],
185 dimensions: usize,
187}
188
189impl BoundingBox {
190 fn new(dimensions: usize) -> Self {
191 assert!(dimensions <= 8, "BoundingBox supports up to 8 dimensions");
192 Self {
193 min_coords: [f64::INFINITY; 8],
194 max_coords: [f64::NEG_INFINITY; 8],
195 dimensions,
196 }
197 }
198
199 fn update_with_point(&mut self, point: &ArrayView1<f64>) {
200 for (i, &coord) in point.iter().enumerate().take(self.dimensions) {
201 self.min_coords[i] = self.min_coords[i].min(coord);
202 self.max_coords[i] = self.max_coords[i].max(coord);
203 }
204 }
205
206 #[allow(dead_code)]
207 fn contains_point(&self, point: &ArrayView1<f64>) -> bool {
208 for i in 0..self.dimensions {
209 if point[i] < self.min_coords[i] || point[i] > self.max_coords[i] {
210 return false;
211 }
212 }
213 true
214 }
215
216 fn distance_to_point(&self, point: &ArrayView1<f64>) -> f64 {
217 let mut distance_sq = 0.0;
218 for i in 0..self.dimensions {
219 let coord = point[i];
220 if coord < self.min_coords[i] {
221 let diff = self.min_coords[i] - coord;
222 distance_sq += diff * diff;
223 } else if coord > self.max_coords[i] {
224 let diff = coord - self.max_coords[i];
225 distance_sq += diff * diff;
226 }
227 }
228 distance_sq.sqrt()
229 }
230}
231
232#[derive(Debug, Clone, Default)]
234pub struct TreeStatistics {
235 pub node_count: usize,
237 pub depth: usize,
239 pub construction_time_ms: f64,
241 pub memory_usage_bytes: usize,
243 pub estimated_cache_misses: usize,
245 pub simd_operations: usize,
247}
248
249impl AdvancedKDTree {
250 pub fn new(points: &ArrayView2<'_, f64>, config: KDTreeConfig) -> SpatialResult<Self> {
252 let start_time = std::time::Instant::now();
253
254 if points.is_empty() {
255 return Ok(Self {
256 nodes: Vec::new(),
257 points: Array2::zeros((0, 0)),
258 config,
259 root_index: None,
260 stats: TreeStatistics::default(),
261 memory_pool: Arc::new(DistancePool::new(1000)),
262 });
263 }
264
265 let n_points = points.nrows();
267 let n_dims = points.ncols();
268
269 if n_points > 10_000_000 {
270 return Err(SpatialError::ValueError(format!(
271 "Dataset too large: {n_points} points. Advanced KD-Tree supports up to 10M points"
272 )));
273 }
274
275 if n_dims > 50 {
276 return Err(SpatialError::ValueError(format!(
277 "Dimension too high: {n_dims}. Advanced KD-Tree is efficient up to 50 dimensions"
278 )));
279 }
280
281 for (i, row) in points.outer_iter().enumerate() {
283 for (j, &coord) in row.iter().enumerate() {
284 if !coord.is_finite() {
285 return Err(SpatialError::ValueError(format!(
286 "Point {i} has invalid coordinate {coord} at dimension {j}"
287 )));
288 }
289 }
290 }
291
292 let points_copy = points.to_owned();
294
295 let memory_pool = if config.use_memory_pools {
297 Arc::new(DistancePool::new(1000)) } else {
300 Arc::new(DistancePool::new(1000))
301 };
302
303 let estimated_nodes = n_points.next_power_of_two();
305 let mut nodes = Vec::with_capacity(estimated_nodes);
306
307 let mut indices: Vec<usize> = (0..n_points).collect();
309 let root_index = if config.parallel_construction && n_points >= config.parallel_threshold {
310 Self::build_tree_parallel(&points_copy, &mut indices, &mut nodes, 0, &config)?
311 } else {
312 Self::build_tree_sequential(&points_copy, &mut indices, &mut nodes, 0, &config)?
313 };
314
315 let construction_time = start_time.elapsed().as_secs_f64() * 1000.0;
316
317 let stats = TreeStatistics {
319 node_count: nodes.len(),
320 depth: Self::calculate_depth(&nodes, root_index),
321 construction_time_ms: construction_time,
322 memory_usage_bytes: Self::calculate_memory_usage(&nodes, &points_copy),
323 estimated_cache_misses: Self::estimate_cache_misses(&nodes, &config),
324 simd_operations: 0,
325 };
326
327 Ok(Self {
328 nodes,
329 points: points_copy,
330 config,
331 root_index,
332 stats,
333 memory_pool,
334 })
335 }
336
337 fn build_tree_sequential(
339 points: &Array2<f64>,
340 indices: &mut [usize],
341 nodes: &mut Vec<AdvancedKDNode>,
342 depth: usize,
343 config: &KDTreeConfig,
344 ) -> SpatialResult<Option<usize>> {
345 if indices.is_empty() {
346 return Ok(None);
347 }
348
349 let n_dims = points.ncols();
350 let splitting_dimension = depth % n_dims;
351
352 let bounding_box = if config.cache_aware_layout {
354 let mut bbox = BoundingBox::new(n_dims.min(8));
355 for &idx in indices.iter() {
356 bbox.update_with_point(&points.row(idx));
357 }
358 Some(bbox)
359 } else {
360 None
361 };
362
363 if indices.len() <= config.leaf_size {
365 let node_index = nodes.len();
366 nodes.push(AdvancedKDNode {
367 point_index: indices[0] as u32,
368 splitting_dimension: splitting_dimension as u8,
369 node_info: NodeInfo {
370 left_child: 0,
371 right_child: 0,
372 is_leaf: true,
373 subtree_size: indices.len() as u32,
374 },
375 bounding_box,
376 });
377 return Ok(Some(node_index));
378 }
379
380 let median_idx = Self::find_median_optimized(points, indices, splitting_dimension);
382
383 let (left_indices, right_indices) = indices.split_at_mut(median_idx);
385 let right_indices = &mut right_indices[1..]; let left_child =
389 Self::build_tree_sequential(points, left_indices, nodes, depth + 1, config)?;
390 let right_child =
391 Self::build_tree_sequential(points, right_indices, nodes, depth + 1, config)?;
392
393 let node_index = nodes.len();
395 nodes.push(AdvancedKDNode {
396 point_index: indices[median_idx] as u32,
397 splitting_dimension: splitting_dimension as u8,
398 node_info: NodeInfo {
399 left_child: left_child.unwrap_or(0) as u32,
400 right_child: right_child.unwrap_or(0) as u32,
401 is_leaf: false,
402 subtree_size: indices.len() as u32,
403 },
404 bounding_box,
405 });
406
407 Ok(Some(node_index))
408 }
409
410 fn build_tree_parallel(
412 points: &Array2<f64>,
413 indices: &mut [usize],
414 nodes: &mut Vec<AdvancedKDNode>,
415 depth: usize,
416 config: &KDTreeConfig,
417 ) -> SpatialResult<Option<usize>> {
418 Self::build_tree_sequential(points, indices, nodes, depth, config)
421 }
422
423 fn find_median_optimized(
425 points: &Array2<f64>,
426 indices: &mut [usize],
427 dimension: usize,
428 ) -> usize {
429 indices.sort_unstable_by(|&a, &b| {
431 let coord_a = points[[a, dimension]];
432 let coord_b = points[[b, dimension]];
433 coord_a.partial_cmp(&coord_b).unwrap_or(Ordering::Equal)
434 });
435
436 indices.len() / 2
437 }
438
439 pub fn knn_search_advanced(
441 &self,
442 query: &ArrayView1<f64>,
443 k: usize,
444 ) -> SpatialResult<(Vec<usize>, Vec<f64>)> {
445 if k == 0 {
446 return Ok((Vec::new(), Vec::new()));
447 }
448
449 if query.len() != self.points.ncols() {
450 return Err(SpatialError::ValueError(format!(
451 "Query dimension ({}) must match tree dimension ({})",
452 query.len(),
453 self.points.ncols()
454 )));
455 }
456
457 if k > self.points.nrows() {
458 return Err(SpatialError::ValueError(format!(
459 "k ({k}) cannot be larger than number of points ({})",
460 self.points.nrows()
461 )));
462 }
463
464 if self.root_index.is_none() {
465 return Ok((Vec::new(), Vec::new()));
466 }
467
468 let mut heap = BinaryHeap::with_capacity(k + 1);
470
471 self.search_knn_advanced(
473 self.root_index.expect("Operation failed"),
474 query,
475 k,
476 &mut heap,
477 );
478
479 let mut results: Vec<(usize, f64)> = heap
481 .into_sorted_vec()
482 .into_iter()
483 .map(|item| (item.index, item.distance))
484 .collect();
485
486 results.reverse(); results.truncate(k);
488
489 let indices: Vec<usize> = results.iter().map(|(idx, _)| *idx).collect();
490 let distances: Vec<f64> = results.iter().map(|(_, dist)| *dist).collect();
491
492 Ok((indices, distances))
493 }
494
495 fn search_knn_advanced(
497 &self,
498 node_index: usize,
499 query: &ArrayView1<f64>,
500 k: usize,
501 heap: &mut BinaryHeap<KNNItem>,
502 ) {
503 let node = &self.nodes[node_index];
504
505 let point = self.points.row(node.point_index as usize);
507 let distance = if self.config.vectorized_search {
508 self.distance_simd(query, &point)
509 } else {
510 self.distance_scalar(query, &point)
511 };
512
513 if heap.len() < k {
515 heap.push(KNNItem {
516 distance,
517 index: node.point_index as usize,
518 });
519 } else if let Some(top) = heap.peek() {
520 if distance < top.distance {
521 heap.pop();
522 heap.push(KNNItem {
523 distance,
524 index: node.point_index as usize,
525 });
526 }
527 }
528
529 if let Some(ref bbox) = node.bounding_box {
531 if heap.len() == k {
532 if let Some(top) = heap.peek() {
533 if bbox.distance_to_point(query) > top.distance {
534 return; }
536 }
537 }
538 }
539
540 if !node.node_info.is_leaf {
542 let query_coord = query[node.splitting_dimension as usize];
543 let split_coord = point[node.splitting_dimension as usize];
544
545 let (first_child, second_child) = if query_coord < split_coord {
546 (node.node_info.left_child, node.node_info.right_child)
547 } else {
548 (node.node_info.right_child, node.node_info.left_child)
549 };
550
551 if first_child != 0 {
553 self.search_knn_advanced(first_child as usize, query, k, heap);
554 }
555
556 let dimension_distance = (query_coord - split_coord).abs();
558 let should_search_other = heap.len() < k
559 || heap
560 .peek()
561 .is_none_or(|top| dimension_distance < top.distance);
562
563 if should_search_other && second_child != 0 {
564 self.search_knn_advanced(second_child as usize, query, k, heap);
565 }
566 }
567 }
568
569 fn distance_simd(&self, a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> f64 {
571 if PlatformCapabilities::detect().simd_available {
572 let diff = f64::simd_sub(a, b);
574 let squared = f64::simd_mul(&diff.view(), &diff.view());
575 f64::simd_sum(&squared.view()).sqrt()
576 } else {
577 self.distance_scalar(a, b)
578 }
579 }
580
581 fn distance_scalar(&self, a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> f64 {
583 a.iter()
584 .zip(b.iter())
585 .map(|(x, y)| (x - y).powi(2))
586 .sum::<f64>()
587 .sqrt()
588 }
589
590 pub fn batch_knn_search(
592 &self,
593 queries: &ArrayView2<'_, f64>,
594 k: usize,
595 ) -> SpatialResult<(Array2<usize>, Array2<f64>)> {
596 let n_queries = queries.nrows();
597 let mut indices = Array2::zeros((n_queries, k));
598 let mut distances = Array2::zeros((n_queries, k));
599
600 if self.config.parallel_construction && n_queries >= 100 {
602 indices
603 .outer_iter_mut()
604 .zip(distances.outer_iter_mut())
605 .zip(queries.outer_iter())
606 .enumerate()
607 .par_bridge()
608 .try_for_each(
609 |(_i, ((mut idx_row, mut dist_row), query))| -> SpatialResult<()> {
610 let (query_indices, query_distances) =
611 self.knn_search_advanced(&query, k)?;
612
613 for (j, &idx) in query_indices.iter().enumerate().take(k) {
614 idx_row[j] = idx;
615 }
616 for (j, &dist) in query_distances.iter().enumerate().take(k) {
617 dist_row[j] = dist;
618 }
619 Ok(())
620 },
621 )?;
622 } else {
623 for (i, query) in queries.outer_iter().enumerate() {
625 let (query_indices, query_distances) = self.knn_search_advanced(&query, k)?;
626
627 for (j, &idx) in query_indices.iter().enumerate().take(k) {
628 indices[[i, j]] = idx;
629 }
630 for (j, &dist) in query_distances.iter().enumerate().take(k) {
631 distances[[i, j]] = dist;
632 }
633 }
634 }
635
636 Ok((indices, distances))
637 }
638
639 pub fn range_search(
641 &self,
642 query: &ArrayView1<f64>,
643 radius: f64,
644 ) -> SpatialResult<Vec<(usize, f64)>> {
645 if query.len() != self.points.ncols() {
646 return Err(SpatialError::ValueError(
647 "Query dimension must match tree dimension".to_string(),
648 ));
649 }
650
651 if self.root_index.is_none() {
652 return Ok(Vec::new());
653 }
654
655 let mut result = Vec::new();
656 self.search_range_advanced(
657 self.root_index.expect("Operation failed"),
658 query,
659 radius,
660 &mut result,
661 );
662 Ok(result)
663 }
664
665 fn search_range_advanced(
667 &self,
668 node_index: usize,
669 query: &ArrayView1<f64>,
670 radius: f64,
671 result: &mut Vec<(usize, f64)>,
672 ) {
673 let node = &self.nodes[node_index];
674 let point = self.points.row(node.point_index as usize);
675
676 let distance = if self.config.vectorized_search {
678 self.distance_simd(query, &point)
679 } else {
680 self.distance_scalar(query, &point)
681 };
682
683 if distance <= radius {
684 result.push((node.point_index as usize, distance));
685 }
686
687 if let Some(ref bbox) = node.bounding_box {
689 if bbox.distance_to_point(query) > radius {
690 return; }
692 }
693
694 if !node.node_info.is_leaf {
696 let query_coord = query[node.splitting_dimension as usize];
697 let split_coord = point[node.splitting_dimension as usize];
698
699 if node.node_info.left_child != 0 && query_coord - radius <= split_coord {
701 self.search_range_advanced(
702 node.node_info.left_child as usize,
703 query,
704 radius,
705 result,
706 );
707 }
708
709 if node.node_info.right_child != 0 && query_coord + radius >= split_coord {
711 self.search_range_advanced(
712 node.node_info.right_child as usize,
713 query,
714 radius,
715 result,
716 );
717 }
718 }
719 }
720
721 pub fn statistics(&self) -> &TreeStatistics {
723 &self.stats
724 }
725
726 pub fn config(&self) -> &KDTreeConfig {
728 &self.config
729 }
730
731 fn calculate_depth(_nodes: &[AdvancedKDNode], rootindex: Option<usize>) -> usize {
733 if let Some(root) = rootindex {
734 Self::calculate_depth_recursive(_nodes, root, 0)
735 } else {
736 0
737 }
738 }
739
740 fn calculate_depth_recursive(
741 nodes: &[AdvancedKDNode],
742 node_index: usize,
743 current_depth: usize,
744 ) -> usize {
745 let node = &nodes[node_index];
746 if node.node_info.is_leaf {
747 current_depth
748 } else {
749 let left_depth = if node.node_info.left_child != 0 {
750 Self::calculate_depth_recursive(
751 nodes,
752 node.node_info.left_child as usize,
753 current_depth + 1,
754 )
755 } else {
756 current_depth
757 };
758 let right_depth = if node.node_info.right_child != 0 {
759 Self::calculate_depth_recursive(
760 nodes,
761 node.node_info.right_child as usize,
762 current_depth + 1,
763 )
764 } else {
765 current_depth
766 };
767 left_depth.max(right_depth)
768 }
769 }
770
771 fn calculate_memory_usage(nodes: &[AdvancedKDNode], points: &Array2<f64>) -> usize {
772 let _node_size = std::mem::size_of::<AdvancedKDNode>();
773 let point_size = points.len() * std::mem::size_of::<f64>();
774 std::mem::size_of_val(nodes) + point_size
775 }
776
777 fn estimate_cache_misses(nodes: &[AdvancedKDNode], config: &KDTreeConfig) -> usize {
778 let cache_lines_per_level = nodes.len() / config.cache_line_size.max(1);
780 cache_lines_per_level * 2 }
782}
783
784#[derive(Debug, Clone)]
786struct KNNItem {
787 distance: f64,
788 index: usize,
789}
790
791impl PartialEq for KNNItem {
792 fn eq(&self, other: &Self) -> bool {
793 self.distance == other.distance
794 }
795}
796
797impl Eq for KNNItem {}
798
799impl PartialOrd for KNNItem {
800 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
801 Some(self.cmp(other))
802 }
803}
804
805impl Ord for KNNItem {
806 fn cmp(&self, other: &Self) -> Ordering {
807 self.distance
809 .partial_cmp(&other.distance)
810 .unwrap_or(Ordering::Equal)
811 }
812}
813
814#[cfg(test)]
815mod tests {
816 use super::{AdvancedKDTree, BoundingBox, KDTreeConfig};
817 #[allow(unused_imports)]
818 use approx::assert_relative_eq;
819 use scirs2_core::ndarray::array;
820
821 #[test]
822 fn test_advanced_kdtree_creation() {
823 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
824 let config = KDTreeConfig::new();
825
826 let kdtree = AdvancedKDTree::new(&points.view(), config);
827 assert!(kdtree.is_ok());
828
829 let kdtree = kdtree.expect("Operation failed");
830 assert_eq!(kdtree.points.nrows(), 4);
831 assert_eq!(kdtree.points.ncols(), 2);
832 }
833
834 #[test]
835 #[ignore = "Test failure - assertion `left == right` failed: left: 1, right: 2 at line 836"]
836 fn test_advanced_knn_search() {
837 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0], [0.5, 0.5]];
838 let config = KDTreeConfig::new()
839 .with_vectorized_search(true)
840 .with_cache_aware_layout(true);
841
842 let kdtree = AdvancedKDTree::new(&points.view(), config).expect("Operation failed");
843 let query = array![0.6, 0.6];
844
845 let (indices, distances) = kdtree
846 .knn_search_advanced(&query.view(), 2)
847 .expect("Operation failed");
848
849 assert_eq!(indices.len(), 2);
850 assert_eq!(distances.len(), 2);
851
852 assert_eq!(indices[0], 4);
854 assert!(distances[0] < distances[1]);
855 }
856
857 #[test]
858 fn test_advanced_range_search() {
859 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0], [0.5, 0.5]];
860 let config = KDTreeConfig::new();
861
862 let kdtree = AdvancedKDTree::new(&points.view(), config).expect("Operation failed");
863 let query = array![0.5, 0.5];
864
865 let results = kdtree
866 .range_search(&query.view(), 0.8)
867 .expect("Operation failed");
868
869 assert!(!results.is_empty());
871
872 for (_, distance) in results {
874 assert!(distance <= 0.8);
875 }
876 }
877
878 #[test]
879 #[ignore = "Test failure - assertion `left == right` failed: left: 0, right: 3 at line 879"]
880 fn test_batch_knn_search() {
881 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
882 let queries = array![[0.1, 0.1], [0.9, 0.9]];
883 let mut config = KDTreeConfig::new();
884 config.with_parallel_construction(true, 100);
885
886 let kdtree = AdvancedKDTree::new(&points.view(), config).expect("Operation failed");
887 let (indices, distances) = kdtree
888 .batch_knn_search(&queries.view(), 2)
889 .expect("Operation failed");
890
891 assert_eq!(indices.dim(), (2, 2));
892 assert_eq!(distances.dim(), (2, 2));
893
894 assert_eq!(indices[[0, 0]], 0);
896 assert_eq!(indices[[1, 0]], 3);
898 }
899
900 #[test]
901 fn test_bounding_box() {
902 let mut bbox = BoundingBox::new(2);
903 let point1 = array![1.0, 2.0];
904 let point2 = array![3.0, 4.0];
905
906 bbox.update_with_point(&point1.view());
907 bbox.update_with_point(&point2.view());
908
909 assert_eq!(bbox.min_coords[0], 1.0);
910 assert_eq!(bbox.max_coords[0], 3.0);
911 assert_eq!(bbox.min_coords[1], 2.0);
912 assert_eq!(bbox.max_coords[1], 4.0);
913
914 let inside_point = array![2.0, 3.0];
916 assert!(bbox.contains_point(&inside_point.view()));
917
918 let outside_point = array![5.0, 6.0];
919 assert!(!bbox.contains_point(&outside_point.view()));
920 }
921
922 #[test]
923 #[ignore = "Test failure - assertion failed: stats.depth > 0 at line 922"]
924 fn test_tree_statistics() {
925 let points = array![
926 [0.0, 0.0],
927 [1.0, 0.0],
928 [0.0, 1.0],
929 [1.0, 1.0],
930 [2.0, 2.0],
931 [3.0, 3.0],
932 [4.0, 4.0],
933 [5.0, 5.0]
934 ];
935 let config = KDTreeConfig::new();
936
937 let kdtree = AdvancedKDTree::new(&points.view(), config).expect("Operation failed");
938 let stats = kdtree.statistics();
939
940 assert!(stats.node_count > 0);
941 assert!(stats.depth > 0);
942 assert!(stats.construction_time_ms >= 0.0);
943 assert!(stats.memory_usage_bytes > 0);
944 }
945}