Skip to main content

nyx_space/od/groundpnt/
sensitivity.rs

1/*
2    Nyx, blazing fast astrodynamics
3    Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU Affero General Public License as published
7    by the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU Affero General Public License for more details.
14
15    You should have received a copy of the GNU Affero General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19use crate::linalg::DefaultAllocator;
20use crate::linalg::allocator::Allocator;
21use crate::od::groundpnt::GroundAsset;
22use crate::od::interlink::InterlinkTxSpacecraft;
23use crate::od::msr::{Measurement, MeasurementType, sensitivity::ScalarSensitivity};
24use crate::od::prelude::sensitivity::{ScalarSensitivityT, TrackerSensitivity};
25use crate::od::{ODAlmanacSnafu, ODError};
26use crate::{Spacecraft, State};
27use anise::errors::AlmanacError;
28use anise::prelude::Almanac;
29use indexmap::IndexSet;
30use nalgebra::{DimName, OMatrix, U1};
31use snafu::ResultExt;
32use std::marker::PhantomData;
33
34impl TrackerSensitivity<GroundAsset, GroundAsset> for InterlinkTxSpacecraft
35where
36    DefaultAllocator: Allocator<<Spacecraft as State>::Size>
37        + Allocator<<Spacecraft as State>::VecLength>
38        + Allocator<<Spacecraft as State>::Size, <Spacecraft as State>::Size>,
39{
40    fn h_tilde<M: DimName>(
41        &self,
42        msr: &Measurement,
43        msr_types: &IndexSet<MeasurementType>,
44        rx: &GroundAsset,
45        almanac: &Almanac,
46    ) -> Result<OMatrix<f64, M, <GroundAsset as State>::Size>, ODError>
47    where
48        DefaultAllocator: Allocator<M> + Allocator<M, <GroundAsset as State>::Size>,
49    {
50        // Rebuild each row of the scalar sensitivities.
51        let mut mat = OMatrix::<f64, M, <GroundAsset as State>::Size>::identity();
52        for (ith_row, msr_type) in msr_types.iter().enumerate() {
53            if !msr.data.contains_key(msr_type) {
54                // Skip computation, this row is zero anyway.
55                continue;
56            }
57            let scalar_h =
58                <ScalarSensitivity<GroundAsset, GroundAsset, InterlinkTxSpacecraft> as ScalarSensitivityT<
59                    GroundAsset,
60                    GroundAsset,
61                    InterlinkTxSpacecraft,
62                >>::new(*msr_type, msr, rx, self, almanac)?;
63
64            mat.set_row(ith_row, &scalar_h.sensitivity_row);
65        }
66        Ok(mat)
67    }
68}
69
70impl ScalarSensitivityT<GroundAsset, GroundAsset, InterlinkTxSpacecraft>
71    for ScalarSensitivity<GroundAsset, GroundAsset, InterlinkTxSpacecraft>
72{
73    /// First, we ensure that the transmitter vehicle is expressed in the same frame as the
74    /// ground asset. Then we compute the AER as seen from the ground asset.
75    fn new(
76        msr_type: MeasurementType,
77        msr: &Measurement,
78        rx: &GroundAsset,
79        tx: &InterlinkTxSpacecraft,
80        almanac: &Almanac,
81    ) -> Result<Self, ODError> {
82        let receiver = rx.orbit();
83        let loc = rx.to_location();
84
85        // Compute the device location in the receiver frame because we compute the sensitivity in that frame.
86        // This frame is required because the scalar measurements are frame independent, but the sensitivity
87        // must be in the estimation frame.
88
89        let transmitter = tx
90            .traj
91            .at(receiver.epoch)
92            .map_err(|source| ODError::ODTrajError {
93                source,
94                details: "computing sensitivity ground asset / interlink".into(),
95            })?
96            .orbit;
97
98        let jac = rx
99            .geodetic_to_cartesian_jacobian()
100            .map_err(|e| ODError::ODAlmanac {
101                source: Box::new(AlmanacError::AlmanacPhysics {
102                    action: "computing Jacobian for geodetics",
103                    source: Box::new(e),
104                }),
105                action: "computing Jacobian for geodetics",
106            })?;
107
108        let delta_r = receiver.radius_km - transmitter.radius_km;
109        let delta_v = receiver.velocity_km_s - transmitter.velocity_km_s;
110
111        match msr_type {
112            MeasurementType::Doppler => {
113                // If we have a simultaneous measurement of the range, use that, otherwise we compute the expected range.
114                let ρ_km = match msr.data.get(&MeasurementType::Range) {
115                    Some(range_km) => *range_km,
116                    None => {
117                        almanac
118                            .azimuth_elevation_range_sez_from_location(transmitter, loc, None, None)
119                            .context(ODAlmanacSnafu {
120                                action: "computing range for Doppler measurement",
121                            })?
122                            .range_km
123                    }
124                };
125
126                let ρ_dot_km_s = msr.data.get(&MeasurementType::Doppler).unwrap();
127                let m11 = delta_r.x / ρ_km;
128                let m12 = delta_r.y / ρ_km;
129                let m13 = delta_r.z / ρ_km;
130                let m21 = delta_v.x / ρ_km - ρ_dot_km_s * delta_r.x / ρ_km.powi(2);
131                let m22 = delta_v.y / ρ_km - ρ_dot_km_s * delta_r.y / ρ_km.powi(2);
132                let m23 = delta_v.z / ρ_km - ρ_dot_km_s * delta_r.z / ρ_km.powi(2);
133
134                let sensitivity_row =
135                    OMatrix::<f64, U1, <GroundAsset as State>::Size>::from_row_slice(&[
136                        m21, m22, m23, m11, m12, m13,
137                    ]) * jac;
138
139                Ok(Self {
140                    sensitivity_row,
141                    _rx: PhantomData::<_>,
142                    _tx: PhantomData::<_>,
143                })
144            }
145            MeasurementType::Range => {
146                let ρ_km = msr.data.get(&MeasurementType::Range).unwrap();
147                let m11 = delta_r.x / ρ_km;
148                let m12 = delta_r.y / ρ_km;
149                let m13 = delta_r.z / ρ_km;
150
151                let sensitivity_row =
152                    OMatrix::<f64, U1, <GroundAsset as State>::Size>::from_row_slice(&[
153                        m11, m12, m13, 0.0, 0.0, 0.0,
154                    ]) * jac;
155
156                Ok(Self {
157                    sensitivity_row,
158                    _rx: PhantomData::<_>,
159                    _tx: PhantomData::<_>,
160                })
161            }
162            _ => Err(ODError::MeasurementSimError {
163                details: format!("{msr_type:?} is only supported in CCSDS TDM parsing"),
164            }),
165        }
166        // let rx_orbit = rx.orbit();
167
168        // // Compute the SEZ DCM
169        // // SEZ DCM is topo to fixed
170        // let sez_dcm = rx_orbit
171        //     .dcm_from_topocentric_to_body_fixed()
172        //     .context(EphemerisPhysicsSnafu { action: "" })
173        //     .context(EphemerisSnafu {
174        //         action: "computing SEZ DCM for sensitivity",
175        //     })
176        //     .context(ODAlmanacSnafu { action: "" })?;
177
178        // let rx_sez = (sez_dcm.transpose() * rx_orbit)
179        //     .context(EphemerisPhysicsSnafu { action: "" })
180        //     .context(EphemerisSnafu {
181        //         action: "transforming ground asset to SEZ",
182        //     })
183        //     .context(ODAlmanacSnafu { action: "" })?;
184
185        // // Convert the transmitter/PNT vehicle into the body fixed transmitter frame.
186        // let tx_in_rx_frame = almanac
187        //     .transform_to(tx.traj.at(rx.epoch).unwrap().orbit, rx.frame, None)
188        //     .context(ODAlmanacSnafu {
189        //         action: "computing transmitter location when computing sensitivity matrix",
190        //     })?;
191
192        // // Convert into SEZ frame
193        // let tx_sez = (sez_dcm.transpose() * tx_in_rx_frame)
194        //     .context(EphemerisPhysicsSnafu { action: "" })
195        //     .context(EphemerisSnafu {
196        //         action: "transforming received to SEZ",
197        //     })
198        //     .context(ODAlmanacSnafu { action: "" })?;
199
200        // // Compute the range ρ in the SEZ frame
201        // let delta_r_km = tx_sez.radius_km - rx_sez.radius_km;
202        // let ρ_km_sez = delta_r_km.norm();
203        // // Compute the velocity difference - BUT note that rx_in_tx_frame is already the relative velocity of rx wrt tx!
204        // let delta_v_km_s = tx_in_rx_frame.velocity_km_s;
205
206        // match msr_type {
207        //     MeasurementType::Doppler => {
208        //         // If we have a simultaneous measurement of the range, use that, otherwise we compute the expected range.
209        //         let ρ_km = match msr.data.get(&MeasurementType::Range) {
210        //             Some(range_km) => *range_km,
211        //             None => ρ_km_sez,
212        //         };
213
214        //         let ρ_dot_km_s = msr.data.get(&MeasurementType::Doppler).unwrap();
215        //         let m11 = delta_r_km.x / ρ_km;
216        //         let m12 = delta_r_km.y / ρ_km;
217        //         let m13 = delta_r_km.z / ρ_km;
218        //         let m21 = delta_v_km_s.x / ρ_km - ρ_dot_km_s * delta_r_km.x / ρ_km.powi(2);
219        //         let m22 = delta_v_km_s.y / ρ_km - ρ_dot_km_s * delta_r_km.y / ρ_km.powi(2);
220        //         let m23 = delta_v_km_s.z / ρ_km - ρ_dot_km_s * delta_r_km.z / ρ_km.powi(2);
221
222        //         let sensitivity_row =
223        //             OMatrix::<f64, U1, <GroundAsset as State>::Size>::from_row_slice(&[
224        //                 m21, m22, m23, m11, m12, m13,
225        //             ]);
226
227        //         Ok(Self {
228        //             sensitivity_row,
229        //             _rx: PhantomData::<_>,
230        //             _tx: PhantomData::<_>,
231        //         })
232        //     }
233        //     MeasurementType::Range => {
234        //         let ρ_km = msr.data.get(&MeasurementType::Range).unwrap();
235        //         let m11 = delta_r_km.x / ρ_km;
236        //         let m12 = delta_r_km.y / ρ_km;
237        //         let m13 = delta_r_km.z / ρ_km;
238
239        //         let sensitivity_row =
240        //             OMatrix::<f64, U1, <GroundAsset as State>::Size>::from_row_slice(&[
241        //                 m11, m12, m13, 0.0, 0.0, 0.0,
242        //             ]);
243
244        //         Ok(Self {
245        //             sensitivity_row,
246        //             _rx: PhantomData::<_>,
247        //             _tx: PhantomData::<_>,
248        //         })
249        //     }
250        //     MeasurementType::Azimuth
251        //     | MeasurementType::Elevation
252        //     | MeasurementType::ReceiveFrequency
253        //     | MeasurementType::TransmitFrequency => Err(ODError::MeasurementSimError {
254        //         details: format!("{msr_type:?} is not supported for interlink"),
255        //     }),
256        // }
257    }
258}