use num_complex::Complex64;
use std::f64::consts::PI;
const HBAR: f64 = 1.054_571_817e-34;
#[derive(Debug, Clone)]
pub struct LgBeam {
pub ell: i32,
pub p: usize,
pub beam_waist: f64,
pub wavelength: f64,
pub power: f64,
}
impl LgBeam {
pub fn new(ell: i32, p: usize, w0: f64, wavelength: f64) -> Self {
Self {
ell,
p,
beam_waist: w0,
wavelength,
power: 1.0,
}
}
pub fn rayleigh_range(&self) -> f64 {
PI * self.beam_waist * self.beam_waist / self.wavelength
}
pub fn beam_radius(&self, z: f64) -> f64 {
let zr = self.rayleigh_range();
self.beam_waist * (1.0 + (z / zr).powi(2)).sqrt()
}
fn radius_of_curvature(&self, z: f64) -> f64 {
if z.abs() < 1e-30 {
return f64::INFINITY;
}
let zr = self.rayleigh_range();
z * (1.0 + (zr / z).powi(2))
}
pub fn gouy_phase(&self, z: f64) -> f64 {
let zr = self.rayleigh_range();
let order = 2.0 * self.p as f64 + self.ell.unsigned_abs() as f64 + 1.0;
order * (z / zr).atan()
}
fn normalization(&self) -> f64 {
let l_abs = self.ell.unsigned_abs() as f64;
let _p = self.p as f64;
let factorial_p = factorial(self.p);
let factorial_p_plus_l = factorial(self.p + self.ell.unsigned_abs() as usize);
(2.0 * self.power / (PI * self.beam_waist * self.beam_waist)
* factorial_p as f64 / factorial_p_plus_l as f64)
.sqrt()
* (2.0_f64).powf(l_abs / 2.0) }
pub fn field(&self, r: f64, phi: f64, z: f64) -> Complex64 {
let w = self.beam_radius(z);
let _zr = self.rayleigh_range();
let k = 2.0 * PI / self.wavelength;
let l_abs = self.ell.unsigned_abs() as usize;
let rho2 = 2.0 * r * r / (w * w);
let radial = (r / w).powi(l_abs as i32)
* (-r * r / (w * w)).exp()
* Self::laguerre_polynomial(self.p, l_abs as f64, rho2);
let gouy = self.gouy_phase(z);
let rc = self.radius_of_curvature(z);
let curvature_phase = if rc.is_finite() {
k * r * r / (2.0 * rc)
} else {
0.0
};
let w_factor = self.beam_waist / w;
let c = self.normalization();
let phase = k * z - gouy + (self.ell as f64) * phi - curvature_phase;
let amplitude = c * w_factor * radial;
Complex64::from_polar(amplitude, phase)
}
pub fn intensity(&self, r: f64, phi: f64, z: f64) -> f64 {
let u = self.field(r, phi, z);
u.norm_sqr()
}
pub fn peak_intensity_radius(&self, z: f64) -> f64 {
let l_abs = self.ell.unsigned_abs() as f64;
self.beam_radius(z) * (l_abs / 2.0).sqrt()
}
pub fn oam_per_photon(&self) -> f64 {
self.ell as f64 * HBAR
}
pub fn m_squared(&self) -> f64 {
2.0 * self.p as f64 + self.ell.unsigned_abs() as f64 + 1.0
}
pub fn laguerre_polynomial(p: usize, alpha: f64, x: f64) -> f64 {
if p == 0 {
return 1.0;
}
let mut l_prev = 1.0_f64;
let mut l_curr = 1.0 + alpha - x;
for k in 1..p {
let kf = k as f64;
let l_next =
((2.0 * kf + 1.0 + alpha - x) * l_curr - (kf + alpha) * l_prev) / (kf + 1.0);
l_prev = l_curr;
l_curr = l_next;
}
l_curr
}
pub fn to_hg_components(&self) -> Option<Vec<(f64, HgBeam)>> {
if self.p != 0 || self.ell.unsigned_abs() != 1 {
return None;
}
let hg10 = HgBeam::new(1, 0, self.beam_waist, self.wavelength);
let hg01 = HgBeam::new(0, 1, self.beam_waist, self.wavelength);
Some(vec![
(1.0 / 2.0_f64.sqrt(), hg10),
(1.0 / 2.0_f64.sqrt(), hg01),
])
}
}
#[derive(Debug, Clone)]
pub struct HgBeam {
pub m: usize,
pub n: usize,
pub beam_waist: f64,
pub wavelength: f64,
pub power: f64,
}
impl HgBeam {
pub fn new(m: usize, n: usize, w0: f64, wavelength: f64) -> Self {
Self {
m,
n,
beam_waist: w0,
wavelength,
power: 1.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()
}
fn gouy_phase(&self, z: f64) -> f64 {
let zr = self.rayleigh_range();
let order = self.m as f64 + self.n as f64 + 1.0;
order * (z / zr).atan()
}
fn radius_of_curvature(&self, z: f64) -> f64 {
if z.abs() < 1e-30 {
return f64::INFINITY;
}
let zr = self.rayleigh_range();
z * (1.0 + (zr / z).powi(2))
}
fn normalization(&self) -> f64 {
let fm = factorial(self.m) as f64;
let fn_ = factorial(self.n) as f64;
let denom = PI / 2.0
* self.beam_waist
* self.beam_waist
* 2.0_f64.powi((self.m + self.n) as i32)
* fm
* fn_;
(self.power / denom).sqrt()
}
pub fn field(&self, x: f64, y: f64, z: f64) -> Complex64 {
let w = self.beam_radius(z);
let k = 2.0 * PI / self.wavelength;
let rc = self.radius_of_curvature(z);
let gouy = self.gouy_phase(z);
let hm = Self::hermite_polynomial(self.m, 2.0_f64.sqrt() * x / w);
let hn = Self::hermite_polynomial(self.n, 2.0_f64.sqrt() * y / w);
let radial = (-(x * x + y * y) / (w * w)).exp();
let curvature_phase = if rc.is_finite() {
k * (x * x + y * y) / (2.0 * rc)
} else {
0.0
};
let w_factor = self.beam_waist / w;
let c = self.normalization();
let amplitude = c * w_factor * hm * hn * radial;
let phase = k * z - gouy - curvature_phase;
Complex64::from_polar(amplitude.abs(), phase) * amplitude.signum()
}
pub fn intensity(&self, x: f64, y: f64, z: f64) -> f64 {
self.field(x, y, z).norm_sqr()
}
pub fn m_squared_x(&self) -> f64 {
2.0 * self.m as f64 + 1.0
}
pub fn m_squared_y(&self) -> f64 {
2.0 * self.n as f64 + 1.0
}
pub fn hermite_polynomial(n: usize, x: f64) -> f64 {
if n == 0 {
return 1.0;
}
let mut h_prev = 1.0_f64;
let mut h_curr = 2.0 * x;
for k in 1..n {
let h_next = 2.0 * x * h_curr - 2.0 * k as f64 * h_prev;
h_prev = h_curr;
h_curr = h_next;
}
h_curr
}
}
#[derive(Debug, Clone)]
pub struct OpticalVortex {
pub topological_charge: i32,
pub beam: LgBeam,
}
impl OpticalVortex {
pub fn new(charge: i32, w0: f64, wavelength: f64) -> Self {
let beam = LgBeam::new(charge, 0, w0, wavelength);
Self {
topological_charge: charge,
beam,
}
}
pub fn phase(&self, phi: f64) -> f64 {
self.topological_charge as f64 * phi
}
pub fn is_pure_oam(&self) -> bool {
self.beam.p == 0
}
pub fn vortex_core_radius(&self) -> f64 {
let l_abs = self.topological_charge.unsigned_abs() as f64;
if l_abs < 1e-10 {
self.beam.beam_waist
} else {
self.beam.beam_waist / l_abs.sqrt()
}
}
pub fn spiral_pattern_arms(&self) -> usize {
self.topological_charge.unsigned_abs() as usize
}
}
fn factorial(n: usize) -> u64 {
(1..=n as u64).product::<u64>().max(1)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
const W0: f64 = 1e-3; const WL: f64 = 1064e-9;
#[test]
fn laguerre_p0_is_one() {
let val = LgBeam::laguerre_polynomial(0, 0.0, std::f64::consts::PI);
assert_abs_diff_eq!(val, 1.0, epsilon = 1e-15);
}
#[test]
fn laguerre_p1_alpha0() {
let x = 0.5;
let val = LgBeam::laguerre_polynomial(1, 0.0, x);
assert_abs_diff_eq!(val, 1.0 - x, epsilon = 1e-14);
}
#[test]
fn laguerre_p2_alpha0() {
let x = 1.0;
let val = LgBeam::laguerre_polynomial(2, 0.0, x);
let expected = 1.0 - 2.0 * x + x * x / 2.0;
assert_abs_diff_eq!(val, expected, epsilon = 1e-14);
}
#[test]
fn hermite_h0_is_one() {
let val = HgBeam::hermite_polynomial(0, 1.23);
assert_abs_diff_eq!(val, 1.0, epsilon = 1e-15);
}
#[test]
fn hermite_h1() {
let x = 2.5;
assert_abs_diff_eq!(HgBeam::hermite_polynomial(1, x), 2.0 * x, epsilon = 1e-14);
}
#[test]
fn hermite_h2() {
let x = 1.0;
assert_abs_diff_eq!(
HgBeam::hermite_polynomial(2, x),
4.0 * x * x - 2.0,
epsilon = 1e-14
);
}
#[test]
fn rayleigh_range() {
let beam = LgBeam::new(1, 0, W0, WL);
let zr = beam.rayleigh_range();
assert_abs_diff_eq!(zr, PI * W0 * W0 / WL, epsilon = 1e-6);
}
#[test]
fn beam_radius_at_focus() {
let beam = LgBeam::new(0, 0, W0, WL);
assert_abs_diff_eq!(beam.beam_radius(0.0), W0, epsilon = 1e-15);
}
#[test]
fn m_squared_fundamental() {
let beam = LgBeam::new(0, 0, W0, WL);
assert_abs_diff_eq!(beam.m_squared(), 1.0, epsilon = 1e-15);
}
#[test]
fn oam_per_photon_sign() {
let beam_pos = LgBeam::new(2, 0, W0, WL);
let beam_neg = LgBeam::new(-2, 0, W0, WL);
assert!(beam_pos.oam_per_photon() > 0.0);
assert!(beam_neg.oam_per_photon() < 0.0);
assert_abs_diff_eq!(
beam_pos.oam_per_photon(),
-beam_neg.oam_per_photon(),
epsilon = 1e-45
);
}
#[test]
fn vortex_spiral_arms() {
let v = OpticalVortex::new(3, W0, WL);
assert_eq!(v.spiral_pattern_arms(), 3);
}
#[test]
fn vortex_is_pure_oam() {
let v = OpticalVortex::new(1, W0, WL);
assert!(v.is_pure_oam());
}
#[test]
fn vortex_core_radius_positive_charge() {
let v = OpticalVortex::new(4, W0, WL);
let expected = W0 / (4.0_f64.sqrt()); assert_abs_diff_eq!(v.vortex_core_radius(), expected, epsilon = 1e-10);
}
#[test]
fn hg_m_squared() {
let hg = HgBeam::new(2, 3, W0, WL);
assert_abs_diff_eq!(hg.m_squared_x(), 5.0, epsilon = 1e-15);
assert_abs_diff_eq!(hg.m_squared_y(), 7.0, epsilon = 1e-15);
}
#[test]
fn lg_to_hg_decomposition() {
let lg = LgBeam::new(1, 0, W0, WL);
let comps = lg.to_hg_components();
assert!(comps.is_some());
let v = comps.unwrap();
assert_eq!(v.len(), 2);
assert_abs_diff_eq!(v[0].0, 1.0 / 2.0_f64.sqrt(), epsilon = 1e-14);
assert_abs_diff_eq!(v[1].0, 1.0 / 2.0_f64.sqrt(), epsilon = 1e-14);
}
#[test]
fn lg_to_hg_none_for_higher_order() {
let lg = LgBeam::new(2, 0, W0, WL);
assert!(lg.to_hg_components().is_none());
}
#[test]
fn gouy_phase_at_rayleigh() {
let beam = LgBeam::new(1, 0, W0, WL);
let zr = beam.rayleigh_range();
let expected = (2.0 * 0.0 + 1.0 + 1.0) * PI / 4.0;
assert_abs_diff_eq!(beam.gouy_phase(zr), expected, epsilon = 1e-14);
}
}