apsis-rs/crates/rf/apsis-noise/src/temperature.rs

140 lines
4.6 KiB
Rust

//! Noise temperature.
use apsis_core::constant::BOLTZMANN;
use apsis_rf::Power;
use core::fmt;
/// The reference temperature used to define the noise figure, in kelvin.
///
/// The value is 290 K, which is the standard reference temperature of the
/// IEEE definition of noise figure. It is a convention rather than a
/// physical temperature, and the choice affects the noise figure quoted for
/// a given noise temperature.
pub const REFERENCE_TEMPERATURE_K: f64 = 290.0;
/// A noise temperature in kelvin.
///
/// A noise temperature is the temperature of a resistor that would deliver
/// the same noise power in the same bandwidth. It is a convenient way to
/// express any noise source, including one that is not thermal in origin,
/// because temperatures add across a cascade while powers do not.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct NoiseTemperature(f64);
impl NoiseTemperature {
/// The standard reference temperature of 290 K.
pub const REFERENCE: Self = Self(REFERENCE_TEMPERATURE_K);
/// Creates a noise temperature from a value in kelvin.
pub const fn from_kelvin(kelvin: f64) -> Self {
Self(kelvin)
}
/// Creates a noise temperature from a noise figure in decibels.
///
/// The relation is `T = T_0 (F - 1)` with `F` the linear noise factor,
/// so a noise figure of zero decibels gives a temperature of zero.
///
/// Returns `None` when the figure is not finite.
pub fn from_noise_figure_db(decibels: f64) -> Option<Self> {
if !decibels.is_finite() {
return None;
}
let factor = 10f64.powf(decibels / 10.0);
Some(Self(REFERENCE_TEMPERATURE_K * (factor - 1.0)))
}
/// Creates a noise temperature from a linear noise factor.
///
/// Returns `None` when the factor is not finite or is below one, which
/// would describe a stage that removes noise.
pub fn from_noise_factor(factor: f64) -> Option<Self> {
if factor.is_finite() && factor >= 1.0 {
Some(Self(REFERENCE_TEMPERATURE_K * (factor - 1.0)))
} else {
None
}
}
/// Returns this temperature in kelvin.
pub const fn kelvin(self) -> f64 {
self.0
}
/// Returns this temperature as a linear noise factor.
///
/// The factor is `T / T_0 + 1`, which is one for zero noise.
pub fn noise_factor(self) -> f64 {
self.0 / REFERENCE_TEMPERATURE_K + 1.0
}
/// Returns this temperature as a noise figure in decibels.
///
/// Returns `None` when the temperature is not finite or is negative,
/// which would give a factor below one.
pub fn noise_figure_db(self) -> Option<f64> {
let factor = self.noise_factor();
if factor > 0.0 && factor.is_finite() && self.0 >= 0.0 {
Some(10.0 * factor.log10())
} else {
None
}
}
/// Returns the sum of two temperatures.
///
/// Noise temperatures of independent sources add, which is the reason
/// the quantity is useful.
pub const fn plus(self, other: Self) -> Self {
Self(self.0 + other.0)
}
/// Returns this temperature scaled by a gain ratio.
///
/// A source followed by a gain contributes its temperature divided by
/// that gain when referred to the input, so the scaling for a
/// contribution referred to the input is the reciprocal.
pub const fn scaled(self, factor: f64) -> Self {
Self(self.0 * factor)
}
/// Returns `true` when the temperature is finite and not negative.
pub const fn is_valid(self) -> bool {
self.0.is_finite() && self.0 >= 0.0
}
}
impl fmt::Display for NoiseTemperature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} K", self.0)
}
}
impl core::ops::Add for NoiseTemperature {
type Output = Self;
fn add(self, rhs: Self) -> Self {
self.plus(rhs)
}
}
/// Returns the thermal noise power in a bandwidth at a noise temperature.
///
/// The relation is `P = k T B`, with `k` the Boltzmann constant. This is the
/// power that a matched resistor at the given temperature would deliver to a
/// load over the stated bandwidth.
///
/// The bandwidth must be positive. Returns `None` for an invalid
/// temperature or bandwidth.
pub fn thermal_noise_power(temperature: NoiseTemperature, bandwidth: f64) -> Option<Power> {
if !temperature.is_valid() {
return None;
}
if !bandwidth.is_finite() || bandwidth <= 0.0 {
return None;
}
Some(Power::from_watts(
BOLTZMANN * temperature.kelvin() * bandwidth,
))
}