use std::f64::consts::PI;
use crate::units::{Length, Wavelength};
use crate::{Error, Result};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Family {
Ex,
Ey,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Rectangle {
core: f64,
above: f64,
left: f64,
below: f64,
right: f64,
width: f64,
height: f64,
}
impl Rectangle {
pub fn uniform(core: f64, cladding: f64, width: Length, height: Length) -> Result<Rectangle> {
Rectangle::new(core, [cladding; 4], width, height)
}
pub fn new(core: f64, claddings: [f64; 4], width: Length, height: Length) -> Result<Rectangle> {
if !(core.is_finite()
&& claddings
.iter()
.all(|&n| n.is_finite() && n >= 1.0 && n < core))
{
return Err(Error::invalid(
"rectangle",
format!(
"the claddings {claddings:?} must be at least 1 and below the core's {core}"
),
));
}
let (a, b) = (width.to_um(), height.to_um());
if !(a.is_finite() && a > 0.0 && b.is_finite() && b > 0.0) {
return Err(Error::invalid(
"rectangle",
format!("the size must be positive, got {width} by {height}"),
));
}
let [above, left, below, right] = claddings;
Ok(Rectangle {
core,
above,
left,
below,
right,
width: a,
height: b,
})
}
fn sides(&self, family: Family) -> [(f64, f64); 4] {
let n1 = self.core * self.core;
let w = |n: f64| n * n / n1;
match family {
Family::Ey => [
(self.left, 1.0),
(self.right, 1.0),
(self.above, w(self.above)),
(self.below, w(self.below)),
],
Family::Ex => [
(self.left, w(self.left)),
(self.right, w(self.right)),
(self.above, 1.0),
(self.below, 1.0),
],
}
}
fn transverse(
k0: f64,
core: f64,
d: f64,
order: usize,
(n_a, w_a): (f64, f64),
(n_b, w_b): (f64, f64),
) -> Option<f64> {
let k1 = k0 * core;
let limit = (k1 * k1 - (k0 * n_a).powi(2))
.min(k1 * k1 - (k0 * n_b).powi(2))
.sqrt();
let f = |k: f64| {
let xi = |n: f64| 1.0 / (k1 * k1 - (k0 * n).powi(2) - k * k).max(0.0).sqrt();
k * d + (w_a * k * xi(n_a)).atan() + (w_b * k * xi(n_b)).atan() - order as f64 * PI
};
let (mut lo, mut hi) = (0.0, limit * (1.0 - 1e-15));
if f(hi) < 0.0 {
return None;
}
while hi - lo > 1e-15 * limit {
let mid = 0.5 * (lo + hi);
if mid <= lo || mid >= hi {
break;
}
if f(mid) < 0.0 {
lo = mid;
} else {
hi = mid;
}
}
Some(0.5 * (lo + hi))
}
pub fn mode(&self, family: Family, p: usize, q: usize, wavelength: Wavelength) -> Option<f64> {
let k0 = wavelength.wavenumber();
let [left, right, above, below] = self.sides(family);
let kx = Rectangle::transverse(k0, self.core, self.width, p, left, right)?;
let ky = Rectangle::transverse(k0, self.core, self.height, q, above, below)?;
self.effective(k0, kx, ky)
}
pub fn closed_form(
&self,
family: Family,
p: usize,
q: usize,
wavelength: Wavelength,
) -> Option<f64> {
let k0 = wavelength.wavenumber();
let big_a = |n: f64| PI / (k0 * (self.core * self.core - n * n).sqrt());
let [(l, wl), (r, wr), (u, wu), (d, wd)] = self.sides(family);
let kx = p as f64 * PI
/ self.width
/ (1.0 + (wl * big_a(l) + wr * big_a(r)) / (PI * self.width));
let ky = q as f64 * PI
/ self.height
/ (1.0 + (wu * big_a(u) + wd * big_a(d)) / (PI * self.height));
self.effective(k0, kx, ky)
}
fn effective(&self, k0: f64, kx: f64, ky: f64) -> Option<f64> {
let k1 = k0 * self.core;
let kz2 = k1 * k1 - kx * kx - ky * ky;
let cut = [self.above, self.left, self.below, self.right]
.into_iter()
.fold(0.0, f64::max)
* k0;
(kz2 > cut * cut).then(|| kz2.sqrt() / k0)
}
}
pub fn normalized(n_eff: f64, core: f64, cladding: f64) -> f64 {
(n_eff * n_eff - cladding * cladding) / (core * core - cladding * cladding)
}
pub(crate) fn closed_form_deviation(core: f64, cladding: f64) -> f64 {
let lam = Wavelength::from_um_unchecked(1.0);
let mut worst: f64 = 0.0;
for k in 0..=32 {
let big_b = 0.8 + 0.1 * f64::from(k);
let b = big_b / (2.0 * (core * core - cladding * cladding).sqrt());
let r = Rectangle::uniform(core, cladding, Length::um(2.0 * b), Length::um(b))
.expect("a valid rectangle");
for family in [Family::Ex, Family::Ey] {
let (Some(exact), Some(closed)) =
(r.mode(family, 1, 1, lam), r.closed_form(family, 1, 1, lam))
else {
continue;
};
let (e, c) = (
normalized(exact, core, cladding),
normalized(closed, core, cladding),
);
if e >= 0.5 {
worst = worst.max((c - e).abs() / e);
}
}
}
worst
}
pub(crate) fn against_vector(big_b: f64, family: Family) -> (f64, f64) {
use crate::mode::vector::{
self, Boundaries, Boundary, CrossSection, Permittivity, graded_nodes,
};
use num_complex::Complex64 as c64;
let lam = Wavelength::from_um_unchecked(1.0);
let (core, clad) = (1.5f64, 1.5f64 / 1.05);
let b = big_b / (2.0 * (core * core - clad * clad).sqrt());
let a = 2.0 * b;
let h = b / 40.0;
let x = graded_nodes(
0.0,
a / 2.0 + 4.0,
(0.0, a / 2.0 + 0.2),
h,
0.05,
0.1,
&[a / 2.0],
);
let y = graded_nodes(
0.0,
b / 2.0 + 4.0,
(0.0, b / 2.0 + 0.2),
h,
0.05,
0.1,
&[b / 2.0],
);
let mut cells = Vec::new();
for i in 0..x.len() - 1 {
for j in 0..y.len() - 1 {
let inside = 0.5 * (x[i] + x[i + 1]) < a / 2.0 && 0.5 * (y[j] + y[j + 1]) < b / 2.0;
let n = if inside { core } else { clad };
cells.push(Permittivity::isotropic(c64::new(n * n, 0.0)));
}
}
let (west, south) = match family {
Family::Ex => (Boundary::ElectricWall, Boundary::MagneticWall),
Family::Ey => (Boundary::MagneticWall, Boundary::ElectricWall),
};
let cs = CrossSection::new(x, y, cells)
.and_then(|cs| {
cs.with_boundaries(Boundaries {
west,
south,
..Boundaries::default()
})
})
.expect("a valid guide");
let found = vector::modes(&cs, lam, 1, None).map_or(f64::NAN, |m| m[0].effective_index().re);
let marcatili = Rectangle::uniform(core, clad, Length::um(a), Length::um(b))
.ok()
.and_then(|r| r.mode(family, 1, 1, lam))
.unwrap_or(f64::NAN);
(
normalized(found, core, clad),
normalized(marcatili, core, clad),
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mode::Polarization;
use crate::mode::slab::Slab;
fn lam() -> Wavelength {
Wavelength::um(1.0).unwrap()
}
#[test]
fn a_very_wide_rectangle_is_the_slab() {
let (core, clad, b) = (1.5, 1.45, 1.0);
let r = Rectangle::uniform(core, clad, Length::um(2000.0), Length::um(b)).unwrap();
let slab = Slab::new(clad, core, clad, Length::um(b)).unwrap();
for (family, pol) in [
(Family::Ey, Polarization::Tm),
(Family::Ex, Polarization::Te),
] {
let want = slab.modes(pol, lam())[0].effective_index();
let got = r.mode(family, 1, 1, lam()).unwrap();
assert!((got - want).abs() < 1e-6, "{family:?}: {got} vs {want}");
}
}
#[test]
fn the_closed_form_is_within_a_few_percent_where_marcatili_says() {
let (core, clad) = (1.5, 1.5 / 1.05);
let worst = closed_form_deviation(core, clad);
assert!(worst < 0.05, "{worst}");
}
#[test]
fn marcatili_meets_the_vector_solver_far_from_cutoff_and_not_near_it() {
for family in [Family::Ex, Family::Ey] {
let (v3, m3) = against_vector(3.0, family);
let (v1, m1) = against_vector(1.0, family);
assert!((v3 - m3).abs() < 5e-4, "{family:?} B 3: {v3} vs {m3}");
assert!(
(v1 - m1).abs() > 5e-3 && (v1 - m1).abs() < 2e-2,
"{family:?} B 1: {v1} vs {m1}"
);
}
}
#[test]
fn bad_rectangles_are_errors() {
let um = Length::um;
assert!(Rectangle::uniform(1.5, 1.6, um(1.0), um(1.0)).is_err());
assert!(Rectangle::uniform(1.5, 1.4, um(0.0), um(1.0)).is_err());
assert!(Rectangle::new(1.5, [1.4, 1.4, 0.5, 1.4], um(1.0), um(1.0)).is_err());
}
}