use num_complex::Complex64;
use std::f64::consts::PI;
const HBAR: f64 = 1.054_571_817e-34;
#[derive(Debug, Clone)]
pub struct BesselBeam {
pub order: u32,
pub cone_angle: f64,
pub wavelength: f64,
pub power: f64,
}
impl BesselBeam {
pub fn new(order: u32, cone_angle_deg: f64, wavelength: f64) -> Self {
Self {
order,
cone_angle: cone_angle_deg.to_radians(),
wavelength,
power: 1.0,
}
}
fn k_total(&self) -> f64 {
2.0 * PI / self.wavelength
}
pub fn k_r(&self) -> f64 {
self.k_total() * self.cone_angle.sin()
}
pub fn k_z(&self) -> f64 {
self.k_total() * self.cone_angle.cos()
}
pub fn field(&self, r: f64, phi: f64, z: f64) -> Complex64 {
let jn = Self::bessel_j(self.order, self.k_r() * r);
let longitudinal = Complex64::from_polar(1.0, self.k_z() * z);
let azimuthal = Complex64::from_polar(1.0, self.order as f64 * phi);
Complex64::new(jn, 0.0) * azimuthal * longitudinal
}
pub fn intensity(&self, r: f64, _z: f64) -> f64 {
let jn = Self::bessel_j(self.order, self.k_r() * r);
jn * jn
}
pub fn central_spot_size(&self) -> f64 {
let kr = self.k_r();
if kr < 1e-30 {
return f64::INFINITY;
}
let first_zeros = [
2.404_825, 3.831_706, 5.135_622, 6.380_162, 7.588_342, 8.771_484,
];
let zero = if (self.order as usize) < first_zeros.len() {
first_zeros[self.order as usize]
} else {
let nf = self.order as f64;
nf + 1.8557 * nf.powf(1.0 / 3.0)
};
zero / kr
}
pub fn nondiffracting_range(&self, aperture: f64) -> f64 {
let tan_theta = self.cone_angle.tan();
if tan_theta.abs() < 1e-30 {
return f64::INFINITY;
}
aperture / (2.0 * tan_theta)
}
pub fn oam_per_photon(&self) -> f64 {
self.order as f64 * HBAR
}
pub fn bessel_j(n: u32, x: f64) -> f64 {
if x.abs() < 1e-30 {
return if n == 0 { 1.0 } else { 0.0 };
}
let half_x = x / 2.0;
let mut term = {
let mut t = 1.0_f64;
for k in 1..=(n as usize) {
t *= half_x / k as f64;
}
t
};
let mut sum = term;
for k in 1_usize..=80 {
term *= -(half_x * half_x) / (k as f64 * (n as f64 + k as f64));
sum += term;
if term.abs() < sum.abs() * 1e-16 {
break;
}
}
sum
}
}
#[derive(Debug, Clone)]
pub struct BesselGaussBeam {
pub order: u32,
pub cone_angle: f64,
pub beam_waist: f64,
pub wavelength: f64,
}
impl BesselGaussBeam {
pub fn new(order: u32, cone_angle_deg: f64, w0: f64, wavelength: f64) -> Self {
Self {
order,
cone_angle: cone_angle_deg.to_radians(),
beam_waist: w0,
wavelength,
}
}
fn k_total(&self) -> f64 {
2.0 * PI / self.wavelength
}
fn k_r(&self) -> f64 {
self.k_total() * self.cone_angle.sin()
}
fn k_z(&self) -> f64 {
self.k_total() * self.cone_angle.cos()
}
fn rayleigh_range(&self) -> f64 {
PI * self.beam_waist * self.beam_waist / self.wavelength
}
fn beam_radius_at(&self, z: f64) -> f64 {
let zr = self.rayleigh_range();
self.beam_waist * (1.0 + (z / zr).powi(2)).sqrt()
}
pub fn field(&self, r: f64, z: f64) -> Complex64 {
let w = self.beam_radius_at(z);
let jn = BesselBeam::bessel_j(self.order, self.k_r() * r);
let gauss = (-r * r / (w * w)).exp();
let phase = self.k_z() * z;
Complex64::new(jn * gauss, 0.0) * Complex64::from_polar(1.0, phase)
}
pub fn intensity(&self, r: f64, z: f64) -> f64 {
self.field(r, z).norm_sqr()
}
pub fn nondiffracting_range(&self) -> f64 {
let tan_theta = self.cone_angle.tan();
if tan_theta.abs() < 1e-30 {
return f64::INFINITY;
}
self.beam_waist / tan_theta
}
pub fn m_squared(&self) -> f64 {
let kr_w0 = self.k_r() * self.beam_waist;
1.0 + kr_w0 * kr_w0 / 2.0
}
}
#[derive(Debug, Clone)]
pub struct AiryBeam {
pub scale: f64,
pub truncation: f64,
pub wavelength: f64,
}
impl AiryBeam {
pub fn new(scale_um: f64, truncation: f64, wavelength: f64) -> Self {
Self {
scale: scale_um * 1e-6,
truncation,
wavelength,
}
}
pub fn airy_function(x: f64) -> f64 {
const AI0: f64 = 0.355_028_053_887_817; const AIP0: f64 = -0.258_819_403_792_807;
if x.abs() <= 4.0 {
let x3 = x * x * x;
let mut f_sum = 1.0_f64;
let mut g_sum = x;
let mut f_term = 1.0_f64;
let mut g_term = x;
for k in 1_usize..=30 {
let kf = k as f64;
f_term *= x3 / ((3.0 * kf) * (3.0 * kf - 1.0));
f_sum += f_term;
g_term *= x3 / ((3.0 * kf) * (3.0 * kf + 1.0));
g_sum += g_term;
if f_term.abs().max(g_term.abs()) < 1e-16 {
break;
}
}
AI0 * f_sum + AIP0 * g_sum
} else if x > 4.0 {
let xi = (2.0 / 3.0) * x.powf(1.5);
let prefactor = 0.5 / (PI.sqrt()) * x.powf(-0.25);
let corr = 1.0 - 5.0 / (72.0 * xi);
prefactor * (-xi).exp() * corr
} else {
let abs_x = x.abs();
let xi = (2.0 / 3.0) * abs_x.powf(1.5);
let prefactor = 1.0 / (PI.sqrt()) * abs_x.powf(-0.25);
prefactor * (xi + PI / 4.0).sin()
}
}
pub fn field_1d(&self, x: f64, z: f64) -> Complex64 {
let x0 = self.scale;
let k = 2.0 * PI / self.wavelength;
let length = k * x0 * x0;
let zeta = z / length;
let xi = x / x0 - zeta * zeta;
let ai = Self::airy_function(xi);
let truncation_factor = (self.truncation * xi).exp();
let phase = zeta * (x / x0) - zeta * zeta * zeta / 3.0
+ self.truncation * self.truncation * zeta / 2.0;
Complex64::new(ai * truncation_factor, 0.0) * Complex64::from_polar(1.0, phase)
}
pub fn trajectory_peak(&self, z: f64) -> f64 {
let x0 = self.scale;
let k = 2.0 * PI / self.wavelength;
let length = k * x0 * x0;
let zeta = z / length;
zeta * zeta * x0
}
pub fn self_healing_distance(&self, obstacle_size: f64) -> f64 {
let theta = self.wavelength / (2.0 * PI * self.scale);
if theta.abs() < 1e-30 {
return f64::INFINITY;
}
obstacle_size / theta.tan()
}
pub fn invariant_range(&self) -> f64 {
if self.truncation.abs() < 1e-20 {
return f64::INFINITY;
}
let k = 2.0 * PI / self.wavelength;
k * self.scale * self.scale / self.truncation
}
}
#[derive(Debug, Clone)]
pub enum VectorBeamType {
RadiallyPolarized,
AzimuthallyPolarized,
Hybrid {
phase_offset: f64,
},
}
#[derive(Debug, Clone)]
pub struct VectorBeam {
pub polarization_order: u32,
pub beam_type: VectorBeamType,
pub beam_waist: f64,
pub wavelength: f64,
}
impl VectorBeam {
pub fn new_radial(w0: f64, wavelength: f64) -> Self {
Self {
polarization_order: 1,
beam_type: VectorBeamType::RadiallyPolarized,
beam_waist: w0,
wavelength,
}
}
pub fn new_azimuthal(w0: f64, wavelength: f64) -> Self {
Self {
polarization_order: 1,
beam_type: VectorBeamType::AzimuthallyPolarized,
beam_waist: w0,
wavelength,
}
}
pub fn jones_vector(&self, phi: f64) -> [f64; 2] {
let m = self.polarization_order as f64;
match &self.beam_type {
VectorBeamType::RadiallyPolarized => [(m * phi).cos(), (m * phi).sin()],
VectorBeamType::AzimuthallyPolarized => [-(m * phi).sin(), (m * phi).cos()],
VectorBeamType::Hybrid { phase_offset } => {
let alpha = m * phi + phase_offset;
[alpha.cos(), alpha.sin()]
}
}
}
pub fn longitudinal_field_fraction(&self, na: f64) -> f64 {
let sin2 = na.min(1.0).powi(2);
match &self.beam_type {
VectorBeamType::RadiallyPolarized => sin2 * (1.0 - sin2 / 4.0),
VectorBeamType::AzimuthallyPolarized => 0.0,
VectorBeamType::Hybrid { .. } => sin2 * 0.5 * (1.0 - sin2 / 4.0),
}
}
fn rayleigh_range(&self) -> f64 {
PI * self.beam_waist * self.beam_waist / self.wavelength
}
fn beam_radius(&self, z: f64) -> f64 {
let zr = self.rayleigh_range();
self.beam_waist * (1.0 + (z / zr).powi(2)).sqrt()
}
pub fn intensity(&self, r: f64, z: f64) -> f64 {
let w = self.beam_radius(z);
let m = self.polarization_order as f64;
let radial = (r / w).powf(m) * (-r * r / (w * w)).exp();
radial * radial
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn bessel_j0_at_zero_is_one() {
assert_abs_diff_eq!(BesselBeam::bessel_j(0, 0.0), 1.0, epsilon = 1e-15);
}
#[test]
fn bessel_j1_at_zero_is_zero() {
assert_abs_diff_eq!(BesselBeam::bessel_j(1, 0.0), 0.0, epsilon = 1e-15);
}
#[test]
fn bessel_j0_known_value() {
let val = BesselBeam::bessel_j(0, 2.404_825);
assert_abs_diff_eq!(val, 0.0, epsilon = 1e-5);
}
#[test]
fn bessel_j1_known_value() {
let val = BesselBeam::bessel_j(1, 1.0);
assert_abs_diff_eq!(val, 0.440_5, epsilon = 1e-3);
}
#[test]
fn bessel_j0_large_x() {
let val = BesselBeam::bessel_j(0, 10.0);
assert_abs_diff_eq!(val, -0.245_936, epsilon = 1e-4);
}
#[test]
fn bessel_beam_kz_positive() {
let bb = BesselBeam::new(0, 5.0, 1064e-9);
assert!(bb.k_z() > 0.0);
assert!(bb.k_r() > 0.0);
let k = 2.0 * PI / bb.wavelength;
assert_abs_diff_eq!(
bb.k_r() * bb.k_r() + bb.k_z() * bb.k_z(),
k * k,
epsilon = 1.0
);
}
#[test]
fn bessel_beam_oam_zero_order() {
let bb = BesselBeam::new(0, 5.0, 1064e-9);
assert_abs_diff_eq!(bb.oam_per_photon(), 0.0, epsilon = 1e-50);
}
#[test]
fn bessel_beam_nondiffracting_range() {
let bb = BesselBeam::new(0, 1.0, 1064e-9); let range = bb.nondiffracting_range(0.01); assert!(range > 0.0 && range.is_finite());
}
#[test]
fn bessel_gauss_m_squared_gt1() {
let bg = BesselGaussBeam::new(0, 5.0, 1e-3, 1064e-9);
assert!(bg.m_squared() >= 1.0);
}
#[test]
fn bessel_gauss_nondiffracting_range() {
let bg = BesselGaussBeam::new(0, 5.0, 1e-3, 1064e-9);
let range = bg.nondiffracting_range();
assert!(range > 0.0 && range.is_finite());
}
#[test]
fn airy_at_zero() {
let val = AiryBeam::airy_function(0.0);
assert_abs_diff_eq!(val, 0.355_028, epsilon = 1e-5);
}
#[test]
fn airy_large_positive_decays() {
let v1 = AiryBeam::airy_function(5.0).abs();
let v2 = AiryBeam::airy_function(10.0).abs();
assert!(v1 > v2, "Airy function should decay for large positive x");
}
#[test]
fn airy_trajectory_parabolic() {
let beam = AiryBeam::new(100.0, 0.1, 800e-9);
assert_abs_diff_eq!(beam.trajectory_peak(0.0), 0.0, epsilon = 1e-20);
let p1 = beam.trajectory_peak(0.01);
let p2 = beam.trajectory_peak(0.02);
assert!(p2 > p1);
}
#[test]
fn radial_jones_at_zero_phi() {
let vb = VectorBeam::new_radial(1e-3, 1064e-9);
let jv = vb.jones_vector(0.0);
assert_abs_diff_eq!(jv[0], 1.0, epsilon = 1e-14);
assert_abs_diff_eq!(jv[1], 0.0, epsilon = 1e-14);
}
#[test]
fn azimuthal_jones_at_zero_phi() {
let vb = VectorBeam::new_azimuthal(1e-3, 1064e-9);
let jv = vb.jones_vector(0.0);
assert_abs_diff_eq!(jv[0], 0.0, epsilon = 1e-14);
assert_abs_diff_eq!(jv[1], 1.0, epsilon = 1e-14);
}
#[test]
fn radial_longitudinal_fraction_increases_with_na() {
let vb = VectorBeam::new_radial(1e-3, 1064e-9);
let f_low = vb.longitudinal_field_fraction(0.5);
let f_high = vb.longitudinal_field_fraction(0.9);
assert!(f_high > f_low);
}
#[test]
fn azimuthal_no_longitudinal_field() {
let vb = VectorBeam::new_azimuthal(1e-3, 1064e-9);
assert_abs_diff_eq!(vb.longitudinal_field_fraction(0.9), 0.0, epsilon = 1e-15);
}
}