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