use std::f64::consts::PI;
use lox_core::units::{Angle, Decibel, Distance, Frequency};
use crate::antenna::AntennaGain;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct GaussianPattern {
pub diameter: Distance,
pub efficiency: f64,
}
impl GaussianPattern {
pub fn new(diameter: Distance, efficiency: f64) -> Self {
Self {
diameter,
efficiency,
}
}
pub fn peak_gain(&self, frequency: Frequency) -> Decibel {
let wavelength_m = frequency.wavelength().to_meters();
let d = self.diameter.to_meters();
let gain_linear = self.efficiency * (PI * d / wavelength_m).powi(2);
Decibel::from_linear(gain_linear)
}
}
impl AntennaGain for GaussianPattern {
fn gain(&self, frequency: Frequency, angle: Angle) -> Decibel {
let theta = angle.to_radians();
let bw = self.beamwidth(frequency).unwrap().to_radians();
let exponent = -4.0 * 2.0_f64.ln() * (theta / bw).powi(2);
self.peak_gain(frequency) + Decibel::from_linear(exponent.exp())
}
fn beamwidth(&self, frequency: Frequency) -> Option<Angle> {
let wavelength_m = frequency.wavelength().to_meters();
let d = self.diameter.to_meters();
Some(Angle::degrees(70.0 * wavelength_m / d))
}
}
#[cfg(test)]
mod tests {
use lox_core::units::FrequencyUnits;
use lox_test_utils::assert_approx_eq;
use super::*;
fn test_frequency() -> Frequency {
29.0.ghz()
}
fn test_pattern() -> GaussianPattern {
GaussianPattern::new(Distance::meters(0.98), 0.45)
}
#[test]
fn test_gaussian_peak_gain_matches_parabolic() {
let p = test_pattern();
let peak = p.peak_gain(test_frequency());
assert_approx_eq!(peak.as_f64(), 46.01119000490658, rtol <= 1e-6);
}
#[test]
fn test_gaussian_on_axis_equals_peak() {
let p = test_pattern();
let gain = p.gain(test_frequency(), Angle::radians(0.0));
let peak = p.peak_gain(test_frequency());
assert_approx_eq!(gain.as_f64(), peak.as_f64(), atol <= 1e-10);
}
#[test]
fn test_gaussian_3db_down_at_half_beamwidth() {
let p = test_pattern();
let f = test_frequency();
let half_bw = Angle::radians(p.beamwidth(f).unwrap().to_radians() / 2.0);
let peak = p.peak_gain(f);
let gain_at_half_bw = p.gain(f, half_bw);
let diff = peak.as_f64() - gain_at_half_bw.as_f64();
assert_approx_eq!(diff, 3.0103, atol <= 0.01);
}
#[test]
fn test_gaussian_symmetric() {
let p = test_pattern();
let f = test_frequency();
let angle = Angle::degrees(1.0);
let gain_pos = p.gain(f, angle);
let gain_neg = p.gain(f, Angle::degrees(-1.0));
assert_approx_eq!(gain_pos.as_f64(), gain_neg.as_f64(), atol <= 1e-10);
}
#[test]
fn test_gaussian_monotonic_decrease() {
let p = test_pattern();
let f = test_frequency();
let g0 = p.gain(f, Angle::degrees(0.0));
let g1 = p.gain(f, Angle::degrees(0.5));
let g2 = p.gain(f, Angle::degrees(1.0));
let g3 = p.gain(f, Angle::degrees(2.0));
assert!(g0.as_f64() > g1.as_f64());
assert!(g1.as_f64() > g2.as_f64());
assert!(g2.as_f64() > g3.as_f64());
}
}