use num_complex::Complex64 as c64;
#[derive(Clone, Debug, PartialEq)]
pub struct Fields {
pub(crate) xc: Vec<f64>,
pub(crate) yc: Vec<f64>,
pub(crate) area: Vec<f64>,
pub(crate) e: Vec<[c64; 3]>,
pub(crate) h: Vec<[c64; 3]>,
}
impl Fields {
pub fn x(&self) -> &[f64] {
&self.xc
}
pub fn y(&self) -> &[f64] {
&self.yc
}
pub fn e(&self, i: usize, j: usize) -> [c64; 3] {
self.e[i * self.yc.len() + j]
}
pub fn h(&self, i: usize, j: usize) -> [c64; 3] {
self.h[i * self.yc.len() + j]
}
pub fn cross(&self, other: &Fields) -> Option<c64> {
if self.xc != other.xc || self.yc != other.yc {
return None;
}
Some(
self.e
.iter()
.zip(&other.h)
.zip(&self.area)
.map(|((e, h), a)| (e[0] * h[1].conj() - e[1] * h[0].conj()) * a)
.sum(),
)
}
pub fn power(&self) -> f64 {
0.5 * self.cross(self).map_or(f64::NAN, |c| c.re)
}
pub fn coupling(&self, other: &Fields) -> Option<f64> {
let (ab, ba) = (self.cross(other)?, other.cross(self)?);
let (aa, bb) = (self.cross(self)?.re, other.cross(other)?.re);
Some((ab * ba).re / (aa * bb))
}
}
pub(crate) fn slab_te(
t: f64,
h: f64,
) -> (
crate::mode::vector::CrossSection,
crate::mode::vector::VectorMode,
) {
use crate::mode::vector::{self, Boundaries, Boundary, CrossSection, Permittivity};
let n = (4.02 / h).round() as usize;
let cs = CrossSection::uniform((-2.01, 2.01, n), (0.0, 4.0 * h, 4), |x, _| {
let n: f64 = if x.abs() < t / 2.0 { 3.473 } else { 1.444 };
Permittivity::isotropic(c64::new(n * n, 0.0))
})
.and_then(|cs| {
cs.with_boundaries(Boundaries {
south: Boundary::ElectricWall,
north: Boundary::ElectricWall,
..Boundaries::default()
})
})
.expect("a valid slab");
let m = vector::modes(
&cs,
crate::units::Wavelength::from_um_unchecked(1.55),
1,
None,
)
.expect("the solver converges")
.remove(0);
(cs, m)
}
pub(crate) fn slab_butt_coupling(h: f64) -> (f64, f64) {
use crate::mode::Polarization;
use crate::mode::slab::Slab;
use crate::units::{Length, Wavelength};
let lam = Wavelength::from_um_unchecked(1.55);
let field = |t: f64| {
let m = Slab::new(1.444, 3.473, 1.444, Length::um(t))
.expect("a valid slab")
.modes(Polarization::Te, lam)[0];
move |x: f64| m.field(Length::um(x + t / 2.0)).0
};
let (a, b) = (field(0.22), field(0.30));
let (mut ab, mut aa, mut bb) = (0.0, 0.0, 0.0);
let steps = 400_000;
for k in 0..steps {
let x = -2.01 + 4.02 * (k as f64 + 0.5) / steps as f64;
let (fa, fb) = (a(x), b(x));
ab += fa * fb;
aa += fa * fa;
bb += fb * fb;
}
let exact = ab * ab / (aa * bb);
let (ca, ma) = slab_te(0.22, h);
let (cb, mb) = slab_te(0.30, h);
let got = ma
.fields(&ca)
.and_then(|fa| mb.fields(&cb).map(|fb| fa.coupling(&fb)))
.ok()
.flatten()
.unwrap_or(f64::NAN);
(got, exact)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mode::vector::{self, CrossSection};
use crate::units::Wavelength;
fn lam() -> Wavelength {
Wavelength::um(1.55).unwrap()
}
fn slab(t: f64) -> (CrossSection, vector::VectorMode) {
slab_te(t, 0.01)
}
#[test]
fn a_te_slabs_impedance_is_k_over_beta() {
let (cs, m) = slab(0.22);
let f = m.fields(&cs).unwrap();
let ratio = -lam().wavenumber() / (m.effective_index().re * lam().wavenumber());
for i in [150, 190, 201, 215] {
let (e, h) = (f.e(i, 1), f.h(i, 1));
let got = (e[1] / h[0]).re;
assert!(
(got / ratio - 1.0).abs() < 2e-3,
"cell {i}: {got} vs {ratio}"
);
assert!(e[0].norm() < 1e-6 * e[1].norm() && h[1].norm() < 1e-6 * h[0].norm());
}
assert!(f.power() > 0.0);
}
#[test]
fn butt_coupling_between_two_slabs_is_their_fields_overlap() {
let (got, exact) = slab_butt_coupling(0.01);
assert!((got - exact).abs() < 1e-4, "{got} vs {exact}");
assert!(exact < 0.999 && exact > 0.9, "{exact}");
}
#[test]
fn a_mode_couples_wholly_to_itself_and_not_to_another() {
let cs = vector::strip(0.02);
let found = vector::modes(&cs, lam(), 2, None).unwrap();
let (te, tm) = (found[0].fields(&cs).unwrap(), found[1].fields(&cs).unwrap());
assert!((te.coupling(&te).unwrap() - 1.0).abs() < 1e-12);
assert!(
te.coupling(&tm).unwrap().abs() < 1e-6,
"{}",
te.coupling(&tm).unwrap()
);
assert!(te.power() > 0.0 && tm.power() > 0.0);
let other = vector::strip(0.025);
assert!(found[0].fields(&other).is_err());
}
}