use std::f64::consts::PI;
pub struct MetalensPhaseFocusing {
pub focal_length: f64,
pub wavelength: f64,
pub aperture_radius: f64,
}
impl MetalensPhaseFocusing {
pub fn new(focal_length: f64, wavelength: f64, aperture_radius: f64) -> Self {
Self {
focal_length,
wavelength,
aperture_radius,
}
}
pub fn phase_continuous(&self, r: f64) -> f64 {
let f = self.focal_length;
let k0 = 2.0 * PI / self.wavelength;
-k0 * ((r * r + f * f).sqrt() - f)
}
pub fn phase_wrapped(&self, r: f64) -> f64 {
self.phase_continuous(r).rem_euclid(2.0 * PI)
}
pub fn phase_profile(&self, n_pts: usize) -> Vec<(f64, f64)> {
let dr = self.aperture_radius / n_pts as f64;
(0..n_pts)
.map(|i| {
let r = (i as f64 + 0.5) * dr;
let phi = self.phase_wrapped(r);
(r, phi)
})
.collect()
}
pub fn numerical_aperture(&self) -> f64 {
let theta = (self.aperture_radius / self.focal_length).atan();
theta.sin()
}
pub fn f_number(&self) -> f64 {
self.focal_length / (2.0 * self.aperture_radius)
}
pub fn airy_disk_radius(&self) -> f64 {
1.22 * self.wavelength * self.f_number()
}
pub fn fresnel_zone_radius(&self, m: usize) -> f64 {
let ml = m as f64 * self.wavelength;
(ml * self.focal_length + (ml / 2.0).powi(2)).sqrt()
}
pub fn n_zones(&self) -> usize {
let n = self.aperture_radius * self.aperture_radius / (self.wavelength * self.focal_length);
n.floor() as usize
}
pub fn phase_gradient(&self, r: f64) -> f64 {
let f = self.focal_length;
let k0 = 2.0 * PI / self.wavelength;
-k0 * r / (r * r + f * f).sqrt()
}
}
pub struct MetalensDeflector {
pub theta: f64,
pub wavelength: f64,
pub period: f64,
}
impl MetalensDeflector {
pub fn new(theta: f64, wavelength: f64) -> Self {
let period = if theta.abs() < 1e-10 {
f64::INFINITY
} else {
wavelength / theta.abs().sin()
};
Self {
theta,
wavelength,
period,
}
}
pub fn phase_at(&self, x: f64) -> f64 {
let k0 = 2.0 * PI / self.wavelength;
let phi = k0 * self.theta.sin() * x;
phi.rem_euclid(2.0 * PI)
}
pub fn grating_vector(&self) -> f64 {
2.0 * PI / self.period
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn metalens_phase_at_center_is_zero() {
let ml = MetalensPhaseFocusing::new(10e-3, 532e-9, 1e-3);
let phi = ml.phase_continuous(0.0);
assert!(
phi.abs() < 1e-10,
"Phase at center should be 0, got {phi:.2e}"
);
}
#[test]
fn metalens_phase_decreases_with_radius() {
let ml = MetalensPhaseFocusing::new(10e-3, 532e-9, 1e-3);
let phi0 = ml.phase_continuous(0.0);
let phi1 = ml.phase_continuous(0.5e-3);
assert!(
phi1 < phi0,
"Phase should decrease (become more negative) with r"
);
}
#[test]
fn metalens_wrapped_in_range() {
let ml = MetalensPhaseFocusing::new(10e-3, 532e-9, 1e-3);
for i in 0..20 {
let r = i as f64 * 0.05e-3;
let phi = ml.phase_wrapped(r);
assert!(
(0.0..2.0 * PI).contains(&phi),
"Wrapped phase={phi:.4} out of [0,2π)"
);
}
}
#[test]
fn metalens_na_physical() {
let ml = MetalensPhaseFocusing::new(10e-3, 532e-9, 1e-3);
let na = ml.numerical_aperture();
assert!(na > 0.0 && na < 1.0, "NA={na:.3}");
}
#[test]
fn metalens_airy_disk_positive() {
let ml = MetalensPhaseFocusing::new(10e-3, 532e-9, 1e-3);
let r_airy = ml.airy_disk_radius();
assert!(r_airy > 0.0);
}
#[test]
fn fresnel_zone_radii_increasing() {
let ml = MetalensPhaseFocusing::new(10e-3, 532e-9, 2e-3);
let r1 = ml.fresnel_zone_radius(1);
let r2 = ml.fresnel_zone_radius(2);
let r3 = ml.fresnel_zone_radius(3);
assert!(r1 < r2 && r2 < r3);
}
#[test]
fn phase_gradient_at_center_zero() {
let ml = MetalensPhaseFocusing::new(10e-3, 532e-9, 1e-3);
let grad = ml.phase_gradient(0.0);
assert!(grad.abs() < 1e-10);
}
#[test]
fn phase_gradient_magnitude_increases() {
let ml = MetalensPhaseFocusing::new(10e-3, 532e-9, 2e-3);
let g1 = ml.phase_gradient(0.5e-3).abs();
let g2 = ml.phase_gradient(1.0e-3).abs();
assert!(g2 > g1);
}
#[test]
fn deflector_period_physical() {
let theta = 15.0_f64.to_radians();
let d = MetalensDeflector::new(theta, 532e-9);
assert!(d.period > 0.0 && d.period > d.wavelength);
}
#[test]
fn deflector_phase_periodic() {
let theta = 15.0_f64.to_radians();
let d = MetalensDeflector::new(theta, 532e-9);
let phi0 = d.phase_at(0.0);
let phi1 = d.phase_at(d.period);
assert!(
(phi0 - phi1).abs() < 1e-6
|| (phi0 - phi1 + 2.0 * PI).abs() < 1e-6
|| (phi0 - phi1 - 2.0 * PI).abs() < 1e-6
);
}
}