1use crate::linalg::allocator::Allocator;
20use crate::linalg::DefaultAllocator;
21use crate::md::prelude::Interpolatable;
22use crate::od::{GroundStation, ODAlmanacSnafu, ODError, TrackingDevice};
23use crate::{Spacecraft, State};
24use anise::prelude::Almanac;
25use indexmap::IndexSet;
26use nalgebra::{DimName, OMatrix, U1};
27use snafu::ResultExt;
28use std::marker::PhantomData;
29use std::sync::Arc;
30
31use super::measurement::Measurement;
32use super::MeasurementType;
33
34trait ScalarSensitivityT<SolveState: State, Rx, Tx>
35where
36 Self: Sized,
37 DefaultAllocator: Allocator<SolveState::Size>
38 + Allocator<SolveState::VecLength>
39 + Allocator<SolveState::Size, SolveState::Size>,
40{
41 fn new(
42 msr_type: MeasurementType,
43 msr: &Measurement,
44 rx: &Rx,
45 tx: &Tx,
46 almanac: Arc<Almanac>,
47 ) -> Result<Self, ODError>;
48}
49
50pub trait TrackerSensitivity<SolveState: Interpolatable, Rx>: TrackingDevice<SolveState>
52where
53 Self: Sized,
54 DefaultAllocator: Allocator<SolveState::Size>
55 + Allocator<SolveState::VecLength>
56 + Allocator<SolveState::Size, SolveState::Size>,
57{
58 fn h_tilde<M: DimName>(
61 &self,
62 msr: &Measurement,
63 msr_types: &IndexSet<MeasurementType>, rx: &Rx,
65 almanac: Arc<Almanac>,
66 ) -> Result<OMatrix<f64, M, SolveState::Size>, ODError>
67 where
68 DefaultAllocator: Allocator<M> + Allocator<M, SolveState::Size>;
69}
70
71struct ScalarSensitivity<SolveState: State, Rx, Tx>
72where
73 DefaultAllocator: Allocator<SolveState::Size>
74 + Allocator<SolveState::VecLength>
75 + Allocator<SolveState::Size, SolveState::Size>
76 + Allocator<U1, SolveState::Size>,
77{
78 sensitivity_row: OMatrix<f64, U1, SolveState::Size>,
79 _rx: PhantomData<Rx>,
80 _tx: PhantomData<Tx>,
81}
82
83impl TrackerSensitivity<Spacecraft, Spacecraft> for GroundStation
84where
85 DefaultAllocator: Allocator<<Spacecraft as State>::Size>
86 + Allocator<<Spacecraft as State>::VecLength>
87 + Allocator<<Spacecraft as State>::Size, <Spacecraft as State>::Size>,
88{
89 fn h_tilde<M: DimName>(
90 &self,
91 msr: &Measurement,
92 msr_types: &IndexSet<MeasurementType>,
93 rx: &Spacecraft,
94 almanac: Arc<Almanac>,
95 ) -> Result<OMatrix<f64, M, <Spacecraft as State>::Size>, ODError>
96 where
97 DefaultAllocator: Allocator<M> + Allocator<M, <Spacecraft as State>::Size>,
98 {
99 let mut mat = OMatrix::<f64, M, <Spacecraft as State>::Size>::identity();
101 for (ith_row, msr_type) in msr_types.iter().enumerate() {
102 if !msr.data.contains_key(msr_type) {
103 continue;
105 }
106 let scalar_h =
107 <ScalarSensitivity<Spacecraft, Spacecraft, GroundStation> as ScalarSensitivityT<
108 Spacecraft,
109 Spacecraft,
110 GroundStation,
111 >>::new(*msr_type, msr, rx, self, almanac.clone())?;
112
113 mat.set_row(ith_row, &scalar_h.sensitivity_row);
114 }
115 Ok(mat)
116 }
117}
118
119impl ScalarSensitivityT<Spacecraft, Spacecraft, GroundStation>
120 for ScalarSensitivity<Spacecraft, Spacecraft, GroundStation>
121{
122 fn new(
123 msr_type: MeasurementType,
124 msr: &Measurement,
125 rx: &Spacecraft,
126 tx: &GroundStation,
127 almanac: Arc<Almanac>,
128 ) -> Result<Self, ODError> {
129 let receiver = rx.orbit;
130
131 let transmitter = tx
135 .location(rx.orbit.epoch, rx.orbit.frame, almanac.clone())
136 .context(ODAlmanacSnafu {
137 action: "computing transmitter location when computing sensitivity matrix",
138 })?;
139
140 let delta_r = receiver.radius_km - transmitter.radius_km;
141 let delta_v = receiver.velocity_km_s - transmitter.velocity_km_s;
142
143 match msr_type {
144 MeasurementType::Doppler => {
145 let ρ_km = match msr.data.get(&MeasurementType::Range) {
147 Some(range_km) => *range_km,
148 None => {
149 tx.azimuth_elevation_of(receiver, None, &almanac)
150 .context(ODAlmanacSnafu {
151 action: "computing range for Doppler measurement",
152 })?
153 .range_km
154 }
155 };
156
157 let ρ_dot_km_s = msr.data.get(&MeasurementType::Doppler).unwrap();
158 let m11 = delta_r.x / ρ_km;
159 let m12 = delta_r.y / ρ_km;
160 let m13 = delta_r.z / ρ_km;
161 let m21 = delta_v.x / ρ_km - ρ_dot_km_s * delta_r.x / ρ_km.powi(2);
162 let m22 = delta_v.y / ρ_km - ρ_dot_km_s * delta_r.y / ρ_km.powi(2);
163 let m23 = delta_v.z / ρ_km - ρ_dot_km_s * delta_r.z / ρ_km.powi(2);
164
165 let sensitivity_row =
166 OMatrix::<f64, U1, <Spacecraft as State>::Size>::from_row_slice(&[
167 m21, m22, m23, m11, m12, m13, 0.0, 0.0, 0.0,
168 ]);
169
170 Ok(Self {
171 sensitivity_row,
172 _rx: PhantomData::<_>,
173 _tx: PhantomData::<_>,
174 })
175 }
176 MeasurementType::Range => {
177 let ρ_km = msr.data.get(&MeasurementType::Range).unwrap();
178 let m11 = delta_r.x / ρ_km;
179 let m12 = delta_r.y / ρ_km;
180 let m13 = delta_r.z / ρ_km;
181
182 let sensitivity_row =
183 OMatrix::<f64, U1, <Spacecraft as State>::Size>::from_row_slice(&[
184 m11, m12, m13, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
185 ]);
186
187 Ok(Self {
188 sensitivity_row,
189 _rx: PhantomData::<_>,
190 _tx: PhantomData::<_>,
191 })
192 }
193 MeasurementType::Azimuth => {
194 let denom = delta_r.x.powi(2) + delta_r.y.powi(2);
195 let m11 = -delta_r.y / denom;
196 let m12 = delta_r.x / denom;
197 let m13 = 0.0;
198
199 let sensitivity_row =
202 OMatrix::<f64, U1, <Spacecraft as State>::Size>::from_row_slice(&[
203 m11, m12, m13, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
204 ]);
205
206 Ok(Self {
207 sensitivity_row,
208 _rx: PhantomData::<_>,
209 _tx: PhantomData::<_>,
210 })
211 }
212 MeasurementType::Elevation => {
213 let r2 = delta_r.norm().powi(2);
214 let z2 = delta_r.z.powi(2);
215
216 let m11 = -(delta_r.x * delta_r.z) / (r2 * (r2 - z2).sqrt());
218 let m12 = -(delta_r.y * delta_r.z) / (r2 * (r2 - z2).sqrt());
219 let m13 = (delta_r.x.powi(2) + delta_r.y.powi(2)).sqrt() / r2;
220
221 let sensitivity_row =
222 OMatrix::<f64, U1, <Spacecraft as State>::Size>::from_row_slice(&[
223 m11, m12, m13, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
224 ]);
225
226 Ok(Self {
227 sensitivity_row,
228 _rx: PhantomData::<_>,
229 _tx: PhantomData::<_>,
230 })
231 }
232 MeasurementType::ReceiveFrequency | MeasurementType::TransmitFrequency => {
233 Err(ODError::MeasurementSimError {
234 details: format!("{msr_type:?} is only supported in CCSDS TDM parsing"),
235 })
236 }
237 }
238 }
239}