use std::f64::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AbcdMatrix {
pub a: f64,
pub b: f64,
pub c: f64,
pub d: f64,
}
impl AbcdMatrix {
pub fn identity() -> Self {
Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
}
}
pub fn free_space(d: f64) -> Self {
Self {
a: 1.0,
b: d,
c: 0.0,
d: 1.0,
}
}
pub fn thin_lens(f: f64) -> Self {
Self {
a: 1.0,
b: 0.0,
c: -1.0 / f,
d: 1.0,
}
}
pub fn flat_interface(n1: f64, n2: f64) -> Self {
Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: n1 / n2,
}
}
pub fn curved_interface(r: f64, n1: f64, n2: f64) -> Self {
Self {
a: 1.0,
b: 0.0,
c: (n1 - n2) / (r * n2),
d: n1 / n2,
}
}
pub fn then(&self, other: &AbcdMatrix) -> AbcdMatrix {
AbcdMatrix {
a: self.a * other.a + self.b * other.c,
b: self.a * other.b + self.b * other.d,
c: self.c * other.a + self.d * other.c,
d: self.c * other.b + self.d * other.d,
}
}
pub fn det(&self) -> f64 {
self.a * self.d - self.b * self.c
}
}
#[derive(Debug, Clone, Copy)]
pub struct GaussianBeam {
pub w: f64,
pub r: f64,
pub n: f64,
pub wavelength: f64,
}
impl GaussianBeam {
pub fn at_waist(w0: f64, wavelength: f64, n: f64) -> Self {
Self {
w: w0,
r: f64::INFINITY,
n,
wavelength,
}
}
pub fn rayleigh_range(&self) -> f64 {
PI * self.n * self.w * self.w / self.wavelength
}
pub fn divergence(&self) -> f64 {
self.wavelength / (PI * self.n * self.w)
}
pub fn q_parameter(&self) -> num_complex::Complex64 {
let lambda_n = self.wavelength / self.n;
let inv_q_re = if self.r.is_infinite() {
0.0
} else {
1.0 / self.r
};
let inv_q_im = -lambda_n / (PI * self.w * self.w);
let inv_q = num_complex::Complex64::new(inv_q_re, inv_q_im);
num_complex::Complex64::new(1.0, 0.0) / inv_q
}
pub fn propagate(&self, m: &AbcdMatrix) -> Self {
let q = self.q_parameter();
let q_new = (m.a * q + m.b) / (m.c * q + m.d);
let inv_q_new = num_complex::Complex64::new(1.0, 0.0) / q_new;
let r_new = if inv_q_new.re.abs() < 1e-30 {
f64::INFINITY
} else {
1.0 / inv_q_new.re
};
let lambda_n = self.wavelength / self.n;
let w_new = (lambda_n / (PI * (-inv_q_new.im).abs())).sqrt();
Self {
w: w_new,
r: r_new,
n: self.n,
wavelength: self.wavelength,
}
}
pub fn propagate_free(&self, z: f64) -> Self {
let m = AbcdMatrix::free_space(z);
self.propagate(&m)
}
pub fn apply_lens(&self, f: f64) -> Self {
let m = AbcdMatrix::thin_lens(f);
self.propagate(&m)
}
pub fn intensity_profile(&self, r: f64, i0: f64) -> f64 {
i0 * (-2.0 * r * r / (self.w * self.w)).exp()
}
pub fn peak_intensity(&self, power: f64) -> f64 {
2.0 * power / (PI * self.w * self.w)
}
}
pub fn focus_gaussian(w0: f64, d_in: f64, f: f64, wavelength: f64, n: f64) -> (f64, f64) {
let beam = GaussianBeam::at_waist(w0, wavelength, n);
let at_lens = beam.propagate_free(d_in);
let after_lens = at_lens.apply_lens(f);
let q_after = after_lens.q_parameter();
let d_out = q_after.re;
let w_focus = after_lens.propagate_free(d_out).w;
(d_out, w_focus)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn abcd_identity() {
let m = AbcdMatrix::identity();
assert_eq!(m.a, 1.0);
assert_eq!(m.d, 1.0);
assert_eq!(m.b, 0.0);
assert_eq!(m.c, 0.0);
}
#[test]
fn abcd_free_space_det() {
let m = AbcdMatrix::free_space(1e-3);
assert!((m.det() - 1.0).abs() < 1e-12);
}
#[test]
fn abcd_thin_lens_det() {
let m = AbcdMatrix::thin_lens(50e-3);
assert!((m.det() - 1.0).abs() < 1e-12);
}
#[test]
fn abcd_cascade() {
let m1 = AbcdMatrix::free_space(10e-3);
let m2 = AbcdMatrix::identity();
let combined = m1.then(&m2);
assert!((combined.b - 10e-3).abs() < 1e-12);
}
#[test]
fn gaussian_beam_rayleigh_range() {
let w0 = 1e-3; let wl = 1550e-9;
let beam = GaussianBeam::at_waist(w0, wl, 1.0);
let zr = beam.rayleigh_range();
let expected = PI * w0 * w0 / wl;
assert!((zr - expected).abs() / expected < 1e-10);
}
#[test]
fn gaussian_propagation_doubles_at_rayleigh() {
let w0 = 1e-3;
let wl = 1550e-9;
let beam = GaussianBeam::at_waist(w0, wl, 1.0);
let zr = beam.rayleigh_range();
let propagated = beam.propagate_free(zr);
let expected_w = w0 * 2.0_f64.sqrt();
let rel_err = (propagated.w - expected_w).abs() / expected_w;
assert!(
rel_err < 1e-6,
"w at z_R should be w0√2, got {:.4e}",
propagated.w
);
}
#[test]
fn gaussian_thin_lens_focuses() {
let w0 = 1e-3;
let f = 50e-3;
let wl = 633e-9;
let beam = GaussianBeam::at_waist(w0, wl, 1.0);
let after_lens = beam.apply_lens(f);
assert!(after_lens.r < 0.0, "Lens should make beam converge");
}
#[test]
fn gaussian_intensity_at_center() {
let beam = GaussianBeam::at_waist(1e-3, 633e-9, 1.0);
let power = 1e-3; let i_peak = beam.peak_intensity(power);
let i_center = beam.intensity_profile(0.0, i_peak);
assert!((i_center - i_peak).abs() < 1e-12);
}
#[test]
fn gaussian_intensity_at_1_over_e2() {
let beam = GaussianBeam::at_waist(1e-3, 633e-9, 1.0);
let i0 = 1.0;
let i_edge = beam.intensity_profile(beam.w, i0);
let expected = i0 * (-2.0f64).exp();
assert!((i_edge - expected).abs() < 1e-12);
}
}