1use crate::io::{ConfigError, ConfigRepr};
20use hifitime::{Duration, Epoch, TimeUnits};
21
22use der::{Decode, Encode, Reader};
23use rand::{Rng, RngExt};
24use rand_distr::Normal;
25use serde::{Deserialize, Serialize};
26use std::fmt;
27use std::ops::{Mul, MulAssign};
28
29#[cfg(feature = "python")]
30use pyo3::prelude::*;
31
32use super::Stochastics;
33
34#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
51#[cfg_attr(feature = "python", pyclass(from_py_object, get_all, set_all))]
52pub struct GaussMarkov {
53 pub tau: Duration,
55 pub process_noise: f64,
56 pub constant: Option<f64>,
58 #[serde(skip)]
60 pub prev_epoch: Option<Epoch>,
61 #[serde(skip)]
63 pub init_sample: Option<f64>,
64}
65
66impl fmt::Display for GaussMarkov {
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
68 write!(
69 f,
70 "First order Gauss-Markov process with τ = {}, σ = {}",
71 self.tau, self.process_noise
72 )
73 }
74}
75
76impl GaussMarkov {
77 pub fn new(tau: Duration, process_noise: f64) -> Result<Self, ConfigError> {
82 if tau <= Duration::ZERO {
83 return Err(ConfigError::InvalidConfig {
84 msg: format!("tau must be positive but got {tau}"),
85 });
86 }
87
88 Ok(Self {
89 tau,
90 process_noise,
91 constant: None,
92 init_sample: None,
93 prev_epoch: None,
94 })
95 }
96
97 pub const ZERO: Self = Self {
99 tau: Duration::MAX,
100 process_noise: 0.0,
101 constant: None,
102 init_sample: None,
103 prev_epoch: None,
104 };
105
106 pub fn default_range_km() -> Self {
109 Self {
110 tau: 1.minutes(),
111 process_noise: 60.0e-5,
112 constant: None,
113 init_sample: None,
114 prev_epoch: None,
115 }
116 }
117
118 pub fn default_doppler_km_s() -> Self {
121 Self {
122 tau: 1.minutes(),
123 process_noise: 0.03e-6,
124 constant: None,
125 init_sample: None,
126 prev_epoch: None,
127 }
128 }
129}
130
131impl Stochastics for GaussMarkov {
132 fn covariance(&self, _epoch: Epoch) -> f64 {
133 self.process_noise.powi(2)
134 }
135
136 fn sample<R: Rng>(&mut self, epoch: Epoch, rng: &mut R) -> f64 {
138 let dt_s = (match self.prev_epoch {
140 None => Duration::ZERO,
141 Some(prev_epoch) => epoch - prev_epoch,
142 })
143 .to_seconds();
144 self.prev_epoch = Some(epoch);
145
146 if self.init_sample.is_none() {
148 self.init_sample = Some(rng.sample(Normal::new(0.0, self.process_noise).unwrap()));
149 }
150
151 let decay = (-dt_s / self.tau.to_seconds()).exp();
152 let anti_decay = 1.0 - decay;
153
154 let steady_noise = 0.5 * self.process_noise * self.tau.to_seconds() * anti_decay;
156 let ss_sample = rng.sample(Normal::new(0.0, steady_noise).unwrap());
157
158 self.init_sample.unwrap() * decay + ss_sample + self.constant.unwrap_or(0.0)
159 }
160}
161
162impl Mul<f64> for GaussMarkov {
163 type Output = Self;
164
165 fn mul(mut self, rhs: f64) -> Self::Output {
167 self.process_noise *= rhs;
168 self.constant = None;
169 self.init_sample = None;
170 self.prev_epoch = None;
171 self
172 }
173}
174
175impl MulAssign<f64> for GaussMarkov {
176 fn mul_assign(&mut self, rhs: f64) {
177 *self = *self * rhs;
178 }
179}
180
181impl Encode for GaussMarkov {
182 fn encoded_len(&self) -> der::Result<der::Length> {
183 self.tau.total_nanoseconds().encoded_len()?
184 + self.process_noise.encoded_len()?
185 + if let Some(constant) = self.constant {
186 (true.encoded_len()? + constant.encoded_len()?)?
187 } else {
188 false.encoded_len()?
189 }
190 }
191
192 fn encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
193 self.tau.total_nanoseconds().encode(encoder)?;
194 self.process_noise.encode(encoder)?;
195 if let Some(constant) = self.constant {
196 true.encode(encoder)?;
197 constant.encode(encoder)
198 } else {
199 false.encode(encoder)
200 }
201 }
202}
203
204impl<'a> Decode<'a> for GaussMarkov {
205 fn decode<R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
206 let tau = Duration::from_total_nanoseconds(decoder.decode::<i128>()?);
207 let process_noise = decoder.decode()?;
208 let constant = if decoder.decode::<bool>()? {
209 Some(decoder.decode()?)
210 } else {
211 None
212 };
213
214 Ok(Self {
215 tau,
216 process_noise,
217 constant,
218 prev_epoch: None,
219 init_sample: None,
220 })
221 }
222}
223
224impl ConfigRepr for GaussMarkov {}
225
226#[cfg(feature = "python")]
227#[cfg_attr(feature = "python", pymethods)]
228impl GaussMarkov {
229 #[new]
230 fn py_new(tau: Duration, process_noise: f64) -> Result<Self, ConfigError> {
231 Self::new(tau, process_noise)
232 }
233
234 fn __str__(&self) -> String {
235 format!("{self}")
236 }
237
238 fn __repr__(&self) -> String {
239 format!("{self} @ {self:p}")
240 }
241}
242
243#[cfg(test)]
244mod ut_gm {
245
246 use hifitime::{Duration, Epoch, TimeUnits};
247 use rand_pcg::Pcg64Mcg;
248 use rstats::{Stats, triangmat::Vecops};
249
250 use crate::{
251 io::ConfigRepr,
252 od::noise::{GaussMarkov, Stochastics},
253 };
254
255 #[test]
256 fn fogm_test() {
257 let mut gm = GaussMarkov::new(24.hours(), 0.1).unwrap();
258
259 let mut biases = Vec::with_capacity(1000);
260 let epoch = Epoch::now().unwrap();
261
262 let mut rng = Pcg64Mcg::new(0);
263 for seconds in 0..1000 {
264 biases.push(gm.sample(epoch + seconds.seconds(), &mut rng));
265 }
266
267 let min_max = biases.minmax();
270
271 assert_eq!(biases.amean().unwrap(), 0.09373233290645445);
272 assert_eq!(min_max.max, 0.24067114622652647);
273 assert_eq!(min_max.min, -0.045552031890295525);
274 }
275
276 #[test]
277 fn zero_noise_test() {
278 use rstats::{Stats, triangmat::Vecops};
279
280 let mut gm = GaussMarkov::ZERO;
281
282 let mut biases = Vec::with_capacity(1000);
283 let epoch = Epoch::now().unwrap();
284
285 let mut rng = Pcg64Mcg::new(0);
286 for seconds in 0..1000 {
287 biases.push(gm.sample(epoch + seconds.seconds(), &mut rng));
288 }
289
290 let min_max = biases.minmax();
291
292 assert_eq!(biases.amean().unwrap(), 0.0);
293 assert_eq!(min_max.min, 0.0);
294 assert_eq!(min_max.max, 0.0);
295 }
296
297 #[test]
298 fn serde_test() {
299 use serde_yml;
300 use std::env;
301 use std::path::PathBuf;
302
303 let gm = GaussMarkov::new(Duration::MAX, 0.1).unwrap();
305 let serialized = serde_yml::to_string(&gm).unwrap();
306 println!("{serialized}");
307 let gm_deser: GaussMarkov = serde_yml::from_str(&serialized).unwrap();
308 assert_eq!(gm_deser, gm);
309
310 let test_data: PathBuf = [
311 env!("CARGO_MANIFEST_DIR"),
312 "../data",
313 "03_tests",
314 "config",
315 "high-prec-network.yaml",
316 ]
317 .iter()
318 .collect();
319
320 let models = <GaussMarkov as ConfigRepr>::load_named(test_data).unwrap();
321 assert_eq!(models.len(), 2);
322 assert_eq!(
323 models["range_noise_model"].tau,
324 12.hours() + 159.milliseconds()
325 );
326 assert_eq!(models["range_noise_model"].process_noise, 5.0e-3);
327
328 assert_eq!(models["doppler_noise_model"].tau, 11.hours() + 59.minutes());
329 assert_eq!(models["doppler_noise_model"].process_noise, 50.0e-6);
330 }
331}