1use anise::almanac::Almanac;
19use anise::astro::Aberration;
20use anise::errors::AlmanacResult;
21use anise::prelude::{Frame, Orbit};
22use hifitime::{Duration, Epoch, TimeUnits};
23use indexmap::{IndexMap, IndexSet};
24use rand_pcg::Pcg64Mcg;
25use serde::{Deserialize, Serialize};
26use snafu::{ensure, ResultExt};
27
28use crate::io::ConfigRepr;
29use crate::md::prelude::Traj;
30use crate::md::Trajectory;
31use crate::od::msr::MeasurementType;
32use crate::od::noise::StochasticNoise;
33use crate::od::prelude::{Measurement, NoiseNotConfiguredSnafu, ODError};
34use crate::od::TrackingDevice;
35use crate::od::{ODAlmanacSnafu, ODTrajSnafu};
36use crate::Spacecraft;
37use crate::State;
38
39use std::sync::Arc;
40
41#[derive(Clone, Debug)]
44pub struct InterlinkTxSpacecraft {
45 pub traj: Trajectory,
47 pub measurement_types: IndexSet<MeasurementType>,
49 pub integration_time: Option<Duration>,
51 pub timestamp_noise_s: Option<StochasticNoise>,
52 pub stochastic_noises: Option<IndexMap<MeasurementType, StochasticNoise>>,
53 pub ab_corr: Option<Aberration>,
55}
56
57impl InterlinkTxSpacecraft {
58 fn noises(&mut self, epoch: Epoch, rng: Option<&mut Pcg64Mcg>) -> Result<Vec<f64>, ODError> {
60 let mut noises = vec![0.0; self.measurement_types.len() + 1];
61
62 if let Some(rng) = rng {
63 ensure!(
64 self.stochastic_noises.is_some(),
65 NoiseNotConfiguredSnafu {
66 kind: "ground station stochastics".to_string(),
67 }
68 );
69 if let Some(mut timestamp_noise) = self.timestamp_noise_s {
72 noises[0] = timestamp_noise.sample(epoch, rng);
73 }
74
75 let stochastics = self.stochastic_noises.as_mut().unwrap();
76
77 for (ii, msr_type) in self.measurement_types.iter().enumerate() {
78 noises[ii + 1] = stochastics
79 .get_mut(msr_type)
80 .ok_or(ODError::NoiseNotConfigured {
81 kind: format!("{msr_type:?}"),
82 })?
83 .sample(epoch, rng);
84 }
85 }
86
87 Ok(noises)
88 }
89}
90
91impl TrackingDevice<Spacecraft> for InterlinkTxSpacecraft {
92 fn name(&self) -> String {
93 self.traj.name.clone().unwrap_or("unnamed".to_string())
94 }
95
96 fn measurement_types(&self) -> &IndexSet<MeasurementType> {
97 &self.measurement_types
98 }
99
100 fn location(&self, epoch: Epoch, frame: Frame, almanac: Arc<Almanac>) -> AlmanacResult<Orbit> {
101 almanac.transform_to(self.traj.at(epoch).unwrap().orbit, frame, self.ab_corr)
102 }
103
104 fn measure(
105 &mut self,
106 epoch: Epoch,
107 traj: &Traj<Spacecraft>,
108 rng: Option<&mut Pcg64Mcg>,
109 almanac: Arc<Almanac>,
110 ) -> Result<Option<Measurement>, ODError> {
111 match self.integration_time {
112 Some(integration_time) => {
113 let rx_0 = match traj.at(epoch - integration_time).context(ODTrajSnafu {
116 details: format!(
117 "fetching state {epoch} at start of ground station integration time {integration_time}"
118 ),
119 }) {
120 Ok(rx) => rx,
121 Err(_) => return Ok(None),
122 };
123
124 let rx_1 = match traj.at(epoch).context(ODTrajSnafu {
125 details: format!(
126 "fetching state {epoch} at end of ground station integration time"
127 ),
128 }) {
129 Ok(rx) => rx,
130 Err(_) => return Ok(None),
131 };
132
133 let msr_t0_opt = self.measure_instantaneous(rx_0, None, almanac.clone())?;
135
136 let msr_t1_opt = self.measure_instantaneous(rx_1, None, almanac.clone())?;
138
139 if let Some(msr_t0) = msr_t0_opt {
140 if let Some(msr_t1) = msr_t1_opt {
141 let noises = self.noises(epoch - integration_time * 0.5, rng)?;
145
146 let mut msr = Measurement::new(self.name(), epoch + noises[0].seconds());
147
148 for (ii, msr_type) in self.measurement_types.iter().enumerate() {
149 let msr_value_0 = msr_t0.data[msr_type];
150 let msr_value_1 = msr_t1.data[msr_type];
151
152 let msr_value =
153 (msr_value_1 + msr_value_0) * 0.5 + noises[ii + 1] / 2.0_f64.sqrt();
154 msr.push(*msr_type, msr_value);
155 }
156
157 Ok(Some(msr))
158 } else {
159 Ok(None)
160 }
161 } else {
162 Ok(None)
163 }
164 }
165 None => self.measure_instantaneous(
166 traj.at(epoch).context(ODTrajSnafu {
167 details: "fetching state for instantaneous measurement".to_string(),
168 })?,
169 rng,
170 almanac,
171 ),
172 }
173 }
174
175 fn measure_instantaneous(
176 &mut self,
177 rx: Spacecraft,
178 rng: Option<&mut Pcg64Mcg>,
179 almanac: Arc<Almanac>,
180 ) -> Result<Option<Measurement>, ODError> {
181 let observer = self.traj.at(rx.epoch()).context(ODTrajSnafu {
182 details: format!("fetching state {} for interlink", rx.epoch()),
183 })?;
184
185 let is_obstructed = almanac
186 .line_of_sight_obstructed(observer.orbit, rx.orbit, observer.orbit.frame, self.ab_corr)
187 .context(ODAlmanacSnafu {
188 action: "computing line of sight",
189 })?;
190
191 if is_obstructed {
192 Ok(None)
193 } else {
194 let rx_in_tx_frame = almanac
196 .transform_to(rx.orbit, observer.orbit.frame, self.ab_corr)
197 .context(ODAlmanacSnafu {
198 action: "transforming receiver to transmitter frame",
199 })?;
200
201 let rho_tx_frame = rx_in_tx_frame.radius_km - observer.orbit.radius_km;
202
203 let range_rate_km_s =
205 rho_tx_frame.dot(&rx_in_tx_frame.velocity_km_s) / rho_tx_frame.norm();
206
207 let noises = self.noises(observer.epoch(), rng)?;
208
209 let mut msr = Measurement::new(self.name(), rx.orbit.epoch + noises[0].seconds());
210
211 for (ii, msr_type) in self.measurement_types.iter().enumerate() {
212 let msr_value = match *msr_type {
213 MeasurementType::Range => rho_tx_frame.norm(),
214 MeasurementType::Doppler => range_rate_km_s,
215 _ => unreachable!("unsupported measurement type for interlink: {:?}", msr_type),
217 } + noises[ii + 1];
218 msr.push(*msr_type, msr_value);
219 }
220
221 Ok(Some(msr))
222 }
223 }
224
225 fn measurement_covar(&self, msr_type: MeasurementType, epoch: Epoch) -> Result<f64, ODError> {
233 let stochastics = self.stochastic_noises.as_ref().unwrap();
234
235 Ok(stochastics
236 .get(&msr_type)
237 .ok_or(ODError::NoiseNotConfigured {
238 kind: format!("{msr_type:?}"),
239 })?
240 .covariance(epoch))
241 }
242
243 fn measurement_bias(&self, msr_type: MeasurementType, _epoch: Epoch) -> Result<f64, ODError> {
244 let stochastics = self.stochastic_noises.as_ref().unwrap();
245
246 if let Some(gm) = stochastics
247 .get(&msr_type)
248 .ok_or(ODError::NoiseNotConfigured {
249 kind: format!("{msr_type:?}"),
250 })?
251 .bias
252 {
253 Ok(gm.constant.unwrap_or(0.0))
254 } else {
255 Ok(0.0)
256 }
257 }
258}
259
260impl Serialize for InterlinkTxSpacecraft {
261 fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
262 where
263 S: serde::Serializer,
264 {
265 unimplemented!("interlink spacecraft cannot be serialized")
266 }
267}
268
269impl<'de> Deserialize<'de> for InterlinkTxSpacecraft {
270 fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
271 where
272 D: serde::Deserializer<'de>,
273 {
274 unimplemented!("interlink spacecraft cannot be deserialized")
275 }
276}
277
278impl ConfigRepr for InterlinkTxSpacecraft {}