Update examples for sampled-interface tapers, explicit physical CPML spacing, copied SCPML widths, and the consolidated complex mode solver. Document breaking API changes, overlap measures, and PML convergence.
135 lines
4.2 KiB
Python
135 lines
4.2 KiB
Python
"""
|
|
Sampled-interface EME 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 reflection |S_00|^2 = {abs(taper_s[0, 0]) ** 2:.6f}')
|
|
print(f'taper transmission |S_{n_modes},0|^2 = {abs(taper_s[n_modes, 0]) ** 2:.6f}')
|
|
print(f'taper total output power = {numpy.sum(abs(taper_s[:, 0]) ** 2):.6f}')
|
|
|
|
|
|
def main() -> None:
|
|
sections, neffs, _omega, dxes_2d = solve_taper_sections()
|
|
prepared = eme.prepare_taper(sections, dxes=dxes_2d)
|
|
taper_s = eme.evaluate_taper_s(prepared)
|
|
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()
|