1#![cfg_attr(feature = "simd", feature(portable_simd))]
43#![deny(unsafe_code)]
44#![warn(missing_docs)]
45
46mod bounded;
47mod error;
48mod table;
49
50#[cfg(feature = "delaunay-3d")]
51pub mod delaunay3d;
52
53#[cfg(feature = "delaunay-3d")]
54pub use delaunay3d::{Delaunay3dKernel, DelaunayManifoldKnn3, DelaunayTriangulation3};
55
56pub use error::Error;
57pub use table::SuccessorTable;
58
59use bounded::{BoundedNeighbors, neighbor_cmp};
60
61#[derive(Clone, Copy, Debug, PartialEq)]
63pub struct Neighbor {
64 pub index: usize,
66 pub squared_distance: f64,
68}
69
70impl Neighbor {
71 #[must_use]
73 pub fn distance(self) -> f64 {
74 self.squared_distance.sqrt()
75 }
76}
77
78#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
80pub struct DeleteReport {
81 pub removed_references: usize,
83 pub inserted_successors: usize,
85 pub already_present: usize,
87}
88
89#[derive(Clone, Debug)]
94pub struct ManifoldKnn<const D: usize> {
95 points: Vec<[f64; D]>,
96 successors: SuccessorTable,
97 active: Vec<bool>,
98}
99
100impl<const D: usize> ManifoldKnn<D> {
101 pub fn new(points: Vec<[f64; D]>, successors: SuccessorTable) -> Result<Self, Error> {
108 validate_points(&points)?;
109 successors.validate_for_len(points.len())?;
110 let active = vec![true; points.len()];
111 Ok(Self {
112 points,
113 successors,
114 active,
115 })
116 }
117
118 pub fn from_complete_successors(points: Vec<[f64; D]>) -> Result<Self, Error> {
124 let n = points.len();
125 Self::new(points, SuccessorTable::complete(n))
126 }
127
128 pub fn from_insertion_neighbors(
135 points: Vec<[f64; D]>,
136 neighbors_at_insertion: Vec<Vec<usize>>,
137 ) -> Result<Self, Error> {
138 let successors =
139 SuccessorTable::from_insertion_neighbors(points.len(), neighbors_at_insertion)?;
140 Self::new(points, successors)
141 }
142
143 #[must_use]
145 pub fn points(&self) -> &[[f64; D]] {
146 &self.points
147 }
148
149 #[must_use]
151 pub fn successors(&self) -> &SuccessorTable {
152 &self.successors
153 }
154
155 #[must_use]
160 pub fn successors_mut(&mut self) -> &mut SuccessorTable {
161 &mut self.successors
162 }
163
164 #[must_use]
166 #[inline]
167 pub fn len(&self) -> usize {
168 self.points.len()
169 }
170
171 #[must_use]
173 #[inline]
174 pub fn is_empty(&self) -> bool {
175 self.points.is_empty()
176 }
177
178 #[must_use]
180 #[inline]
181 pub fn active_len(&self) -> usize {
182 self.active.iter().filter(|&&is_active| is_active).count()
183 }
184
185 #[must_use]
187 #[inline]
188 pub fn is_active(&self, index: usize) -> bool {
189 self.active.get(index).copied().unwrap_or(false)
190 }
191
192 #[inline]
194 pub fn active_indices(&self) -> impl Iterator<Item = usize> + '_ {
195 self.active
196 .iter()
197 .enumerate()
198 .filter_map(|(index, &is_active)| is_active.then_some(index))
199 }
200
201 #[inline]
210 pub fn insert_with_neighbors<I>(
211 &mut self,
212 point: [f64; D],
213 prior_neighbors: I,
214 ) -> Result<usize, Error>
215 where
216 I: IntoIterator<Item = usize>,
217 {
218 validate_point(self.points.len(), &point)?;
219
220 let new_index = self.points.len();
221 let mut neighbors: Vec<usize> = prior_neighbors.into_iter().collect();
222 neighbors.sort_unstable();
223 neighbors.dedup();
224
225 for &neighbor in &neighbors {
226 if neighbor >= new_index {
227 return Err(Error::InvalidInsertionNeighbor {
228 inserted: new_index,
229 neighbor,
230 });
231 }
232 if !self.active[neighbor] {
233 return Err(Error::InactivePoint { index: neighbor });
234 }
235 }
236
237 self.points.push(point);
238 self.active.push(true);
239 self.successors.push_empty_list();
240
241 for neighbor in neighbors {
242 self.successors.insert_successor(neighbor, new_index)?;
243 }
244
245 Ok(new_index)
246 }
247
248 #[inline]
250 pub fn nearest(&self, query: &[f64; D]) -> Result<Option<Neighbor>, Error> {
251 WORKSPACE.with(|ws| {
252 let mut workspace = ws.borrow_mut();
253 self.nearest_with_workspace(query, &mut workspace)
254 })
255 }
256
257 #[inline]
259 pub fn nearest_with_workspace(
260 &self,
261 query: &[f64; D],
262 workspace: &mut QueryWorkspace,
263 ) -> Result<Option<Neighbor>, Error> {
264 let slice = self.knn_prefix_with_workspace(query, 1, self.points.len(), workspace)?;
265 Ok(slice.first().copied())
266 }
267
268 #[inline]
273 pub fn knn(&self, query: &[f64; D], k: usize) -> Result<Vec<Neighbor>, Error> {
274 self.knn_prefix(query, k, self.points.len())
275 }
276
277 #[inline]
279 pub fn knn_with_workspace<'a>(
280 &self,
281 query: &[f64; D],
282 k: usize,
283 workspace: &'a mut QueryWorkspace,
284 ) -> Result<&'a [Neighbor], Error> {
285 self.knn_prefix_with_workspace(query, k, self.points.len(), workspace)
286 }
287
288 #[inline]
293 pub fn knn_prefix(
294 &self,
295 query: &[f64; D],
296 k: usize,
297 prefix_len: usize,
298 ) -> Result<Vec<Neighbor>, Error> {
299 WORKSPACE.with(|ws| {
300 let mut workspace = ws.borrow_mut();
301 let slice = self.knn_prefix_with_workspace(query, k, prefix_len, &mut workspace)?;
302 Ok(slice.to_vec())
303 })
304 }
305
306 #[inline]
308 pub fn knn_prefix_with_workspace<'a>(
309 &self,
310 query: &[f64; D],
311 k: usize,
312 prefix_len: usize,
313 workspace: &'a mut QueryWorkspace,
314 ) -> Result<&'a [Neighbor], Error> {
315 self.validate_query_and_prefix(query, prefix_len)?;
316 if k == 0 {
317 workspace.candidates.reset(0);
318 return Ok(&[]);
319 }
320
321 let Some(first_active) = self.first_active_before(prefix_len) else {
322 workspace.candidates.reset(0);
323 return Ok(&[]);
324 };
325
326 self.transition_sites_internal_from_index_with_workspace(
327 query,
328 k,
329 prefix_len,
330 first_active,
331 workspace,
332 )?;
333
334 if workspace.processed.len() < self.points.len() {
335 workspace.processed.resize(self.points.len(), false);
336 }
337 if workspace.discovered.len() < self.points.len() {
338 workspace.discovered.resize(self.points.len(), false);
339 }
340
341 for candidate in workspace.candidates.as_slice() {
342 let idx = candidate.index;
343 if !workspace.discovered[idx] {
344 workspace.discovered[idx] = true;
345 workspace.visited_indices.push(idx);
346 }
347 }
348
349 while let Some(index) = workspace
350 .candidates
351 .as_slice()
352 .iter()
353 .find(|candidate| !workspace.processed[candidate.index])
354 .map(|candidate| candidate.index)
355 {
356 workspace.processed[index] = true;
357
358 for &successor in self.successors.list(index) {
359 if successor >= prefix_len {
360 break;
361 }
362 if workspace.discovered[successor] {
363 continue;
364 }
365 if !self.active[successor] {
366 continue;
367 }
368 workspace.discovered[successor] = true;
369 workspace.visited_indices.push(successor);
370
371 workspace.candidates.insert(Neighbor {
372 index: successor,
373 squared_distance: squared_distance(&self.points[successor], query),
374 });
375 }
376 }
377
378 for &index in &workspace.visited_indices {
379 workspace.processed[index] = false;
380 workspace.discovered[index] = false;
381 }
382 workspace.visited_indices.clear();
383
384 Ok(workspace.candidates.as_slice())
385 }
386
387 #[inline]
393 pub fn transition_sites(
394 &self,
395 query: &[f64; D],
396 capacity: usize,
397 ) -> Result<Vec<Neighbor>, Error> {
398 self.transition_sites_prefix(query, capacity, self.points.len())
399 }
400
401 #[inline]
403 pub fn transition_sites_with_workspace<'a>(
404 &self,
405 query: &[f64; D],
406 capacity: usize,
407 workspace: &'a mut QueryWorkspace,
408 ) -> Result<&'a [Neighbor], Error> {
409 self.transition_sites_prefix_with_workspace(query, capacity, self.points.len(), workspace)
410 }
411
412 #[inline]
414 pub fn transition_sites_prefix(
415 &self,
416 query: &[f64; D],
417 capacity: usize,
418 prefix_len: usize,
419 ) -> Result<Vec<Neighbor>, Error> {
420 WORKSPACE.with(|ws| {
421 let mut workspace = ws.borrow_mut();
422 let slice = self.transition_sites_prefix_with_workspace(
423 query,
424 capacity,
425 prefix_len,
426 &mut workspace,
427 )?;
428 Ok(slice.to_vec())
429 })
430 }
431
432 #[inline]
434 pub fn transition_sites_prefix_with_workspace<'a>(
435 &self,
436 query: &[f64; D],
437 capacity: usize,
438 prefix_len: usize,
439 workspace: &'a mut QueryWorkspace,
440 ) -> Result<&'a [Neighbor], Error> {
441 self.validate_query_and_prefix(query, prefix_len)?;
442 self.transition_sites_internal_with_workspace(query, capacity, prefix_len, workspace)?;
443 Ok(workspace.candidates.as_slice())
444 }
445
446 #[inline]
450 pub fn brute_force_knn(&self, query: &[f64; D], k: usize) -> Result<Vec<Neighbor>, Error> {
451 self.brute_force_knn_prefix(query, k, self.points.len())
452 }
453
454 #[inline]
456 pub fn brute_force_knn_prefix(
457 &self,
458 query: &[f64; D],
459 k: usize,
460 prefix_len: usize,
461 ) -> Result<Vec<Neighbor>, Error> {
462 self.validate_query_and_prefix(query, prefix_len)?;
463 if k == 0 {
464 return Ok(Vec::new());
465 }
466
467 let mut neighbors = Vec::new();
468 for index in 0..prefix_len {
469 if self.active[index] {
470 neighbors.push(Neighbor {
471 index,
472 squared_distance: squared_distance(&self.points[index], query),
473 });
474 }
475 }
476 neighbors.sort_by(neighbor_cmp);
477 neighbors.truncate(k);
478 Ok(neighbors)
479 }
480
481 #[inline]
492 pub fn delete_with_new_successors<I>(
493 &mut self,
494 index: usize,
495 new_successors: I,
496 ) -> Result<DeleteReport, Error>
497 where
498 I: IntoIterator<Item = (usize, usize)>,
499 {
500 self.ensure_active_index(index)?;
501
502 let mut edges: Vec<(usize, usize)> = new_successors.into_iter().collect();
503 edges.sort_unstable();
504 edges.dedup();
505
506 for &(owner, successor) in &edges {
507 self.validate_new_successor_edge(index, owner, successor)?;
508 }
509
510 let removed_references = self.successors.remove_references_to(index);
511 self.successors.clear_list(index)?;
512 self.active[index] = false;
513
514 let mut inserted_successors = 0;
515 let mut already_present = 0;
516 for (owner, successor) in edges {
517 if self.successors.insert_successor(owner, successor)? {
518 inserted_successors += 1;
519 } else {
520 already_present += 1;
521 }
522 }
523
524 Ok(DeleteReport {
525 removed_references,
526 inserted_successors,
527 already_present,
528 })
529 }
530
531 #[inline]
539 pub fn delete_rebuild_complete(&mut self, index: usize) -> Result<DeleteReport, Error> {
540 self.ensure_active_index(index)?;
541 let removed_references = self.successors.remove_references_to(index);
542 self.successors.clear_all();
543 self.active[index] = false;
544
545 let mut inserted_successors = 0;
546 for owner in 0..self.points.len() {
547 if !self.active[owner] {
548 continue;
549 }
550 for successor in (owner + 1)..self.points.len() {
551 if self.active[successor] {
552 self.successors.insert_successor(owner, successor)?;
553 inserted_successors += 1;
554 }
555 }
556 }
557
558 Ok(DeleteReport {
559 removed_references,
560 inserted_successors,
561 already_present: 0,
562 })
563 }
564
565 #[inline]
566 fn transition_sites_internal_with_workspace(
567 &self,
568 query: &[f64; D],
569 capacity: usize,
570 prefix_len: usize,
571 workspace: &mut QueryWorkspace,
572 ) -> Result<(), Error> {
573 if capacity == 0 {
574 workspace.candidates.reset(0);
575 return Ok(());
576 }
577 let Some(first_active) = self.first_active_before(prefix_len) else {
578 workspace.candidates.reset(capacity);
579 return Ok(());
580 };
581 self.transition_sites_internal_from_index_with_workspace(
582 query,
583 capacity,
584 prefix_len,
585 first_active,
586 workspace,
587 )
588 }
589
590 #[inline]
591 fn transition_sites_internal_from_index_with_workspace(
592 &self,
593 query: &[f64; D],
594 capacity: usize,
595 prefix_len: usize,
596 mut current: usize,
597 workspace: &mut QueryWorkspace,
598 ) -> Result<(), Error> {
599 workspace.candidates.reset(capacity);
600 if capacity == 0 {
601 return Ok(());
602 }
603
604 workspace.candidates.insert(Neighbor {
605 index: current,
606 squared_distance: squared_distance(&self.points[current], query),
607 });
608
609 loop {
610 let current_distance = squared_distance(&self.points[current], query);
611 let mut next = None;
612
613 for &successor in self.successors.list(current) {
614 if successor >= prefix_len {
615 break;
616 }
617 if !self.active[successor] {
618 continue;
619 }
620
621 let successor_distance = squared_distance(&self.points[successor], query);
622 if is_strictly_better(successor_distance, successor, current_distance, current) {
623 next = Some((successor, successor_distance));
624 break;
625 }
626 }
627
628 let Some((successor, successor_distance)) = next else {
629 break;
630 };
631
632 current = successor;
633 workspace.candidates.insert(Neighbor {
634 index: current,
635 squared_distance: successor_distance,
636 });
637 }
638
639 Ok(())
640 }
641
642 #[inline]
643 fn validate_query_and_prefix(&self, query: &[f64; D], prefix_len: usize) -> Result<(), Error> {
644 validate_query(query)?;
645 if prefix_len > self.points.len() {
646 return Err(Error::InvalidPrefix {
647 prefix_len,
648 len: self.points.len(),
649 });
650 }
651 Ok(())
652 }
653
654 #[inline]
655 fn first_active_before(&self, prefix_len: usize) -> Option<usize> {
656 self.active[..prefix_len]
657 .iter()
658 .position(|&is_active| is_active)
659 }
660
661 #[inline]
662 fn ensure_active_index(&self, index: usize) -> Result<(), Error> {
663 if index >= self.points.len() {
664 return Err(Error::InvalidIndex {
665 index,
666 len: self.points.len(),
667 });
668 }
669 if !self.active[index] {
670 return Err(Error::InactivePoint { index });
671 }
672 Ok(())
673 }
674
675 #[inline]
676 fn validate_new_successor_edge(
677 &self,
678 deleted: usize,
679 owner: usize,
680 successor: usize,
681 ) -> Result<(), Error> {
682 if owner >= self.points.len() {
683 return Err(Error::InvalidIndex {
684 index: owner,
685 len: self.points.len(),
686 });
687 }
688 if successor >= self.points.len() {
689 return Err(Error::InvalidIndex {
690 index: successor,
691 len: self.points.len(),
692 });
693 }
694 if owner == deleted || successor == deleted || owner >= successor {
695 return Err(Error::InvalidSuccessor {
696 owner,
697 successor,
698 len: self.points.len(),
699 });
700 }
701 if !self.active[owner] {
702 return Err(Error::InactivePoint { index: owner });
703 }
704 if !self.active[successor] {
705 return Err(Error::InactivePoint { index: successor });
706 }
707 Ok(())
708 }
709}
710
711#[derive(Clone, Debug, Default)]
713pub struct QueryWorkspace {
714 processed: Vec<bool>,
715 discovered: Vec<bool>,
716 visited_indices: Vec<usize>,
717 candidates: BoundedNeighbors,
718}
719
720impl QueryWorkspace {
721 #[must_use]
723 #[inline]
724 pub const fn new() -> Self {
725 Self {
726 processed: Vec::new(),
727 discovered: Vec::new(),
728 visited_indices: Vec::new(),
729 candidates: BoundedNeighbors::new_empty(),
730 }
731 }
732}
733
734thread_local! {
735 static WORKSPACE: std::cell::RefCell<QueryWorkspace> = const { std::cell::RefCell::new(QueryWorkspace::new()) };
736}
737
738#[inline]
739pub(crate) fn validate_points<const D: usize>(points: &[[f64; D]]) -> Result<(), Error> {
740 #[cfg(feature = "parallel")]
741 {
742 use rayon::prelude::*;
743 points
744 .par_iter()
745 .enumerate()
746 .try_for_each(|(index, point)| validate_point(index, point))
747 }
748 #[cfg(not(feature = "parallel"))]
749 {
750 for (index, point) in points.iter().enumerate() {
751 validate_point(index, point)?;
752 }
753 Ok(())
754 }
755}
756
757#[inline]
758pub(crate) fn validate_point<const D: usize>(index: usize, point: &[f64; D]) -> Result<(), Error> {
759 for (coordinate, &value) in point.iter().enumerate() {
760 if !value.is_finite() {
761 return Err(Error::NonFiniteCoordinate {
762 point: index,
763 coordinate,
764 value,
765 });
766 }
767 }
768 Ok(())
769}
770
771#[inline]
772pub(crate) fn validate_query<const D: usize>(query: &[f64; D]) -> Result<(), Error> {
773 for (coordinate, &value) in query.iter().enumerate() {
774 if !value.is_finite() {
775 return Err(Error::NonFiniteQuery { coordinate, value });
776 }
777 }
778 Ok(())
779}
780
781#[cfg(feature = "simd")]
782#[inline]
783pub(crate) fn squared_distance<const D: usize>(point: &[f64; D], query: &[f64; D]) -> f64 {
784 use std::simd::{f64x4, num::SimdFloat};
785
786 if D == 0 {
787 return 0.0;
788 }
789
790 if D <= 4 {
793 let mut p = [0.0_f64; 4];
794 let mut q = [0.0_f64; 4];
795 p[..D].copy_from_slice(&point[..D]);
796 q[..D].copy_from_slice(&query[..D]);
797 let pv = f64x4::from_array(p);
798 let qv = f64x4::from_array(q);
799 let diff = pv - qv;
800 return (diff * diff).reduce_sum();
801 }
802
803 let mut sum = 0.0_f64;
805 let mut i = 0;
806
807 while i + 4 <= D {
808 let pv = f64x4::from_array([point[i], point[i + 1], point[i + 2], point[i + 3]]);
809 let qv = f64x4::from_array([query[i], query[i + 1], query[i + 2], query[i + 3]]);
810 let diff = pv - qv;
811 sum += (diff * diff).reduce_sum();
812 i += 4;
813 }
814
815 for j in i..D {
817 let delta = point[j] - query[j];
818 sum += delta * delta;
819 }
820
821 sum
822}
823
824#[cfg(not(feature = "simd"))]
825#[inline]
826pub(crate) fn squared_distance<const D: usize>(point: &[f64; D], query: &[f64; D]) -> f64 {
827 let mut sum = 0.0;
828 for i in 0..D {
829 let delta = point[i] - query[i];
830 sum += delta * delta;
831 }
832 sum
833}
834
835#[inline]
836pub(crate) fn is_strictly_better(
837 new_distance: f64,
838 new_index: usize,
839 old_distance: f64,
840 old_index: usize,
841) -> bool {
842 new_distance < old_distance || (new_distance == old_distance && new_index < old_index)
843}