use std::f64::consts::PI;
use super::Polarization;
use crate::units::{Length, Wavelength};
use crate::{Error, Result};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Slab {
below: f64,
core: f64,
above: f64,
thickness: Length,
}
impl Slab {
pub fn new(below: f64, core: f64, above: f64, thickness: Length) -> Result<Slab> {
let ok = |n: f64| n.is_finite() && n >= 1.0;
if !ok(below) || !ok(core) || !ok(above) {
return Err(Error::invalid(
"slab",
format!("indices must be finite and at least 1, got {below}, {core}, {above}"),
));
}
if core <= below || core <= above {
return Err(Error::invalid(
"slab",
format!("the core ({core}) must have the highest index, not {below} and {above}"),
));
}
let t = thickness.to_um();
if !(t.is_finite() && t > 0.0) {
return Err(Error::invalid(
"slab",
format!("the thickness must be positive, got {thickness}"),
));
}
Ok(Slab {
below,
core,
above,
thickness,
})
}
pub fn thickness(&self) -> Length {
self.thickness
}
fn condition(&self, pol: Polarization, k: f64, n: f64, m: usize) -> f64 {
let (n1, n2, n3) = (self.below, self.core, self.above);
let h = k * (n2 * n2 - n * n).max(0.0).sqrt();
let q = k * (n * n - n1 * n1).max(0.0).sqrt();
let p = k * (n * n - n3 * n3).max(0.0).sqrt();
let (q, p) = match pol {
Polarization::Te => (q, p),
Polarization::Tm => ((n2 / n1).powi(2) * q, (n2 / n3).powi(2) * p),
};
h * self.thickness.to_um() - q.atan2(h) - p.atan2(h) - m as f64 * PI
}
pub fn modes(&self, polarization: Polarization, wavelength: Wavelength) -> Vec<SlabMode> {
let k = wavelength.wavenumber();
let lo = self.below.max(self.above);
let hi = self.core;
let mut modes = Vec::new();
for m in 0.. {
if self.condition(polarization, k, lo, m) <= 0.0 {
break;
}
let (mut a, mut b) = (lo, hi);
while b - a > f64::EPSILON * hi {
let mid = 0.5 * (a + b);
if mid <= a || mid >= b {
break;
}
if self.condition(polarization, k, mid, m) > 0.0 {
a = mid;
} else {
b = mid;
}
}
let n = 0.5 * (a + b);
modes.push(SlabMode::new(*self, polarization, m, k, n));
}
modes
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SlabMode {
slab: Slab,
polarization: Polarization,
order: usize,
effective_index: f64,
h: f64,
q: f64,
p: f64,
}
impl SlabMode {
fn new(slab: Slab, polarization: Polarization, order: usize, k: f64, n: f64) -> SlabMode {
SlabMode {
slab,
polarization,
order,
effective_index: n,
h: k * (slab.core * slab.core - n * n).max(0.0).sqrt(),
q: k * (n * n - slab.below * slab.below).max(0.0).sqrt(),
p: k * (n * n - slab.above * slab.above).max(0.0).sqrt(),
}
}
pub fn polarization(&self) -> Polarization {
self.polarization
}
pub fn order(&self) -> usize {
self.order
}
pub fn effective_index(&self) -> f64 {
self.effective_index
}
pub fn wavenumbers(&self) -> (f64, f64, f64) {
(self.h, self.q, self.p)
}
pub fn field(&self, x: Length) -> (f64, f64) {
let x = x.to_um();
let t = self.slab.thickness.to_um();
let (h, q, p) = (self.h, self.q, self.p);
let (a, b, lower) = match self.polarization {
Polarization::Te => (1.0, q / h, q),
Polarization::Tm => {
let qb = (self.slab.core / self.slab.below).powi(2) * q;
(h / qb, 1.0, q)
}
};
if x < 0.0 {
let v = a * (lower * x).exp();
(v, lower * v)
} else if x <= t {
(
a * (h * x).cos() + b * (h * x).sin(),
h * (b * (h * x).cos() - a * (h * x).sin()),
)
} else {
let top = a * (h * t).cos() + b * (h * t).sin();
let v = top * (-p * (x - t)).exp();
(v, -p * v)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn lam(um: f64) -> Wavelength {
Wavelength::um(um).unwrap()
}
fn soi() -> Slab {
Slab::new(1.444, 3.473, 1.444, Length::nm(220.0)).unwrap()
}
#[test]
fn the_books_slab_gives_its_effective_indices() {
let te = soi().modes(Polarization::Te, lam(1.55));
let tm = soi().modes(Polarization::Tm, lam(1.55));
assert_eq!(
(te.len(), tm.len()),
(1, 1),
"a single TE and TM mode at 220 nm"
);
assert!(
(te[0].effective_index() - 2.845).abs() < 5e-4,
"{}",
te[0].effective_index()
);
assert!(
(tm[0].effective_index() - 2.051).abs() < 5e-4,
"{}",
tm[0].effective_index()
);
}
fn assert_printed(modes: &[SlabMode], printed: &[f64]) {
let got: Vec<f64> = modes.iter().map(SlabMode::effective_index).collect();
assert_eq!(got.len(), printed.len(), "{got:?}");
for (n, want) in got.iter().zip(printed) {
assert!((n - want).abs() < 5e-5, "{n} vs the printed {want}");
}
}
#[test]
fn yariv_and_yehs_asymmetric_slab_gives_their_effective_indices() {
let slab = Slab::new(1.0, 2.0, 1.7, Length::um(1.0)).unwrap();
assert_printed(&slab.modes(Polarization::Te, lam(1.0)), &[1.9594, 1.8375]);
assert_eq!(slab.modes(Polarization::Tm, lam(1.0)).len(), 2);
}
#[test]
fn yariv_and_yehs_symmetric_slab_gives_their_effective_indices() {
let slab = Slab::new(1.5, 1.6, 1.5, Length::um(5.0)).unwrap();
assert_printed(
&slab.modes(Polarization::Te, lam(1.55)),
&[1.5946, 1.5785, 1.5521, 1.5175],
);
}
#[test]
fn the_roots_satisfy_the_books_tangent_form() {
let slab = Slab::new(1.0, 3.473, 1.444, Length::nm(600.0)).unwrap();
let t = 0.6;
for pol in [Polarization::Te, Polarization::Tm] {
let modes = slab.modes(pol, lam(1.31));
assert!(modes.len() >= 2);
for m in modes {
let (h, q, p) = m.wavenumbers();
let rhs = match pol {
Polarization::Te => (p + q) / h / (1.0 - p * q / (h * h)),
Polarization::Tm => {
let (qb, pb) = (3.473f64.powi(2) * q, (3.473f64 / 1.444).powi(2) * p);
h * (pb + qb) / (h * h - pb * qb)
}
};
let lhs = (h * t).tan();
assert!(
(lhs - rhs).abs() < 1e-9 * (1.0 + rhs.abs()),
"{pol:?} {}: {lhs} vs {rhs}",
m.order()
);
}
}
}
#[test]
fn a_symmetric_slab_guides_one_te_mode_per_pi_of_v() {
for t_nm in [100.0, 300.0, 700.0, 1500.0] {
let slab = Slab::new(1.5, 2.0, 1.5, Length::nm(t_nm)).unwrap();
let v = lam(1.0).wavenumber() * t_nm / 1000.0 * (4.0f64 - 2.25).sqrt();
let expected = (v / PI).ceil() as usize;
assert_eq!(
slab.modes(Polarization::Te, lam(1.0)).len(),
expected,
"{t_nm} nm"
);
}
}
#[test]
fn an_asymmetric_slab_has_a_cutoff() {
let (n1, n2, n3): (f64, f64, f64) = (1.0, 2.0, 1.5);
let k = lam(1.0).wavenumber();
let root = (n2 * n2 - n3 * n3).sqrt();
let t_c = ((n3 * n3 - n1 * n1).sqrt() / root).atan() / (k * root);
let below = Slab::new(n3, n2, n1, Length::um(0.98 * t_c)).unwrap();
let above = Slab::new(n3, n2, n1, Length::um(1.02 * t_c)).unwrap();
assert!(below.modes(Polarization::Te, lam(1.0)).is_empty());
assert_eq!(above.modes(Polarization::Te, lam(1.0)).len(), 1);
}
#[test]
fn the_fields_meet_the_boundary_conditions() {
let slab = Slab::new(1.444, 3.473, 1.0, Length::nm(400.0)).unwrap();
let eps = 1e-9;
for pol in [Polarization::Te, Polarization::Tm] {
for m in slab.modes(pol, lam(1.55)) {
for (x, n_in, n_out) in [(0.0, 1.444, 3.473), (0.4, 3.473, 1.0)] {
let (v0, d0) = m.field(Length::um(x - eps));
let (v1, d1) = m.field(Length::um(x + eps));
let scale = v0.abs().max(1.0);
assert!((v0 - v1).abs() < 1e-6 * scale, "{pol:?} value at {x}");
let (d0, d1) = match pol {
Polarization::Te => (d0, d1),
Polarization::Tm => (d0 / (n_in * n_in), d1 / (n_out * n_out)),
};
assert!(
(d0 - d1).abs() < 1e-5 * (d0.abs().max(1.0)),
"{pol:?} slope at {x}: {d0} vs {d1}"
);
}
}
}
}
#[test]
fn modes_come_fundamental_first_and_decay_outside() {
let slab = Slab::new(1.444, 3.473, 1.444, Length::um(1.0)).unwrap();
let te = slab.modes(Polarization::Te, lam(1.55));
assert!(te.len() >= 3);
assert!(
te.windows(2)
.all(|w| w[0].effective_index() > w[1].effective_index())
);
assert!(te.iter().enumerate().all(|(i, m)| m.order() == i));
let (far, _) = te[0].field(Length::um(-2.0));
let (near, _) = te[0].field(Length::um(-0.1));
assert!(far.abs() < 1e-3 * near.abs());
let tm = slab.modes(Polarization::Tm, lam(1.55));
assert!(te[0].effective_index() > tm[0].effective_index());
}
#[test]
fn bad_slabs_are_errors() {
assert!(Slab::new(1.444, 1.4, 1.444, Length::nm(220.0)).is_err());
assert!(Slab::new(1.444, 3.473, 3.5, Length::nm(220.0)).is_err());
assert!(Slab::new(0.5, 3.473, 1.444, Length::nm(220.0)).is_err());
assert!(Slab::new(1.444, f64::NAN, 1.444, Length::nm(220.0)).is_err());
assert!(Slab::new(1.444, 3.473, 1.444, Length::ZERO).is_err());
}
}