meanas/examples/eme_taper_pml_modes.py
2026-07-14 21:15:45 -07:00

90 lines
2.9 KiB
Python

"""Build tracked complex/PML modal bases for a slowly varying taper."""
from __future__ import annotations
import numpy
from meanas.fdmath import vec
from meanas.fdfd import eme, scpml, waveguide_2d_complex
WAVELENGTH = 1.55
OMEGA = 2 * numpy.pi / WAVELENGTH
SHAPE = (17, 17)
SPACING = 0.25
PML_CELLS = 3
EPS_CLADDING = 1.45 ** 2
EPS_CORE = 2.0 ** 2
def build_grids() -> tuple[list[list[numpy.ndarray]], list[list[numpy.ndarray]]]:
"""Build matching physical and complex-stretched transverse Yee grids.
Returns:
The real overlap grid followed by the PML-stretched solve grid.
"""
physical = [[numpy.full(size, SPACING) for size in SHAPE] for _ in range(2)]
stretched = [[values.copy() for values in family] for family in physical]
for axis in (0, 1):
for polarity in (-1, +1):
stretched = scpml.stretch_with_scpml(
stretched,
axis=axis,
polarity=polarity,
omega=OMEGA,
epsilon_effective=EPS_CLADDING,
thickness=PML_CELLS,
)
return physical, stretched
def build_epsilon(edge_fill: float) -> numpy.ndarray:
"""Construct and vectorize one taper cross-section permittivity profile.
Args:
edge_fill: Fractional core fill used for the two cells beside the
central three-cell-wide core.
Returns:
Vectorized three-component relative permittivity field.
"""
epsilon = numpy.full((3, *SHAPE), EPS_CLADDING)
center_x, center_y = (size // 2 for size in SHAPE)
epsilon[:, center_x - 1:center_x + 2, center_y] = EPS_CORE
mixed_epsilon = EPS_CLADDING + edge_fill * (EPS_CORE - EPS_CLADDING)
epsilon[:, center_x - 1:center_x + 2, center_y - 1] = mixed_epsilon
epsilon[:, center_x - 1:center_x + 2, center_y + 1] = mixed_epsilon
return vec(epsilon)
def main() -> None:
"""Solve, track, and summarize the sampled complex modal bases."""
physical_dxes, solve_dxes = build_grids()
z_coords = numpy.array([0.0, 5.0, 10.0])
bases = [
waveguide_2d_complex.solve_modes_near(
OMEGA,
solve_dxes,
build_epsilon(edge_fill),
target_wavenumbers=[1.45 * OMEGA],
modes_per_target=3,
candidate_margin=3,
overlap_dxes=physical_dxes,
)
for edge_fill in (0.30, 0.35, 0.40)
]
tracked = waveguide_2d_complex.track_mode_path(bases)
sections = eme.mode_sections_from_bases(z_coords, tracked)
for z_coord, basis in zip(z_coords, tracked, strict=True):
print(
f'z={z_coord:5.1f}: '
f'beta={basis.wavenumbers[0]:.6g}, '
f'PML participation={basis.diagnostics.pml_participation[0]:.3f}, '
f'right residual={basis.diagnostics.right_residuals[0]:.2e}',
)
print(f'constructed {len(sections)} tracked EME sections with {len(sections[0].modes)} modes each')
if __name__ == '__main__':
main()