use super::{StochasticNoise, WhiteNoise};
use anise::constants::SPEED_OF_LIGHT_KM_S;
use hifitime::Duration;
use serde::{Deserialize, Serialize};
use std::f64::consts::TAU;
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyclass(from_py_object))]
pub enum SN0 {
Strong(),
Average(),
Poor(),
ManualDbHz(f64),
}
impl SN0 {
pub(crate) fn value_hz(self) -> f64 {
match self {
Self::Strong() => 10.0_f64.powf(6.5),
Self::Average() => 10.0_f64.powi(5),
Self::Poor() => 10.0_f64.powi(4),
Self::ManualDbHz(value) => 10.0_f64.powf(value / 10.0),
}
}
}
impl Default for SN0 {
fn default() -> Self {
Self::Average()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyclass(from_py_object))]
pub enum CN0 {
Strong(),
Average(),
Poor(),
ManualDbHz(f64),
}
impl Default for CN0 {
fn default() -> Self {
CN0::Average()
}
}
impl CN0 {
pub(crate) fn value_hz(self) -> f64 {
match self {
Self::Strong() => 10.0_f64.powi(7),
Self::Average() => 10.0_f64.powf(5.5),
Self::Poor() => 10.0_f64.powf(4.5),
Self::ManualDbHz(value) => 10.0_f64.powf(value / 10.0),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyclass(from_py_object))]
pub enum CarrierFreq {
SBand(),
XBand(),
KaBand(),
ManualHz(f64),
}
impl CarrierFreq {
pub(crate) fn value_hz(self) -> f64 {
match self {
Self::SBand() => 2.2e9,
Self::XBand() => 8.4e9,
Self::KaBand() => 32e9,
Self::ManualHz(value) => value,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyclass(from_py_object))]
pub enum ChipRate {
Lowest(),
Low(),
StandardT4B(),
High(),
VeryHigh(),
ManualHz(f64),
}
impl Default for ChipRate {
fn default() -> Self {
Self::StandardT4B()
}
}
impl ChipRate {
pub(crate) fn value_chip_s(self) -> f64 {
match self {
Self::Lowest() => 1e3,
Self::Low() => 1e5,
Self::StandardT4B() => 1e6,
Self::High() => 1e7,
Self::VeryHigh() => 2.5e7,
Self::ManualHz(value) => value,
}
}
}
impl StochasticNoise {
pub fn from_hardware_range_km(
allan_deviation: f64,
integration_time: Duration,
chip_rate: ChipRate,
s_n0: SN0,
) -> Self {
let sigma_thermal_km =
SPEED_OF_LIGHT_KM_S / (TAU * chip_rate.value_chip_s() * (2.0 * s_n0.value_hz()).sqrt());
let sigma_clock_km =
(SPEED_OF_LIGHT_KM_S * allan_deviation * integration_time.to_seconds())
/ (3.0_f64.sqrt());
Self {
white_noise: Some(WhiteNoise::constant_white_noise(
(sigma_clock_km.powi(2) + sigma_thermal_km.powi(2)).sqrt(),
)),
bias: None,
}
}
pub fn from_hardware_doppler_km_s(
allan_deviation: f64,
integration_time: Duration,
carrier: CarrierFreq,
c_n0: CN0,
) -> Self {
let sigma_thermal_km_s = SPEED_OF_LIGHT_KM_S
/ (TAU
* carrier.value_hz()
* (2.0 * c_n0.value_hz() * integration_time.to_seconds()).sqrt());
let sigma_clock_km_s = SPEED_OF_LIGHT_KM_S * allan_deviation;
Self {
white_noise: Some(WhiteNoise::constant_white_noise(
(sigma_clock_km_s.powi(2) + sigma_thermal_km_s.powi(2)).sqrt(),
)),
bias: None,
}
}
}
#[cfg(test)]
mod link_noise {
use super::{CN0, CarrierFreq, ChipRate, SN0, StochasticNoise};
use hifitime::Unit;
#[test]
fn nasa_dsac() {
for (case_num, allan_dev) in [1e-14, 3.8e-13].iter().copied().enumerate() {
println!("AD = {allan_dev:e}");
let range_dsac_no_flicker = StochasticNoise::from_hardware_range_km(
allan_dev,
Unit::Minute * 1,
ChipRate::StandardT4B(),
SN0::Average(),
);
let range_sigma_m = range_dsac_no_flicker.white_noise.unwrap().sigma * 1e3;
println!("range sigma = {range_sigma_m:.3e} m",);
assert!(range_sigma_m.abs() < 1.1e-1);
let doppler_dsac_no_flicker = StochasticNoise::from_hardware_doppler_km_s(
allan_dev,
Unit::Minute * 1,
CarrierFreq::XBand(),
CN0::Average(),
);
let doppler_sigma_m_s = doppler_dsac_no_flicker.white_noise.unwrap().sigma * 1e3;
println!("doppler sigma = {doppler_sigma_m_s:.3e} m/s");
match case_num {
0 => assert!(doppler_sigma_m_s < 3.2e-6),
1 => assert!(doppler_sigma_m_s < 1.2e-4),
_ => unreachable!(),
};
}
}
}