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(self.root_index.unwrap(), query, k, &mut heap);
473
474 let mut results: Vec<(usize, f64)> = heap
476 .into_sorted_vec()
477 .into_iter()
478 .map(|item| (item.index, item.distance))
479 .collect();
480
481 results.reverse(); results.truncate(k);
483
484 let indices: Vec<usize> = results.iter().map(|(idx, _)| *idx).collect();
485 let distances: Vec<f64> = results.iter().map(|(_, dist)| *dist).collect();
486
487 Ok((indices, distances))
488 }
489
490 fn search_knn_advanced(
492 &self,
493 node_index: usize,
494 query: &ArrayView1<f64>,
495 k: usize,
496 heap: &mut BinaryHeap<KNNItem>,
497 ) {
498 let node = &self.nodes[node_index];
499
500 let point = self.points.row(node.point_index as usize);
502 let distance = if self.config.vectorized_search {
503 self.distance_simd(query, &point)
504 } else {
505 self.distance_scalar(query, &point)
506 };
507
508 if heap.len() < k {
510 heap.push(KNNItem {
511 distance,
512 index: node.point_index as usize,
513 });
514 } else if let Some(top) = heap.peek() {
515 if distance < top.distance {
516 heap.pop();
517 heap.push(KNNItem {
518 distance,
519 index: node.point_index as usize,
520 });
521 }
522 }
523
524 if let Some(ref bbox) = node.bounding_box {
526 if heap.len() == k {
527 if let Some(top) = heap.peek() {
528 if bbox.distance_to_point(query) > top.distance {
529 return; }
531 }
532 }
533 }
534
535 if !node.node_info.is_leaf {
537 let query_coord = query[node.splitting_dimension as usize];
538 let split_coord = point[node.splitting_dimension as usize];
539
540 let (first_child, second_child) = if query_coord < split_coord {
541 (node.node_info.left_child, node.node_info.right_child)
542 } else {
543 (node.node_info.right_child, node.node_info.left_child)
544 };
545
546 if first_child != 0 {
548 self.search_knn_advanced(first_child as usize, query, k, heap);
549 }
550
551 let dimension_distance = (query_coord - split_coord).abs();
553 let should_search_other = heap.len() < k
554 || heap
555 .peek()
556 .is_none_or(|top| dimension_distance < top.distance);
557
558 if should_search_other && second_child != 0 {
559 self.search_knn_advanced(second_child as usize, query, k, heap);
560 }
561 }
562 }
563
564 fn distance_simd(&self, a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> f64 {
566 if PlatformCapabilities::detect().simd_available {
567 let diff = f64::simd_sub(a, b);
569 let squared = f64::simd_mul(&diff.view(), &diff.view());
570 f64::simd_sum(&squared.view()).sqrt()
571 } else {
572 self.distance_scalar(a, b)
573 }
574 }
575
576 fn distance_scalar(&self, a: &ArrayView1<f64>, b: &ArrayView1<f64>) -> f64 {
578 a.iter()
579 .zip(b.iter())
580 .map(|(x, y)| (x - y).powi(2))
581 .sum::<f64>()
582 .sqrt()
583 }
584
585 pub fn batch_knn_search(
587 &self,
588 queries: &ArrayView2<'_, f64>,
589 k: usize,
590 ) -> SpatialResult<(Array2<usize>, Array2<f64>)> {
591 let n_queries = queries.nrows();
592 let mut indices = Array2::zeros((n_queries, k));
593 let mut distances = Array2::zeros((n_queries, k));
594
595 if self.config.parallel_construction && n_queries >= 100 {
597 indices
598 .outer_iter_mut()
599 .zip(distances.outer_iter_mut())
600 .zip(queries.outer_iter())
601 .enumerate()
602 .par_bridge()
603 .try_for_each(
604 |(_i, ((mut idx_row, mut dist_row), query))| -> SpatialResult<()> {
605 let (query_indices, query_distances) =
606 self.knn_search_advanced(&query, k)?;
607
608 for (j, &idx) in query_indices.iter().enumerate().take(k) {
609 idx_row[j] = idx;
610 }
611 for (j, &dist) in query_distances.iter().enumerate().take(k) {
612 dist_row[j] = dist;
613 }
614 Ok(())
615 },
616 )?;
617 } else {
618 for (i, query) in queries.outer_iter().enumerate() {
620 let (query_indices, query_distances) = self.knn_search_advanced(&query, k)?;
621
622 for (j, &idx) in query_indices.iter().enumerate().take(k) {
623 indices[[i, j]] = idx;
624 }
625 for (j, &dist) in query_distances.iter().enumerate().take(k) {
626 distances[[i, j]] = dist;
627 }
628 }
629 }
630
631 Ok((indices, distances))
632 }
633
634 pub fn range_search(
636 &self,
637 query: &ArrayView1<f64>,
638 radius: f64,
639 ) -> SpatialResult<Vec<(usize, f64)>> {
640 if query.len() != self.points.ncols() {
641 return Err(SpatialError::ValueError(
642 "Query dimension must match tree dimension".to_string(),
643 ));
644 }
645
646 if self.root_index.is_none() {
647 return Ok(Vec::new());
648 }
649
650 let mut result = Vec::new();
651 self.search_range_advanced(self.root_index.unwrap(), query, radius, &mut result);
652 Ok(result)
653 }
654
655 fn search_range_advanced(
657 &self,
658 node_index: usize,
659 query: &ArrayView1<f64>,
660 radius: f64,
661 result: &mut Vec<(usize, f64)>,
662 ) {
663 let node = &self.nodes[node_index];
664 let point = self.points.row(node.point_index as usize);
665
666 let distance = if self.config.vectorized_search {
668 self.distance_simd(query, &point)
669 } else {
670 self.distance_scalar(query, &point)
671 };
672
673 if distance <= radius {
674 result.push((node.point_index as usize, distance));
675 }
676
677 if let Some(ref bbox) = node.bounding_box {
679 if bbox.distance_to_point(query) > radius {
680 return; }
682 }
683
684 if !node.node_info.is_leaf {
686 let query_coord = query[node.splitting_dimension as usize];
687 let split_coord = point[node.splitting_dimension as usize];
688
689 if node.node_info.left_child != 0 && query_coord - radius <= split_coord {
691 self.search_range_advanced(
692 node.node_info.left_child as usize,
693 query,
694 radius,
695 result,
696 );
697 }
698
699 if node.node_info.right_child != 0 && query_coord + radius >= split_coord {
701 self.search_range_advanced(
702 node.node_info.right_child as usize,
703 query,
704 radius,
705 result,
706 );
707 }
708 }
709 }
710
711 pub fn statistics(&self) -> &TreeStatistics {
713 &self.stats
714 }
715
716 pub fn config(&self) -> &KDTreeConfig {
718 &self.config
719 }
720
721 fn calculate_depth(_nodes: &[AdvancedKDNode], rootindex: Option<usize>) -> usize {
723 if let Some(root) = rootindex {
724 Self::calculate_depth_recursive(_nodes, root, 0)
725 } else {
726 0
727 }
728 }
729
730 fn calculate_depth_recursive(
731 nodes: &[AdvancedKDNode],
732 node_index: usize,
733 current_depth: usize,
734 ) -> usize {
735 let node = &nodes[node_index];
736 if node.node_info.is_leaf {
737 current_depth
738 } else {
739 let left_depth = if node.node_info.left_child != 0 {
740 Self::calculate_depth_recursive(
741 nodes,
742 node.node_info.left_child as usize,
743 current_depth + 1,
744 )
745 } else {
746 current_depth
747 };
748 let right_depth = if node.node_info.right_child != 0 {
749 Self::calculate_depth_recursive(
750 nodes,
751 node.node_info.right_child as usize,
752 current_depth + 1,
753 )
754 } else {
755 current_depth
756 };
757 left_depth.max(right_depth)
758 }
759 }
760
761 fn calculate_memory_usage(nodes: &[AdvancedKDNode], points: &Array2<f64>) -> usize {
762 let _node_size = std::mem::size_of::<AdvancedKDNode>();
763 let point_size = points.len() * std::mem::size_of::<f64>();
764 std::mem::size_of_val(nodes) + point_size
765 }
766
767 fn estimate_cache_misses(nodes: &[AdvancedKDNode], config: &KDTreeConfig) -> usize {
768 let cache_lines_per_level = nodes.len() / config.cache_line_size.max(1);
770 cache_lines_per_level * 2 }
772}
773
774#[derive(Debug, Clone)]
776struct KNNItem {
777 distance: f64,
778 index: usize,
779}
780
781impl PartialEq for KNNItem {
782 fn eq(&self, other: &Self) -> bool {
783 self.distance == other.distance
784 }
785}
786
787impl Eq for KNNItem {}
788
789impl PartialOrd for KNNItem {
790 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
791 Some(self.cmp(other))
792 }
793}
794
795impl Ord for KNNItem {
796 fn cmp(&self, other: &Self) -> Ordering {
797 self.distance
799 .partial_cmp(&other.distance)
800 .unwrap_or(Ordering::Equal)
801 }
802}
803
804#[cfg(test)]
805mod tests {
806 use super::{AdvancedKDTree, BoundingBox, KDTreeConfig};
807 #[allow(unused_imports)]
808 use approx::assert_relative_eq;
809 use scirs2_core::ndarray::array;
810
811 #[test]
812 fn test_advanced_kdtree_creation() {
813 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
814 let config = KDTreeConfig::new();
815
816 let kdtree = AdvancedKDTree::new(&points.view(), config);
817 assert!(kdtree.is_ok());
818
819 let kdtree = kdtree.unwrap();
820 assert_eq!(kdtree.points.nrows(), 4);
821 assert_eq!(kdtree.points.ncols(), 2);
822 }
823
824 #[test]
825 #[ignore]
826 fn test_advanced_knn_search() {
827 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0], [0.5, 0.5]];
828 let config = KDTreeConfig::new()
829 .with_vectorized_search(true)
830 .with_cache_aware_layout(true);
831
832 let kdtree = AdvancedKDTree::new(&points.view(), config).unwrap();
833 let query = array![0.6, 0.6];
834
835 let (indices, distances) = kdtree.knn_search_advanced(&query.view(), 2).unwrap();
836
837 assert_eq!(indices.len(), 2);
838 assert_eq!(distances.len(), 2);
839
840 assert_eq!(indices[0], 4);
842 assert!(distances[0] < distances[1]);
843 }
844
845 #[test]
846 #[ignore]
847 fn test_advanced_range_search() {
848 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0], [0.5, 0.5]];
849 let config = KDTreeConfig::new();
850
851 let kdtree = AdvancedKDTree::new(&points.view(), config).unwrap();
852 let query = array![0.5, 0.5];
853
854 let results = kdtree.range_search(&query.view(), 0.8).unwrap();
855
856 assert!(!results.is_empty());
858
859 for (_, distance) in results {
861 assert!(distance <= 0.8);
862 }
863 }
864
865 #[test]
866 #[ignore]
867 fn test_batch_knn_search() {
868 let points = array![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]];
869 let queries = array![[0.1, 0.1], [0.9, 0.9]];
870 let mut config = KDTreeConfig::new();
871 config.with_parallel_construction(true, 100);
872
873 let kdtree = AdvancedKDTree::new(&points.view(), config).unwrap();
874 let (indices, distances) = kdtree.batch_knn_search(&queries.view(), 2).unwrap();
875
876 assert_eq!(indices.dim(), (2, 2));
877 assert_eq!(distances.dim(), (2, 2));
878
879 assert_eq!(indices[[0, 0]], 0);
881 assert_eq!(indices[[1, 0]], 3);
883 }
884
885 #[test]
886 fn test_bounding_box() {
887 let mut bbox = BoundingBox::new(2);
888 let point1 = array![1.0, 2.0];
889 let point2 = array![3.0, 4.0];
890
891 bbox.update_with_point(&point1.view());
892 bbox.update_with_point(&point2.view());
893
894 assert_eq!(bbox.min_coords[0], 1.0);
895 assert_eq!(bbox.max_coords[0], 3.0);
896 assert_eq!(bbox.min_coords[1], 2.0);
897 assert_eq!(bbox.max_coords[1], 4.0);
898
899 let inside_point = array![2.0, 3.0];
901 assert!(bbox.contains_point(&inside_point.view()));
902
903 let outside_point = array![5.0, 6.0];
904 assert!(!bbox.contains_point(&outside_point.view()));
905 }
906
907 #[test]
908 #[ignore]
909 fn test_tree_statistics() {
910 let points = array![
911 [0.0, 0.0],
912 [1.0, 0.0],
913 [0.0, 1.0],
914 [1.0, 1.0],
915 [2.0, 2.0],
916 [3.0, 3.0],
917 [4.0, 4.0],
918 [5.0, 5.0]
919 ];
920 let config = KDTreeConfig::new();
921
922 let kdtree = AdvancedKDTree::new(&points.view(), config).unwrap();
923 let stats = kdtree.statistics();
924
925 assert!(stats.node_count > 0);
926 assert!(stats.depth > 0);
927 assert!(stats.construction_time_ms >= 0.0);
928 assert!(stats.memory_usage_bytes > 0);
929 }
930}