use lox_core::units::{Angle, Decibel, Frequency, Kelvin};
use crate::ROOM_TEMPERATURE;
use crate::antenna::AntennaGain;
pub fn noise_figure_to_temperature(nf: Decibel) -> Kelvin {
ROOM_TEMPERATURE * (nf.to_linear() - 1.0)
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SimpleReceiver {
pub frequency: Frequency,
pub system_noise_temperature: Kelvin,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NoiseStage {
pub gain: Decibel,
pub noise_temperature: Kelvin,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ComplexReceiver {
pub frequency: Frequency,
pub antenna_noise_temperature: Kelvin,
pub stages: Vec<NoiseStage>,
pub demodulator_loss: Decibel,
pub implementation_loss: Decibel,
}
impl ComplexReceiver {
pub fn from_feed_loss_and_noise_figure(
frequency: Frequency,
antenna_noise_temperature: Kelvin,
feed_loss: Decibel,
receiver_noise_figure: Decibel,
receiver_gain: Decibel,
demodulator_loss: Decibel,
implementation_loss: Decibel,
) -> Self {
let feed_stage = NoiseStage {
gain: -feed_loss,
noise_temperature: ROOM_TEMPERATURE * (feed_loss.to_linear() - 1.0),
};
let rx_stage = NoiseStage {
gain: receiver_gain,
noise_temperature: noise_figure_to_temperature(receiver_noise_figure),
};
Self {
frequency,
antenna_noise_temperature,
stages: vec![feed_stage, rx_stage],
demodulator_loss,
implementation_loss,
}
}
pub fn from_lna_and_noise_figure(
frequency: Frequency,
antenna_noise_temperature: Kelvin,
lna_gain: Decibel,
lna_noise_temperature: Kelvin,
receiver_noise_figure: Decibel,
demodulator_loss: Decibel,
implementation_loss: Decibel,
) -> Self {
let lna_stage = NoiseStage {
gain: lna_gain,
noise_temperature: lna_noise_temperature,
};
let rx_stage = NoiseStage {
gain: Decibel::new(0.0),
noise_temperature: noise_figure_to_temperature(receiver_noise_figure),
};
Self {
frequency,
antenna_noise_temperature,
stages: vec![lna_stage, rx_stage],
demodulator_loss,
implementation_loss,
}
}
pub fn system_noise_temperature(&self) -> Kelvin {
let mut t_sys = self.antenna_noise_temperature;
let mut cumulative_gain_linear = 1.0;
for stage in &self.stages {
t_sys += stage.noise_temperature / cumulative_gain_linear;
cumulative_gain_linear *= stage.gain.to_linear();
}
t_sys
}
pub fn chain_gain(&self) -> Decibel {
self.stages
.iter()
.fold(Decibel::new(0.0), |acc, s| acc + s.gain)
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
pub enum Receiver {
Simple(SimpleReceiver),
Complex(ComplexReceiver),
}
impl Receiver {
pub fn system_noise_temperature(&self) -> Kelvin {
match self {
Receiver::Simple(r) => r.system_noise_temperature,
Receiver::Complex(r) => r.system_noise_temperature(),
}
}
pub fn total_gain(&self, antenna: &impl AntennaGain, angle: Angle) -> Decibel {
match self {
Receiver::Simple(r) => antenna.gain(r.frequency, angle),
Receiver::Complex(r) => {
antenna.gain(r.frequency, angle) - r.demodulator_loss - r.implementation_loss
}
}
}
pub fn gain_to_noise_temperature(&self, antenna: &impl AntennaGain, angle: Angle) -> Decibel {
let g_total = self.total_gain(antenna, angle);
let t_sys = self.system_noise_temperature();
g_total - Decibel::from_linear(t_sys)
}
pub fn frequency(&self) -> Frequency {
match self {
Receiver::Simple(r) => r.frequency,
Receiver::Complex(r) => r.frequency,
}
}
}
#[cfg(test)]
mod tests {
use lox_core::units::{DecibelUnits, FrequencyUnits};
use lox_test_utils::assert_approx_eq;
use crate::antenna::SimpleAntenna;
use super::*;
#[test]
fn test_noise_figure_to_temperature() {
assert_approx_eq!(
noise_figure_to_temperature(5.0.db()),
627.0605214,
rtol <= 1e-6
);
}
#[test]
fn test_from_feed_loss_and_noise_figure() {
let rx = ComplexReceiver::from_feed_loss_and_noise_figure(
29.0.ghz(),
265.0,
3.0.db(),
5.0.db(),
20.0.db(),
0.0.db(),
0.0.db(),
);
let t_sys = rx.system_noise_temperature();
let loss_linear = 10.0_f64.powf(-3.0 / 10.0);
let old_t_sys_output = 904.53084061;
assert_approx_eq!(t_sys, old_t_sys_output / loss_linear, rtol <= 1e-5);
}
#[test]
fn test_from_lna_and_noise_figure() {
let rx = ComplexReceiver::from_lna_and_noise_figure(
26.5.ghz(),
290.0,
20.0.db(),
175.0,
2.0.db(),
0.0.db(),
0.0.db(),
);
assert_approx_eq!(rx.system_noise_temperature(), 466.696, atol <= 0.01);
}
#[test]
fn test_simple_receiver_gt() {
let antenna = SimpleAntenna {
gain: 30.0.db(),
beamwidth: Angle::degrees(1.0),
};
let rx = Receiver::Simple(SimpleReceiver {
frequency: 29.0.ghz(),
system_noise_temperature: 500.0,
});
let gt = rx.gain_to_noise_temperature(&antenna, Angle::radians(0.0));
assert_approx_eq!(gt.as_f64(), 3.0103, atol <= 0.001);
}
#[test]
fn test_complex_receiver_three_stage() {
let rx = ComplexReceiver {
frequency: 8.0.ghz(),
antenna_noise_temperature: 100.0,
stages: vec![
NoiseStage {
gain: 20.0.db(),
noise_temperature: 50.0,
},
NoiseStage {
gain: (-3.0).db(),
noise_temperature: 290.0,
},
NoiseStage {
gain: 30.0.db(),
noise_temperature: 500.0,
},
],
demodulator_loss: 0.0.db(),
implementation_loss: 0.0.db(),
};
let g1 = 100.0_f64; let g2 = 10.0_f64.powf(-3.0 / 10.0); let expected = 100.0 + 50.0 + 290.0 / g1 + 500.0 / (g1 * g2);
assert_approx_eq!(rx.system_noise_temperature(), expected, rtol <= 1e-6);
}
#[test]
fn test_complex_receiver_chain_gain() {
let rx = ComplexReceiver {
frequency: 8.0.ghz(),
antenna_noise_temperature: 100.0,
stages: vec![
NoiseStage {
gain: 20.0.db(),
noise_temperature: 50.0,
},
NoiseStage {
gain: (-3.0).db(),
noise_temperature: 290.0,
},
NoiseStage {
gain: 30.0.db(),
noise_temperature: 500.0,
},
],
demodulator_loss: 0.0.db(),
implementation_loss: 0.0.db(),
};
assert_approx_eq!(rx.chain_gain().as_f64(), 47.0, atol <= 1e-10);
}
#[test]
fn test_complex_receiver_gt() {
let antenna = SimpleAntenna {
gain: 30.0.db(),
beamwidth: Angle::degrees(1.0),
};
let rx = Receiver::Complex(ComplexReceiver::from_lna_and_noise_figure(
29.0.ghz(),
150.0,
30.0.db(),
75.0,
0.01.db(), 1.0.db(),
0.5.db(),
));
let gt = rx.gain_to_noise_temperature(&antenna, Angle::radians(0.0));
assert!(gt.as_f64() > 0.0);
}
#[test]
fn test_complex_receiver_total_gain() {
let antenna = SimpleAntenna {
gain: 30.0.db(),
beamwidth: Angle::degrees(1.0),
};
let rx = Receiver::Complex(ComplexReceiver {
frequency: 29.0.ghz(),
antenna_noise_temperature: 265.0,
stages: vec![
NoiseStage {
gain: 20.0.db(),
noise_temperature: 75.0,
},
NoiseStage {
gain: 10.0.db(),
noise_temperature: 500.0,
},
],
demodulator_loss: 1.0.db(),
implementation_loss: 0.5.db(),
});
let g_total = rx.total_gain(&antenna, Angle::radians(0.0));
assert_approx_eq!(g_total.as_f64(), 28.5, atol <= 1e-10);
}
}