nyx_space/od/noise/
white.rs1use std::ops::{Mul, MulAssign};
20
21use crate::io::ConfigError;
22use anise::constants::SPEED_OF_LIGHT_KM_S;
23use der::Sequence;
24use hifitime::{Duration, Epoch};
25use rand::{Rng, RngExt};
26use rand_distr::Normal;
27use serde::{Deserialize, Serialize};
28
29#[cfg(feature = "python")]
30use pyo3::prelude::*;
31
32use super::Stochastics;
33
34#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize, Sequence)]
39#[cfg_attr(feature = "python", pyclass(from_py_object, get_all, set_all))]
40pub struct WhiteNoise {
41 pub mean: f64,
43 pub sigma: f64,
45}
46
47impl WhiteNoise {
48 pub fn new(process_noise: f64, integration_time: Duration) -> Result<Self, ConfigError> {
51 if process_noise.is_sign_negative() {
52 return Err(ConfigError::InvalidConfig {
53 msg: format!("process noise must be positive: {process_noise}"),
54 });
55 }
56 if integration_time.to_seconds() <= 0.0 {
57 return Err(ConfigError::InvalidConfig {
58 msg: format!("integration time must be positive: {integration_time}"),
59 });
60 }
61 Ok(Self {
62 sigma: process_noise / integration_time.to_seconds(),
63 ..Default::default()
64 })
65 }
66
67 pub fn constant_white_noise(process_noise: f64) -> Self {
70 Self {
71 sigma: process_noise,
72 ..Default::default()
73 }
74 }
75
76 pub fn from_pr_n0(pr_n0: f64, bandwidth_hz: f64) -> Self {
84 Self {
85 sigma: SPEED_OF_LIGHT_KM_S / (2.0 * bandwidth_hz * (pr_n0).sqrt()),
86 mean: 0.0,
87 }
88 }
89}
90
91#[cfg(feature = "python")]
92#[cfg_attr(feature = "python", pymethods)]
93impl WhiteNoise {
94 #[new]
95 fn py_new(mean: f64, sigma: f64) -> Self {
96 Self { mean, sigma }
97 }
98
99 fn __str__(&self) -> String {
100 format!("{self:?}")
101 }
102
103 fn __repr__(&self) -> String {
104 format!("{self:?} @ {self:p}")
105 }
106}
107
108impl Stochastics for WhiteNoise {
109 fn covariance(&self, _epoch: Epoch) -> f64 {
110 self.sigma.powi(2)
111 }
112
113 fn sample<R: Rng>(&mut self, _epoch: Epoch, rng: &mut R) -> f64 {
114 rng.sample(Normal::new(self.mean, self.sigma).unwrap())
115 }
116}
117
118impl Mul<f64> for WhiteNoise {
119 type Output = Self;
120
121 fn mul(mut self, rhs: f64) -> Self::Output {
123 self.sigma *= rhs;
124 self
125 }
126}
127
128impl MulAssign<f64> for WhiteNoise {
129 fn mul_assign(&mut self, rhs: f64) {
130 *self = *self * rhs;
131 }
132}
133
134#[cfg(test)]
135mod ut_wn {
136 use hifitime::{Epoch, TimeUnits};
137 use rand_pcg::Pcg64Mcg;
138
139 use super::{Stochastics, WhiteNoise};
140
141 #[test]
142 fn white_noise_test() {
143 let sigma = 10.0_f64;
144 let mut wn = WhiteNoise { mean: 0.0, sigma };
145
146 let mut larger_wn = WhiteNoise {
147 mean: 0.0,
148 sigma: sigma * 10.0,
149 };
150
151 let epoch = Epoch::now().unwrap();
152
153 let mut rng = Pcg64Mcg::new(1000);
154 let mut cnt_above_3sigma = 0;
155 let mut cnt_below_3sigma = 0;
156 let mut larger_cnt_above_3sigma = 0;
157 let mut larger_cnt_below_3sigma = 0;
158 for seconds in 0..1000_i64 {
159 let bias = wn.sample(epoch + seconds.seconds(), &mut rng);
160
161 if bias > 3.0 * sigma {
162 cnt_above_3sigma += 1;
163 } else if bias < -3.0 * sigma {
164 cnt_below_3sigma += 1;
165 }
166
167 let larger_bias = larger_wn.sample(epoch + seconds.seconds(), &mut rng);
168 if larger_bias > 30.0 * sigma {
169 larger_cnt_above_3sigma += 1;
170 } else if larger_bias < -30.0 * sigma {
171 larger_cnt_below_3sigma += 1;
172 }
173 }
174
175 assert!(dbg!(cnt_above_3sigma) <= 3);
176 assert!(dbg!(cnt_below_3sigma) <= 3);
177
178 assert!(dbg!(larger_cnt_above_3sigma) <= 3);
179 assert!(dbg!(larger_cnt_below_3sigma) <= 3);
180 }
181}