use serde::{Deserialize, Serialize};
use std::f64::consts::PI;
use super::conversion::{BOLTZMANN, ELECTRON_CHARGE, ELECTRON_MASS, SPEED_OF_LIGHT, Z0};
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Wavelength(pub f64);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Frequency(pub f64);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct WaveNumber(pub f64);
impl Wavelength {
pub fn from_nm(nm: f64) -> Self {
Wavelength(nm * 1e-9)
}
pub fn as_nm(self) -> f64 {
self.0 * 1e9
}
pub fn from_um(um: f64) -> Self {
Wavelength(um * 1e-6)
}
pub fn as_um(self) -> f64 {
self.0 * 1e6
}
pub fn to_frequency(self) -> Frequency {
Frequency(SPEED_OF_LIGHT / self.0)
}
pub fn to_wavenumber(self) -> WaveNumber {
WaveNumber(2.0 * PI / self.0)
}
}
impl Frequency {
pub fn to_wavelength(self) -> Wavelength {
Wavelength(SPEED_OF_LIGHT / self.0)
}
pub fn to_wavenumber(self) -> WaveNumber {
WaveNumber(2.0 * PI * self.0 / SPEED_OF_LIGHT)
}
}
impl WaveNumber {
pub fn to_wavelength(self) -> Wavelength {
Wavelength(2.0 * PI / self.0)
}
pub fn to_frequency(self) -> Frequency {
Frequency(self.0 * SPEED_OF_LIGHT / (2.0 * PI))
}
}
pub fn skin_depth(sigma: f64, mu_r: f64, freq_hz: f64) -> f64 {
let mu_0 = 4.0 * PI * 1e-7; (1.0 / (PI * freq_hz * mu_r * mu_0 * sigma)).sqrt()
}
pub fn plasma_frequency(n_carriers: f64, mass_ratio: f64) -> f64 {
let eps_0 = 8.854_187_812_8e-12;
(n_carriers * ELECTRON_CHARGE * ELECTRON_CHARGE / (eps_0 * ELECTRON_MASS * mass_ratio)).sqrt()
}
pub fn debye_length(temperature_k: f64, n_carriers: f64) -> f64 {
let eps_0 = 8.854_187_812_8e-12;
(eps_0 * BOLTZMANN * temperature_k / (n_carriers * ELECTRON_CHARGE * ELECTRON_CHARGE)).sqrt()
}
pub fn wave_impedance(eps_r: f64, mu_r: f64) -> f64 {
Z0 * (mu_r / eps_r).sqrt()
}
pub fn quality_factor(f0_hz: f64, fwhm_hz: f64) -> f64 {
f0_hz / fwhm_hz
}
pub fn loaded_q(q_unloaded: f64, coupling: f64) -> f64 {
q_unloaded / (1.0 + coupling)
}
pub fn coupling_from_q(q_loaded: f64, q_unloaded: f64) -> f64 {
q_unloaded / q_loaded - 1.0
}
pub fn hertzian_dipole_radiation_resistance(length_m: f64, lambda_m: f64) -> f64 {
80.0 * PI * PI * (length_m / lambda_m).powi(2)
}
pub fn hertzian_dipole_directivity() -> f64 {
1.5
}
pub fn effective_aperture(directivity: f64, lambda_m: f64) -> f64 {
directivity * lambda_m * lambda_m / (4.0 * PI)
}
pub fn noise_figure_db(signal_in: f64, noise_in: f64, signal_out: f64, noise_out: f64) -> f64 {
let snr_in = signal_in / noise_in;
let snr_out = signal_out / noise_out;
10.0 * (snr_in / snr_out).log10()
}
pub fn johnson_noise_psd(temperature_k: f64) -> f64 {
BOLTZMANN * temperature_k
}
pub fn check_tangential_e_continuity(e_tan1: f64, e_tan2: f64, tolerance: f64) -> bool {
(e_tan1 - e_tan2).abs() < tolerance
}
pub fn normal_d_jump(eps1: f64, en1: f64, eps2: f64, en2: f64) -> f64 {
eps1 * en1 - eps2 * en2
}
pub fn rectangular_waveguide_kz(k0: f64, a: f64, m: u32) -> Option<f64> {
let kc_sq = (m as f64 * PI / a).powi(2);
let kz_sq = k0 * k0 - kc_sq;
if kz_sq < 0.0 {
None
} else {
Some(kz_sq.sqrt())
}
}
pub fn rectangular_waveguide_cutoff_te(a: f64, b: f64, m: u32, n: u32) -> f64 {
let kc = ((m as f64 * PI / a).powi(2) + (n as f64 * PI / b).powi(2)).sqrt();
SPEED_OF_LIGHT * kc / (2.0 * PI)
}
pub fn circular_waveguide_te11_cutoff(radius: f64) -> f64 {
SPEED_OF_LIGHT * 1.841_18 / (2.0 * PI * radius)
}
pub fn circular_waveguide_tm01_cutoff(radius: f64) -> f64 {
SPEED_OF_LIGHT * 2.404_83 / (2.0 * PI * radius)
}
pub fn rectangular_waveguide_te10_power(e_max: f64, a: f64, b: f64, kz: f64, k0: f64) -> f64 {
let z_te = Z0 * k0 / kz;
a * b * e_max * e_max / (4.0 * z_te)
}
pub fn group_velocity(omega: f64, k: f64, d_omega: f64, d_k: f64) -> f64 {
let _ = (omega, k); if d_k.abs() < 1e-30 {
SPEED_OF_LIGHT
} else {
d_omega / d_k
}
}
pub fn phase_velocity(omega: f64, k: f64) -> f64 {
omega / k
}
pub fn waveguide_phase_velocity(omega: f64, kz: f64) -> f64 {
omega / kz
}
pub fn waveguide_group_velocity(omega: f64, kz: f64) -> f64 {
SPEED_OF_LIGHT * SPEED_OF_LIGHT * kz / omega
}
pub fn fresnel_power_coefficients(n1: f64, n2: f64, theta_i_rad: f64) -> (f64, f64) {
let sin_t = n1 / n2 * theta_i_rad.sin();
if sin_t.abs() > 1.0 {
return (1.0, 1.0); }
let cos_i = theta_i_rad.cos();
let cos_t = (1.0 - sin_t * sin_t).sqrt();
let rs = ((n1 * cos_i - n2 * cos_t) / (n1 * cos_i + n2 * cos_t)).powi(2);
let rp = ((n2 * cos_i - n1 * cos_t) / (n2 * cos_i + n1 * cos_t)).powi(2);
(rs, rp)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn wavelength_nm_roundtrip() {
let wl = Wavelength::from_nm(1550.0);
assert_relative_eq!(wl.as_nm(), 1550.0, epsilon = 1e-10);
}
#[test]
fn wavelength_um_roundtrip() {
let wl = Wavelength::from_um(1.55);
assert_relative_eq!(wl.as_um(), 1.55, epsilon = 1e-10);
}
#[test]
fn wavelength_frequency_roundtrip() {
let wl = Wavelength::from_nm(1550.0);
let f = wl.to_frequency();
let wl2 = f.to_wavelength();
assert_relative_eq!(wl.0, wl2.0, epsilon = 1e-20);
}
#[test]
fn wavelength_wavenumber_roundtrip() {
let wl = Wavelength::from_nm(632.8);
let k = wl.to_wavenumber();
let wl2 = k.to_wavelength();
assert_relative_eq!(wl.0, wl2.0, epsilon = 1e-20);
}
#[test]
fn frequency_wavenumber_roundtrip() {
let f = Frequency(193.4e12);
let k = f.to_wavenumber();
let f2 = k.to_frequency();
assert_relative_eq!(f.0, f2.0, epsilon = 1e-2);
}
#[test]
fn skin_depth_copper_1ghz() {
let delta = skin_depth(5.8e7, 1.0, 1e9);
assert!(delta > 1.5e-6 && delta < 3.0e-6, "δ(Cu,1GHz)={delta:.2e} m");
}
#[test]
fn skin_depth_decreases_with_freq() {
let d1 = skin_depth(5.8e7, 1.0, 1e6);
let d2 = skin_depth(5.8e7, 1.0, 1e9);
assert!(d1 > d2, "Skin depth should decrease with frequency");
}
#[test]
fn plasma_frequency_electron_density() {
let wp = plasma_frequency(1e18, 1.0);
assert!(wp > 1e8 && wp < 1e11, "ω_p={wp:.2e}");
}
#[test]
fn debye_length_basic() {
let ld = debye_length(300.0, 1e15);
assert!(ld > 1e-7 && ld < 1e-2, "λ_D={ld:.2e} m");
}
#[test]
fn wave_impedance_vacuum() {
let eta = wave_impedance(1.0, 1.0);
assert_relative_eq!(eta, Z0, epsilon = 1e-10);
}
#[test]
fn wave_impedance_glass() {
let eta = wave_impedance(1.5 * 1.5, 1.0);
assert_relative_eq!(eta, Z0 / 1.5, epsilon = 1e-6);
}
#[test]
fn quality_factor_basic() {
let q = quality_factor(193e12, 193e6); assert_relative_eq!(q, 1e6, epsilon = 1.0);
}
#[test]
fn loaded_q_basic() {
let ql = loaded_q(1e6, 1.0); assert_relative_eq!(ql, 5e5, epsilon = 1.0);
}
#[test]
fn hertzian_dipole_rrad() {
let r = hertzian_dipole_radiation_resistance(0.01, 1.0);
assert_relative_eq!(r, 80.0 * PI * PI * 1e-4, epsilon = 1e-10);
}
#[test]
fn waveguide_cutoff_te10() {
let fc = rectangular_waveguide_cutoff_te(22.86e-3, 10.16e-3, 1, 0);
assert_relative_eq!(fc, 6.557e9, epsilon = 0.01e9);
}
#[test]
fn waveguide_kz_below_cutoff_is_none() {
let k0 = 2.0 * PI * 5e9 / SPEED_OF_LIGHT;
let result = rectangular_waveguide_kz(k0, 22.86e-3, 1);
assert!(result.is_none());
}
#[test]
fn waveguide_kz_above_cutoff() {
let k0 = 2.0 * PI * 10e9 / SPEED_OF_LIGHT;
let kz = rectangular_waveguide_kz(k0, 22.86e-3, 1).expect("propagating mode");
assert!(kz > 0.0);
}
#[test]
fn circular_waveguide_te11() {
let fc = circular_waveguide_te11_cutoff(10e-3);
assert_relative_eq!(fc, 8.79e9, epsilon = 0.01e9);
}
#[test]
fn phase_velocity_equals_c_in_vacuum() {
let omega = 2.0 * PI * 300e12;
let k = omega / SPEED_OF_LIGHT;
let vph = phase_velocity(omega, k);
assert_relative_eq!(vph, SPEED_OF_LIGHT, epsilon = 1.0);
}
#[test]
fn fresnel_tir_at_grazing() {
let (rs, rp) = fresnel_power_coefficients(1.5, 1.0, std::f64::consts::FRAC_PI_2);
assert_relative_eq!(rs, 1.0, epsilon = 1e-10);
assert_relative_eq!(rp, 1.0, epsilon = 1e-10);
}
#[test]
fn johnson_noise_psd_room_temp() {
let n0 = johnson_noise_psd(290.0);
assert_relative_eq!(n0, 4.0e-21, epsilon = 0.1e-21);
}
#[test]
fn boundary_condition_passes() {
assert!(check_tangential_e_continuity(1.0, 1.0 + 1e-10, 1e-9));
}
#[test]
fn boundary_condition_fails() {
assert!(!check_tangential_e_continuity(1.0, 2.0, 1e-9));
}
}
#[cfg(test)]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn wavelength_frequency_roundtrip(wl_nm in 100.0..20000.0_f64) {
let wl = Wavelength::from_nm(wl_nm);
let f = wl.to_frequency();
let wl2 = f.to_wavelength();
prop_assert!((wl.0 - wl2.0).abs() / wl.0 < 1e-12);
}
#[test]
fn wavelength_wavenumber_roundtrip(wl_nm in 100.0..20000.0_f64) {
let wl = Wavelength::from_nm(wl_nm);
let k = wl.to_wavenumber();
let wl2 = k.to_wavelength();
prop_assert!((wl.0 - wl2.0).abs() / wl.0 < 1e-12);
}
#[test]
fn frequency_wavenumber_roundtrip(freq in 1e12..3e15_f64) {
let f = Frequency(freq);
let k = f.to_wavenumber();
let f2 = k.to_frequency();
prop_assert!((f.0 - f2.0).abs() / f.0 < 1e-12);
}
}
}