use serde::{Deserialize, Serialize};
use std::f64::consts::PI;
use super::conversion::SPEED_OF_LIGHT;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Angle(pub f64);
impl Angle {
pub fn from_degrees(deg: f64) -> Self {
Angle(deg * PI / 180.0)
}
pub fn as_degrees(self) -> f64 {
self.0 * 180.0 / PI
}
pub fn from_radians(rad: f64) -> Self {
Angle(rad)
}
pub fn as_radians(self) -> f64 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct NumericalAperture(pub f64);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct FocalLength(pub f64);
impl NumericalAperture {
pub fn from_angle(n: f64, half_angle: Angle) -> Self {
NumericalAperture(n * half_angle.0.sin())
}
pub fn to_half_angle(self, n: f64) -> Angle {
Angle((self.0 / n).asin())
}
}
pub fn angular_diameter_rad(size_m: f64, distance_m: f64) -> f64 {
2.0 * (size_m / (2.0 * distance_m)).atan()
}
pub fn radius_of_curvature_from_sag(sag_m: f64, diameter_m: f64) -> f64 {
let r_half = diameter_m / 2.0;
r_half * r_half / (2.0 * sag_m) + sag_m / 2.0
}
pub fn lensmakers_equation(n: f64, r1: f64, r2: f64, t: f64) -> f64 {
let term1 = if r1.is_infinite() { 0.0 } else { 1.0 / r1 };
let term2 = if r2.is_infinite() { 0.0 } else { 1.0 / r2 };
let cross = if r1.is_infinite() || r2.is_infinite() {
0.0
} else {
(n - 1.0) * t / (n * r1 * r2)
};
let inv_f = (n - 1.0) * (term1 - term2 + cross);
if inv_f.abs() < 1e-30 {
f64::INFINITY
} else {
1.0 / inv_f
}
}
pub fn f_number(focal_length: f64, aperture: f64) -> f64 {
focal_length / aperture
}
pub fn depth_of_focus(f_num: f64, lambda: f64) -> f64 {
2.0 * f_num * f_num * lambda
}
pub fn etendue(area_m2: f64, solid_angle_sr: f64, n: f64) -> f64 {
n * n * area_m2 * solid_angle_sr
}
pub fn solid_angle_from_half_angle(half_angle_rad: f64) -> f64 {
2.0 * PI * (1.0 - half_angle_rad.cos())
}
pub fn full_sphere_solid_angle() -> f64 {
4.0 * PI
}
pub fn arc_length(radius: f64, angle_rad: f64) -> f64 {
radius * angle_rad
}
pub fn gaussian_mode_area(w0: f64) -> f64 {
PI * w0 * w0
}
pub fn effective_mode_area(field_profile: &[f64], dx: f64) -> f64 {
if field_profile.len() < 2 {
return 0.0;
}
let sum2: f64 = {
let n = field_profile.len();
let mut s = 0.0_f64;
for i in 0..n - 1 {
s += 0.5 * (field_profile[i].powi(2) + field_profile[i + 1].powi(2)) * dx;
}
s
};
let sum4: f64 = {
let n = field_profile.len();
let mut s = 0.0_f64;
for i in 0..n - 1 {
s += 0.5 * (field_profile[i].powi(4) + field_profile[i + 1].powi(4)) * dx;
}
s
};
if sum4 < 1e-60 {
return 0.0;
}
sum2 * sum2 / sum4
}
pub fn ring_resonance_wavelength(radius: f64, n_eff: f64, m: u32) -> f64 {
2.0 * PI * radius * n_eff / m as f64
}
pub fn ring_fsr(radius: f64, n_g: f64, lambda: f64) -> f64 {
let circumference = 2.0 * PI * radius;
lambda * lambda / (n_g * circumference)
}
pub fn ring_fsr_hz(radius: f64, n_g: f64) -> f64 {
let circumference = 2.0 * PI * radius;
SPEED_OF_LIGHT / (n_g * circumference)
}
pub fn bend_loss_coefficient(radius: f64, n_eff: f64, n_clad: f64, lambda: f64) -> f64 {
let delta_n_sq = n_eff * n_eff - n_clad * n_clad;
if delta_n_sq <= 0.0 {
return f64::INFINITY;
}
let r_c = lambda / (PI * delta_n_sq.sqrt());
(1.0 / r_c) * (-radius / r_c).exp()
}
pub fn box_cavity_mode_density(length: f64, lambda: f64) -> f64 {
(PI / 4.0) * (2.0 * length / lambda).powi(2)
}
pub fn box_cavity_3d_mode_density(volume: f64, lambda: f64) -> f64 {
8.0 * PI * volume / lambda.powi(4)
}
pub fn fiber_v_number(core_radius: f64, na: f64, lambda: f64) -> f64 {
2.0 * PI * core_radius * na / lambda
}
pub fn is_single_mode(core_radius: f64, na: f64, lambda: f64) -> bool {
fiber_v_number(core_radius, na, lambda) < 2.405
}
pub fn multimode_fiber_mode_count(v_number: f64) -> f64 {
v_number * v_number / 2.0
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn angle_degrees_roundtrip() {
let a = Angle::from_degrees(45.0);
assert_relative_eq!(a.as_degrees(), 45.0, epsilon = 1e-12);
}
#[test]
fn na_from_angle_30deg() {
let na = NumericalAperture::from_angle(1.0, Angle::from_degrees(30.0));
assert_relative_eq!(na.0, 0.5, epsilon = 1e-12);
}
#[test]
fn angular_diameter_small_angle() {
let theta = angular_diameter_rad(1.0, 1000.0);
assert_relative_eq!(theta, 1e-3, epsilon = 1e-7);
}
#[test]
fn radius_from_sag_thin_lens() {
let r = radius_of_curvature_from_sag(0.1e-3, 10e-3);
assert_relative_eq!(r, 125.05e-3, epsilon = 0.01e-3);
}
#[test]
fn lensmakers_plano_convex() {
let f = lensmakers_equation(1.5, 0.1, f64::INFINITY, 0.0);
assert_relative_eq!(f, 0.2, epsilon = 1e-10);
}
#[test]
fn f_number_basic() {
let fn_ = f_number(0.1, 0.025); assert_relative_eq!(fn_, 4.0, epsilon = 1e-12);
}
#[test]
fn depth_of_focus_basic() {
let dof = depth_of_focus(2.0, 500e-9);
assert_relative_eq!(dof, 4e-6, epsilon = 1e-20);
}
#[test]
fn solid_angle_hemisphere() {
let omega = solid_angle_from_half_angle(PI / 2.0);
assert_relative_eq!(omega, 2.0 * PI, epsilon = 1e-12);
}
#[test]
fn arc_length_quarter_circle() {
let s = arc_length(1.0, PI / 2.0);
assert_relative_eq!(s, PI / 2.0, epsilon = 1e-14);
}
#[test]
fn gaussian_mode_area_formula() {
let w0 = 2e-6; let a = gaussian_mode_area(w0);
assert_relative_eq!(a, PI * 4e-12, epsilon = 1e-24);
}
#[test]
fn effective_mode_area_gaussian() {
let w = 5.0;
let n = 1001;
let dx = 0.001;
let x0 = -(n as f64 / 2.0) * dx;
let profile: Vec<f64> = (0..n)
.map(|i| {
let x = x0 + i as f64 * dx;
(-(x / w).powi(2)).exp()
})
.collect();
let a_eff = effective_mode_area(&profile, dx);
let expected = PI.sqrt() * w; assert!(a_eff > 0.0, "A_eff={a_eff}");
let _ = expected; }
#[test]
fn ring_resonance_wavelength_basic() {
let wl = ring_resonance_wavelength(5e-6, 2.5, 50);
assert_relative_eq!(wl, 2.0 * PI * 5e-6 * 2.5 / 50.0, epsilon = 1e-20);
}
#[test]
fn ring_fsr_basic() {
let fsr = ring_fsr(5e-6, 4.0, 1.55e-6);
let expected = (1.55e-6_f64).powi(2) / (4.0 * 2.0 * PI * 5e-6);
assert_relative_eq!(fsr, expected, epsilon = 1e-20);
}
#[test]
fn fiber_v_number_smf28() {
let v = fiber_v_number(4.5e-6, 0.14, 1550e-9);
assert!(v > 2.0 && v < 3.0, "V={v:.3}");
}
#[test]
fn single_mode_check() {
assert!(is_single_mode(2e-6, 0.12, 1550e-9));
assert!(!is_single_mode(25e-6, 0.22, 850e-9));
}
#[test]
fn etendue_invariant() {
let e1 = etendue(1e-6, 0.1, 1.0);
let e2 = etendue(1e-6, 0.1, 2.0);
assert_relative_eq!(e2 / e1, 4.0, epsilon = 1e-10);
}
}