134 lines
4.1 KiB
Python
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()
|