use lox_core::units::{Angle, Decibel, Distance, Frequency};
use crate::BOLTZMANN_CONSTANT;
use crate::antenna::Antenna;
use crate::receiver::Receiver;
use crate::transmitter::Transmitter;
use crate::utils::free_space_path_loss;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CommunicationSystem {
pub antenna: Antenna,
pub receiver: Option<Receiver>,
pub transmitter: Option<Transmitter>,
}
impl CommunicationSystem {
pub fn carrier_to_noise_density(
&self,
rx: &CommunicationSystem,
losses: Decibel,
range: Distance,
tx_angle: Angle,
rx_angle: Angle,
) -> Decibel {
let tx = self
.transmitter
.as_ref()
.expect("transmitting system must have a transmitter");
let receiver = rx
.receiver
.as_ref()
.expect("receiving system must have a receiver");
let eirp = tx.eirp(&self.antenna, tx_angle);
let gt = receiver.gain_to_noise_temperature(&rx.antenna, rx_angle);
let fspl = free_space_path_loss(range, tx.frequency);
let k_db = Decibel::from_linear(BOLTZMANN_CONSTANT);
eirp + gt - fspl - losses - k_db
}
pub fn carrier_power(
&self,
rx: &CommunicationSystem,
losses: Decibel,
range: Distance,
tx_angle: Angle,
rx_angle: Angle,
) -> Decibel {
let tx = self
.transmitter
.as_ref()
.expect("transmitting system must have a transmitter");
let receiver = rx
.receiver
.as_ref()
.expect("receiving system must have a receiver");
let eirp = tx.eirp(&self.antenna, tx_angle);
let fspl = free_space_path_loss(range, tx.frequency);
let g_rx = receiver.total_gain(&rx.antenna, rx_angle);
eirp - fspl - losses + g_rx
}
pub fn noise_power(&self, bandwidth_hz: f64) -> Decibel {
let receiver = self.receiver.as_ref().expect("system must have a receiver");
let t_sys = receiver.system_noise_temperature();
let p_noise_w = t_sys * BOLTZMANN_CONSTANT * bandwidth_hz;
Decibel::from_linear(p_noise_w)
}
pub fn tx_frequency(&self) -> Option<Frequency> {
self.transmitter.as_ref().map(|tx| tx.frequency)
}
pub fn rx_frequency(&self) -> Option<Frequency> {
self.receiver.as_ref().map(|rx| rx.frequency())
}
}
#[cfg(test)]
mod tests {
use lox_core::units::{DecibelUnits, FrequencyUnits};
use lox_test_utils::assert_approx_eq;
use crate::antenna::SimpleAntenna;
use crate::receiver::SimpleReceiver;
use super::*;
fn tx_system() -> CommunicationSystem {
CommunicationSystem {
antenna: Antenna::Simple(SimpleAntenna {
gain: 46.0.db(),
beamwidth: Angle::degrees(0.7),
}),
receiver: None,
transmitter: Some(Transmitter::new(29.0.ghz(), 10.0, 1.0.db(), 0.0.db())),
}
}
fn rx_system() -> CommunicationSystem {
CommunicationSystem {
antenna: Antenna::Simple(SimpleAntenna {
gain: 30.0.db(),
beamwidth: Angle::degrees(3.0),
}),
receiver: Some(Receiver::Simple(SimpleReceiver {
frequency: 29.0.ghz(),
system_noise_temperature: 500.0,
})),
transmitter: None,
}
}
#[test]
fn test_carrier_to_noise_density() {
let tx = tx_system();
let rx = rx_system();
let c_n0 = tx.carrier_to_noise_density(
&rx,
0.0.db(),
Distance::kilometers(1000.0),
Angle::radians(0.0),
Angle::radians(0.0),
);
assert_approx_eq!(c_n0.as_f64(), 104.913, atol <= 0.1);
}
#[test]
fn test_carrier_power() {
let tx = tx_system();
let rx = rx_system();
let p_rx = tx.carrier_power(
&rx,
0.0.db(),
Distance::kilometers(1000.0),
Angle::radians(0.0),
Angle::radians(0.0),
);
assert_approx_eq!(p_rx.as_f64(), -96.696, atol <= 0.1);
}
#[test]
fn test_noise_power() {
let rx = rx_system();
let p_noise = rx.noise_power(1e6);
assert_approx_eq!(p_noise.as_f64(), -141.61, atol <= 0.01);
}
#[test]
fn test_c_n0_consistency() {
let tx = tx_system();
let rx = rx_system();
let range = Distance::kilometers(1000.0);
let bw = 1e6;
let c_n0 = tx.carrier_to_noise_density(
&rx,
0.0.db(),
range,
Angle::radians(0.0),
Angle::radians(0.0),
);
let p_rx = tx.carrier_power(
&rx,
0.0.db(),
range,
Angle::radians(0.0),
Angle::radians(0.0),
);
let p_noise = rx.noise_power(bw);
let c_n0_from_power = p_rx - p_noise + Decibel::from_linear(bw);
assert_approx_eq!(c_n0.as_f64(), c_n0_from_power.as_f64(), atol <= 0.01);
}
}