use std::f64::consts::PI;
use crate::error::{PrakashError, Result};
#[must_use = "returns the computed NA"]
#[inline]
pub fn fiber_na(n_core: f64, n_clad: f64) -> Result<f64> {
let na2 = n_core * n_core - n_clad * n_clad;
if na2 < 0.0 {
return Err(PrakashError::InvalidParameter {
reason: "core index must be greater than cladding index".into(),
});
}
Ok(na2.sqrt())
}
#[must_use = "returns the V-number"]
#[inline]
pub fn v_number(core_radius: f64, na: f64, wavelength: f64) -> f64 {
2.0 * PI * core_radius * na / wavelength
}
#[must_use]
#[inline]
pub fn num_modes(v: f64) -> u32 {
if v < 2.405 {
1
} else {
((v * v / 2.0) as u32).max(1)
}
}
#[must_use]
#[inline]
pub fn is_single_mode(v: f64) -> bool {
v < 2.405
}
#[must_use]
#[inline]
pub fn mode_field_diameter(core_radius: f64, v: f64) -> f64 {
2.0 * core_radius * (0.65 + 1.619 * v.powf(-1.5) + 2.879 * v.powi(-6))
}
#[must_use]
#[inline]
pub fn coupling_efficiency_gaussian(w_beam: f64, w_fiber: f64) -> f64 {
let num = 2.0 * w_beam * w_fiber;
let den = w_beam * w_beam + w_fiber * w_fiber;
let ratio = num / den;
ratio * ratio
}
#[cfg(test)]
mod tests {
use super::*;
const EPS: f64 = 1e-6;
#[test]
fn test_fiber_na_smf28() {
let na = fiber_na(1.4682, 1.4629).unwrap();
assert!((na - 0.12).abs() < 0.02, "SMF-28 NA ≈ 0.12, got {na}");
}
#[test]
fn test_fiber_na_invalid() {
assert!(fiber_na(1.45, 1.47).is_err()); }
#[test]
fn test_v_number_single_mode() {
let na = 0.12;
let v = v_number(4.1e-6, na, 1.55e-6);
assert!(v < 2.405, "SMF-28 at 1550nm should be single-mode, V={v}");
}
#[test]
fn test_v_number_multimode() {
let v = v_number(25e-6, 0.22, 0.85e-6);
assert!(v > 2.405, "25μm core should be multimode, V={v}");
}
#[test]
fn test_num_modes_single() {
assert_eq!(num_modes(2.0), 1);
}
#[test]
fn test_num_modes_multi() {
let v = v_number(25e-6, 0.22, 0.85e-6);
let n = num_modes(v);
assert!(n > 100, "Many modes expected, got {n}");
}
#[test]
fn test_is_single_mode() {
assert!(is_single_mode(2.0));
assert!(!is_single_mode(3.0));
}
#[test]
fn test_mfd_typical() {
let v = v_number(4.1e-6, 0.12, 1.55e-6);
let mfd = mode_field_diameter(4.1e-6, v);
assert!(
(mfd * 1e6 - 10.4).abs() < 2.0,
"MFD ≈ 10.4μm, got {:.1}μm",
mfd * 1e6
);
}
#[test]
fn test_coupling_perfect() {
let eta = coupling_efficiency_gaussian(5e-6, 5e-6);
assert!((eta - 1.0).abs() < EPS);
}
#[test]
fn test_coupling_mismatch() {
let eta = coupling_efficiency_gaussian(5e-6, 10e-6);
assert!(eta < 1.0);
assert!(eta > 0.5);
}
#[test]
fn test_coupling_symmetric() {
let e1 = coupling_efficiency_gaussian(3e-6, 5e-6);
let e2 = coupling_efficiency_gaussian(5e-6, 3e-6);
assert!((e1 - e2).abs() < EPS, "Coupling should be symmetric");
}
}