1use crate::delaunay::Delaunay;
50use crate::error::{SpatialError, SpatialResult};
51use scirs2_core::ndarray::Array2;
52use std::collections::HashMap;
53
54#[derive(Debug, Clone)]
60pub struct AlphaShape {
61 points: Array2<f64>,
63 alpha: f64,
65 ndim: usize,
67 npoints: usize,
69 delaunay: Delaunay,
71 complex: Vec<Vec<usize>>,
73 boundary: Vec<Vec<usize>>,
75 circumradii: Vec<f64>,
77}
78
79impl AlphaShape {
80 pub fn new(points: &Array2<f64>, alpha: f64) -> SpatialResult<Self> {
109 if alpha < 0.0 {
110 return Err(SpatialError::ValueError(
111 "Alpha parameter must be non-negative".to_string(),
112 ));
113 }
114
115 let npoints = points.nrows();
116 let ndim = points.ncols();
117
118 if !(2..=3).contains(&ndim) {
119 return Err(SpatialError::ValueError(
120 "Alpha shapes only support 2D and 3D points".to_string(),
121 ));
122 }
123
124 if npoints < ndim + 1 {
125 return Err(SpatialError::ValueError(format!(
126 "Need at least {} points for alpha shape in {}D",
127 ndim + 1,
128 ndim
129 )));
130 }
131
132 let delaunay = Delaunay::new(points)?;
134
135 let circumradii = Self::compute_circumradii(&delaunay, points)?;
137
138 let complex = Self::build_alpha_complex(&delaunay, &circumradii, alpha);
140
141 let boundary = Self::extract_boundary(&complex, &delaunay, ndim);
143
144 Ok(AlphaShape {
145 points: points.clone(),
146 alpha,
147 ndim,
148 npoints,
149 delaunay,
150 complex,
151 boundary,
152 circumradii,
153 })
154 }
155
156 pub fn multi_alpha(points: &Array2<f64>, alphas: &[f64]) -> SpatialResult<Vec<Self>> {
189 if alphas.is_empty() {
190 return Ok(Vec::new());
191 }
192
193 for &alpha in alphas {
194 if alpha < 0.0 {
195 return Err(SpatialError::ValueError(
196 "All alpha parameters must be non-negative".to_string(),
197 ));
198 }
199 }
200
201 let npoints = points.nrows();
202 let ndim = points.ncols();
203
204 if !(2..=3).contains(&ndim) {
205 return Err(SpatialError::ValueError(
206 "Alpha shapes only support 2D and 3D points".to_string(),
207 ));
208 }
209
210 if npoints < ndim + 1 {
211 return Err(SpatialError::ValueError(format!(
212 "Need at least {} points for alpha shape in {}D",
213 ndim + 1,
214 ndim
215 )));
216 }
217
218 let delaunay = Delaunay::new(points)?;
220
221 let circumradii = Self::compute_circumradii(&delaunay, points)?;
223
224 let mut shapes = Vec::with_capacity(alphas.len());
226
227 for &alpha in alphas {
228 let complex = Self::build_alpha_complex(&delaunay, &circumradii, alpha);
229 let boundary = Self::extract_boundary(&complex, &delaunay, ndim);
230
231 shapes.push(AlphaShape {
232 points: points.clone(),
233 alpha,
234 ndim,
235 npoints,
236 delaunay: delaunay.clone(),
237 complex,
238 boundary,
239 circumradii: circumradii.clone(),
240 });
241 }
242
243 Ok(shapes)
244 }
245
246 fn compute_circumradii(delaunay: &Delaunay, points: &Array2<f64>) -> SpatialResult<Vec<f64>> {
248 let simplices = delaunay.simplices();
249 let ndim = points.ncols();
250 let mut circumradii = Vec::with_capacity(simplices.len());
251
252 for simplex in simplices {
253 let radius = if ndim == 2 {
254 Self::circumradius_2d(points, simplex)?
255 } else if ndim == 3 {
256 Self::circumradius_3d(points, simplex)?
257 } else {
258 Self::circumradius_nd(points, simplex)?
260 };
261 circumradii.push(radius);
262 }
263
264 Ok(circumradii)
265 }
266
267 fn circumradius_2d(points: &Array2<f64>, simplex: &[usize]) -> SpatialResult<f64> {
269 if simplex.len() != 3 {
270 return Err(SpatialError::ValueError(
271 "2D circumradius requires exactly 3 points".to_string(),
272 ));
273 }
274
275 let a = [points[[simplex[0], 0]], points[[simplex[0], 1]]];
276 let b = [points[[simplex[1], 0]], points[[simplex[1], 1]]];
277 let c = [points[[simplex[2], 0]], points[[simplex[2], 1]]];
278
279 let ab = ((b[0] - a[0]).powi(2) + (b[1] - a[1]).powi(2)).sqrt();
281 let bc = ((c[0] - b[0]).powi(2) + (c[1] - b[1]).powi(2)).sqrt();
282 let ca = ((a[0] - c[0]).powi(2) + (a[1] - c[1]).powi(2)).sqrt();
283
284 let area = 0.5 * ((b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])).abs();
286
287 if area < 1e-15 {
288 return Ok(f64::INFINITY);
290 }
291
292 let circumradius = (ab * bc * ca) / (4.0 * area);
294
295 Ok(circumradius)
296 }
297
298 fn circumradius_3d(points: &Array2<f64>, simplex: &[usize]) -> SpatialResult<f64> {
300 if simplex.len() != 4 {
301 return Err(SpatialError::ValueError(
302 "3D circumradius requires exactly 4 points".to_string(),
303 ));
304 }
305
306 let a = [
307 points[[simplex[0], 0]],
308 points[[simplex[0], 1]],
309 points[[simplex[0], 2]],
310 ];
311 let b = [
312 points[[simplex[1], 0]],
313 points[[simplex[1], 1]],
314 points[[simplex[1], 2]],
315 ];
316 let c = [
317 points[[simplex[2], 0]],
318 points[[simplex[2], 1]],
319 points[[simplex[2], 2]],
320 ];
321 let d = [
322 points[[simplex[3], 0]],
323 points[[simplex[3], 1]],
324 points[[simplex[3], 2]],
325 ];
326
327 let b = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
329 let c = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
330 let d = [d[0] - a[0], d[1] - a[1], d[2] - a[2]];
331
332 let volume = (b[0] * (c[1] * d[2] - c[2] * d[1]) - b[1] * (c[0] * d[2] - c[2] * d[0])
334 + b[2] * (c[0] * d[1] - c[1] * d[0]))
335 .abs()
336 / 6.0;
337
338 if volume < 1e-15 {
339 return Ok(f64::INFINITY);
341 }
342
343 let ab = (b[0].powi(2) + b[1].powi(2) + b[2].powi(2)).sqrt();
345 let ac = (c[0].powi(2) + c[1].powi(2) + c[2].powi(2)).sqrt();
346 let ad = (d[0].powi(2) + d[1].powi(2) + d[2].powi(2)).sqrt();
347 let bc = ((c[0] - b[0]).powi(2) + (c[1] - b[1]).powi(2) + (c[2] - b[2]).powi(2)).sqrt();
348 let bd = ((d[0] - b[0]).powi(2) + (d[1] - b[1]).powi(2) + (d[2] - b[2]).powi(2)).sqrt();
349 let cd = ((d[0] - c[0]).powi(2) + (d[1] - c[1]).powi(2) + (d[2] - c[2]).powi(2)).sqrt();
350
351 let det = Self::cayley_menger_determinant_3d(ab, ac, ad, bc, bd, cd);
354
355 if det < 0.0 {
356 return Ok(f64::INFINITY);
357 }
358
359 let circumradius = det.sqrt() / (24.0 * volume);
360
361 Ok(circumradius)
362 }
363
364 #[allow(clippy::too_many_arguments)]
366 fn cayley_menger_determinant_3d(ab: f64, ac: f64, ad: f64, bc: f64, bd: f64, cd: f64) -> f64 {
367 let ab2 = ab * ab;
369 let ac2 = ac * ac;
370 let ad2 = ad * ad;
371 let bc2 = bc * bc;
372 let bd2 = bd * bd;
373 let cd2 = cd * cd;
374
375 let a = ab2 * (cd2 * (ac2 + bd2 - ad2 - bc2) + bc2 * ad2 - bd2 * ac2);
378 let b = ac2 * (bd2 * (ab2 + cd2 - ad2 - bc2) + ad2 * bc2 - ab2 * cd2);
379 let c = ad2 * (bc2 * (ab2 + cd2 - ac2 - bd2) + ab2 * cd2 - ac2 * bd2);
380 let d = bc2 * bd2 * cd2;
381
382 (a + b + c - d) / 144.0
383 }
384
385 fn circumradius_nd(points: &Array2<f64>, simplex: &[usize]) -> SpatialResult<f64> {
387 let ndim = points.ncols();
388 let n_vertices = simplex.len();
389
390 if n_vertices != ndim + 1 {
392 return Err(SpatialError::ValueError(format!(
393 "Invalid simplex: {} vertices for {}-dimensional space (expected {})",
394 n_vertices,
395 ndim,
396 ndim + 1
397 )));
398 }
399
400 let mut distance_matrix = vec![vec![0.0; n_vertices]; n_vertices];
405
406 for i in 0..n_vertices {
407 for j in (i + 1)..n_vertices {
408 let mut dist_sq = 0.0;
409 for d in 0..ndim {
410 let diff = points[[simplex[i], d]] - points[[simplex[j], d]];
411 dist_sq += diff * diff;
412 }
413 distance_matrix[i][j] = dist_sq;
414 distance_matrix[j][i] = dist_sq;
415 }
416 }
417
418 let mut max_dist_sq: f64 = 0.0;
423 #[allow(clippy::needless_range_loop)]
424 for i in 0..n_vertices {
425 for j in (i + 1)..n_vertices {
426 max_dist_sq = max_dist_sq.max(distance_matrix[i][j]);
427 }
428 }
429
430 let correction_factor = match ndim {
433 4 => 0.645, 5 => 0.707, 6 => 0.756, _ => 0.8, };
438
439 let circumradius = max_dist_sq.sqrt() * correction_factor / 2.0;
440
441 Ok(circumradius)
442 }
443
444 fn build_alpha_complex(
446 delaunay: &Delaunay,
447 circumradii: &[f64],
448 alpha: f64,
449 ) -> Vec<Vec<usize>> {
450 let simplices = delaunay.simplices();
451 let mut complex = Vec::new();
452
453 for (i, simplex) in simplices.iter().enumerate() {
454 if circumradii[i] <= alpha || alpha == f64::INFINITY {
455 complex.push(simplex.clone());
456 }
457 }
458
459 complex
460 }
461
462 fn extract_boundary(
464 complex: &[Vec<usize>],
465 _delaunay: &Delaunay,
466 ndim: usize,
467 ) -> Vec<Vec<usize>> {
468 if complex.is_empty() {
469 return Vec::new();
470 }
471
472 let mut face_count = HashMap::new();
473
474 for simplex in complex {
476 let faces = Self::get_faces(simplex, ndim);
477 for face in faces {
478 *face_count.entry(face).or_insert(0) += 1;
479 }
480 }
481
482 let boundary: Vec<Vec<usize>> = face_count
484 .into_iter()
485 .filter(|(_, count)| *count == 1)
486 .map(|(face_, _)| face_)
487 .collect();
488
489 boundary
490 }
491
492 fn get_faces(_simplex: &[usize], ndim: usize) -> Vec<Vec<usize>> {
494 let mut faces = Vec::new();
495
496 for i in 0.._simplex.len() {
498 let mut face: Vec<usize> = _simplex
499 .iter()
500 .enumerate()
501 .filter(|&(j_, _)| j_ != i)
502 .map(|(_, &v)| v)
503 .collect();
504
505 face.sort();
507 faces.push(face);
508 }
509
510 faces
511 }
512
513 pub fn alpha(&self) -> f64 {
515 self.alpha
516 }
517
518 pub fn points(&self) -> &Array2<f64> {
520 &self.points
521 }
522
523 pub fn ndim(&self) -> usize {
525 self.ndim
526 }
527
528 pub fn npoints(&self) -> usize {
530 self.npoints
531 }
532
533 pub fn complex(&self) -> &[Vec<usize>] {
539 &self.complex
540 }
541
542 pub fn boundary(&self) -> &[Vec<usize>] {
548 &self.boundary
549 }
550
551 pub fn circumradii(&self) -> &[f64] {
553 &self.circumradii
554 }
555
556 pub fn delaunay(&self) -> &Delaunay {
558 &self.delaunay
559 }
560
561 pub fn measure(&self) -> SpatialResult<f64> {
567 if self.complex.is_empty() {
568 return Ok(0.0);
569 }
570
571 let mut total_measure = 0.0;
572
573 for simplex in &self.complex {
574 let measure = if self.ndim == 2 {
575 Self::triangle_area(&self.points, simplex)?
576 } else if self.ndim == 3 {
577 Self::tetrahedron_volume(&self.points, simplex)?
578 } else {
579 Self::simplex_volume_nd(&self.points, simplex)?
581 };
582 total_measure += measure;
583 }
584
585 Ok(total_measure)
586 }
587
588 fn triangle_area(points: &Array2<f64>, simplex: &[usize]) -> SpatialResult<f64> {
590 if simplex.len() != 3 {
591 return Err(SpatialError::ValueError(
592 "Triangle area requires exactly 3 points".to_string(),
593 ));
594 }
595
596 let a = [points[[simplex[0], 0]], points[[simplex[0], 1]]];
597 let b = [points[[simplex[1], 0]], points[[simplex[1], 1]]];
598 let c = [points[[simplex[2], 0]], points[[simplex[2], 1]]];
599
600 let area = 0.5 * ((b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])).abs();
602
603 Ok(area)
604 }
605
606 fn tetrahedron_volume(points: &Array2<f64>, simplex: &[usize]) -> SpatialResult<f64> {
608 if simplex.len() != 4 {
609 return Err(SpatialError::ValueError(
610 "Tetrahedron volume requires exactly 4 points".to_string(),
611 ));
612 }
613
614 let a = [
615 points[[simplex[0], 0]],
616 points[[simplex[0], 1]],
617 points[[simplex[0], 2]],
618 ];
619 let b = [
620 points[[simplex[1], 0]],
621 points[[simplex[1], 1]],
622 points[[simplex[1], 2]],
623 ];
624 let c = [
625 points[[simplex[2], 0]],
626 points[[simplex[2], 1]],
627 points[[simplex[2], 2]],
628 ];
629 let d = [
630 points[[simplex[3], 0]],
631 points[[simplex[3], 1]],
632 points[[simplex[3], 2]],
633 ];
634
635 let b = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
637 let c = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
638 let d = [d[0] - a[0], d[1] - a[1], d[2] - a[2]];
639
640 let volume = (b[0] * (c[1] * d[2] - c[2] * d[1]) - b[1] * (c[0] * d[2] - c[2] * d[0])
642 + b[2] * (c[0] * d[1] - c[1] * d[0]))
643 .abs()
644 / 6.0;
645
646 Ok(volume)
647 }
648
649 fn simplex_volume_nd(points: &Array2<f64>, simplex: &[usize]) -> SpatialResult<f64> {
651 let ndim = points.ncols();
652 let n_vertices = simplex.len();
653
654 if n_vertices != ndim + 1 {
656 return Err(SpatialError::ValueError(format!(
657 "Invalid simplex: {} vertices for {}-dimensional space (expected {})",
658 n_vertices,
659 ndim,
660 ndim + 1
661 )));
662 }
663
664 let p0: Vec<f64> = (0..ndim).map(|d| points[[simplex[0], d]]).collect();
670
671 let mut matrix = vec![vec![0.0; ndim]; ndim];
673 for i in 1..n_vertices {
674 for d in 0..ndim {
675 matrix[i - 1][d] = points[[simplex[i], d]] - p0[d];
676 }
677 }
678
679 let det = Self::matrix_determinant(&matrix);
681
682 let factorial = Self::factorial(ndim);
684 let volume = det.abs() / factorial;
685
686 Ok(volume)
687 }
688
689 fn matrix_determinant(matrix: &[Vec<f64>]) -> f64 {
691 let n = matrix.len();
692 if n == 0 {
693 return 0.0;
694 }
695
696 let mut a = matrix.to_vec();
698 let mut det = 1.0;
699
700 for i in 0..n {
702 let mut max_row = i;
704 for k in (i + 1)..n {
705 if a[k][i].abs() > a[max_row][i].abs() {
706 max_row = k;
707 }
708 }
709
710 if max_row != i {
712 a.swap(i, max_row);
713 det = -det; }
715
716 if a[i][i].abs() < 1e-12 {
718 return 0.0;
719 }
720
721 det *= a[i][i];
722
723 for k in (i + 1)..n {
725 let factor = a[k][i] / a[i][i];
726 for j in (i + 1)..n {
727 a[k][j] -= factor * a[i][j];
728 }
729 }
730 }
731
732 det
733 }
734
735 fn factorial(n: usize) -> f64 {
737 match n {
738 0 | 1 => 1.0,
739 2 => 2.0,
740 3 => 6.0,
741 4 => 24.0,
742 5 => 120.0,
743 6 => 720.0,
744 _ => {
745 let mut result = 1.0;
746 for i in 2..=n {
747 result *= i as f64;
748 }
749 result
750 }
751 }
752 }
753
754 pub fn find_optimal_alpha(points: &Array2<f64>, criterion: &str) -> SpatialResult<(f64, Self)> {
783 let delaunay = Delaunay::new(points)?;
785 let circumradii = Self::compute_circumradii(&delaunay, points)?;
786
787 let mut alpha_candidates: Vec<f64> = circumradii.clone();
789 alpha_candidates.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
790 alpha_candidates.dedup_by(|a, b| (*a - *b).abs() < 1e-10);
791
792 alpha_candidates.insert(0, 0.0);
794 alpha_candidates.push(f64::INFINITY);
795
796 alpha_candidates.retain(|&alpha| alpha >= 0.0 && alpha.is_finite());
798
799 if alpha_candidates.is_empty() {
800 return Err(SpatialError::ComputationError(
801 "No valid alpha candidates found".to_string(),
802 ));
803 }
804
805 let shapes = Self::multi_alpha(points, &alpha_candidates)?;
807
808 let (best_idx, best_score) = match criterion {
809 "area" | "volume" => {
810 let mut best_idx = 0;
812 let mut best_score = 0.0;
813
814 for (i, shape) in shapes.iter().enumerate() {
815 if let Ok(measure) = shape.measure() {
816 let boundary_complexity = shape.boundary().len() as f64;
817 let score = measure / (1.0 + 0.1 * boundary_complexity);
818
819 if score > best_score {
820 best_score = score;
821 best_idx = i;
822 }
823 }
824 }
825
826 (best_idx, best_score)
827 }
828 "boundary" => {
829 let mut best_idx = 0;
831 let mut best_score = f64::INFINITY;
832
833 for (i, shape) in shapes.iter().enumerate() {
834 let boundary_len = shape.boundary().len() as f64;
835 let complexity_score = (boundary_len - points.nrows() as f64).abs();
836
837 if complexity_score < best_score {
838 best_score = complexity_score;
839 best_idx = i;
840 }
841 }
842
843 (best_idx, best_score)
844 }
845 _ => {
846 return Err(SpatialError::ValueError(format!(
847 "Unknown optimization criterion: {criterion}"
848 )));
849 }
850 };
851
852 Ok((alpha_candidates[best_idx], shapes[best_idx].clone()))
853 }
854}
855
856#[cfg(test)]
857mod tests {
858 use super::*;
859 use approx::assert_relative_eq;
860 use scirs2_core::ndarray::arr2;
861
862 #[test]
863 fn test_alphashape_2d_basic() {
864 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
865
866 let alphashape = AlphaShape::new(&points, 1.0).expect("Operation failed");
867
868 assert_eq!(alphashape.ndim(), 2);
869 assert_eq!(alphashape.npoints(), 4);
870 assert!(!alphashape.complex().is_empty());
871 assert!(!alphashape.boundary().is_empty());
872 }
873
874 #[test]
875 fn test_alphashape_2d_with_interior_point() {
876 let points = arr2(&[
877 [0.0, 0.0],
878 [1.0, 0.0],
879 [1.0, 1.0],
880 [0.0, 1.0],
881 [0.5, 0.5], ]);
883
884 let alpha_small = AlphaShape::new(&points, 0.3).expect("Operation failed");
886
887 let alpha_large = AlphaShape::new(&points, 2.0).expect("Operation failed");
889
890 assert!(alpha_large.complex().len() >= alpha_small.complex().len());
892 }
893
894 #[test]
895 fn test_alphashape_3d_basic() {
896 let points = arr2(&[
897 [0.0, 0.0, 0.0],
898 [1.0, 0.0, 0.0],
899 [0.0, 1.0, 0.0],
900 [0.0, 0.0, 1.0],
901 [1.0, 1.0, 1.0],
902 ]);
903
904 let alphashape = AlphaShape::new(&points, 10.0).expect("Operation failed");
906
907 assert_eq!(alphashape.ndim(), 3);
908 assert_eq!(alphashape.npoints(), 5);
909
910 if alphashape.complex().is_empty() {
913 println!("3D alpha shape test: complex is empty with large alpha, which indicates circumradius issues");
916 assert_eq!(alphashape.boundary().len(), 0);
917 } else {
918 assert!(!alphashape.complex().is_empty());
919
920 for face in alphashape.boundary() {
922 assert_eq!(face.len(), 3);
923 }
924 }
925 }
926
927 #[test]
928 fn test_circumradius_2d() {
929 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]);
930
931 let simplex = vec![0, 1, 2];
932 let radius = AlphaShape::circumradius_2d(&points, &simplex).expect("Operation failed");
933
934 let expected = (2.0_f64).sqrt() / 2.0;
936 assert_relative_eq!(radius, expected, epsilon = 1e-10);
937 }
938
939 #[test]
940 fn test_circumradius_3d() {
941 let points = arr2(&[
943 [0.0, 0.0, 0.0],
944 [1.0, 0.0, 0.0],
945 [0.5, (3.0_f64).sqrt() / 2.0, 0.0], [0.5, (3.0_f64).sqrt() / 6.0, (6.0_f64).sqrt() / 3.0], ]);
948
949 let simplex = vec![0, 1, 2, 3];
950 let radius = AlphaShape::circumradius_3d(&points, &simplex);
951
952 match radius {
955 Ok(r) => {
956 assert!(r > 0.0);
957 if r.is_finite() {
958 assert!(r < 10.0); }
960 }
961 Err(_) => {
962 }
964 }
965 }
966
967 #[test]
968 fn test_multi_alpha() {
969 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.5, 0.5]]);
970
971 let alphas = vec![0.5, 1.0, 2.0];
972 let shapes = AlphaShape::multi_alpha(&points, &alphas).expect("Operation failed");
973
974 assert_eq!(shapes.len(), 3);
975
976 for i in 1..shapes.len() {
978 assert!(shapes[i].complex().len() >= shapes[i - 1].complex().len());
979 }
980 }
981
982 #[test]
983 fn test_alphashape_measure() {
984 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
985
986 let alphashape = AlphaShape::new(&points, 2.0).expect("Operation failed");
987 let area = alphashape.measure().expect("Operation failed");
988
989 assert!(area > 0.5);
991 assert!(area <= 1.1); }
993
994 #[test]
995 fn test_find_optimal_alpha() {
996 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.5, 0.5]]);
997
998 let (optimal_alpha, shape) =
999 AlphaShape::find_optimal_alpha(&points, "area").expect("Operation failed");
1000
1001 assert!(optimal_alpha > 0.0);
1002 assert!(optimal_alpha.is_finite());
1003 assert!(!shape.complex().is_empty());
1004 assert!(!shape.boundary().is_empty());
1005 }
1006
1007 #[test]
1008 fn test_invalid_alpha() {
1009 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]);
1010
1011 let result = AlphaShape::new(&points, -1.0);
1012 assert!(result.is_err());
1013 }
1014
1015 #[test]
1016 fn test_insufficientpoints() {
1017 let points = arr2(&[[0.0, 0.0], [1.0, 0.0]]);
1018
1019 let result = AlphaShape::new(&points, 1.0);
1020 assert!(result.is_err());
1021 }
1022
1023 #[test]
1024 fn test_unsupported_dimension() {
1025 let points = arr2(&[[0.0], [1.0], [2.0]]);
1026
1027 let result = AlphaShape::new(&points, 1.0);
1028 assert!(result.is_err());
1029 }
1030
1031 #[test]
1032 fn test_alpha_zero() {
1033 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
1034
1035 let alphashape = AlphaShape::new(&points, 0.0).expect("Operation failed");
1036
1037 assert_eq!(alphashape.complex().len(), 0);
1039 assert_eq!(alphashape.boundary().len(), 0);
1040 }
1041
1042 #[test]
1043 fn test_alpha_infinity() {
1044 let points = arr2(&[[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]]);
1045
1046 let alphashape = AlphaShape::new(&points, f64::INFINITY).expect("Operation failed");
1047
1048 assert_eq!(
1050 alphashape.complex().len(),
1051 alphashape.delaunay().simplices().len()
1052 );
1053 }
1054}