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