use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct OamMultiplexLink {
pub oam_channels: Vec<i32>,
pub wavelength: f64,
pub beam_waist: f64,
pub distance: f64,
pub aperture: f64,
}
impl OamMultiplexLink {
pub fn new(channels: Vec<i32>, wavelength: f64, w0: f64, distance: f64, aperture: f64) -> Self {
Self {
oam_channels: channels,
wavelength,
beam_waist: w0,
distance,
aperture,
}
}
pub fn n_channels(&self) -> usize {
self.oam_channels.len()
}
pub fn modes_orthogonal(&self) -> bool {
let mut sorted = self.oam_channels.clone();
sorted.sort_unstable();
sorted.windows(2).all(|w| w[0] != w[1])
}
pub fn capacity_bits_per_s_per_hz(&self, snr_db: f64) -> f64 {
let snr_linear = 10.0_f64.powf(snr_db / 10.0);
self.oam_channels
.iter()
.map(|&ell| {
let eta = self.aperture_efficiency(ell);
(1.0 + snr_linear * eta).log2()
})
.sum()
}
pub fn crosstalk_db(&self, l1: i32, l2: i32, r0: f64) -> f64 {
if l1 == l2 {
return 0.0; }
let w_at_z = self.beam_radius_at_distance();
let delta_l = (l1 - l2).unsigned_abs() as f64;
let turb_strength = (w_at_z / r0).powf(5.0 / 3.0);
let spectral_weight = 1.0 / (1.0 + delta_l * delta_l);
let xt_linear = turb_strength * spectral_weight;
10.0 * xt_linear.log10()
}
pub fn mode_purity_after_atmosphere(&self, ell: i32, r0: f64) -> f64 {
let w = self.beam_radius_at_distance();
let turb = (w / r0).powf(5.0 / 3.0);
let l_abs = ell.unsigned_abs() as f64;
let c_mode = 1.3 * (1.0 + 0.1 * l_abs);
(-turb * c_mode).exp().clamp(0.0, 1.0)
}
pub fn aperture_efficiency(&self, ell: i32) -> f64 {
let w = self.beam_radius_at_distance();
let ra = self.aperture / 2.0;
let u = 2.0 * ra * ra / (w * w);
let l_abs = ell.unsigned_abs() as usize;
let mut partial = 0.0_f64;
let mut term = (-u).exp();
partial += term;
for k in 1..=l_abs {
term *= u / k as f64;
partial += term;
}
(1.0 - partial).clamp(0.0, 1.0)
}
fn beam_radius_at_distance(&self) -> f64 {
let zr = PI * self.beam_waist * self.beam_waist / self.wavelength;
self.beam_waist * (1.0 + (self.distance / zr).powi(2)).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct OamModeSorter {
pub n_modes: usize,
pub min_oam: i32,
pub max_oam: i32,
pub efficiency: f64,
pub crosstalk_db: f64,
}
impl OamModeSorter {
pub fn new(min_l: i32, max_l: i32) -> Self {
let n_modes = (max_l - min_l).unsigned_abs() as usize + 1;
Self {
n_modes,
min_oam: min_l,
max_oam: max_l,
efficiency: 0.90, crosstalk_db: -20.0, }
}
pub fn output_position(&self, ell: i32) -> f64 {
if self.max_oam == self.min_oam {
return 0.5;
}
(ell - self.min_oam) as f64 / (self.max_oam - self.min_oam) as f64
}
pub fn mode_separation_mm(&self, pixel_pitch: f64) -> f64 {
pixel_pitch
}
pub fn theoretical_efficiency(&self) -> f64 {
0.95
}
pub fn channel_isolation_db(&self) -> f64 {
-self.crosstalk_db }
}
#[derive(Debug, Clone)]
pub struct SpiralPhasePlate {
pub topological_charge: i32,
pub diameter: f64,
pub step_height: f64,
pub n_material: f64,
pub wavelength: f64,
}
impl SpiralPhasePlate {
pub fn new(charge: i32, wavelength: f64, n: f64) -> Self {
let h = wavelength / (n - 1.0);
Self {
topological_charge: charge,
diameter: 10e-3, step_height: h,
n_material: n,
wavelength,
}
}
pub fn phase_at_angle(&self, phi: f64) -> f64 {
self.topological_charge as f64 * phi
}
pub fn step_height_m(&self) -> f64 {
if (self.n_material - 1.0).abs() < 1e-30 {
return f64::INFINITY;
}
self.wavelength / (self.n_material - 1.0)
}
pub fn efficiency(&self) -> f64 {
1.0
}
pub fn fractional_mode_spectrum(charge: f64, n_modes: usize) -> Vec<(i32, f64)> {
let l_center = charge.round() as i32;
let half = (n_modes / 2) as i32;
let mut spectrum: Vec<(i32, f64)> = Vec::with_capacity(n_modes);
let mut total_power = 0.0_f64;
for n in (l_center - half)..=(l_center + half) {
let diff = charge - n as f64;
let amplitude_sq = if diff.abs() < 1e-12 {
1.0
} else {
let sinc = (PI * diff).sin() / (PI * diff);
sinc * sinc
};
spectrum.push((n, amplitude_sq));
total_power += amplitude_sq;
}
if total_power > 1e-30 {
for entry in spectrum.iter_mut() {
entry.1 /= total_power;
}
}
spectrum
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
#[test]
fn oam_link_modes_orthogonal() {
let link = OamMultiplexLink::new(vec![-2, -1, 0, 1, 2], 1550e-9, 0.05, 1000.0, 0.2);
assert!(link.modes_orthogonal());
}
#[test]
fn oam_link_modes_not_orthogonal_if_duplicate() {
let link = OamMultiplexLink::new(vec![1, 1, 2], 1550e-9, 0.05, 1000.0, 0.2);
assert!(!link.modes_orthogonal());
}
#[test]
fn oam_link_capacity_increases_with_snr() {
let link = OamMultiplexLink::new(vec![-1, 0, 1], 1550e-9, 0.05, 100.0, 0.1);
let c_low = link.capacity_bits_per_s_per_hz(10.0);
let c_high = link.capacity_bits_per_s_per_hz(30.0);
assert!(c_high > c_low);
}
#[test]
fn oam_link_aperture_efficiency_in_bounds() {
let link = OamMultiplexLink::new(vec![0], 1550e-9, 0.02, 500.0, 0.1);
let eta = link.aperture_efficiency(0);
assert!((0.0..=1.0).contains(&eta));
}
#[test]
fn oam_crosstalk_same_mode_zero() {
let link = OamMultiplexLink::new(vec![0, 1], 1550e-9, 0.02, 100.0, 0.1);
let xt = link.crosstalk_db(1, 1, 0.1);
assert_abs_diff_eq!(xt, 0.0, epsilon = 1e-12);
}
#[test]
fn sorter_n_modes() {
let sorter = OamModeSorter::new(-3, 3);
assert_eq!(sorter.n_modes, 7);
}
#[test]
fn sorter_output_position_extremes() {
let sorter = OamModeSorter::new(-2, 2);
assert_abs_diff_eq!(sorter.output_position(-2), 0.0, epsilon = 1e-14);
assert_abs_diff_eq!(sorter.output_position(2), 1.0, epsilon = 1e-14);
}
#[test]
fn sorter_theoretical_efficiency() {
let sorter = OamModeSorter::new(-5, 5);
assert!(sorter.theoretical_efficiency() > 0.9);
}
#[test]
fn spp_step_height() {
let spp = SpiralPhasePlate::new(1, 1064e-9, 1.5);
assert_abs_diff_eq!(spp.step_height_m(), 1064e-9 / 0.5, epsilon = 1e-18);
}
#[test]
fn spp_phase_at_pi() {
let spp = SpiralPhasePlate::new(2, 1064e-9, 1.5);
use std::f64::consts::PI;
assert_abs_diff_eq!(spp.phase_at_angle(PI), 2.0 * PI, epsilon = 1e-14);
}
#[test]
fn spp_fractional_spectrum_normalised() {
let spectrum = SpiralPhasePlate::fractional_mode_spectrum(2.5, 11);
let total: f64 = spectrum.iter().map(|&(_, p)| p).sum();
assert_abs_diff_eq!(total, 1.0, epsilon = 1e-12);
}
#[test]
fn spp_fractional_spectrum_peak_at_nearest_integer() {
let spectrum = SpiralPhasePlate::fractional_mode_spectrum(3.0, 11);
let peak = spectrum
.iter()
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
assert!(peak.is_some());
assert_eq!(peak.unwrap().0, 3);
}
}