1use crate::cosmic::Orbit;
20use crate::linalg::{
21 DefaultAllocator, DimName, Matrix3, OVector, Vector3, Vector6, allocator::Allocator,
22};
23use nalgebra::Complex;
24
25pub fn tilde_matrix(v: &Vector3<f64>) -> Matrix3<f64> {
39 Matrix3::new(0.0, -v.z, v.y, v.z, 0.0, -v.x, -v.y, v.x, 0.0)
40}
41
42pub fn is_diagonal(m: &Matrix3<f64>) -> bool {
74 for i in 0..3 {
75 for j in 0..3 {
76 if i != j && m[(i, j)].abs() > f64::EPSILON {
77 return false;
78 }
79 }
80 }
81 true
82}
83
84pub fn are_eigenvalues_stable<N: DimName>(eigenvalues: &OVector<Complex<f64>, N>) -> bool
114where
115 DefaultAllocator: Allocator<N>,
116{
117 eigenvalues.iter().all(|ev| ev.re <= 0.0)
118}
119
120pub fn between_0_360(angle: f64) -> f64 {
131 let mut bounded = angle % 360.0;
132 if bounded < 0.0 {
133 bounded += 360.0;
134 }
135 bounded
136}
137
138pub fn between_pm_180(angle: f64) -> f64 {
140 between_pm_x(angle, 180.0)
141}
142
143pub fn between_pm_x(angle: f64, x: f64) -> f64 {
154 let mut bounded = angle % (2.0 * x);
155 if bounded > x {
156 bounded -= 2.0 * x;
157 }
158 if bounded < -x {
159 bounded += 2.0 * x;
160 }
161 bounded
162}
163
164pub fn kronecker(a: f64, b: f64) -> f64 {
166 if (a - b).abs() <= f64::EPSILON {
167 1.0
168 } else {
169 0.0
170 }
171}
172
173pub fn r1(angle_rad: f64) -> Matrix3<f64> {
198 let (s, c) = angle_rad.sin_cos();
199 Matrix3::new(1.0, 0.0, 0.0, 0.0, c, s, 0.0, -s, c)
200}
201
202pub fn r2(angle_rad: f64) -> Matrix3<f64> {
227 let (s, c) = angle_rad.sin_cos();
228 Matrix3::new(c, 0.0, -s, 0.0, 1.0, 0.0, s, 0.0, c)
229}
230
231pub fn r3(angle_rad: f64) -> Matrix3<f64> {
256 let (s, c) = angle_rad.sin_cos();
257 Matrix3::new(c, s, 0.0, -s, c, 0.0, 0.0, 0.0, 1.0)
258}
259
260pub fn rotv(v: &Vector3<f64>, axis: &Vector3<f64>, theta: f64) -> Vector3<f64> {
272 let k_hat = axis.normalize();
273 v.scale(theta.cos())
274 + k_hat.cross(v).scale(theta.sin())
275 + k_hat.scale(k_hat.dot(v) * (1.0 - theta.cos()))
276}
277
278pub fn perpv(a: &Vector3<f64>, b: &Vector3<f64>) -> Vector3<f64> {
289 let big_a = a.amax();
290 let big_b = b.amax();
291 if big_a < f64::EPSILON {
292 Vector3::zeros()
293 } else if big_b < f64::EPSILON {
294 *a
295 } else {
296 let a_scl = a / big_a;
297 let b_scl = b / big_b;
298 let v = projv(&a_scl, &b_scl);
299 (a_scl - v) * big_a
300 }
301}
302
303pub fn projv(a: &Vector3<f64>, b: &Vector3<f64>) -> Vector3<f64> {
314 let b_norm_squared = b.norm_squared();
315 if b_norm_squared.abs() < f64::EPSILON {
316 Vector3::zeros()
317 } else {
318 b.scale(a.dot(b) / b_norm_squared)
319 }
320}
321
322pub fn rss_errors<N: DimName>(prop_err: &OVector<f64, N>, cur_state: &OVector<f64, N>) -> f64
333where
334 DefaultAllocator: Allocator<N>,
335{
336 prop_err
337 .iter()
338 .zip(cur_state.iter())
339 .map(|(&x, &y)| (x - y).powi(2))
340 .sum::<f64>()
341 .sqrt()
342}
343
344pub fn rss_orbit_errors(prop_err: &Orbit, cur_state: &Orbit) -> (f64, f64) {
355 (
356 rss_errors(&prop_err.radius_km, &cur_state.radius_km),
357 rss_errors(&prop_err.velocity_km_s, &cur_state.velocity_km_s),
358 )
359}
360
361pub fn rss_orbit_vec_errors(prop_err: &Vector6<f64>, cur_state: &Vector6<f64>) -> (f64, f64) {
372 let err_radius = (prop_err.fixed_rows::<3>(0) - cur_state.fixed_rows::<3>(0)).norm();
373 let err_velocity = (prop_err.fixed_rows::<3>(3) - cur_state.fixed_rows::<3>(3)).norm();
374 (err_radius, err_velocity)
375}
376
377pub fn normalize(x: f64, min_x: f64, max_x: f64) -> f64 {
389 2.0 * (x - min_x) / (max_x - min_x) - 1.0
390}
391
392pub fn denormalize(xp: f64, min_x: f64, max_x: f64) -> f64 {
404 (max_x - min_x) * (xp + 1.0) / 2.0 + min_x
405}
406
407pub fn capitalize(s: &str) -> String {
421 let mut c = s.chars();
422 match c.next() {
423 None => String::new(),
424 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
425 }
426}
427
428#[macro_export]
429macro_rules! pseudo_inverse {
430 ($mat:expr) => {{
431 use $crate::md::TargetingError;
432 let (rows, cols) = $mat.shape();
433 if rows < cols {
434 match ($mat * $mat.transpose()).try_inverse() {
435 Some(m1_inv) => Ok($mat.transpose() * m1_inv),
436 None => Err(TargetingError::SingularJacobian),
437 }
438 } else {
439 match ($mat.transpose() * $mat).try_inverse() {
440 Some(m2_inv) => Ok(m2_inv * $mat.transpose()),
441 None => Err(TargetingError::SingularJacobian),
442 }
443 }
444 }};
445}
446
447pub fn mag_order(value: f64) -> i32 {
455 value.abs().log10().floor() as i32
456}
457
458pub fn unitize(v: Vector3<f64>) -> Vector3<f64> {
460 if v.norm() < f64::EPSILON {
461 v
462 } else {
463 v / v.norm()
464 }
465}
466
467pub fn cartesian_to_spherical(v: &Vector3<f64>) -> (f64, f64, f64) {
470 if v.norm() < f64::EPSILON {
471 (0.0, 0.0, 0.0)
472 } else {
473 let range_ρ = v.norm();
474 let θ = v.y.atan2(v.x);
475 let φ = (v.z / range_ρ).acos();
476 (range_ρ, θ, φ)
477 }
478}
479
480#[allow(clippy::many_single_char_names)]
483pub fn spherical_to_cartesian(range_ρ: f64, θ: f64, φ: f64) -> Vector3<f64> {
484 if range_ρ < f64::EPSILON {
485 Vector3::zeros()
487 } else {
488 let x = range_ρ * φ.sin() * θ.cos();
489 let y = range_ρ * φ.sin() * θ.sin();
490 let z = range_ρ * φ.cos();
491 Vector3::new(x, y, z)
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498 use approx::assert_abs_diff_eq;
499 use nalgebra::{Complex, OVector};
500
501 #[test]
502 fn test_stable_eigenvalues() {
503 let eigenvalues = OVector::<Complex<f64>, nalgebra::U2>::from_column_slice(&[
504 Complex::new(-1.0, 0.0),
505 Complex::new(0.0, 0.0),
506 ]);
507 assert!(are_eigenvalues_stable(&eigenvalues));
508 }
509
510 #[test]
511 fn test_unstable_eigenvalues() {
512 let eigenvalues = OVector::<Complex<f64>, nalgebra::U2>::from_column_slice(&[
513 Complex::new(1.0, 0.0),
514 Complex::new(0.0, 0.0),
515 ]);
516 assert!(!are_eigenvalues_stable(&eigenvalues));
517 }
518
519 #[test]
520 fn test_oscillatory_eigenvalues() {
521 let eigenvalues = OVector::<Complex<f64>, nalgebra::U2>::from_column_slice(&[
522 Complex::new(0.0, 1.0),
523 Complex::new(0.0, -1.0),
524 ]);
525 assert!(are_eigenvalues_stable(&eigenvalues));
526 }
527
528 #[test]
529 fn test_invariant_eigenvalues() {
530 let eigenvalues =
531 OVector::<Complex<f64>, nalgebra::U1>::from_column_slice(&[Complex::new(0.0, 0.0)]);
532 assert!(are_eigenvalues_stable(&eigenvalues));
533 }
534
535 #[test]
536 fn test_angle_bounds() {
537 approx::assert_relative_eq!(between_pm_180(181.0), -179.0);
538 approx::assert_relative_eq!(between_0_360(-179.0), 181.0);
539 }
540
541 #[test]
542 fn test_positive_angle() {
543 approx::assert_relative_eq!(between_0_360(450.0), 90.0);
544 approx::assert_relative_eq!(between_pm_x(270.0, 180.0), -90.0);
545 }
546
547 #[test]
548 fn test_negative_angle() {
549 approx::assert_relative_eq!(between_0_360(-90.0), 270.0);
550 approx::assert_relative_eq!(between_pm_x(-270.0, 180.0), 90.0);
551 }
552
553 #[test]
554 fn test_angle_in_range() {
555 approx::assert_relative_eq!(between_0_360(180.0), 180.0);
556 approx::assert_relative_eq!(between_pm_x(90.0, 180.0), 90.0);
557 }
558
559 #[test]
560 fn test_zero_angle() {
561 approx::assert_relative_eq!(between_0_360(0.0), 0.0);
562 approx::assert_relative_eq!(between_pm_x(0.0, 180.0), 0.0);
563 }
564
565 #[test]
566 fn test_full_circle_angle() {
567 approx::assert_relative_eq!(between_0_360(360.0), 0.0);
568 approx::assert_relative_eq!(between_pm_x(360.0, 180.0), 0.0);
569 }
570
571 #[test]
572 fn test_pseudo_inv() {
573 use crate::linalg::{DMatrix, SMatrix};
574 let mut mat = DMatrix::from_element(1, 3, 0.0);
575 mat[(0, 0)] = -1407.273208782421;
576 mat[(0, 1)] = -2146.3100013104886;
577 mat[(0, 2)] = 84.05022886527551;
578
579 println!("{}", pseudo_inverse!(&mat).unwrap());
580
581 let mut mat = SMatrix::<f64, 1, 3>::zeros();
582 mat[(0, 0)] = -1407.273208782421;
583 mat[(0, 1)] = -2146.3100013104886;
584 mat[(0, 2)] = 84.05022886527551;
585
586 println!("{}", pseudo_inverse!(&mat).unwrap());
587
588 let mut mat = SMatrix::<f64, 3, 1>::zeros();
589 mat[(0, 0)] = -1407.273208782421;
590 mat[(1, 0)] = -2146.3100013104886;
591 mat[(2, 0)] = 84.05022886527551;
592
593 println!("{}", pseudo_inverse!(&mat).unwrap());
594
595 let mat = SMatrix::<f64, 2, 2>::new(3.0, 4.0, -2.0, 1.0);
597 println!("{}", mat.try_inverse().unwrap());
598
599 println!("{}", pseudo_inverse!(&mat).unwrap());
600 }
601
602 #[test]
603 fn spherical() {
604 for v in &[
605 Vector3::<f64>::x(),
606 Vector3::<f64>::y(),
607 Vector3::<f64>::z(),
608 Vector3::<f64>::zeros(),
609 Vector3::<f64>::new(159.1, 561.2, 756.3),
610 ] {
611 let (range_ρ, θ, φ) = cartesian_to_spherical(v);
612 let v_prime = spherical_to_cartesian(range_ρ, θ, φ);
613
614 assert!(rss_errors(v, &v_prime) < 1e-12, "{} != {}", v, &v_prime);
615 }
616 }
617
618 #[rustfmt::skip]
619 #[test]
620 fn test_diagonality() {
621 assert!(!is_diagonal(&Matrix3::new(10.0, 0.0, 0.0,
622 1.0, 5.0, 0.0,
623 0.0, 0.0, 2.0)),
624 "lower triangular"
625 );
626 assert!(!is_diagonal(&Matrix3::new(10.0, 1.0, 0.0,
627 1.0, 5.0, 0.0,
628 0.0, 0.0, 2.0)),
629 "symmetric but not diag"
630 );
631 assert!(!is_diagonal(&Matrix3::new(10.0, 1.0, 0.0,
632 0.0, 5.0, 0.0,
633 0.0, 0.0, 2.0)),
634 "upper triangular"
635 );
636 assert!(is_diagonal(&Matrix3::new(10.0, 0.0, 0.0,
637 0.0, 0.0, 0.0,
638 0.0, 0.0, 2.0)),
639 "diagonal with zero diagonal element"
640 );
641 assert!(is_diagonal(&Matrix3::new(10.0, 0.0, 0.0,
642 0.0, 5.0, 0.0,
643 0.0, 0.0, 2.0)),
644 "diagonal"
645 );
646 }
647
648 #[test]
649 fn test_projv() {
650 assert_eq!(
651 projv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(2.0, 0.0, 0.0)),
652 Vector3::new(6.0, 0.0, 0.0)
653 );
654 assert_eq!(
655 projv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(-3.0, 0.0, 0.0)),
656 Vector3::new(6.0, 0.0, 0.0)
657 );
658 assert_eq!(
659 projv(&Vector3::new(6.0, 6.0, 0.0), &Vector3::new(0.0, 7.0, 0.0)),
660 Vector3::new(0.0, 6.0, 0.0)
661 );
662 assert_eq!(
663 projv(&Vector3::new(6.0, 0.0, 0.0), &Vector3::new(0.0, 0.0, 9.0)),
664 Vector3::new(0.0, 0.0, 0.0)
665 );
666
667 let a = Vector3::new(1.0, 1.0, 0.0);
668 let b = Vector3::new(1.0, 0.0, 0.0);
669 let result = projv(&a, &b);
670 assert_abs_diff_eq!(result, Vector3::new(1.0, 0.0, 0.0), epsilon = 1e-7);
671 }
672
673 #[test]
674 fn test_rss_errors() {
675 use nalgebra::U3;
676 let prop_err = OVector::<f64, U3>::from_iterator([1.0, 2.0, 3.0]);
677 let cur_state = OVector::<f64, U3>::from_iterator([1.0, 2.0, 3.0]);
678 approx::assert_relative_eq!(rss_errors(&prop_err, &cur_state), 0.0);
679
680 let prop_err = OVector::<f64, U3>::from_iterator([1.0, 2.0, 3.0]);
681 let cur_state = OVector::<f64, U3>::from_iterator([4.0, 5.0, 6.0]);
682 approx::assert_relative_eq!(rss_errors(&prop_err, &cur_state), 5.196152422706632);
683 }
684
685 #[test]
686 fn test_normalize() {
687 let x = 5.0;
688 let min_x = 0.0;
689 let max_x = 10.0;
690 let result = normalize(x, min_x, max_x);
691 approx::assert_relative_eq!(result, 0.0);
692 }
693
694 #[test]
695 fn test_denormalize() {
696 let xp = 0.0;
697 let min_x = 0.0;
698 let max_x = 10.0;
699 let result = denormalize(xp, min_x, max_x);
700 approx::assert_relative_eq!(result, 5.0);
701 }
702
703 #[test]
704 fn test_capitalize() {
705 let s = "hello";
706 let result = capitalize(s);
707 assert_eq!(result, "Hello");
708 }
709
710 #[test]
711 fn test_r2() {
712 let angle = 0.0;
713 let rotation_matrix = r2(angle);
714 assert!((rotation_matrix - Matrix3::identity()).abs().max() <= f64::EPSILON);
715
716 let angle = std::f64::consts::PI / 2.0;
717 let rotation_matrix = r2(angle);
718 let expected_matrix = Matrix3::new(0.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0);
719 assert!((rotation_matrix - expected_matrix).abs().max() <= f64::EPSILON);
720
721 let v = Vector3::new(0.0, 1.0, 0.0);
722 let rotated_v = rotation_matrix * v;
723 assert!((rotated_v - v).norm() <= f64::EPSILON);
724 }
725
726 #[test]
727 fn test_r1() {
728 let angle = 0.0;
729 let rotation_matrix = r1(angle);
730 assert!((rotation_matrix - Matrix3::identity()).abs().max() <= f64::EPSILON);
731
732 let angle = std::f64::consts::PI / 2.0;
733 let rotation_matrix = r1(angle);
734 let expected_matrix = Matrix3::new(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0);
735 assert!((rotation_matrix - expected_matrix).abs().max() <= f64::EPSILON);
736
737 let v = Vector3::new(1.0, 0.0, 0.0);
738 let rotated_v = rotation_matrix * v;
739 assert!((rotated_v - v).norm() <= f64::EPSILON);
740 }
741
742 #[test]
743 fn test_tilde_matrix() {
744 let vec = Vector3::new(1.0, 2.0, 3.0);
745 let rslt = Matrix3::new(0.0, -3.0, 2.0, 3.0, 0.0, -1.0, -2.0, 1.0, 0.0);
746 assert_eq!(tilde_matrix(&vec), rslt);
747
748 let v = Vector3::new(1.0, 2.0, 3.0);
749 let m = tilde_matrix(&v);
750
751 approx::assert_relative_eq!(m[(0, 0)], 0.0);
752 approx::assert_relative_eq!(m[(0, 1)], -v.z);
753 approx::assert_relative_eq!(m[(0, 2)], v.y);
754 approx::assert_relative_eq!(m[(1, 0)], v.z);
755 approx::assert_relative_eq!(m[(1, 1)], 0.0);
756 approx::assert_relative_eq!(m[(1, 2)], -v.x);
757 approx::assert_relative_eq!(m[(2, 0)], -v.y);
758 approx::assert_relative_eq!(m[(2, 1)], v.x);
759 approx::assert_relative_eq!(m[(2, 2)], 0.0);
760 }
761
762 #[test]
763 fn test_perpv() {
764 assert_eq!(
765 perpv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(2.0, 0.0, 0.0)),
766 Vector3::new(0.0, 6.0, 6.0)
767 );
768 assert_eq!(
769 perpv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(-3.0, 0.0, 0.0)),
770 Vector3::new(0.0, 6.0, 6.0)
771 );
772 assert_eq!(
773 perpv(&Vector3::new(6.0, 6.0, 0.0), &Vector3::new(0.0, 7.0, 0.0)),
774 Vector3::new(6.0, 0.0, 0.0)
775 );
776 assert_eq!(
777 perpv(&Vector3::new(6.0, 0.0, 0.0), &Vector3::new(0.0, 0.0, 9.0)),
778 Vector3::new(6.0, 0.0, 0.0)
779 );
780 let a = Vector3::new(1.0, 1.0, 0.0);
781 let b = Vector3::new(1.0, 0.0, 0.0);
782 let result = perpv(&a, &b);
783 assert_abs_diff_eq!(result, Vector3::new(0.0, 1.0, 0.0), epsilon = 1e-7);
784 }
785
786 #[test]
787 fn test_r3() {
788 let angle = 0.0;
789 let rotation_matrix = r3(angle);
790 assert!((rotation_matrix - Matrix3::identity()).abs().max() <= f64::EPSILON);
791
792 let angle = std::f64::consts::PI / 2.0;
793 let rotation_matrix = r3(angle);
794 let expected_matrix = Matrix3::new(0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0);
795 assert!((rotation_matrix - expected_matrix).abs().max() <= f64::EPSILON);
796
797 let v = Vector3::new(0.0, 0.0, 1.0);
798 let rotated_v = rotation_matrix * v;
799 assert!((rotated_v - v).norm() <= f64::EPSILON);
800 }
801
802 #[test]
803 fn test_rotv() {
804 use approx::assert_abs_diff_eq;
805 let v = Vector3::new(1.0, 0.0, 0.0);
806 let axis = Vector3::new(0.0, 0.0, 1.0);
807 let theta = std::f64::consts::PI / 2.0;
808 let result = rotv(&v, &axis, theta);
809 assert_abs_diff_eq!(result, Vector3::new(0.0, 1.0, 0.0), epsilon = 1e-7);
810 }
811}