1use crate::error::AlgorithmError;
54use oxigdal_core::vector::Point;
55
56const LCG_MULTIPLIER: u64 = 6_364_136_223_846_793_005;
60
61const LCG_INCREMENT: u64 = 1_442_695_040_888_963_407;
63
64const DEGENERATE_EPSILON: f64 = 1e-12;
67
68#[derive(Debug, Clone, PartialEq)]
73pub struct RansacOptions {
74 pub max_iterations: usize,
76 pub inlier_threshold: f64,
78 pub min_inlier_count: usize,
80 pub seed: u64,
82 pub confidence: f64,
85}
86
87impl Default for RansacOptions {
88 fn default() -> Self {
89 Self {
90 max_iterations: 1000,
91 inlier_threshold: 1e-3,
92 min_inlier_count: 2,
93 seed: 0xDEAD_BEEF,
94 confidence: 0.99,
95 }
96 }
97}
98
99#[derive(Debug, Clone)]
103pub struct RansacResult<M> {
104 pub model: M,
106 pub inliers: Vec<usize>,
108 pub iterations: usize,
110 pub converged: bool,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq)]
117pub struct RansacLineModel {
118 pub a: f64,
120 pub b: f64,
122 pub c: f64,
124}
125
126impl RansacLineModel {
127 #[must_use]
133 pub fn from_two_points(p1: Point, p2: Point) -> Option<Self> {
134 let dx = p2.x() - p1.x();
135 let dy = p2.y() - p1.y();
136 let norm = (dx * dx + dy * dy).sqrt();
137 if !norm.is_finite() || norm <= DEGENERATE_EPSILON {
138 return None;
139 }
140 let a = -dy / norm;
142 let b = dx / norm;
143 let c = -(a * p1.x() + b * p1.y());
144 Some(Self { a, b, c })
145 }
146
147 #[must_use]
149 pub fn distance_to(&self, p: Point) -> f64 {
150 (self.a * p.x() + self.b * p.y() + self.c).abs()
151 }
152
153 #[must_use]
155 pub fn direction(&self) -> (f64, f64) {
156 (-self.b, self.a)
157 }
158
159 #[must_use]
161 pub fn point_on_line(&self) -> (f64, f64) {
162 (-self.a * self.c, -self.b * self.c)
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq)]
169pub struct RansacPlaneModel {
170 pub a: f64,
172 pub b: f64,
174 pub c: f64,
176 pub d: f64,
178}
179
180impl RansacPlaneModel {
181 #[must_use]
186 pub fn from_three_points(
187 p1: (f64, f64, f64),
188 p2: (f64, f64, f64),
189 p3: (f64, f64, f64),
190 ) -> Option<Self> {
191 let v1 = (p2.0 - p1.0, p2.1 - p1.1, p2.2 - p1.2);
192 let v2 = (p3.0 - p1.0, p3.1 - p1.1, p3.2 - p1.2);
193 let nx = v1.1 * v2.2 - v1.2 * v2.1;
195 let ny = v1.2 * v2.0 - v1.0 * v2.2;
196 let nz = v1.0 * v2.1 - v1.1 * v2.0;
197 let norm = (nx * nx + ny * ny + nz * nz).sqrt();
198 if !norm.is_finite() || norm <= DEGENERATE_EPSILON {
199 return None;
200 }
201 let a = nx / norm;
202 let b = ny / norm;
203 let c = nz / norm;
204 let d = -(a * p1.0 + b * p1.1 + c * p1.2);
205 Some(Self { a, b, c, d })
206 }
207
208 #[must_use]
211 pub fn distance_to(&self, p: (f64, f64, f64)) -> f64 {
212 (self.a * p.0 + self.b * p.1 + self.c * p.2 + self.d).abs()
213 }
214
215 #[must_use]
217 pub fn normal(&self) -> (f64, f64, f64) {
218 (self.a, self.b, self.c)
219 }
220}
221
222#[inline]
228fn lcg_next(state: &mut u64) -> u64 {
229 *state = state
230 .wrapping_mul(LCG_MULTIPLIER)
231 .wrapping_add(LCG_INCREMENT);
232 *state
233}
234
235#[inline]
240fn lcg_index(n: usize, state: &mut u64) -> usize {
241 let raw = lcg_next(state);
242 ((raw >> 33) as usize) % n
243}
244
245fn sample_two_distinct(n: usize, state: &mut u64) -> (usize, usize) {
249 let i = lcg_index(n, state);
250 let mut j = lcg_index(n, state);
251 while j == i {
252 j = lcg_index(n, state);
253 }
254 (i, j)
255}
256
257fn sample_three_distinct(n: usize, state: &mut u64) -> (usize, usize, usize) {
261 let i = lcg_index(n, state);
262 let mut j = lcg_index(n, state);
263 while j == i {
264 j = lcg_index(n, state);
265 }
266 let mut k = lcg_index(n, state);
267 while k == i || k == j {
268 k = lcg_index(n, state);
269 }
270 (i, j, k)
271}
272
273fn adaptive_iteration_count(
284 inlier_ratio: f64,
285 sample_size: usize,
286 confidence: f64,
287 max: usize,
288) -> usize {
289 if max == 0 {
290 return 0;
291 }
292 if !inlier_ratio.is_finite() || inlier_ratio <= 0.0 {
293 return max;
294 }
295 if inlier_ratio >= 1.0 {
296 return 1;
297 }
298 let conf = if !confidence.is_finite() {
300 return max;
301 } else {
302 confidence.clamp(0.0, 1.0 - f64::EPSILON)
303 };
304 let prob_all_inliers = inlier_ratio.powi(sample_size as i32);
305 let denom = (1.0 - prob_all_inliers).ln();
306 let numer = (1.0 - conf).ln();
307 if !denom.is_finite() || !numer.is_finite() || denom >= 0.0 {
308 return max;
311 }
312 let raw = numer / denom;
313 if !raw.is_finite() {
314 return max;
315 }
316 let needed = raw.ceil();
317 if needed <= 1.0 {
318 1
319 } else if needed >= max as f64 {
320 max
321 } else {
322 needed as usize
323 }
324}
325
326fn least_squares_line(points: &[Point], indices: &[usize]) -> Option<RansacLineModel> {
333 let n = indices.len();
334 if n < 2 {
335 return None;
336 }
337 let inv_n = 1.0 / n as f64;
338 let mut mean_x = 0.0;
339 let mut mean_y = 0.0;
340 for &idx in indices {
341 mean_x += points[idx].x();
342 mean_y += points[idx].y();
343 }
344 mean_x *= inv_n;
345 mean_y *= inv_n;
346
347 let mut sxx = 0.0;
348 let mut sxy = 0.0;
349 let mut syy = 0.0;
350 for &idx in indices {
351 let dx = points[idx].x() - mean_x;
352 let dy = points[idx].y() - mean_y;
353 sxx += dx * dx;
354 sxy += dx * dy;
355 syy += dy * dy;
356 }
357
358 if sxx + syy <= DEGENERATE_EPSILON {
360 return None;
361 }
362
363 let trace_half = 0.5 * (sxx + syy);
367 let diff_half = 0.5 * (sxx - syy);
368 let radius = (diff_half * diff_half + sxy * sxy).sqrt();
369 let lambda_min = trace_half - radius;
370
371 let (mut a, mut b) = {
374 let r0x = sxx - lambda_min;
375 let r0y = sxy;
376 let r1x = sxy;
377 let r1y = syy - lambda_min;
378 if r0x * r0x + r0y * r0y >= r1x * r1x + r1y * r1y {
380 (-r0y, r0x)
381 } else {
382 (-r1y, r1x)
383 }
384 };
385
386 let norm = (a * a + b * b).sqrt();
387 if !norm.is_finite() || norm <= DEGENERATE_EPSILON {
388 return None;
389 }
390 a /= norm;
391 b /= norm;
392 let c = -(a * mean_x + b * mean_y);
393 Some(RansacLineModel { a, b, c })
394}
395
396#[inline]
399fn sym3_mul(m: &[f64; 6], v: (f64, f64, f64)) -> (f64, f64, f64) {
400 (
401 m[0] * v.0 + m[1] * v.1 + m[2] * v.2,
402 m[1] * v.0 + m[3] * v.1 + m[4] * v.2,
403 m[2] * v.0 + m[4] * v.1 + m[5] * v.2,
404 )
405}
406
407#[inline]
408fn vec3_norm(v: (f64, f64, f64)) -> f64 {
409 (v.0 * v.0 + v.1 * v.1 + v.2 * v.2).sqrt()
410}
411
412#[inline]
413fn vec3_normalize(v: (f64, f64, f64)) -> Option<(f64, f64, f64)> {
414 let n = vec3_norm(v);
415 if !n.is_finite() || n <= DEGENERATE_EPSILON {
416 None
417 } else {
418 Some((v.0 / n, v.1 / n, v.2 / n))
419 }
420}
421
422#[inline]
423fn vec3_cross(a: (f64, f64, f64), b: (f64, f64, f64)) -> (f64, f64, f64) {
424 (
425 a.1 * b.2 - a.2 * b.1,
426 a.2 * b.0 - a.0 * b.2,
427 a.0 * b.1 - a.1 * b.0,
428 )
429}
430
431#[inline]
432fn vec3_dot(a: (f64, f64, f64), b: (f64, f64, f64)) -> f64 {
433 a.0 * b.0 + a.1 * b.1 + a.2 * b.2
434}
435
436fn dominant_eigenvector(m: &[f64; 6], seed: (f64, f64, f64)) -> Option<((f64, f64, f64), f64)> {
441 const MAX_ITER: usize = 50;
442 const TOL: f64 = 1e-12;
443 let mut v = vec3_normalize(seed).unwrap_or((1.0, 0.0, 0.0));
444 let mut prev_eigenvalue = 0.0;
445 for _ in 0..MAX_ITER {
446 let mv = sym3_mul(m, v);
447 let next = vec3_normalize(mv)?;
449 let eigenvalue = vec3_dot(next, sym3_mul(m, next));
451 if (eigenvalue - prev_eigenvalue).abs() <= TOL * eigenvalue.abs().max(1.0) {
452 return Some((next, eigenvalue));
453 }
454 prev_eigenvalue = eigenvalue;
455 v = next;
456 }
457 let eigenvalue = vec3_dot(v, sym3_mul(m, v));
458 Some((v, eigenvalue))
459}
460
461fn least_squares_plane(points: &[(f64, f64, f64)], indices: &[usize]) -> Option<RansacPlaneModel> {
470 let n = indices.len();
471 if n < 3 {
472 return None;
473 }
474 let inv_n = 1.0 / n as f64;
475 let mut mean = (0.0, 0.0, 0.0);
476 for &idx in indices {
477 let p = points[idx];
478 mean.0 += p.0;
479 mean.1 += p.1;
480 mean.2 += p.2;
481 }
482 mean.0 *= inv_n;
483 mean.1 *= inv_n;
484 mean.2 *= inv_n;
485
486 let mut cov = [0.0_f64; 6];
488 for &idx in indices {
489 let p = points[idx];
490 let dx = p.0 - mean.0;
491 let dy = p.1 - mean.1;
492 let dz = p.2 - mean.2;
493 cov[0] += dx * dx;
494 cov[1] += dx * dy;
495 cov[2] += dx * dz;
496 cov[3] += dy * dy;
497 cov[4] += dy * dz;
498 cov[5] += dz * dz;
499 }
500
501 let total_spread = cov[0] + cov[3] + cov[5];
502 if total_spread <= DEGENERATE_EPSILON {
503 return None;
504 }
505
506 let (v1, lambda1) = dominant_eigenvector(&cov, (1.0, 0.0, 0.0))?;
508
509 let deflated = [
512 cov[0] - lambda1 * v1.0 * v1.0,
513 cov[1] - lambda1 * v1.0 * v1.1,
514 cov[2] - lambda1 * v1.0 * v1.2,
515 cov[3] - lambda1 * v1.1 * v1.1,
516 cov[4] - lambda1 * v1.1 * v1.2,
517 cov[5] - lambda1 * v1.2 * v1.2,
518 ];
519 let helper = if v1.0.abs() <= 0.9 {
522 (1.0, 0.0, 0.0)
523 } else {
524 (0.0, 1.0, 0.0)
525 };
526 let seed2 = vec3_cross(v1, helper);
527 let (v2, _lambda2) = dominant_eigenvector(&deflated, seed2)?;
528
529 let normal = vec3_normalize(vec3_cross(v1, v2))?;
531 let d = -(normal.0 * mean.0 + normal.1 * mean.1 + normal.2 * mean.2);
532 Some(RansacPlaneModel {
533 a: normal.0,
534 b: normal.1,
535 c: normal.2,
536 d,
537 })
538}
539
540fn all_points_finite_2d(points: &[Point]) -> bool {
542 points
543 .iter()
544 .all(|p| p.x().is_finite() && p.y().is_finite())
545}
546
547fn all_points_finite_3d(points: &[(f64, f64, f64)]) -> bool {
549 points
550 .iter()
551 .all(|p| p.0.is_finite() && p.1.is_finite() && p.2.is_finite())
552}
553
554pub fn ransac_fit_line(
570 points: &[Point],
571 options: &RansacOptions,
572) -> Result<RansacResult<RansacLineModel>, AlgorithmError> {
573 if points.len() < 2 {
574 return Err(AlgorithmError::InvalidInput(format!(
575 "ransac_fit_line requires at least 2 points, got {}",
576 points.len()
577 )));
578 }
579 if !all_points_finite_2d(points) {
580 return Err(AlgorithmError::InvalidInput(
581 "ransac_fit_line requires all point coordinates to be finite".to_string(),
582 ));
583 }
584
585 let n = points.len();
586 let threshold = options.inlier_threshold;
587 let min_inliers = options.min_inlier_count.max(2);
588 let mut state = options.seed;
589
590 let mut best_model: Option<RansacLineModel> = None;
591 let mut best_inlier_count = 0usize;
592 let mut iterations = 0usize;
593 let mut needed_iterations = options.max_iterations;
595
596 while iterations < needed_iterations && iterations < options.max_iterations {
597 iterations += 1;
598 let (i, j) = sample_two_distinct(n, &mut state);
599 let candidate = match RansacLineModel::from_two_points(points[i].clone(), points[j].clone())
600 {
601 Some(model) => model,
602 None => continue,
603 };
604
605 let mut inlier_count = 0usize;
606 for p in points {
607 if candidate.distance_to(p.clone()) <= threshold {
608 inlier_count += 1;
609 }
610 }
611
612 if inlier_count > best_inlier_count {
613 best_inlier_count = inlier_count;
614 best_model = Some(candidate);
615 let inlier_ratio = inlier_count as f64 / n as f64;
616 needed_iterations = adaptive_iteration_count(
617 inlier_ratio,
618 2,
619 options.confidence,
620 options.max_iterations,
621 );
622 }
623 }
624
625 let converged = best_inlier_count >= min_inliers;
626
627 let sampled_model = best_model.unwrap_or_else(|| {
631 RansacLineModel::from_two_points(points[0].clone(), points[1].clone()).unwrap_or(
632 RansacLineModel {
633 a: 0.0,
634 b: 1.0,
635 c: -points[0].y(),
636 },
637 )
638 });
639
640 let inliers: Vec<usize> = points
642 .iter()
643 .enumerate()
644 .filter(|(_, p)| sampled_model.distance_to((*p).clone()) <= threshold)
645 .map(|(idx, _)| idx)
646 .collect();
647
648 let model = match least_squares_line(points, &inliers) {
651 Some(refined) => {
652 let refit_inliers = points
655 .iter()
656 .filter(|p| refined.distance_to((*p).clone()) <= threshold)
657 .count();
658 if refit_inliers >= inliers.len() {
659 refined
660 } else {
661 sampled_model
662 }
663 }
664 None => sampled_model,
665 };
666
667 let final_inliers: Vec<usize> = points
669 .iter()
670 .enumerate()
671 .filter(|(_, p)| model.distance_to((*p).clone()) <= threshold)
672 .map(|(idx, _)| idx)
673 .collect();
674
675 Ok(RansacResult {
676 model,
677 inliers: final_inliers,
678 iterations,
679 converged,
680 })
681}
682
683pub fn ransac_fit_plane(
697 points: &[(f64, f64, f64)],
698 options: &RansacOptions,
699) -> Result<RansacResult<RansacPlaneModel>, AlgorithmError> {
700 if points.len() < 3 {
701 return Err(AlgorithmError::InvalidInput(format!(
702 "ransac_fit_plane requires at least 3 points, got {}",
703 points.len()
704 )));
705 }
706 if !all_points_finite_3d(points) {
707 return Err(AlgorithmError::InvalidInput(
708 "ransac_fit_plane requires all point coordinates to be finite".to_string(),
709 ));
710 }
711
712 let n = points.len();
713 let threshold = options.inlier_threshold;
714 let min_inliers = options.min_inlier_count.max(3);
715 let mut state = options.seed;
716
717 let mut best_model: Option<RansacPlaneModel> = None;
718 let mut best_inlier_count = 0usize;
719 let mut iterations = 0usize;
720 let mut needed_iterations = options.max_iterations;
721
722 while iterations < needed_iterations && iterations < options.max_iterations {
723 iterations += 1;
724 let (i, j, k) = sample_three_distinct(n, &mut state);
725 let candidate = match RansacPlaneModel::from_three_points(points[i], points[j], points[k]) {
726 Some(model) => model,
727 None => continue,
728 };
729
730 let mut inlier_count = 0usize;
731 for &p in points {
732 if candidate.distance_to(p) <= threshold {
733 inlier_count += 1;
734 }
735 }
736
737 if inlier_count > best_inlier_count {
738 best_inlier_count = inlier_count;
739 best_model = Some(candidate);
740 let inlier_ratio = inlier_count as f64 / n as f64;
741 needed_iterations = adaptive_iteration_count(
742 inlier_ratio,
743 3,
744 options.confidence,
745 options.max_iterations,
746 );
747 }
748 }
749
750 let converged = best_inlier_count >= min_inliers;
751
752 let sampled_model = best_model.unwrap_or_else(|| {
753 RansacPlaneModel::from_three_points(points[0], points[1], points[2]).unwrap_or(
754 RansacPlaneModel {
755 a: 0.0,
756 b: 0.0,
757 c: 1.0,
758 d: -points[0].2,
759 },
760 )
761 });
762
763 let inliers: Vec<usize> = points
764 .iter()
765 .enumerate()
766 .filter(|(_, p)| sampled_model.distance_to(**p) <= threshold)
767 .map(|(idx, _)| idx)
768 .collect();
769
770 let model = match least_squares_plane(points, &inliers) {
771 Some(refined) => {
772 let refit_inliers = points
773 .iter()
774 .filter(|p| refined.distance_to(**p) <= threshold)
775 .count();
776 if refit_inliers >= inliers.len() {
777 refined
778 } else {
779 sampled_model
780 }
781 }
782 None => sampled_model,
783 };
784
785 let final_inliers: Vec<usize> = points
786 .iter()
787 .enumerate()
788 .filter(|(_, p)| model.distance_to(**p) <= threshold)
789 .map(|(idx, _)| idx)
790 .collect();
791
792 Ok(RansacResult {
793 model,
794 inliers: final_inliers,
795 iterations,
796 converged,
797 })
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803
804 #[test]
805 fn lcg_is_deterministic_and_advances() {
806 let mut s1 = 12345;
807 let mut s2 = 12345;
808 let a = lcg_next(&mut s1);
809 let b = lcg_next(&mut s2);
810 assert_eq!(a, b);
811 let c = lcg_next(&mut s1);
812 assert_ne!(a, c);
813 }
814
815 #[test]
816 fn lcg_index_in_range() {
817 let mut state = 7;
818 for _ in 0..1000 {
819 let idx = lcg_index(10, &mut state);
820 assert!(idx < 10);
821 }
822 }
823
824 #[test]
825 fn sample_two_distinct_yields_distinct() {
826 let mut state = 99;
827 for _ in 0..1000 {
828 let (i, j) = sample_two_distinct(5, &mut state);
829 assert_ne!(i, j);
830 assert!(i < 5 && j < 5);
831 }
832 }
833
834 #[test]
835 fn sample_three_distinct_yields_distinct() {
836 let mut state = 99;
837 for _ in 0..1000 {
838 let (i, j, k) = sample_three_distinct(6, &mut state);
839 assert_ne!(i, j);
840 assert_ne!(i, k);
841 assert_ne!(j, k);
842 assert!(i < 6 && j < 6 && k < 6);
843 }
844 }
845
846 #[test]
847 fn adaptive_count_guards() {
848 assert_eq!(adaptive_iteration_count(1.0, 2, 0.99, 1000), 1);
850 assert_eq!(adaptive_iteration_count(1.5, 2, 0.99, 1000), 1);
851 assert_eq!(adaptive_iteration_count(0.0, 2, 0.99, 1000), 1000);
853 assert_eq!(adaptive_iteration_count(-0.5, 2, 0.99, 1000), 1000);
854 assert_eq!(adaptive_iteration_count(f64::NAN, 2, 0.99, 1000), 1000);
856 assert_eq!(adaptive_iteration_count(f64::INFINITY, 2, 0.99, 1000), 1000);
857 assert_eq!(adaptive_iteration_count(0.5, 2, f64::NAN, 1000), 1000);
859 let mid = adaptive_iteration_count(0.5, 2, 0.99, 1000);
861 assert!((1..=1000).contains(&mid));
862 }
863
864 #[test]
865 fn line_from_coincident_points_is_none() {
866 let p = Point::new(1.0, 1.0);
867 assert!(RansacLineModel::from_two_points(p.clone(), p).is_none());
868 }
869
870 #[test]
871 fn plane_from_collinear_points_is_none() {
872 let p1 = (0.0, 0.0, 0.0);
873 let p2 = (1.0, 1.0, 1.0);
874 let p3 = (2.0, 2.0, 2.0);
875 assert!(RansacPlaneModel::from_three_points(p1, p2, p3).is_none());
876 }
877}