1use crate::cosmic::Orbit;
20use crate::linalg::{
21 allocator::Allocator, DefaultAllocator, DimName, Matrix3, OVector, Vector3, Vector6,
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
42#[test]
43fn test_tilde_matrix() {
44 let vec = Vector3::new(1.0, 2.0, 3.0);
45 let rslt = Matrix3::new(0.0, -3.0, 2.0, 3.0, 0.0, -1.0, -2.0, 1.0, 0.0);
46 assert_eq!(tilde_matrix(&vec), rslt);
47
48 let v = Vector3::new(1.0, 2.0, 3.0);
49 let m = tilde_matrix(&v);
50
51 assert_eq!(m[(0, 0)], 0.0);
52 assert_eq!(m[(0, 1)], -v.z);
53 assert_eq!(m[(0, 2)], v.y);
54 assert_eq!(m[(1, 0)], v.z);
55 assert_eq!(m[(1, 1)], 0.0);
56 assert_eq!(m[(1, 2)], -v.x);
57 assert_eq!(m[(2, 0)], -v.y);
58 assert_eq!(m[(2, 1)], v.x);
59 assert_eq!(m[(2, 2)], 0.0);
60}
61
62pub fn is_diagonal(m: &Matrix3<f64>) -> bool {
94 for i in 0..3 {
95 for j in 0..3 {
96 if i != j && m[(i, j)].abs() > f64::EPSILON {
97 return false;
98 }
99 }
100 }
101 true
102}
103
104pub fn are_eigenvalues_stable<N: DimName>(eigenvalues: OVector<Complex<f64>, N>) -> bool
134where
135 DefaultAllocator: Allocator<N>,
136{
137 eigenvalues.iter().all(|ev| ev.re <= 0.0)
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use nalgebra::{Complex, OVector};
144
145 #[test]
146 fn test_stable_eigenvalues() {
147 let eigenvalues = OVector::<Complex<f64>, nalgebra::U2>::from_column_slice(&[
148 Complex::new(-1.0, 0.0),
149 Complex::new(0.0, 0.0),
150 ]);
151 assert!(are_eigenvalues_stable(eigenvalues));
152 }
153
154 #[test]
155 fn test_unstable_eigenvalues() {
156 let eigenvalues = OVector::<Complex<f64>, nalgebra::U2>::from_column_slice(&[
157 Complex::new(1.0, 0.0),
158 Complex::new(0.0, 0.0),
159 ]);
160 assert!(!are_eigenvalues_stable(eigenvalues));
161 }
162
163 #[test]
164 fn test_oscillatory_eigenvalues() {
165 let eigenvalues = OVector::<Complex<f64>, nalgebra::U2>::from_column_slice(&[
166 Complex::new(0.0, 1.0),
167 Complex::new(0.0, -1.0),
168 ]);
169 assert!(are_eigenvalues_stable(eigenvalues));
170 }
171
172 #[test]
173 fn test_invariant_eigenvalues() {
174 let eigenvalues =
175 OVector::<Complex<f64>, nalgebra::U1>::from_column_slice(&[Complex::new(0.0, 0.0)]);
176 assert!(are_eigenvalues_stable(eigenvalues));
177 }
178}
179
180pub fn between_0_360(angle: f64) -> f64 {
191 let mut bounded = angle % 360.0;
192 if bounded < 0.0 {
193 bounded += 360.0;
194 }
195 bounded
196}
197
198pub fn between_pm_180(angle: f64) -> f64 {
200 between_pm_x(angle, 180.0)
201}
202
203pub fn between_pm_x(angle: f64, x: f64) -> f64 {
214 let mut bounded = angle % (2.0 * x);
215 if bounded > x {
216 bounded -= 2.0 * x;
217 }
218 if bounded < -x {
219 bounded += 2.0 * x;
220 }
221 bounded
222}
223
224pub fn kronecker(a: f64, b: f64) -> f64 {
226 if (a - b).abs() <= f64::EPSILON {
227 1.0
228 } else {
229 0.0
230 }
231}
232
233pub fn r1(angle_rad: f64) -> Matrix3<f64> {
258 let (s, c) = angle_rad.sin_cos();
259 Matrix3::new(1.0, 0.0, 0.0, 0.0, c, s, 0.0, -s, c)
260}
261
262#[test]
263fn test_r1() {
264 let angle = 0.0;
265 let rotation_matrix = r1(angle);
266 assert!((rotation_matrix - Matrix3::identity()).abs().max() <= f64::EPSILON);
267
268 let angle = std::f64::consts::PI / 2.0;
269 let rotation_matrix = r1(angle);
270 let expected_matrix = Matrix3::new(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, -1.0, 0.0);
271 assert!((rotation_matrix - expected_matrix).abs().max() <= f64::EPSILON);
272
273 let v = Vector3::new(1.0, 0.0, 0.0);
274 let rotated_v = rotation_matrix * v;
275 assert!((rotated_v - v).norm() <= f64::EPSILON);
276}
277
278pub fn r2(angle_rad: f64) -> Matrix3<f64> {
303 let (s, c) = angle_rad.sin_cos();
304 Matrix3::new(c, 0.0, -s, 0.0, 1.0, 0.0, s, 0.0, c)
305}
306
307#[test]
308fn test_r2() {
309 let angle = 0.0;
310 let rotation_matrix = r2(angle);
311 assert!((rotation_matrix - Matrix3::identity()).abs().max() <= f64::EPSILON);
312
313 let angle = std::f64::consts::PI / 2.0;
314 let rotation_matrix = r2(angle);
315 let expected_matrix = Matrix3::new(0.0, 0.0, -1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0);
316 assert!((rotation_matrix - expected_matrix).abs().max() <= f64::EPSILON);
317
318 let v = Vector3::new(0.0, 1.0, 0.0);
319 let rotated_v = rotation_matrix * v;
320 assert!((rotated_v - v).norm() <= f64::EPSILON);
321}
322
323pub fn r3(angle_rad: f64) -> Matrix3<f64> {
348 let (s, c) = angle_rad.sin_cos();
349 Matrix3::new(c, s, 0.0, -s, c, 0.0, 0.0, 0.0, 1.0)
350}
351
352#[test]
353fn test_r3() {
354 let angle = 0.0;
355 let rotation_matrix = r3(angle);
356 assert!((rotation_matrix - Matrix3::identity()).abs().max() <= f64::EPSILON);
357
358 let angle = std::f64::consts::PI / 2.0;
359 let rotation_matrix = r3(angle);
360 let expected_matrix = Matrix3::new(0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0);
361 assert!((rotation_matrix - expected_matrix).abs().max() <= f64::EPSILON);
362
363 let v = Vector3::new(0.0, 0.0, 1.0);
364 let rotated_v = rotation_matrix * v;
365 assert!((rotated_v - v).norm() <= f64::EPSILON);
366}
367
368pub fn rotv(v: &Vector3<f64>, axis: &Vector3<f64>, theta: f64) -> Vector3<f64> {
380 let k_hat = axis.normalize();
381 v.scale(theta.cos())
382 + k_hat.cross(v).scale(theta.sin())
383 + k_hat.scale(k_hat.dot(v) * (1.0 - theta.cos()))
384}
385
386#[test]
387fn test_rotv() {
388 use approx::assert_abs_diff_eq;
389 let v = Vector3::new(1.0, 0.0, 0.0);
390 let axis = Vector3::new(0.0, 0.0, 1.0);
391 let theta = std::f64::consts::PI / 2.0;
392 let result = rotv(&v, &axis, theta);
393 assert_abs_diff_eq!(result, Vector3::new(0.0, 1.0, 0.0), epsilon = 1e-7);
394}
395
396pub fn perpv(a: &Vector3<f64>, b: &Vector3<f64>) -> Vector3<f64> {
407 let big_a = a.amax();
408 let big_b = b.amax();
409 if big_a < f64::EPSILON {
410 Vector3::zeros()
411 } else if big_b < f64::EPSILON {
412 *a
413 } else {
414 let a_scl = a / big_a;
415 let b_scl = b / big_b;
416 let v = projv(&a_scl, &b_scl);
417 (a_scl - v) * big_a
418 }
419}
420
421#[test]
422fn test_perpv() {
423 assert_eq!(
424 perpv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(2.0, 0.0, 0.0)),
425 Vector3::new(0.0, 6.0, 6.0)
426 );
427 assert_eq!(
428 perpv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(-3.0, 0.0, 0.0)),
429 Vector3::new(0.0, 6.0, 6.0)
430 );
431 assert_eq!(
432 perpv(&Vector3::new(6.0, 6.0, 0.0), &Vector3::new(0.0, 7.0, 0.0)),
433 Vector3::new(6.0, 0.0, 0.0)
434 );
435 assert_eq!(
436 perpv(&Vector3::new(6.0, 0.0, 0.0), &Vector3::new(0.0, 0.0, 9.0)),
437 Vector3::new(6.0, 0.0, 0.0)
438 );
439 use approx::assert_abs_diff_eq;
440 let a = Vector3::new(1.0, 1.0, 0.0);
441 let b = Vector3::new(1.0, 0.0, 0.0);
442 let result = perpv(&a, &b);
443 assert_abs_diff_eq!(result, Vector3::new(0.0, 1.0, 0.0), epsilon = 1e-7);
444}
445
446pub fn projv(a: &Vector3<f64>, b: &Vector3<f64>) -> Vector3<f64> {
457 let b_norm_squared = b.norm_squared();
458 if b_norm_squared.abs() < f64::EPSILON {
459 Vector3::zeros()
460 } else {
461 b.scale(a.dot(b) / b_norm_squared)
462 }
463}
464
465#[test]
466fn test_projv() {
467 assert_eq!(
468 projv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(2.0, 0.0, 0.0)),
469 Vector3::new(6.0, 0.0, 0.0)
470 );
471 assert_eq!(
472 projv(&Vector3::new(6.0, 6.0, 6.0), &Vector3::new(-3.0, 0.0, 0.0)),
473 Vector3::new(6.0, 0.0, 0.0)
474 );
475 assert_eq!(
476 projv(&Vector3::new(6.0, 6.0, 0.0), &Vector3::new(0.0, 7.0, 0.0)),
477 Vector3::new(0.0, 6.0, 0.0)
478 );
479 assert_eq!(
480 projv(&Vector3::new(6.0, 0.0, 0.0), &Vector3::new(0.0, 0.0, 9.0)),
481 Vector3::new(0.0, 0.0, 0.0)
482 );
483
484 use approx::assert_abs_diff_eq;
485 let a = Vector3::new(1.0, 1.0, 0.0);
486 let b = Vector3::new(1.0, 0.0, 0.0);
487 let result = projv(&a, &b);
488 assert_abs_diff_eq!(result, Vector3::new(1.0, 0.0, 0.0), epsilon = 1e-7);
489}
490
491pub fn rss_errors<N: DimName>(prop_err: &OVector<f64, N>, cur_state: &OVector<f64, N>) -> f64
502where
503 DefaultAllocator: Allocator<N>,
504{
505 prop_err
506 .iter()
507 .zip(cur_state.iter())
508 .map(|(&x, &y)| (x - y).powi(2))
509 .sum::<f64>()
510 .sqrt()
511}
512
513#[test]
514fn test_rss_errors() {
515 use nalgebra::U3;
516 let prop_err = OVector::<f64, U3>::from_iterator([1.0, 2.0, 3.0]);
517 let cur_state = OVector::<f64, U3>::from_iterator([1.0, 2.0, 3.0]);
518 assert_eq!(rss_errors(&prop_err, &cur_state), 0.0);
519
520 let prop_err = OVector::<f64, U3>::from_iterator([1.0, 2.0, 3.0]);
521 let cur_state = OVector::<f64, U3>::from_iterator([4.0, 5.0, 6.0]);
522 assert_eq!(rss_errors(&prop_err, &cur_state), 5.196152422706632);
523}
524
525pub fn rss_orbit_errors(prop_err: &Orbit, cur_state: &Orbit) -> (f64, f64) {
536 (
537 rss_errors(&prop_err.radius_km, &cur_state.radius_km),
538 rss_errors(&prop_err.velocity_km_s, &cur_state.velocity_km_s),
539 )
540}
541
542pub fn rss_orbit_vec_errors(prop_err: &Vector6<f64>, cur_state: &Vector6<f64>) -> (f64, f64) {
553 let err_radius = (prop_err.fixed_rows::<3>(0) - cur_state.fixed_rows::<3>(0)).norm();
554 let err_velocity = (prop_err.fixed_rows::<3>(3) - cur_state.fixed_rows::<3>(3)).norm();
555 (err_radius, err_velocity)
556}
557
558pub fn normalize(x: f64, min_x: f64, max_x: f64) -> f64 {
570 2.0 * (x - min_x) / (max_x - min_x) - 1.0
571}
572
573#[test]
574fn test_normalize() {
575 let x = 5.0;
576 let min_x = 0.0;
577 let max_x = 10.0;
578 let result = normalize(x, min_x, max_x);
579 assert_eq!(result, 0.0);
580}
581
582pub fn denormalize(xp: f64, min_x: f64, max_x: f64) -> f64 {
594 (max_x - min_x) * (xp + 1.0) / 2.0 + min_x
595}
596
597#[test]
598fn test_denormalize() {
599 let xp = 0.0;
600 let min_x = 0.0;
601 let max_x = 10.0;
602 let result = denormalize(xp, min_x, max_x);
603 assert_eq!(result, 5.0);
604}
605
606pub fn capitalize(s: &str) -> String {
620 let mut c = s.chars();
621 match c.next() {
622 None => String::new(),
623 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
624 }
625}
626
627#[test]
628fn test_capitalize() {
629 let s = "hello";
630 let result = capitalize(s);
631 assert_eq!(result, "Hello");
632}
633
634#[macro_export]
635macro_rules! pseudo_inverse {
636 ($mat:expr) => {{
637 use $crate::md::TargetingError;
638 let (rows, cols) = $mat.shape();
639 if rows < cols {
640 match ($mat * $mat.transpose()).try_inverse() {
641 Some(m1_inv) => Ok($mat.transpose() * m1_inv),
642 None => Err(TargetingError::SingularJacobian),
643 }
644 } else {
645 match ($mat.transpose() * $mat).try_inverse() {
646 Some(m2_inv) => Ok(m2_inv * $mat.transpose()),
647 None => Err(TargetingError::SingularJacobian),
648 }
649 }
650 }};
651}
652
653pub fn mag_order(value: f64) -> i32 {
661 value.abs().log10().floor() as i32
662}
663
664pub fn unitize(v: Vector3<f64>) -> Vector3<f64> {
666 if v.norm() < f64::EPSILON {
667 v
668 } else {
669 v / v.norm()
670 }
671}
672
673pub fn cartesian_to_spherical(v: &Vector3<f64>) -> (f64, f64, f64) {
676 if v.norm() < f64::EPSILON {
677 (0.0, 0.0, 0.0)
678 } else {
679 let range_ρ = v.norm();
680 let θ = v.y.atan2(v.x);
681 let φ = (v.z / range_ρ).acos();
682 (range_ρ, θ, φ)
683 }
684}
685
686pub fn spherical_to_cartesian(range_ρ: f64, θ: f64, φ: f64) -> Vector3<f64> {
689 if range_ρ < f64::EPSILON {
690 Vector3::zeros()
692 } else {
693 let x = range_ρ * φ.sin() * θ.cos();
694 let y = range_ρ * φ.sin() * θ.sin();
695 let z = range_ρ * φ.cos();
696 Vector3::new(x, y, z)
697 }
698}
699
700#[rustfmt::skip]
701#[test]
702fn test_diagonality() {
703 assert!(!is_diagonal(&Matrix3::new(10.0, 0.0, 0.0,
704 1.0, 5.0, 0.0,
705 0.0, 0.0, 2.0)),
706 "lower triangular"
707 );
708 assert!(!is_diagonal(&Matrix3::new(10.0, 1.0, 0.0,
709 1.0, 5.0, 0.0,
710 0.0, 0.0, 2.0)),
711 "symmetric but not diag"
712 );
713 assert!(!is_diagonal(&Matrix3::new(10.0, 1.0, 0.0,
714 0.0, 5.0, 0.0,
715 0.0, 0.0, 2.0)),
716 "upper triangular"
717 );
718 assert!(is_diagonal(&Matrix3::new(10.0, 0.0, 0.0,
719 0.0, 0.0, 0.0,
720 0.0, 0.0, 2.0)),
721 "diagonal with zero diagonal element"
722 );
723 assert!(is_diagonal(&Matrix3::new(10.0, 0.0, 0.0,
724 0.0, 5.0, 0.0,
725 0.0, 0.0, 2.0)),
726 "diagonal"
727 );
728}
729
730#[test]
731fn test_angle_bounds() {
732 assert!((between_pm_180(181.0) - -179.0).abs() < f64::EPSILON);
733 assert!((between_0_360(-179.0) - 181.0).abs() < f64::EPSILON);
734}
735
736#[test]
737fn test_positive_angle() {
738 assert_eq!(between_0_360(450.0), 90.0);
739 assert_eq!(between_pm_x(270.0, 180.0), -90.0);
740}
741
742#[test]
743fn test_negative_angle() {
744 assert_eq!(between_0_360(-90.0), 270.0);
745 assert_eq!(between_pm_x(-270.0, 180.0), 90.0);
746}
747
748#[test]
749fn test_angle_in_range() {
750 assert_eq!(between_0_360(180.0), 180.0);
751 assert_eq!(between_pm_x(90.0, 180.0), 90.0);
752}
753
754#[test]
755fn test_zero_angle() {
756 assert_eq!(between_0_360(0.0), 0.0);
757 assert_eq!(between_pm_x(0.0, 180.0), 0.0);
758}
759
760#[test]
761fn test_full_circle_angle() {
762 assert_eq!(between_0_360(360.0), 0.0);
763 assert_eq!(between_pm_x(360.0, 180.0), 0.0);
764}
765
766#[test]
767fn test_pseudo_inv() {
768 use crate::linalg::{DMatrix, SMatrix};
769 let mut mat = DMatrix::from_element(1, 3, 0.0);
770 mat[(0, 0)] = -1407.273208782421;
771 mat[(0, 1)] = -2146.3100013104886;
772 mat[(0, 2)] = 84.05022886527551;
773
774 println!("{}", pseudo_inverse!(&mat).unwrap());
775
776 let mut mat = SMatrix::<f64, 1, 3>::zeros();
777 mat[(0, 0)] = -1407.273208782421;
778 mat[(0, 1)] = -2146.3100013104886;
779 mat[(0, 2)] = 84.05022886527551;
780
781 println!("{}", pseudo_inverse!(&mat).unwrap());
782
783 let mut mat = SMatrix::<f64, 3, 1>::zeros();
784 mat[(0, 0)] = -1407.273208782421;
785 mat[(1, 0)] = -2146.3100013104886;
786 mat[(2, 0)] = 84.05022886527551;
787
788 println!("{}", pseudo_inverse!(&mat).unwrap());
789
790 let mat = SMatrix::<f64, 2, 2>::new(3.0, 4.0, -2.0, 1.0);
792 println!("{}", mat.try_inverse().unwrap());
793
794 println!("{}", pseudo_inverse!(&mat).unwrap());
795}
796
797#[test]
798fn spherical() {
799 for v in &[
800 Vector3::<f64>::x(),
801 Vector3::<f64>::y(),
802 Vector3::<f64>::z(),
803 Vector3::<f64>::zeros(),
804 Vector3::<f64>::new(159.1, 561.2, 756.3),
805 ] {
806 let (range_ρ, θ, φ) = cartesian_to_spherical(v);
807 let v_prime = spherical_to_cartesian(range_ρ, θ, φ);
808
809 assert!(rss_errors(v, &v_prime) < 1e-12, "{} != {}", v, &v_prime);
810 }
811}