1use std::path::Path;
12
13use nalgebra::{SMatrix, SVector};
14
15use crate::ccsds::common::{CCSDSFormat, CCSDSRefFrame, CCSDSUserDefined, CDMCovarianceDimension};
16use crate::time::Epoch;
17use crate::utils::errors::BraheError;
18
19#[derive(Debug, Clone)]
29pub struct CDMHeader {
30 pub format_version: f64,
32 pub classification: Option<String>,
34 pub creation_date: Epoch,
36 pub originator: String,
38 pub message_for: Option<String>,
40 pub message_id: String,
42 pub comments: Vec<String>,
44}
45
46#[derive(Debug, Clone)]
55pub struct CDMRelativeMetadata {
56 pub conjunction_id: Option<String>,
58 pub tca: Epoch,
60 pub miss_distance: f64,
62 pub mahalanobis_distance: Option<f64>,
64 pub relative_speed: Option<f64>,
66 pub relative_position_r: Option<f64>,
68 pub relative_position_t: Option<f64>,
70 pub relative_position_n: Option<f64>,
72 pub relative_velocity_r: Option<f64>,
74 pub relative_velocity_t: Option<f64>,
76 pub relative_velocity_n: Option<f64>,
78 pub approach_angle: Option<f64>,
80
81 pub start_screen_period: Option<Epoch>,
84 pub stop_screen_period: Option<Epoch>,
86 pub screen_type: Option<String>,
88 pub screen_volume_frame: Option<CCSDSRefFrame>,
90 pub screen_volume_shape: Option<String>,
92 pub screen_volume_radius: Option<f64>,
94 pub screen_volume_x: Option<f64>,
96 pub screen_volume_y: Option<f64>,
98 pub screen_volume_z: Option<f64>,
100 pub screen_entry_time: Option<Epoch>,
102 pub screen_exit_time: Option<Epoch>,
104 pub screen_pc_threshold: Option<f64>,
106
107 pub collision_percentile: Option<Vec<u32>>,
110 pub collision_probability: Option<f64>,
112 pub collision_probability_method: Option<String>,
114 pub collision_max_probability: Option<f64>,
116 pub collision_max_pc_method: Option<String>,
118
119 pub sefi_collision_probability: Option<f64>,
122 pub sefi_collision_probability_method: Option<String>,
124 pub sefi_fragmentation_model: Option<String>,
126
127 pub previous_message_id: Option<String>,
130 pub previous_message_epoch: Option<Epoch>,
132 pub next_message_epoch: Option<Epoch>,
134
135 pub comments: Vec<String>,
137}
138
139#[derive(Debug, Clone)]
145pub struct CDMObjectMetadata {
146 pub object: String,
148 pub object_designator: String,
150 pub catalog_name: String,
152 pub object_name: String,
154 pub international_designator: String,
156 pub object_type: Option<String>,
158 pub ops_status: Option<String>,
160 pub operator_contact_position: Option<String>,
162 pub operator_organization: Option<String>,
164 pub operator_phone: Option<String>,
166 pub operator_email: Option<String>,
168 pub ephemeris_name: String,
170 pub odm_msg_link: Option<String>,
172 pub adm_msg_link: Option<String>,
174 pub obs_before_next_message: Option<String>,
176 pub covariance_method: String,
178 pub covariance_source: Option<String>,
180 pub maneuverable: String,
182 pub orbit_center: Option<String>,
184 pub ref_frame: CCSDSRefFrame,
186 pub alt_cov_type: Option<String>,
188 pub alt_cov_ref_frame: Option<CCSDSRefFrame>,
190 pub gravity_model: Option<String>,
192 pub atmospheric_model: Option<String>,
194 pub n_body_perturbations: Option<String>,
196 pub solar_rad_pressure: Option<String>,
198 pub earth_tides: Option<String>,
200 pub intrack_thrust: Option<String>,
202 pub comments: Vec<String>,
204}
205
206#[derive(Debug, Clone)]
212pub struct CDMODParameters {
213 pub time_lastob_start: Option<Epoch>,
215 pub time_lastob_end: Option<Epoch>,
217 pub recommended_od_span: Option<f64>,
219 pub actual_od_span: Option<f64>,
221 pub obs_available: Option<u32>,
223 pub obs_used: Option<u32>,
225 pub tracks_available: Option<u32>,
227 pub tracks_used: Option<u32>,
229 pub residuals_accepted: Option<f64>,
231 pub weighted_rms: Option<f64>,
233 pub od_epoch: Option<Epoch>,
235 pub comments: Vec<String>,
237}
238
239#[derive(Debug, Clone)]
245pub struct CDMAdditionalParameters {
246 pub area_pc: Option<f64>,
248 pub area_pc_min: Option<f64>,
250 pub area_pc_max: Option<f64>,
252 pub area_drg: Option<f64>,
254 pub area_srp: Option<f64>,
256
257 pub oeb_parent_frame: Option<String>,
260 pub oeb_parent_frame_epoch: Option<Epoch>,
262 pub oeb_q1: Option<f64>,
264 pub oeb_q2: Option<f64>,
266 pub oeb_q3: Option<f64>,
268 pub oeb_qc: Option<f64>,
270 pub oeb_max: Option<f64>,
272 pub oeb_int: Option<f64>,
274 pub oeb_min: Option<f64>,
276 pub area_along_oeb_max: Option<f64>,
278 pub area_along_oeb_int: Option<f64>,
280 pub area_along_oeb_min: Option<f64>,
282
283 pub rcs: Option<f64>,
286 pub rcs_min: Option<f64>,
288 pub rcs_max: Option<f64>,
290 pub vm_absolute: Option<f64>,
292 pub vm_apparent_min: Option<f64>,
294 pub vm_apparent: Option<f64>,
296 pub vm_apparent_max: Option<f64>,
298 pub reflectance: Option<f64>,
300
301 pub mass: Option<f64>,
303 pub hbr: Option<f64>,
305 pub cd_area_over_mass: Option<f64>,
307 pub cr_area_over_mass: Option<f64>,
309 pub thrust_acceleration: Option<f64>,
311 pub sedr: Option<f64>,
313
314 pub min_dv: Option<[f64; 3]>,
317 pub max_dv: Option<[f64; 3]>,
319
320 pub lead_time_reqd_before_tca: Option<f64>,
322
323 pub apoapsis_altitude: Option<f64>,
326 pub periapsis_altitude: Option<f64>,
328 pub inclination: Option<f64>,
330
331 pub cov_confidence: Option<f64>,
334 pub cov_confidence_method: Option<String>,
336
337 pub comments: Vec<String>,
339}
340
341#[derive(Debug, Clone)]
350pub struct CDMStateVector {
351 pub position: [f64; 3],
353 pub velocity: [f64; 3],
355 pub comments: Vec<String>,
357}
358
359#[derive(Debug, Clone)]
374pub struct CDMRTNCovariance {
375 pub matrix: SMatrix<f64, 9, 9>,
378 pub dimension: CDMCovarianceDimension,
380 pub comments: Vec<String>,
382}
383
384#[derive(Debug, Clone)]
389pub struct CDMXYZCovariance {
390 pub matrix: SMatrix<f64, 9, 9>,
393 pub dimension: CDMCovarianceDimension,
395 pub comments: Vec<String>,
397}
398
399#[derive(Debug, Clone)]
405pub struct CDMAdditionalCovarianceMetadata {
406 pub density_forecast_uncertainty: Option<f64>,
408 pub cscale_factor_min: Option<f64>,
410 pub cscale_factor: Option<f64>,
412 pub cscale_factor_max: Option<f64>,
414 pub screening_data_source: Option<String>,
416 pub dcp_sensitivity_vector_position: Option<[f64; 3]>,
418 pub dcp_sensitivity_vector_velocity: Option<[f64; 3]>,
420 pub comments: Vec<String>,
422}
423
424#[derive(Debug, Clone)]
430pub struct CDMObjectData {
431 pub od_parameters: Option<CDMODParameters>,
433 pub additional_parameters: Option<CDMAdditionalParameters>,
435 pub state_vector: CDMStateVector,
437 pub rtn_covariance: CDMRTNCovariance,
439 pub xyz_covariance: Option<CDMXYZCovariance>,
441 pub additional_covariance_metadata: Option<CDMAdditionalCovarianceMetadata>,
443 pub csig3eigvec3: Option<String>,
445 pub comments: Vec<String>,
447}
448
449#[derive(Debug, Clone)]
455pub struct CDMObject {
456 pub metadata: CDMObjectMetadata,
458 pub data: CDMObjectData,
460}
461
462#[derive(Debug, Clone)]
472pub struct CDM {
473 pub header: CDMHeader,
475 pub relative_metadata: CDMRelativeMetadata,
477 pub object1: CDMObject,
479 pub object2: CDMObject,
481 pub user_defined: Option<CCSDSUserDefined>,
483}
484
485impl CDMRelativeMetadata {
490 pub fn new(tca: Epoch, miss_distance: f64) -> Self {
492 Self {
493 conjunction_id: None,
494 tca,
495 miss_distance,
496 mahalanobis_distance: None,
497 relative_speed: None,
498 relative_position_r: None,
499 relative_position_t: None,
500 relative_position_n: None,
501 relative_velocity_r: None,
502 relative_velocity_t: None,
503 relative_velocity_n: None,
504 approach_angle: None,
505 start_screen_period: None,
506 stop_screen_period: None,
507 screen_type: None,
508 screen_volume_frame: None,
509 screen_volume_shape: None,
510 screen_volume_radius: None,
511 screen_volume_x: None,
512 screen_volume_y: None,
513 screen_volume_z: None,
514 screen_entry_time: None,
515 screen_exit_time: None,
516 screen_pc_threshold: None,
517 collision_percentile: None,
518 collision_probability: None,
519 collision_probability_method: None,
520 collision_max_probability: None,
521 collision_max_pc_method: None,
522 sefi_collision_probability: None,
523 sefi_collision_probability_method: None,
524 sefi_fragmentation_model: None,
525 previous_message_id: None,
526 previous_message_epoch: None,
527 next_message_epoch: None,
528 comments: Vec::new(),
529 }
530 }
531}
532
533impl CDMObjectMetadata {
534 #[allow(clippy::too_many_arguments)]
536 pub fn new(
537 object: String,
538 object_designator: String,
539 catalog_name: String,
540 object_name: String,
541 international_designator: String,
542 ephemeris_name: String,
543 covariance_method: String,
544 maneuverable: String,
545 ref_frame: CCSDSRefFrame,
546 ) -> Self {
547 Self {
548 object,
549 object_designator,
550 catalog_name,
551 object_name,
552 international_designator,
553 object_type: None,
554 ops_status: None,
555 operator_contact_position: None,
556 operator_organization: None,
557 operator_phone: None,
558 operator_email: None,
559 ephemeris_name,
560 odm_msg_link: None,
561 adm_msg_link: None,
562 obs_before_next_message: None,
563 covariance_method,
564 covariance_source: None,
565 maneuverable,
566 orbit_center: None,
567 ref_frame,
568 alt_cov_type: None,
569 alt_cov_ref_frame: None,
570 gravity_model: None,
571 atmospheric_model: None,
572 n_body_perturbations: None,
573 solar_rad_pressure: None,
574 earth_tides: None,
575 intrack_thrust: None,
576 comments: Vec::new(),
577 }
578 }
579}
580
581impl CDMStateVector {
582 pub fn new(position: [f64; 3], velocity: [f64; 3]) -> Self {
589 Self {
590 position,
591 velocity,
592 comments: Vec::new(),
593 }
594 }
595}
596
597impl CDMRTNCovariance {
598 pub fn from_6x6(matrix: SMatrix<f64, 6, 6>) -> Self {
600 let mut full = SMatrix::<f64, 9, 9>::zeros();
601 for i in 0..6 {
602 for j in 0..6 {
603 full[(i, j)] = matrix[(i, j)];
604 }
605 }
606 Self {
607 matrix: full,
608 dimension: CDMCovarianceDimension::SixBySix,
609 comments: Vec::new(),
610 }
611 }
612
613 pub fn to_6x6(&self) -> SMatrix<f64, 6, 6> {
615 self.matrix.fixed_view::<6, 6>(0, 0).into()
616 }
617}
618
619impl CDMObject {
620 pub fn new(
622 metadata: CDMObjectMetadata,
623 state_vector: CDMStateVector,
624 rtn_covariance: CDMRTNCovariance,
625 ) -> Self {
626 Self {
627 metadata,
628 data: CDMObjectData {
629 od_parameters: None,
630 additional_parameters: None,
631 state_vector,
632 rtn_covariance,
633 xyz_covariance: None,
634 additional_covariance_metadata: None,
635 csig3eigvec3: None,
636 comments: Vec::new(),
637 },
638 }
639 }
640}
641
642impl CDM {
643 pub fn new(
654 originator: String,
655 message_id: String,
656 tca: Epoch,
657 miss_distance: f64,
658 object1: CDMObject,
659 object2: CDMObject,
660 ) -> Self {
661 Self {
662 header: CDMHeader {
663 format_version: 1.0,
664 classification: None,
665 creation_date: Epoch::now(),
666 originator,
667 message_for: None,
668 message_id,
669 comments: Vec::new(),
670 },
671 relative_metadata: CDMRelativeMetadata::new(tca, miss_distance),
672 object1,
673 object2,
674 user_defined: None,
675 }
676 }
677
678 #[allow(clippy::should_implement_trait)]
680 pub fn from_str(content: &str) -> Result<Self, BraheError> {
681 let format = crate::ccsds::common::detect_format(content);
682 match format {
683 CCSDSFormat::KVN => crate::ccsds::kvn::parse_cdm(content),
684 CCSDSFormat::XML => crate::ccsds::xml::parse_cdm_xml(content),
685 CCSDSFormat::JSON => crate::ccsds::json::parse_cdm_json(content),
686 }
687 }
688
689 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, BraheError> {
691 let content = std::fs::read_to_string(path.as_ref())
692 .map_err(|e| BraheError::IoError(format!("Failed to read CDM file: {}", e)))?;
693 Self::from_str(&content)
694 }
695
696 pub fn to_string(&self, format: CCSDSFormat) -> Result<String, BraheError> {
698 match format {
699 CCSDSFormat::KVN => crate::ccsds::kvn::write_cdm(self),
700 CCSDSFormat::XML => crate::ccsds::xml::write_cdm_xml(self),
701 CCSDSFormat::JSON => crate::ccsds::json::write_cdm_json(
702 self,
703 crate::ccsds::common::CCSDSJsonKeyCase::Lower,
704 ),
705 }
706 }
707
708 pub fn to_json_string(
710 &self,
711 key_case: crate::ccsds::common::CCSDSJsonKeyCase,
712 ) -> Result<String, BraheError> {
713 crate::ccsds::json::write_cdm_json(self, key_case)
714 }
715
716 pub fn to_file<P: AsRef<Path>>(&self, path: P, format: CCSDSFormat) -> Result<(), BraheError> {
718 let content = self.to_string(format)?;
719 std::fs::write(path.as_ref(), content)
720 .map_err(|e| BraheError::IoError(format!("Failed to write CDM file: {}", e)))
721 }
722
723 pub fn tca(&self) -> &Epoch {
729 &self.relative_metadata.tca
730 }
731
732 pub fn miss_distance(&self) -> f64 {
734 self.relative_metadata.miss_distance
735 }
736
737 pub fn collision_probability(&self) -> Option<f64> {
739 self.relative_metadata.collision_probability
740 }
741
742 pub fn object1_state(&self) -> SVector<f64, 6> {
744 let sv = &self.object1.data.state_vector;
745 SVector::<f64, 6>::new(
746 sv.position[0],
747 sv.position[1],
748 sv.position[2],
749 sv.velocity[0],
750 sv.velocity[1],
751 sv.velocity[2],
752 )
753 }
754
755 pub fn object2_state(&self) -> SVector<f64, 6> {
757 let sv = &self.object2.data.state_vector;
758 SVector::<f64, 6>::new(
759 sv.position[0],
760 sv.position[1],
761 sv.position[2],
762 sv.velocity[0],
763 sv.velocity[1],
764 sv.velocity[2],
765 )
766 }
767
768 pub fn relative_position_rtn(&self) -> Option<[f64; 3]> {
770 let rm = &self.relative_metadata;
771 match (
772 rm.relative_position_r,
773 rm.relative_position_t,
774 rm.relative_position_n,
775 ) {
776 (Some(r), Some(t), Some(n)) => Some([r, t, n]),
777 _ => None,
778 }
779 }
780
781 pub fn relative_velocity_rtn(&self) -> Option<[f64; 3]> {
783 let rm = &self.relative_metadata;
784 match (
785 rm.relative_velocity_r,
786 rm.relative_velocity_t,
787 rm.relative_velocity_n,
788 ) {
789 (Some(r), Some(t), Some(n)) => Some([r, t, n]),
790 _ => None,
791 }
792 }
793
794 pub fn object1_rtn_covariance_6x6(&self) -> SMatrix<f64, 6, 6> {
796 self.object1.data.rtn_covariance.to_6x6()
797 }
798
799 pub fn object2_rtn_covariance_6x6(&self) -> SMatrix<f64, 6, 6> {
801 self.object2.data.rtn_covariance.to_6x6()
802 }
803}
804
805#[cfg(test)]
810#[cfg_attr(coverage_nightly, coverage(off))]
811mod tests {
812 use super::*;
813
814 #[test]
815 fn test_cdm_new() {
816 let sv1 = CDMStateVector::new([7000e3, 0.0, 0.0], [0.0, 7500.0, 0.0]);
817 let sv2 = CDMStateVector::new([7001e3, 0.0, 0.0], [0.0, -7500.0, 0.0]);
818 let cov1 = CDMRTNCovariance::from_6x6(SMatrix::<f64, 6, 6>::identity());
819 let cov2 = CDMRTNCovariance::from_6x6(SMatrix::<f64, 6, 6>::identity());
820
821 let meta1 = CDMObjectMetadata::new(
822 "OBJECT1".to_string(),
823 "12345".to_string(),
824 "SATCAT".to_string(),
825 "SAT A".to_string(),
826 "2020-001A".to_string(),
827 "NONE".to_string(),
828 "CALCULATED".to_string(),
829 "YES".to_string(),
830 CCSDSRefFrame::EME2000,
831 );
832 let meta2 = CDMObjectMetadata::new(
833 "OBJECT2".to_string(),
834 "67890".to_string(),
835 "SATCAT".to_string(),
836 "SAT B".to_string(),
837 "2021-002B".to_string(),
838 "NONE".to_string(),
839 "CALCULATED".to_string(),
840 "NO".to_string(),
841 CCSDSRefFrame::EME2000,
842 );
843
844 let obj1 = CDMObject::new(meta1, sv1, cov1);
845 let obj2 = CDMObject::new(meta2, sv2, cov2);
846
847 let tca = Epoch::from_datetime(2024, 1, 15, 12, 0, 0.0, 0.0, crate::time::TimeSystem::UTC);
848 let cdm = CDM::new(
849 "TEST_ORG".to_string(),
850 "MSG001".to_string(),
851 tca,
852 715.0,
853 obj1,
854 obj2,
855 );
856
857 assert_eq!(cdm.header.originator, "TEST_ORG");
858 assert_eq!(cdm.header.message_id, "MSG001");
859 assert_eq!(cdm.miss_distance(), 715.0);
860 assert_eq!(cdm.object1.metadata.object_name, "SAT A");
861 assert_eq!(cdm.object2.metadata.object_name, "SAT B");
862
863 let s1 = cdm.object1_state();
864 assert_eq!(s1[0], 7000e3);
865 assert_eq!(s1[4], 7500.0);
866
867 assert!(cdm.collision_probability().is_none());
868 assert!(cdm.relative_position_rtn().is_none());
869 }
870
871 #[test]
872 fn test_cdm_kvn_parse_example1() {
873 let cdm = CDM::from_file("test_assets/ccsds/cdm/CDMExample1.txt").unwrap();
874
875 assert_eq!(cdm.header.format_version, 1.0);
877 assert_eq!(cdm.header.originator, "JSPOC");
878 assert_eq!(cdm.header.message_id, "201113719185");
879 assert!(cdm.header.message_for.is_none());
880
881 assert_eq!(cdm.miss_distance(), 715.0);
883 assert!(cdm.collision_probability().is_none());
884 assert!(cdm.relative_position_rtn().is_none());
885
886 assert_eq!(cdm.object1.metadata.object, "OBJECT1");
888 assert_eq!(cdm.object1.metadata.object_designator, "12345");
889 assert_eq!(cdm.object1.metadata.object_name, "SATELLITE A");
890 assert_eq!(cdm.object1.metadata.ephemeris_name, "EPHEMERIS SATELLITE A");
891 assert_eq!(cdm.object1.metadata.maneuverable, "YES");
892 assert_eq!(cdm.object1.metadata.ref_frame, CCSDSRefFrame::EME2000);
893
894 let s1 = cdm.object1_state();
896 assert!((s1[0] - 2570097.065).abs() < 0.01);
897 assert!((s1[1] - 2244654.904).abs() < 0.01);
898 assert!((s1[2] - 6281497.978).abs() < 0.01);
899 assert!((s1[3] - 4418.769571).abs() < 0.0001);
900 assert!((s1[4] - 4833.547743).abs() < 0.0001);
901 assert!((s1[5] - (-3526.774282)).abs() < 0.0001);
902
903 let cov1 = cdm.object1_rtn_covariance_6x6();
905 assert!((cov1[(0, 0)] - 4.142e+01).abs() < 1e-10);
906 assert!((cov1[(1, 0)] - (-8.579e+00)).abs() < 1e-10);
907 assert!((cov1[(1, 1)] - 2.533e+03).abs() < 1e-10);
908 assert_eq!(
909 cdm.object1.data.rtn_covariance.dimension,
910 CDMCovarianceDimension::SixBySix
911 );
912
913 assert_eq!(cdm.object2.metadata.object, "OBJECT2");
915 assert_eq!(cdm.object2.metadata.object_designator, "30337");
916 assert_eq!(cdm.object2.metadata.object_name, "FENGYUN 1C DEB");
917 assert_eq!(cdm.object2.metadata.maneuverable, "NO");
918
919 let s2 = cdm.object2_state();
920 assert!((s2[0] - 2569540.800).abs() < 0.01);
921 assert!((s2[3] - (-2888.6125)).abs() < 0.001);
922 }
923
924 #[test]
925 fn test_cdm_kvn_parse_example2_extended_cov() {
926 let cdm = CDM::from_file("test_assets/ccsds/cdm/CDMExample2.txt").unwrap();
927
928 assert!(cdm.header.message_for.is_some());
930 assert_eq!(cdm.header.message_for.as_deref(), Some("SATELLITE A"));
931 assert_eq!(cdm.relative_metadata.relative_speed, Some(14762.0));
932 assert!((cdm.collision_probability().unwrap() - 4.835e-05).abs() < 1e-10);
933 assert_eq!(
934 cdm.relative_metadata
935 .collision_probability_method
936 .as_deref(),
937 Some("FOSTER-1992")
938 );
939
940 let rp = cdm.relative_position_rtn().unwrap();
942 assert!((rp[0] - 27.4).abs() < 0.01);
943 assert!((rp[1] - (-70.2)).abs() < 0.01);
944
945 assert_eq!(cdm.object1.metadata.object_type.as_deref(), Some("PAYLOAD"));
947 assert_eq!(
948 cdm.object1.metadata.operator_email.as_deref(),
949 Some("JOHN.DOE@SOMEWHERE.NET")
950 );
951 assert_eq!(
952 cdm.object1.metadata.gravity_model.as_deref(),
953 Some("EGM-96: 36D 36O")
954 );
955
956 let od = cdm.object1.data.od_parameters.as_ref().unwrap();
958 assert_eq!(od.obs_available, Some(592));
959 assert_eq!(od.obs_used, Some(579));
960 assert!((od.recommended_od_span.unwrap() - 7.88).abs() < 0.01);
961 assert!((od.residuals_accepted.unwrap() - 97.8).abs() < 0.01);
962
963 let ap = cdm.object1.data.additional_parameters.as_ref().unwrap();
965 assert!((ap.area_pc.unwrap() - 5.2).abs() < 0.01);
966 assert!((ap.mass.unwrap() - 251.6).abs() < 0.01);
967 assert!((ap.sedr.unwrap() - 4.54570e-05).abs() < 1e-10);
968
969 assert_eq!(
971 cdm.object1.data.rtn_covariance.dimension,
972 CDMCovarianceDimension::EightByEight
973 );
974 let cov = &cdm.object1.data.rtn_covariance.matrix;
975 assert!((cov[(6, 0)] - (-1.862e+00)).abs() < 1e-10);
977 assert!((cov[(7, 7)] - 1.593e-02).abs() < 1e-10);
979 }
980
981 #[test]
982 fn test_cdm_kvn_parse_issue940_v2() {
983 let cdm = CDM::from_file("test_assets/ccsds/cdm/CDMExample_issue_940.txt").unwrap();
984
985 assert_eq!(cdm.header.format_version, 2.0);
986 assert!(cdm.header.classification.is_some());
987 assert_eq!(
988 cdm.relative_metadata.conjunction_id.as_deref(),
989 Some("20220708T10hz SATELLITEA SATELLITEB")
990 );
991 assert_eq!(cdm.relative_metadata.approach_angle, Some(180.0));
992 assert_eq!(cdm.relative_metadata.screen_type.as_deref(), Some("SHAPE"));
993 assert_eq!(cdm.relative_metadata.screen_pc_threshold, Some(1.0e-03));
994 assert!(cdm.relative_metadata.collision_percentile.is_some());
995 assert_eq!(
996 cdm.relative_metadata.collision_percentile.as_ref().unwrap(),
997 &[50, 51, 52]
998 );
999 assert!(cdm.relative_metadata.sefi_collision_probability.is_some());
1000 assert!(cdm.relative_metadata.previous_message_id.is_some());
1001
1002 assert_eq!(
1004 cdm.object1.metadata.odm_msg_link.as_deref(),
1005 Some("ODM_MSG_35132.txt")
1006 );
1007 assert_eq!(
1008 cdm.object1.metadata.covariance_source.as_deref(),
1009 Some("HAC Covariance")
1010 );
1011 assert_eq!(cdm.object1.metadata.alt_cov_type.as_deref(), Some("XYZ"));
1012
1013 let ap = cdm.object1.data.additional_parameters.as_ref().unwrap();
1015 assert!((ap.oeb_q1.unwrap() - 0.03123).abs() < 1e-10);
1016 assert!((ap.hbr.unwrap() - 2.5).abs() < 0.01);
1017 assert!((ap.apoapsis_altitude.unwrap() - 800e3).abs() < 0.1); assert!((ap.inclination.unwrap() - 89.0).abs() < 0.01);
1019
1020 assert!(cdm.object1.data.xyz_covariance.is_some());
1022 let xyz = cdm.object1.data.xyz_covariance.as_ref().unwrap();
1023 assert_eq!(xyz.dimension, CDMCovarianceDimension::NineByNine);
1024 assert!((xyz.matrix[(0, 0)] - 0.1).abs() < 1e-10);
1025
1026 let acm = cdm
1028 .object1
1029 .data
1030 .additional_covariance_metadata
1031 .as_ref()
1032 .unwrap();
1033 assert!((acm.density_forecast_uncertainty.unwrap() - 2.5).abs() < 0.01);
1034 assert!((acm.cscale_factor.unwrap() - 1.0).abs() < 0.01);
1035 assert!(acm.dcp_sensitivity_vector_position.is_some());
1036
1037 assert!(cdm.object2.data.csig3eigvec3.is_some());
1039
1040 assert!(cdm.user_defined.is_some());
1042 let ud = cdm.user_defined.as_ref().unwrap();
1043 assert!(ud.parameters.contains_key("OBJ1_TIME_LASTOB_START"));
1044 }
1045
1046 #[test]
1047 fn test_cdm_kvn_parse_issue942_maneuverable_na() {
1048 let cdm = CDM::from_file("test_assets/ccsds/cdm/CDMExample_issue942.txt").unwrap();
1049 assert_eq!(cdm.object1.metadata.maneuverable, "N/A");
1050 }
1051
1052 #[test]
1053 fn test_cdm_kvn_parse_alfano01() {
1054 let cdm = CDM::from_file("test_assets/ccsds/cdm/AlfanoTestCase01.cdm").unwrap();
1055 assert!(cdm.miss_distance() > 0.0);
1056 let s1 = cdm.object1_state();
1058 assert!(s1[0].abs() > 1.0); assert_eq!(
1061 cdm.object1.data.rtn_covariance.dimension,
1062 CDMCovarianceDimension::EightByEight
1063 );
1064 }
1065
1066 #[test]
1067 fn test_cdm_kvn_parse_real_world() {
1068 let cdm = CDM::from_file("test_assets/ccsds/cdm/ION_SCV8_vs_STARLINK_1233.txt").unwrap();
1069 assert!(cdm.miss_distance() > 0.0);
1070 assert!(cdm.object1.data.od_parameters.is_some());
1071 assert!(cdm.object2.data.od_parameters.is_some());
1072 }
1073
1074 #[test]
1075 fn test_cdm_kvn_missing_tca() {
1076 let result = CDM::from_file("test_assets/ccsds/cdm/CDM-missing-TCA.txt");
1077 assert!(result.is_err());
1078 let err_msg = format!("{}", result.unwrap_err());
1079 assert!(
1080 err_msg.contains("TCA"),
1081 "Error should mention TCA: {}",
1082 err_msg
1083 );
1084 }
1085
1086 #[test]
1087 fn test_cdm_kvn_missing_obj2_state() {
1088 let result = CDM::from_file("test_assets/ccsds/cdm/CDM-missing-object2-state-vector.txt");
1089 assert!(result.is_err());
1090 }
1091
1092 #[test]
1093 fn test_cdm_kvn_round_trip_example1() {
1094 let cdm1 = CDM::from_file("test_assets/ccsds/cdm/CDMExample1.txt").unwrap();
1095 let kvn = cdm1.to_string(CCSDSFormat::KVN).unwrap();
1096 let cdm2 = CDM::from_str(&kvn).unwrap();
1097
1098 assert_eq!(cdm1.header.format_version, cdm2.header.format_version);
1100 assert_eq!(cdm1.header.originator, cdm2.header.originator);
1101 assert_eq!(cdm1.header.message_id, cdm2.header.message_id);
1102 assert!((cdm1.miss_distance() - cdm2.miss_distance()).abs() < 1e-6);
1103
1104 for i in 0..6 {
1106 assert!(
1107 (cdm1.object1_state()[i] - cdm2.object1_state()[i]).abs() < 0.01,
1108 "Object1 state[{}] mismatch: {} vs {}",
1109 i,
1110 cdm1.object1_state()[i],
1111 cdm2.object1_state()[i]
1112 );
1113 assert!(
1114 (cdm1.object2_state()[i] - cdm2.object2_state()[i]).abs() < 0.01,
1115 "Object2 state[{}] mismatch: {} vs {}",
1116 i,
1117 cdm1.object2_state()[i],
1118 cdm2.object2_state()[i]
1119 );
1120 }
1121
1122 let c1 = cdm1.object1_rtn_covariance_6x6();
1124 let c2 = cdm2.object1_rtn_covariance_6x6();
1125 for i in 0..6 {
1126 for j in 0..6 {
1127 let rel = if c1[(i, j)].abs() > 1e-20 {
1128 ((c1[(i, j)] - c2[(i, j)]) / c1[(i, j)]).abs()
1129 } else {
1130 (c1[(i, j)] - c2[(i, j)]).abs()
1131 };
1132 assert!(
1133 rel < 1e-4,
1134 "Cov({},{}) mismatch: {} vs {}",
1135 i,
1136 j,
1137 c1[(i, j)],
1138 c2[(i, j)]
1139 );
1140 }
1141 }
1142 }
1143
1144 #[test]
1145 fn test_cdm_xml_parse_example1() {
1146 let cdm = CDM::from_file("test_assets/ccsds/cdm/CDMExample1.xml").unwrap();
1147
1148 assert_eq!(cdm.header.format_version, 1.0);
1149 assert_eq!(cdm.header.originator, "JSPOC");
1150 assert_eq!(cdm.header.message_for.as_deref(), Some("SATELLITE A"));
1151 assert_eq!(cdm.miss_distance(), 715.0);
1152
1153 let rp = cdm.relative_position_rtn().unwrap();
1155 assert!((rp[0] - 27.4).abs() < 0.01);
1156
1157 assert_eq!(cdm.object1.metadata.object_name, "SATELLITE A");
1159 assert_eq!(cdm.object1.metadata.ref_frame, CCSDSRefFrame::EME2000);
1160
1161 let s1 = cdm.object1_state();
1162 assert!((s1[0] - 2570097.065).abs() < 0.01);
1163
1164 let cov1 = cdm.object1_rtn_covariance_6x6();
1166 assert!((cov1[(0, 0)] - 4.142e+01).abs() < 1e-10);
1167
1168 assert_eq!(cdm.object2.metadata.object_name, "FENGYUN 1C DEB");
1170 }
1171
1172 #[test]
1173 fn test_cdm_xml_round_trip() {
1174 let cdm1 = CDM::from_file("test_assets/ccsds/cdm/CDMExample1.xml").unwrap();
1175 let xml = cdm1.to_string(CCSDSFormat::XML).unwrap();
1176 let cdm2 = CDM::from_str(&xml).unwrap();
1177
1178 assert_eq!(cdm1.header.originator, cdm2.header.originator);
1179 assert!((cdm1.miss_distance() - cdm2.miss_distance()).abs() < 1e-6);
1180
1181 for i in 0..6 {
1182 assert!((cdm1.object1_state()[i] - cdm2.object1_state()[i]).abs() < 0.01);
1183 }
1184 }
1185
1186 #[test]
1187 fn test_cdm_json_round_trip() {
1188 let cdm1 = CDM::from_file("test_assets/ccsds/cdm/CDMExample1.txt").unwrap();
1189 let json = cdm1.to_string(CCSDSFormat::JSON).unwrap();
1190 let cdm2 = CDM::from_str(&json).unwrap();
1191
1192 assert_eq!(cdm1.header.originator, cdm2.header.originator);
1193 assert!((cdm1.miss_distance() - cdm2.miss_distance()).abs() < 1e-6);
1194 for i in 0..6 {
1195 assert!(
1196 (cdm1.object1_state()[i] - cdm2.object1_state()[i]).abs() < 0.01,
1197 "Object1 state[{}]: {} vs {}",
1198 i,
1199 cdm1.object1_state()[i],
1200 cdm2.object1_state()[i]
1201 );
1202 }
1203 }
1204
1205 #[test]
1206 fn test_cdm_kvn_to_xml_cross_format() {
1207 let cdm_kvn = CDM::from_file("test_assets/ccsds/cdm/CDMExample1.txt").unwrap();
1209 let xml = cdm_kvn.to_string(CCSDSFormat::XML).unwrap();
1211 let cdm_xml = CDM::from_str(&xml).unwrap();
1213
1214 assert_eq!(cdm_kvn.header.originator, cdm_xml.header.originator);
1216 assert!((cdm_kvn.miss_distance() - cdm_xml.miss_distance()).abs() < 1e-6);
1217 for i in 0..6 {
1218 assert!((cdm_kvn.object1_state()[i] - cdm_xml.object1_state()[i]).abs() < 0.01);
1219 assert!((cdm_kvn.object2_state()[i] - cdm_xml.object2_state()[i]).abs() < 0.01);
1220 }
1221 }
1222
1223 #[test]
1224 fn test_cdm_kvn_round_trip_example2() {
1225 let cdm1 = CDM::from_file("test_assets/ccsds/cdm/CDMExample2.txt").unwrap();
1226 let kvn = cdm1.to_string(CCSDSFormat::KVN).unwrap();
1227 let cdm2 = CDM::from_str(&kvn).unwrap();
1228
1229 assert_eq!(cdm1.header.message_for, cdm2.header.message_for);
1230 assert!(
1231 (cdm1.collision_probability().unwrap() - cdm2.collision_probability().unwrap()).abs()
1232 < 1e-10
1233 );
1234 assert_eq!(
1235 cdm1.relative_metadata.collision_probability_method,
1236 cdm2.relative_metadata.collision_probability_method
1237 );
1238
1239 assert_eq!(
1241 cdm1.object1.data.rtn_covariance.dimension,
1242 cdm2.object1.data.rtn_covariance.dimension
1243 );
1244
1245 let od1 = cdm1.object1.data.od_parameters.as_ref().unwrap();
1247 let od2 = cdm2.object1.data.od_parameters.as_ref().unwrap();
1248 assert_eq!(od1.obs_available, od2.obs_available);
1249 assert_eq!(od1.obs_used, od2.obs_used);
1250 }
1251
1252 #[test]
1253 fn test_cdm_xml_round_trip_issue940_all_fields() {
1254 let cdm1 = CDM::from_file("test_assets/ccsds/cdm/CDMExample_issue_940.txt").unwrap();
1256
1257 let xml = cdm1.to_string(CCSDSFormat::XML).unwrap();
1259 let cdm2 = CDM::from_str(&xml).unwrap();
1260
1261 assert_eq!(cdm1.header.originator, cdm2.header.originator);
1263 assert_eq!(cdm1.header.message_id, cdm2.header.message_id);
1264 assert_eq!(cdm1.header.classification, cdm2.header.classification);
1265
1266 assert!((cdm1.miss_distance() - cdm2.miss_distance()).abs() < 1e-6);
1268 assert_eq!(
1269 cdm1.relative_metadata.conjunction_id,
1270 cdm2.relative_metadata.conjunction_id
1271 );
1272 assert_eq!(
1273 cdm1.relative_metadata.approach_angle,
1274 cdm2.relative_metadata.approach_angle
1275 );
1276 assert_eq!(
1277 cdm1.relative_metadata.screen_type,
1278 cdm2.relative_metadata.screen_type
1279 );
1280 assert_eq!(
1281 cdm1.relative_metadata.screen_pc_threshold,
1282 cdm2.relative_metadata.screen_pc_threshold
1283 );
1284 assert_eq!(
1285 cdm1.relative_metadata.collision_percentile,
1286 cdm2.relative_metadata.collision_percentile
1287 );
1288 assert_eq!(
1289 cdm1.relative_metadata.collision_probability,
1290 cdm2.relative_metadata.collision_probability
1291 );
1292 assert_eq!(
1293 cdm1.relative_metadata.collision_probability_method,
1294 cdm2.relative_metadata.collision_probability_method
1295 );
1296 assert_eq!(
1297 cdm1.relative_metadata.collision_max_probability,
1298 cdm2.relative_metadata.collision_max_probability
1299 );
1300 assert_eq!(
1301 cdm1.relative_metadata.collision_max_pc_method,
1302 cdm2.relative_metadata.collision_max_pc_method
1303 );
1304 assert_eq!(
1305 cdm1.relative_metadata.previous_message_id,
1306 cdm2.relative_metadata.previous_message_id
1307 );
1308
1309 for i in 0..6 {
1311 assert!((cdm1.object1_state()[i] - cdm2.object1_state()[i]).abs() < 0.01);
1312 assert!((cdm1.object2_state()[i] - cdm2.object2_state()[i]).abs() < 0.01);
1313 }
1314
1315 assert_eq!(
1317 cdm1.object1.metadata.odm_msg_link,
1318 cdm2.object1.metadata.odm_msg_link
1319 );
1320 assert_eq!(
1321 cdm1.object1.metadata.covariance_source,
1322 cdm2.object1.metadata.covariance_source
1323 );
1324 assert_eq!(
1325 cdm1.object1.metadata.alt_cov_type,
1326 cdm2.object1.metadata.alt_cov_type
1327 );
1328
1329 let od1 = cdm1.object1.data.od_parameters.as_ref().unwrap();
1331 let od2 = cdm2.object1.data.od_parameters.as_ref().unwrap();
1332 assert_eq!(od1.obs_available, od2.obs_available);
1333 assert_eq!(od1.obs_used, od2.obs_used);
1334
1335 let ap1 = cdm1.object1.data.additional_parameters.as_ref().unwrap();
1337 let ap2 = cdm2.object1.data.additional_parameters.as_ref().unwrap();
1338 assert!((ap1.hbr.unwrap() - ap2.hbr.unwrap()).abs() < 0.01);
1339 assert!((ap1.oeb_q1.unwrap() - ap2.oeb_q1.unwrap()).abs() < 1e-10);
1340
1341 assert!(cdm2.object1.data.xyz_covariance.is_some());
1343 let xyz1 = cdm1.object1.data.xyz_covariance.as_ref().unwrap();
1344 let xyz2 = cdm2.object1.data.xyz_covariance.as_ref().unwrap();
1345 assert_eq!(xyz1.dimension, xyz2.dimension);
1346 assert!((xyz1.matrix[(0, 0)] - xyz2.matrix[(0, 0)]).abs() < 1e-10);
1347
1348 let acm1 = cdm1
1350 .object1
1351 .data
1352 .additional_covariance_metadata
1353 .as_ref()
1354 .unwrap();
1355 let acm2 = cdm2
1356 .object1
1357 .data
1358 .additional_covariance_metadata
1359 .as_ref()
1360 .unwrap();
1361 assert_eq!(
1362 acm1.density_forecast_uncertainty,
1363 acm2.density_forecast_uncertainty
1364 );
1365 assert_eq!(acm1.cscale_factor, acm2.cscale_factor);
1366
1367 assert!(cdm2.user_defined.is_some());
1369 }
1370
1371 #[test]
1372 fn test_cdm_rtn_covariance_6x6() {
1373 let mut m6 = SMatrix::<f64, 6, 6>::zeros();
1374 m6[(0, 0)] = 41.42;
1375 m6[(1, 0)] = -8.579;
1376 m6[(0, 1)] = -8.579;
1377 m6[(1, 1)] = 2533.0;
1378 let cov = CDMRTNCovariance::from_6x6(m6);
1379 assert_eq!(cov.dimension, CDMCovarianceDimension::SixBySix);
1380 let extracted = cov.to_6x6();
1381 assert_eq!(extracted[(0, 0)], 41.42);
1382 assert_eq!(extracted[(1, 0)], -8.579);
1383 assert_eq!(extracted[(1, 1)], 2533.0);
1384 assert_eq!(cov.matrix[(6, 0)], 0.0);
1386 assert_eq!(cov.matrix[(8, 8)], 0.0);
1387 }
1388
1389 fn assert_cdm_fields_match(cdm1: &CDM, cdm2: &CDM) {
1391 assert_eq!(cdm1.header.format_version, cdm2.header.format_version);
1393 assert_eq!(cdm1.header.originator, cdm2.header.originator);
1394 assert_eq!(cdm1.header.message_id, cdm2.header.message_id);
1395 assert_eq!(cdm1.header.classification, cdm2.header.classification);
1396 assert_eq!(cdm1.header.message_for, cdm2.header.message_for);
1397
1398 assert!((cdm1.miss_distance() - cdm2.miss_distance()).abs() < 1e-6);
1400 assert_eq!(
1401 cdm1.relative_metadata.conjunction_id,
1402 cdm2.relative_metadata.conjunction_id
1403 );
1404 assert_eq!(
1405 cdm1.relative_metadata.relative_speed.is_some(),
1406 cdm2.relative_metadata.relative_speed.is_some()
1407 );
1408 if let (Some(r1), Some(r2)) = (
1409 cdm1.relative_metadata.relative_speed,
1410 cdm2.relative_metadata.relative_speed,
1411 ) {
1412 assert!((r1 - r2).abs() < 0.01);
1413 }
1414 assert_eq!(
1415 cdm1.relative_metadata.collision_probability,
1416 cdm2.relative_metadata.collision_probability
1417 );
1418 assert_eq!(
1419 cdm1.relative_metadata.collision_probability_method,
1420 cdm2.relative_metadata.collision_probability_method
1421 );
1422 assert_eq!(
1423 cdm1.relative_metadata.collision_percentile,
1424 cdm2.relative_metadata.collision_percentile
1425 );
1426 assert_eq!(
1427 cdm1.relative_metadata.collision_max_probability,
1428 cdm2.relative_metadata.collision_max_probability
1429 );
1430 assert_eq!(
1431 cdm1.relative_metadata.screen_type,
1432 cdm2.relative_metadata.screen_type
1433 );
1434
1435 for i in 0..6 {
1437 assert!(
1438 (cdm1.object1_state()[i] - cdm2.object1_state()[i]).abs() < 0.01,
1439 "obj1 state[{}] mismatch",
1440 i
1441 );
1442 assert!(
1443 (cdm1.object2_state()[i] - cdm2.object2_state()[i]).abs() < 0.01,
1444 "obj2 state[{}] mismatch",
1445 i
1446 );
1447 }
1448
1449 assert_eq!(
1451 cdm1.object1.metadata.object_name,
1452 cdm2.object1.metadata.object_name
1453 );
1454 assert_eq!(
1455 cdm1.object1.metadata.object_designator,
1456 cdm2.object1.metadata.object_designator
1457 );
1458 assert_eq!(
1459 cdm1.object1.metadata.maneuverable,
1460 cdm2.object1.metadata.maneuverable
1461 );
1462 assert_eq!(
1463 cdm1.object1.metadata.ref_frame,
1464 cdm2.object1.metadata.ref_frame
1465 );
1466
1467 assert_eq!(
1469 cdm1.object1.data.rtn_covariance.dimension,
1470 cdm2.object1.data.rtn_covariance.dimension
1471 );
1472 let c1 = cdm1.object1_rtn_covariance_6x6();
1473 let c2 = cdm2.object1_rtn_covariance_6x6();
1474 for i in 0..6 {
1475 for j in 0..6 {
1476 let rel = if c1[(i, j)].abs() > 1e-20 {
1477 ((c1[(i, j)] - c2[(i, j)]) / c1[(i, j)]).abs()
1478 } else {
1479 (c1[(i, j)] - c2[(i, j)]).abs()
1480 };
1481 assert!(rel < 1e-4, "obj1 cov({},{}) mismatch", i, j);
1482 }
1483 }
1484
1485 assert_eq!(
1487 cdm1.object1.data.od_parameters.is_some(),
1488 cdm2.object1.data.od_parameters.is_some()
1489 );
1490 if let (Some(od1), Some(od2)) = (
1491 &cdm1.object1.data.od_parameters,
1492 &cdm2.object1.data.od_parameters,
1493 ) {
1494 assert_eq!(od1.obs_available, od2.obs_available);
1495 assert_eq!(od1.obs_used, od2.obs_used);
1496 assert_eq!(od1.tracks_available, od2.tracks_available);
1497 assert_eq!(od1.tracks_used, od2.tracks_used);
1498 }
1499
1500 assert_eq!(
1502 cdm1.object1.data.additional_parameters.is_some(),
1503 cdm2.object1.data.additional_parameters.is_some()
1504 );
1505 if let (Some(ap1), Some(ap2)) = (
1506 &cdm1.object1.data.additional_parameters,
1507 &cdm2.object1.data.additional_parameters,
1508 ) {
1509 assert_eq!(ap1.area_pc.is_some(), ap2.area_pc.is_some());
1510 assert_eq!(ap1.mass.is_some(), ap2.mass.is_some());
1511 assert_eq!(ap1.hbr.is_some(), ap2.hbr.is_some());
1512 if let (Some(h1), Some(h2)) = (ap1.hbr, ap2.hbr) {
1513 assert!((h1 - h2).abs() < 0.01);
1514 }
1515 }
1516
1517 assert_eq!(cdm1.user_defined.is_some(), cdm2.user_defined.is_some());
1519 if let (Some(ud1), Some(ud2)) = (&cdm1.user_defined, &cdm2.user_defined) {
1520 assert_eq!(ud1.parameters.len(), ud2.parameters.len());
1521 for (k, v) in &ud1.parameters {
1522 assert_eq!(
1523 ud2.parameters.get(k),
1524 Some(v),
1525 "user_defined key {} mismatch",
1526 k
1527 );
1528 }
1529 }
1530 }
1531
1532 #[test]
1533 fn test_cdm_kvn_full_round_trip() {
1534 let cdm1 = CDM::from_file("test_assets/ccsds/cdm/CDMExample_issue_940.txt").unwrap();
1536 let kvn = cdm1.to_string(CCSDSFormat::KVN).unwrap();
1537 let cdm2 = CDM::from_str(&kvn).unwrap();
1538 assert_cdm_fields_match(&cdm1, &cdm2);
1539 }
1540
1541 #[test]
1542 fn test_cdm_xml_full_round_trip() {
1543 let cdm1 = CDM::from_file("test_assets/ccsds/cdm/CDMExample_issue_940.txt").unwrap();
1544 let xml = cdm1.to_string(CCSDSFormat::XML).unwrap();
1545 let cdm2 = CDM::from_str(&xml).unwrap();
1546 assert_cdm_fields_match(&cdm1, &cdm2);
1547 }
1548
1549 #[test]
1550 fn test_cdm_json_full_round_trip() {
1551 let cdm1 = CDM::from_file("test_assets/ccsds/cdm/CDMExample2.txt").unwrap();
1554 let json = cdm1.to_string(CCSDSFormat::JSON).unwrap();
1555 let cdm2 = CDM::from_str(&json).unwrap();
1556
1557 assert_eq!(cdm1.header.originator, cdm2.header.originator);
1559 assert_eq!(cdm1.header.message_id, cdm2.header.message_id);
1560 assert_eq!(cdm1.header.message_for, cdm2.header.message_for);
1561
1562 assert!((cdm1.miss_distance() - cdm2.miss_distance()).abs() < 1e-6);
1564 assert_eq!(
1565 cdm1.relative_metadata.collision_probability,
1566 cdm2.relative_metadata.collision_probability
1567 );
1568 assert_eq!(
1569 cdm1.relative_metadata.collision_probability_method,
1570 cdm2.relative_metadata.collision_probability_method
1571 );
1572
1573 for i in 0..6 {
1575 assert!(
1576 (cdm1.object1_state()[i] - cdm2.object1_state()[i]).abs() < 0.01,
1577 "obj1 state[{}] mismatch",
1578 i
1579 );
1580 assert!(
1581 (cdm1.object2_state()[i] - cdm2.object2_state()[i]).abs() < 0.01,
1582 "obj2 state[{}] mismatch",
1583 i
1584 );
1585 }
1586
1587 let c1 = cdm1.object1_rtn_covariance_6x6();
1589 let c2 = cdm2.object1_rtn_covariance_6x6();
1590 for i in 0..6 {
1591 for j in 0..6 {
1592 let rel = if c1[(i, j)].abs() > 1e-20 {
1593 ((c1[(i, j)] - c2[(i, j)]) / c1[(i, j)]).abs()
1594 } else {
1595 (c1[(i, j)] - c2[(i, j)]).abs()
1596 };
1597 assert!(rel < 1e-4, "obj1 cov({},{}) mismatch", i, j);
1598 }
1599 }
1600 }
1601}