nyx_space/od/noise/white.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
/*
Nyx, blazing fast astrodynamics
Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::ops::{Mul, MulAssign};
use anise::constants::SPEED_OF_LIGHT_KM_S;
use hifitime::{Duration, Epoch};
use rand::Rng;
use rand_distr::Normal;
use serde_derive::{Deserialize, Serialize};
use super::Stochastics;
/// White noise is an uncorrelated random variable.
#[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct WhiteNoise {
/// Mean value of this white noise
pub mean: f64,
/// Process noise as a one-sigma of the Normal distribution.
pub sigma: f64,
}
impl WhiteNoise {
/// Initializes a new random walk stochastic noise model from the process noise and the integration time.
/// This will compute the process noise per second automatically.
pub fn new(process_noise: f64, integration_time: Duration) -> Self {
Self {
sigma: process_noise / integration_time.to_seconds(),
..Default::default()
}
}
/// Initializes a new random walk stochastic noise model from the provided process noise, assuming that the noise level
/// is fixed regardless of the integration time.
pub fn constant_white_noise(process_noise: f64) -> Self {
Self {
sigma: process_noise,
..Default::default()
}
}
// Initializes a new time-uncorrelated white noise process, using only the Pr/N0 value and the bandwidth.
// This returns a white noise sigma in kilometers.
//
// # Equation
// σ = c / (2 * B * √(Pr/N0))
//
// Where c is the speed of light, B is the bandwidth in Hz, and the Pr/N0 is the signal-to-noise ratio.
pub fn from_pr_n0(pr_n0: f64, bandwidth_hz: f64) -> Self {
Self {
sigma: SPEED_OF_LIGHT_KM_S / (2.0 * bandwidth_hz * (pr_n0).sqrt()),
mean: 0.0,
}
}
}
impl Stochastics for WhiteNoise {
fn covariance(&self, _epoch: Epoch) -> f64 {
self.sigma.powi(2)
}
fn sample<R: Rng>(&mut self, _epoch: Epoch, rng: &mut R) -> f64 {
rng.sample(Normal::new(self.mean, self.sigma).unwrap())
}
}
impl Mul<f64> for WhiteNoise {
type Output = Self;
/// Scale the white noise sigmas by a constant.
fn mul(mut self, rhs: f64) -> Self::Output {
self.sigma *= rhs;
self
}
}
impl MulAssign<f64> for WhiteNoise {
fn mul_assign(&mut self, rhs: f64) {
*self = *self * rhs;
}
}
#[cfg(test)]
mod ut_wn {
use hifitime::{Epoch, TimeUnits};
use rand_pcg::Pcg64Mcg;
use super::{Stochastics, WhiteNoise};
#[test]
fn white_noise_test() {
let sigma = 10.0_f64;
let mut wn = WhiteNoise { mean: 0.0, sigma };
let mut larger_wn = WhiteNoise {
mean: 0.0,
sigma: sigma * 10.0,
};
let epoch = Epoch::now().unwrap();
let mut rng = Pcg64Mcg::new(1000);
let mut cnt_above_3sigma = 0;
let mut cnt_below_3sigma = 0;
let mut larger_cnt_above_3sigma = 0;
let mut larger_cnt_below_3sigma = 0;
for seconds in 0..1000_i64 {
let bias = wn.sample(epoch + seconds.seconds(), &mut rng);
if bias > 3.0 * sigma {
cnt_above_3sigma += 1;
} else if bias < -3.0 * sigma {
cnt_below_3sigma += 1;
}
let larger_bias = larger_wn.sample(epoch + seconds.seconds(), &mut rng);
if larger_bias > 30.0 * sigma {
larger_cnt_above_3sigma += 1;
} else if larger_bias < -30.0 * sigma {
larger_cnt_below_3sigma += 1;
}
}
assert!(dbg!(cnt_above_3sigma) <= 3);
assert!(dbg!(cnt_below_3sigma) <= 3);
assert!(dbg!(larger_cnt_above_3sigma) <= 3);
assert!(dbg!(larger_cnt_below_3sigma) <= 3);
}
}