use std::f64::consts::PI;
const C_LIGHT: f64 = 2.99792458e8;
#[derive(Debug, Clone)]
pub struct CylindricalCloak {
pub r1_m: f64,
pub r2_m: f64,
}
impl CylindricalCloak {
pub fn eps_r(&self, r_m: f64) -> f64 {
if r_m < self.r1_m || r_m > self.r2_m {
return 1.0;
}
(r_m - self.r1_m) / r_m
}
pub fn eps_theta(&self, r_m: f64) -> f64 {
if r_m < self.r1_m || r_m > self.r2_m {
return 1.0;
}
let denom = r_m - self.r1_m;
if denom.abs() < 1e-30 {
return f64::INFINITY;
}
r_m / denom
}
pub fn eps_z(&self) -> f64 {
let ratio = self.r2_m / (self.r2_m - self.r1_m).max(1e-30);
ratio * ratio
}
pub fn is_perfectly_matched_at_outer(&self) -> bool {
true
}
pub fn min_refractive_index(&self) -> f64 {
0.0
}
pub fn max_refractive_index_at(&self, dr: f64) -> f64 {
self.eps_theta(self.r1_m + dr.abs().max(1e-30)).sqrt()
}
pub fn phase_delay_rad(&self, _wavelength_m: f64) -> f64 {
0.0
}
pub fn scattering_cross_section_reduction(&self) -> f64 {
0.0
}
pub fn outer_optical_path_m(&self) -> f64 {
2.0 * self.r1_m
}
pub fn anisotropy_ratio(&self, r_m: f64) -> f64 {
let er = self.eps_r(r_m);
if er.abs() < 1e-30 {
return f64::INFINITY;
}
self.eps_theta(r_m) / er
}
}
#[derive(Debug, Clone)]
pub struct CarpetCloak {
pub bump_height_m: f64,
pub bump_width_m: f64,
pub n_background: f64,
}
impl CarpetCloak {
pub fn index_variation(&self) -> f64 {
self.bump_height_m / self.bump_width_m.max(1e-30)
}
pub fn is_feasible(&self) -> bool {
self.index_variation() < 0.5
}
pub fn bandwidth_fraction(&self) -> f64 {
let delta_n_over_n = self.index_variation();
if delta_n_over_n.abs() < 1e-30 || self.n_background < 1e-30 {
return f64::INFINITY;
}
let aspect = self.bump_width_m / self.bump_height_m.max(1e-30);
1.0 / (self.n_background * delta_n_over_n * aspect)
}
pub fn index_range(&self) -> (f64, f64) {
let delta_n = self.n_background * self.index_variation();
(
(self.n_background - delta_n).max(1.0),
self.n_background + delta_n,
)
}
}
#[derive(Debug, Clone)]
pub struct LuneburgLens {
pub radius_m: f64,
pub n_max: f64,
}
impl LuneburgLens {
pub fn index_at(&self, r_m: f64) -> f64 {
(2.0 - (r_m / self.radius_m.max(1e-30)).powi(2))
.max(0.0)
.sqrt()
}
pub fn focal_point_m(&self) -> f64 {
self.radius_m
}
pub fn numerical_aperture(&self) -> f64 {
1.0
}
pub fn average_index(&self, n_steps: usize) -> f64 {
let n = n_steps.max(2);
let dr = self.radius_m / n as f64;
let mut sum_n_r2 = 0.0_f64;
let mut sum_r2 = 0.0_f64;
for i in 0..n {
let r = (i as f64 + 0.5) * dr;
sum_n_r2 += self.index_at(r) * r * r;
sum_r2 += r * r;
}
if sum_r2 < 1e-60 {
self.n_max
} else {
sum_n_r2 / sum_r2
}
}
pub fn phase_delay_rad(&self, wavelength_m: f64, impact_param_m: f64) -> f64 {
let b = impact_param_m.abs().min(self.radius_m);
let r_entry = (self.radius_m * self.radius_m - b * b).max(0.0).sqrt();
let path_factor = self.index_at(r_entry);
2.0 * PI / wavelength_m.max(1e-30) * path_factor * 2.0 * r_entry
}
pub fn lambda_diameter_m(&self) -> f64 {
2.0 * self.radius_m
}
}
#[derive(Debug, Clone)]
pub struct MaxwellFishEye {
pub radius_m: f64,
pub n0: f64,
}
impl MaxwellFishEye {
pub fn index_at(&self, r_m: f64) -> f64 {
let rho = r_m / self.radius_m.max(1e-30);
2.0 * self.n0 / (1.0 + rho * rho)
}
pub fn index_at_edge(&self) -> f64 {
self.n0
}
pub fn phase_velocity_at(&self, r_m: f64) -> f64 {
C_LIGHT / self.index_at(r_m).max(1e-30)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cylindrical_cloak_eps_r_at_inner_boundary() {
let cloak = CylindricalCloak {
r1_m: 0.1,
r2_m: 0.2,
};
let eps = cloak.eps_r(0.1 + 1e-9);
assert!(
eps < 1e-8,
"eps_r should → 0 at inner boundary, got {}",
eps
);
}
#[test]
fn cylindrical_cloak_eps_theta_diverges_near_inner() {
let cloak = CylindricalCloak {
r1_m: 0.1,
r2_m: 0.2,
};
let eps = cloak.eps_theta(0.1 + 1e-6);
assert!(
eps > 1e4,
"eps_theta should be very large near R1, got {}",
eps
);
}
#[test]
fn cylindrical_cloak_outside_returns_unity() {
let cloak = CylindricalCloak {
r1_m: 0.1,
r2_m: 0.2,
};
assert_eq!(cloak.eps_r(0.0), 1.0);
assert_eq!(cloak.eps_r(0.3), 1.0);
assert_eq!(cloak.eps_theta(0.3), 1.0);
}
#[test]
fn carpet_cloak_feasibility() {
let carpet = CarpetCloak {
bump_height_m: 1e-3,
bump_width_m: 10e-3,
n_background: 1.5,
};
assert!(carpet.is_feasible());
let steep = CarpetCloak {
bump_height_m: 5e-3,
bump_width_m: 6e-3,
n_background: 1.5,
};
assert!(!steep.is_feasible());
}
#[test]
fn luneburg_index_profile() {
let lens = LuneburgLens {
radius_m: 1.0,
n_max: 2.0_f64.sqrt(),
};
let n_center = lens.index_at(0.0);
let n_edge = lens.index_at(1.0);
assert!(
(n_center - 2.0_f64.sqrt()).abs() < 1e-10,
"n(0) should be √2"
);
assert!((n_edge - 1.0).abs() < 1e-10, "n(R) should be 1");
}
#[test]
fn luneburg_focal_point_equals_radius() {
let lens = LuneburgLens {
radius_m: 0.05,
n_max: 2.0_f64.sqrt(),
};
assert_eq!(lens.focal_point_m(), 0.05);
assert_eq!(lens.numerical_aperture(), 1.0);
}
#[test]
fn maxwell_fish_eye_index() {
let mfe = MaxwellFishEye {
radius_m: 1.0,
n0: 2.0,
};
assert!((mfe.index_at(0.0) - 4.0).abs() < 1e-10, "n(0) = 2 n0");
assert!((mfe.index_at(1.0) - 2.0).abs() < 1e-10, "n(R) = n0");
assert_eq!(mfe.index_at_edge(), 2.0);
}
#[test]
fn cylindrical_cloak_phase_delay_ideal() {
let cloak = CylindricalCloak {
r1_m: 0.05,
r2_m: 0.10,
};
assert_eq!(cloak.phase_delay_rad(500e-9), 0.0);
assert_eq!(cloak.scattering_cross_section_reduction(), 0.0);
}
}