use std::f64::consts::PI;
const C_LIGHT: f64 = 2.99792458e8;
const EPS0: f64 = 8.854187817e-12;
const MU0: f64 = 1.2566370614e-6;
#[derive(Debug, Clone)]
pub struct DoubleNegativeMedium {
pub eps_r: f64,
pub mu_r: f64,
pub frequency_hz: f64,
}
impl DoubleNegativeMedium {
pub fn refractive_index(&self) -> f64 {
let sign = if self.eps_r < 0.0 && self.mu_r < 0.0 {
-1.0
} else {
1.0
};
sign * (self.eps_r.abs() * self.mu_r.abs()).sqrt()
}
pub fn impedance_ohm(&self) -> f64 {
let z0 = (MU0 / EPS0).sqrt(); (self.mu_r.abs() / self.eps_r.abs()).sqrt() * z0
}
pub fn phase_velocity(&self) -> f64 {
C_LIGHT / self.refractive_index()
}
pub fn group_velocity_positive(&self) -> bool {
true
}
pub fn refraction_angle_rad(&self, n_incident: f64, theta_inc_rad: f64) -> f64 {
(n_incident * theta_inc_rad.sin() / self.refractive_index())
.clamp(-1.0, 1.0)
.asin()
}
pub fn reflection_coeff_s(&self, theta_inc_rad: f64) -> f64 {
let z_vac = (MU0 / EPS0).sqrt();
let z_dnm = self.impedance_ohm();
let n1 = 1.0_f64;
let n2 = self.refractive_index();
let cos_i = theta_inc_rad.cos();
let sin_t_arg = if n2.abs() < 1e-30 {
0.0
} else {
(n1 * theta_inc_rad.sin() / n2).clamp(-1.0, 1.0)
};
let cos_t = (1.0 - sin_t_arg * sin_t_arg).max(0.0).sqrt();
let num = z_dnm * cos_i - z_vac * cos_t;
let den = z_dnm * cos_i + z_vac * cos_t;
if den.abs() < 1e-30 {
0.0
} else {
num / den
}
}
pub fn reflection_coeff_p(&self, theta_inc_rad: f64) -> f64 {
let z_vac = (MU0 / EPS0).sqrt();
let z_dnm = self.impedance_ohm();
let n1 = 1.0_f64;
let n2 = self.refractive_index();
let cos_i = theta_inc_rad.cos();
let sin_t_arg = if n2.abs() < 1e-30 {
0.0
} else {
(n1 * theta_inc_rad.sin() / n2).clamp(-1.0, 1.0)
};
let cos_t = (1.0 - sin_t_arg * sin_t_arg).max(0.0).sqrt();
let num = z_vac * cos_i - z_dnm * cos_t;
let den = z_vac * cos_i + z_dnm * cos_t;
if den.abs() < 1e-30 {
0.0
} else {
num / den
}
}
pub fn is_backward_wave(&self) -> bool {
self.refractive_index() < 0.0
}
pub fn is_perfect_lens(&self) -> bool {
(self.eps_r + 1.0).abs() < 0.01 && (self.mu_r + 1.0).abs() < 0.01
}
pub fn omega(&self) -> f64 {
2.0 * PI * self.frequency_hz
}
pub fn k0(&self) -> f64 {
self.omega() / C_LIGHT
}
pub fn wave_number(&self) -> f64 {
self.refractive_index() * self.k0()
}
}
#[derive(Debug, Clone)]
pub struct SplitRingResonator {
pub ring_radius_m: f64,
pub wire_radius_m: f64,
pub gap_width_m: f64,
pub lattice_constant_m: f64,
pub conductivity: f64,
}
impl SplitRingResonator {
pub fn resonance_frequency_hz(&self) -> f64 {
let r = self.ring_radius_m;
let l = MU0 * PI * r * r;
let c = EPS0 * r * r / self.gap_width_m.max(1e-30);
1.0 / (2.0 * PI * (l * c).max(0.0).sqrt())
}
pub fn filling_fraction(&self) -> f64 {
PI * self.ring_radius_m * self.ring_radius_m
/ (self.lattice_constant_m * self.lattice_constant_m)
}
fn loss_rate_rad_s(&self) -> f64 {
let r = self.ring_radius_m;
let a = self.wire_radius_m;
let resistance = 2.0 * PI * r / (self.conductivity * PI * a * a).max(1e-60);
let l = MU0 * PI * r * r;
resistance / l.max(1e-60)
}
pub fn effective_permeability(&self, omega: f64) -> (f64, f64) {
let omega0 = 2.0 * PI * self.resonance_frequency_hz();
let f = self.filling_fraction();
let gamma = self.loss_rate_rad_s();
let d_re = omega * omega - omega0 * omega0;
let d_im = gamma * omega;
let d_mag2 = d_re * d_re + d_im * d_im;
if d_mag2 < 1e-60 {
return (1.0 - f, 0.0);
}
let num = f * omega * omega;
let mu_re = 1.0 - num * d_re / d_mag2;
let mu_im = num * d_im / d_mag2;
(mu_re, mu_im)
}
pub fn loss_tangent(&self, omega: f64) -> f64 {
let (mu_re, mu_im) = self.effective_permeability(omega);
if mu_re.abs() < 1e-30 {
f64::INFINITY
} else {
mu_im / mu_re
}
}
}
#[derive(Debug, Clone)]
pub struct DrudeWireArray {
pub wire_radius_m: f64,
pub lattice_constant_m: f64,
pub conductivity: f64,
}
impl DrudeWireArray {
pub fn plasma_frequency_hz(&self) -> f64 {
let a = self.lattice_constant_m;
let r = self.wire_radius_m;
let ratio = (a / r.max(1e-30)).max(1.0 + 1e-10);
let omega_p_sq = 2.0 * PI * C_LIGHT * C_LIGHT / (a * a * ratio.ln().max(1e-30));
omega_p_sq.sqrt() / (2.0 * PI)
}
pub fn effective_permittivity(&self, omega: f64) -> f64 {
if omega.abs() < 1e-30 {
return f64::NEG_INFINITY;
}
let omega_p = 2.0 * PI * self.plasma_frequency_hz();
1.0 - (omega_p * omega_p) / (omega * omega)
}
pub fn is_epsilon_negative(&self, omega: f64) -> bool {
self.effective_permittivity(omega) < 0.0
}
pub fn effective_index(&self, omega: f64) -> f64 {
self.effective_permittivity(omega).max(0.0).sqrt()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn double_negative_refraction() {
let dnm = DoubleNegativeMedium {
eps_r: -1.0,
mu_r: -1.0,
frequency_hz: 1e12,
};
assert!(
(dnm.refractive_index() + 1.0).abs() < 1e-10,
"Expected n = -1, got {}",
dnm.refractive_index()
);
assert!(dnm.is_backward_wave());
assert!(dnm.is_perfect_lens());
}
#[test]
fn impedance_z0_for_matched_medium() {
let dnm = DoubleNegativeMedium {
eps_r: -1.0,
mu_r: -1.0,
frequency_hz: 1e10,
};
let z0 = (MU0 / EPS0).sqrt();
assert!((dnm.impedance_ohm() - z0).abs() < 1.0);
}
#[test]
fn snell_negative_refraction_angle_sign() {
let dnm = DoubleNegativeMedium {
eps_r: -2.25,
mu_r: -1.0,
frequency_hz: 1e12,
};
let theta_t = dnm.refraction_angle_rad(1.0, 30_f64.to_radians());
assert!(
theta_t < 0.0,
"Expected negative refraction angle, got {}",
theta_t
);
}
#[test]
fn srr_resonance_frequency_positive() {
let srr = SplitRingResonator {
ring_radius_m: 1.5e-3,
wire_radius_m: 0.2e-3,
gap_width_m: 0.5e-3,
lattice_constant_m: 5.0e-3,
conductivity: 6e7,
};
let f0 = srr.resonance_frequency_hz();
assert!(
f0 > 0.0 && f0 < 1e12,
"Resonance frequency unrealistic: {}",
f0
);
}
#[test]
fn srr_negative_permeability_near_resonance() {
let srr = SplitRingResonator {
ring_radius_m: 1.5e-3,
wire_radius_m: 0.05e-3,
gap_width_m: 0.3e-3,
lattice_constant_m: 5.0e-3,
conductivity: 1e12, };
let omega0 = 2.0 * PI * srr.resonance_frequency_hz();
let (mu_re, _) = srr.effective_permeability(omega0 * 1.05);
assert!(
mu_re < 1.0,
"Expected μ_eff < 1 above resonance, got {}",
mu_re
);
}
#[test]
fn drude_wire_epsilon_negative_below_plasma() {
let wire = DrudeWireArray {
wire_radius_m: 1e-6,
lattice_constant_m: 5e-3,
conductivity: 6e7,
};
let omega_p = 2.0 * PI * wire.plasma_frequency_hz();
assert!(wire.is_epsilon_negative(omega_p * 0.5));
assert!(!wire.is_epsilon_negative(omega_p * 2.0));
}
#[test]
fn drude_wire_plasma_frequency_realistic() {
let wire = DrudeWireArray {
wire_radius_m: 1e-6,
lattice_constant_m: 5e-3,
conductivity: 6e7,
};
let fp = wire.plasma_frequency_hz();
assert!(
fp > 1e8 && fp < 1e13,
"Unexpected plasma frequency: {:.3e}",
fp
);
}
#[test]
fn reflection_coeff_normal_incidence() {
let dnm = DoubleNegativeMedium {
eps_r: -1.0,
mu_r: -1.0,
frequency_hz: 1e12,
};
let rs = dnm.reflection_coeff_s(0.0);
let rp = dnm.reflection_coeff_p(0.0);
assert!(
rs.abs() < 1e-10,
"r_s should be 0 for matched medium, got {}",
rs
);
assert!(
rp.abs() < 1e-10,
"r_p should be 0 for matched medium, got {}",
rp
);
}
}