meanas/examples/eme_taper_cmt.py
2026-05-04 18:41:38 -07:00

134 lines
4.1 KiB
Python

"""
Local-mode CMT example for a continuously varying rib-waveguide taper.
This example keeps geometry construction outside `meanas.fdfd.eme`: it samples a
width taper at several cross-sections, solves and normalizes each local mode with
`waveguide_2d`, then asks `eme.get_taper_s(...)` for the bidirectional taper
scattering matrix.
"""
from __future__ import annotations
import numpy
from numpy import pi
from meanas.fdmath import vec
from meanas.fdfd import eme, waveguide_2d
WL = 1310.0
DX = 80.0
TAPER_LENGTH = 4e3
WIDTH_LEFT = 280.0
WIDTH_RIGHT = 520.0
THF = 160.0
THP = 80.0
EPS_SI = 3.51 ** 2
EPS_OX = 1.453 ** 2
MODE_NUMBERS = numpy.array([0])
N_SECTIONS = 7
def build_dxes(shape: tuple[int, int], dx: float = DX) -> list[list[numpy.ndarray]]:
nx, ny = shape
return [
[numpy.full(nx, dx), numpy.full(ny, dx)],
[numpy.full(nx, dx), numpy.full(ny, dx)],
]
def build_cross_section(
*,
width: float,
x: numpy.ndarray,
y: numpy.ndarray,
eps_si: float = EPS_SI,
eps_ox: float = EPS_OX,
thf: float = THF,
thp: float = THP,
) -> numpy.ndarray:
epsilon = numpy.full((3, x.size, y.size), eps_ox, dtype=float)
xx = x[:, None]
yy = y[None, :]
slab = (yy >= 0) & (yy <= thp)
rib = (abs(xx) <= width / 2) & (yy >= 0) & (yy <= thf)
epsilon[:, slab.repeat(x.size, axis=0)] = eps_si
epsilon[:, rib] = eps_si
return epsilon
def solve_cross_section_modes(
epsilon: numpy.ndarray,
*,
omega: float,
dxes_2d: list[list[numpy.ndarray]],
mode_numbers: numpy.ndarray = MODE_NUMBERS,
) -> tuple[list[tuple[numpy.ndarray, numpy.ndarray]], numpy.ndarray]:
epsilon_vec = vec(epsilon)
e_xys, wavenumbers = waveguide_2d.solve_modes(
epsilon=epsilon_vec,
omega=omega,
dxes=dxes_2d,
mode_numbers=mode_numbers,
)
eh_fields = [
waveguide_2d.normalized_fields_e(
e_xy,
wavenumber=wavenumber,
dxes=dxes_2d,
omega=omega,
epsilon=epsilon_vec,
)
for e_xy, wavenumber in zip(e_xys, wavenumbers, strict=True)
]
return eh_fields, wavenumbers
def solve_taper_sections() -> tuple[list[eme.ModeSection], list[float], float, list[list[numpy.ndarray]]]:
omega = 2 * pi / WL
x = numpy.arange(-480, 480 + DX, DX)
y = numpy.arange(-240, 400 + DX, DX)
dxes_2d = build_dxes((x.size, y.size))
z_samples = numpy.linspace(0, TAPER_LENGTH, N_SECTIONS)
widths = numpy.linspace(WIDTH_LEFT, WIDTH_RIGHT, N_SECTIONS)
sections = []
neffs = []
for z_coord, width in zip(z_samples, widths, strict=True):
epsilon = build_cross_section(width=float(width), x=x, y=y)
modes, wavenumbers = solve_cross_section_modes(epsilon, omega=omega, dxes_2d=dxes_2d)
sections.append(eme.ModeSection(float(z_coord), modes, wavenumbers))
neffs.append(float(numpy.real(wavenumbers[0] / omega)))
return sections, neffs, omega, dxes_2d
def print_summary(
taper_s: numpy.ndarray,
abrupt_s: numpy.ndarray,
neffs: list[float],
) -> None:
n_modes = len(MODE_NUMBERS)
print('sampled taper effective indices:', ', '.join(f'{value:.5f}' for value in neffs))
print(f'abrupt endpoint reflection |S_00|^2 = {abs(abrupt_s[0, 0]) ** 2:.6f}')
print(f'abrupt endpoint transmission |S_{n_modes},0|^2 = {abs(abrupt_s[n_modes, 0]) ** 2:.6f}')
print(f'taper CMT reflection |S_00|^2 = {abs(taper_s[0, 0]) ** 2:.6f}')
print(f'taper CMT transmission |S_{n_modes},0|^2 = {abs(taper_s[n_modes, 0]) ** 2:.6f}')
print(f'taper CMT total output power = {numpy.sum(abs(taper_s[:, 0]) ** 2):.6f}')
def main() -> None:
sections, neffs, _omega, dxes_2d = solve_taper_sections()
taper_s = eme.get_taper_s(sections, dxes=dxes_2d)
abrupt_s = eme.get_s(
sections[0].modes,
sections[0].wavenumbers,
sections[-1].modes,
sections[-1].wavenumbers,
dxes=dxes_2d,
)
print_summary(taper_s, abrupt_s, neffs)
if __name__ == '__main__':
main()