use num_complex::Complex64;
use serde::{Deserialize, Serialize};
use std::f64::consts::PI;
use super::conversion::{SPEED_OF_LIGHT, Z0};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct RefractiveIndex {
pub n: f64,
pub k: f64,
}
impl RefractiveIndex {
pub fn new(n: f64, k: f64) -> Self {
Self { n, k }
}
pub fn real(n: f64) -> Self {
Self { n, k: 0.0 }
}
pub fn to_permittivity_scalar(&self) -> Complex64 {
let n = self.n;
let k = self.k;
Complex64::new(n * n - k * k, 2.0 * n * k)
}
pub fn to_complex(&self) -> Complex64 {
Complex64::new(self.n, self.k)
}
pub fn absorption_coefficient(&self, lambda_m: f64) -> f64 {
4.0 * PI * self.k / lambda_m
}
pub fn penetration_depth(&self, lambda_m: f64) -> f64 {
let alpha = self.absorption_coefficient(lambda_m);
if alpha < 1e-30 {
f64::INFINITY
} else {
1.0 / alpha
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Permittivity {
Isotropic(Complex64),
Diagonal([Complex64; 3]),
Full([[Complex64; 3]; 3]),
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Permeability(pub f64);
impl Default for Permeability {
fn default() -> Self {
Self(1.0)
}
}
pub fn fresnel_r_normal(n1: f64, n2: f64) -> f64 {
((n1 - n2) / (n1 + n2)).powi(2)
}
pub fn fresnel_t_normal(n1: f64, n2: f64) -> f64 {
1.0 - fresnel_r_normal(n1, n2)
}
pub fn fresnel_rs_amplitude(n1: f64, n2: f64, theta_i_rad: f64) -> Option<f64> {
let sin_t = n1 / n2 * theta_i_rad.sin();
if sin_t.abs() > 1.0 {
return None; }
let cos_i = theta_i_rad.cos();
let cos_t = (1.0 - sin_t * sin_t).sqrt();
Some((n1 * cos_i - n2 * cos_t) / (n1 * cos_i + n2 * cos_t))
}
pub fn fresnel_rs(n1: f64, n2: f64, theta_i_rad: f64) -> f64 {
match fresnel_rs_amplitude(n1, n2, theta_i_rad) {
Some(r) => r * r,
None => 1.0,
}
}
pub fn fresnel_rp_amplitude(n1: f64, n2: f64, theta_i_rad: f64) -> Option<f64> {
let sin_t = n1 / n2 * theta_i_rad.sin();
if sin_t.abs() > 1.0 {
return None;
}
let cos_i = theta_i_rad.cos();
let cos_t = (1.0 - sin_t * sin_t).sqrt();
Some((n2 * cos_i - n1 * cos_t) / (n2 * cos_i + n1 * cos_t))
}
pub fn fresnel_rp(n1: f64, n2: f64, theta_i_rad: f64) -> f64 {
match fresnel_rp_amplitude(n1, n2, theta_i_rad) {
Some(r) => r * r,
None => 1.0,
}
}
pub fn fresnel_r_unpolarised(n1: f64, n2: f64, theta_i_rad: f64) -> f64 {
0.5 * (fresnel_rs(n1, n2, theta_i_rad) + fresnel_rp(n1, n2, theta_i_rad))
}
pub fn brewster_angle(n1: f64, n2: f64) -> f64 {
(n2 / n1).atan()
}
pub fn critical_angle(n1: f64, n2: f64) -> Option<f64> {
if n1 <= n2 {
return None;
}
Some((n2 / n1).asin())
}
pub fn snell_theta_t(n1: f64, n2: f64, theta_i_rad: f64) -> Option<f64> {
let sin_t = n1 / n2 * theta_i_rad.sin();
if sin_t.abs() > 1.0 {
None
} else {
Some(sin_t.asin())
}
}
pub fn optical_path_length(geometric_length_m: f64, n: f64) -> f64 {
geometric_length_m * n
}
pub fn coherence_length(lambda_m: f64, delta_lambda_m: f64) -> f64 {
lambda_m * lambda_m / delta_lambda_m
}
pub fn gvd_from_group_index(ng1: f64, ng2: f64, lambda1_m: f64, lambda2_m: f64) -> f64 {
let d_lambda = lambda2_m - lambda1_m;
if d_lambda.abs() < 1e-30 {
return 0.0;
}
(ng2 - ng1) / (SPEED_OF_LIGHT * d_lambda)
}
pub fn phase_matching_bandwidth(l: f64, d_beta: f64) -> f64 {
if d_beta.abs() < 1e-50 {
return f64::INFINITY;
}
0.886 * PI / (d_beta.abs() * l)
}
pub fn abbe_number(nd: f64, nf: f64, nc: f64) -> f64 {
(nd - 1.0) / (nf - nc)
}
pub fn numerical_aperture(n: f64, half_angle_rad: f64) -> f64 {
n * half_angle_rad.sin()
}
pub fn diffraction_limited_spot(wavelength_m: f64, na: f64) -> f64 {
0.61 * wavelength_m / na
}
pub fn rayleigh_range(w0: f64, lambda_m: f64) -> f64 {
PI * w0 * w0 / lambda_m
}
pub fn gaussian_beam_radius(w0: f64, z: f64, z_r: f64) -> f64 {
w0 * (1.0 + (z / z_r).powi(2)).sqrt()
}
pub fn gaussian_beam_peak_irradiance(power_w: f64, w0: f64) -> f64 {
2.0 * power_w / (PI * w0 * w0)
}
pub fn abcd_freespace(d: f64) -> [[f64; 2]; 2] {
[[1.0, d], [0.0, 1.0]]
}
pub fn abcd_thin_lens(f: f64) -> [[f64; 2]; 2] {
[[1.0, 0.0], [-1.0 / f, 1.0]]
}
pub fn abcd_flat_interface(n1: f64, n2: f64) -> [[f64; 2]; 2] {
[[1.0, 0.0], [0.0, n1 / n2]]
}
pub fn abcd_curved_mirror(radius: f64) -> [[f64; 2]; 2] {
[[1.0, 0.0], [-2.0 / radius, 1.0]]
}
pub fn abcd_multiply(a: [[f64; 2]; 2], b: [[f64; 2]; 2]) -> [[f64; 2]; 2] {
[
[
a[0][0] * b[0][0] + a[0][1] * b[1][0],
a[0][0] * b[0][1] + a[0][1] * b[1][1],
],
[
a[1][0] * b[0][0] + a[1][1] * b[1][0],
a[1][0] * b[0][1] + a[1][1] * b[1][1],
],
]
}
pub fn abcd_transform_q(mat: [[f64; 2]; 2], q: Complex64) -> Complex64 {
let a = Complex64::new(mat[0][0], 0.0);
let b = Complex64::new(mat[0][1], 0.0);
let c = Complex64::new(mat[1][0], 0.0);
let d = Complex64::new(mat[1][1], 0.0);
(a * q + b) / (c * q + d)
}
pub fn gaussian_q_at_waist(w0: f64, lambda_m: f64) -> Complex64 {
let z_r = rayleigh_range(w0, lambda_m);
Complex64::new(0.0, z_r)
}
pub fn optical_irradiance_from_e_field(e_amplitude: f64, n: f64) -> f64 {
0.5 * n * e_amplitude * e_amplitude / Z0
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn refractive_index_to_permittivity() {
let ri = RefractiveIndex::real(1.5);
let eps = ri.to_permittivity_scalar();
assert_relative_eq!(eps.re, 2.25, epsilon = 1e-12);
assert_relative_eq!(eps.im, 0.0, epsilon = 1e-12);
}
#[test]
fn complex_refractive_index_to_permittivity() {
let ri = RefractiveIndex::new(0.5, 10.0);
let eps = ri.to_permittivity_scalar();
assert_relative_eq!(eps.re, -99.75, epsilon = 1e-12);
assert_relative_eq!(eps.im, 10.0, epsilon = 1e-12);
}
#[test]
fn fresnel_normal_air_glass() {
let r = fresnel_r_normal(1.0, 1.5);
assert_relative_eq!(r, 0.04, epsilon = 1e-10);
}
#[test]
fn fresnel_t_plus_r_equals_one_normal() {
let r = fresnel_r_normal(1.0, 1.5);
let t = fresnel_t_normal(1.0, 1.5);
assert_relative_eq!(r + t, 1.0, epsilon = 1e-14);
}
#[test]
fn fresnel_rs_at_zero_equals_normal() {
let r_normal = fresnel_r_normal(1.0, 1.5);
let rs = fresnel_rs(1.0, 1.5, 0.0);
assert_relative_eq!(r_normal, rs, epsilon = 1e-10);
}
#[test]
fn fresnel_tir_at_critical_angle() {
let theta_c = critical_angle(1.5, 1.0).expect("critical angle exists");
let r = fresnel_rs(1.5, 1.0, theta_c + 0.01);
assert_relative_eq!(r, 1.0, epsilon = 1e-10);
}
#[test]
fn critical_angle_none_when_n1_less_n2() {
assert!(critical_angle(1.0, 1.5).is_none());
}
#[test]
fn brewster_angle_glass() {
let theta_b = brewster_angle(1.0, 1.5);
assert_relative_eq!(theta_b.to_degrees(), 56.310, epsilon = 0.001);
}
#[test]
fn snell_tir_returns_none() {
let result = snell_theta_t(1.5, 1.0, std::f64::consts::FRAC_PI_2);
assert!(result.is_none());
}
#[test]
fn snell_normal_incidence() {
let theta_t = snell_theta_t(1.0, 1.5, 0.0).expect("valid");
assert_relative_eq!(theta_t, 0.0, epsilon = 1e-12);
}
#[test]
fn coherence_length_basic() {
let lc = coherence_length(1550e-9, 0.1e-9);
assert_relative_eq!(lc, 24.025e-3, epsilon = 1e-5);
}
#[test]
fn rayleigh_range_basic() {
let z_r = rayleigh_range(1e-6, 1550e-9);
assert_relative_eq!(z_r, 2.027e-6, epsilon = 1e-9);
}
#[test]
fn abcd_freespace_determinant_one() {
let m = abcd_freespace(10e-3);
let det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
assert_relative_eq!(det, 1.0, epsilon = 1e-14);
}
#[test]
fn abcd_thin_lens_determinant_one() {
let m = abcd_thin_lens(0.05);
let det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
assert_relative_eq!(det, 1.0, epsilon = 1e-14);
}
#[test]
fn abcd_multiply_identity() {
let id = [[1.0, 0.0], [0.0, 1.0]];
let m = abcd_freespace(5e-3);
let result = abcd_multiply(id, m);
assert_relative_eq!(result[0][1], m[0][1], epsilon = 1e-14);
}
#[test]
fn abcd_multiply_lens_freespace() {
let lens = abcd_thin_lens(0.1);
let space = abcd_freespace(0.1);
let m = abcd_multiply(space, lens);
assert_relative_eq!(m[0][0], 0.0, epsilon = 1e-14);
assert_relative_eq!(m[0][1], 0.1, epsilon = 1e-14);
assert_relative_eq!(m[1][0], -10.0, epsilon = 1e-12);
assert_relative_eq!(m[1][1], 1.0, epsilon = 1e-14);
}
#[test]
fn abbe_number_bk7() {
let v = abbe_number(1.5168, 1.5224, 1.5143);
assert!(v > 60.0 && v < 70.0, "V_BK7={v:.1}");
}
#[test]
fn numerical_aperture_basic() {
let na = numerical_aperture(1.0, std::f64::consts::FRAC_PI_6); assert_relative_eq!(na, 0.5, epsilon = 1e-12);
}
#[test]
fn irradiance_from_e_field_air() {
let i = optical_irradiance_from_e_field(1.0, 1.0);
assert_relative_eq!(i, 0.5 / Z0, epsilon = 1e-12);
}
#[test]
fn penetration_depth_zero_k() {
let ri = RefractiveIndex::real(1.5);
let depth = ri.penetration_depth(1550e-9);
assert!(depth.is_infinite());
}
}