[examples] add waveguide_real
This commit is contained in:
parent
3e4aee1197
commit
f7aa21a42a
1 changed files with 152 additions and 0 deletions
152
examples/waveguide_real.py
Normal file
152
examples/waveguide_real.py
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
"""
|
||||
Real-valued straight-waveguide FDTD/FDFD comparison.
|
||||
|
||||
This example shows the user-facing "compare real FDTD against reconstructed real
|
||||
FDFD" workflow:
|
||||
|
||||
1. build a straight waveguide on a uniform Yee grid,
|
||||
2. drive it with a real-valued continuous-wave mode source,
|
||||
3. solve the matching FDFD problem from the analytic source phasor, and
|
||||
4. compare late real monitor slices against `fdtd.reconstruct_real_e/h(...)`.
|
||||
|
||||
Unlike the complex-source examples, this script does not use phasor extraction
|
||||
as the main output. The comparison target is the real field itself.
|
||||
"""
|
||||
|
||||
import numpy
|
||||
|
||||
from meanas import fdfd, fdtd
|
||||
from meanas.fdfd import functional, scpml, waveguide_3d
|
||||
from meanas.fdmath import vec, unvec
|
||||
|
||||
|
||||
DT = 0.25
|
||||
PERIOD_STEPS = 64
|
||||
OMEGA = 2 * numpy.pi / (PERIOD_STEPS * DT)
|
||||
CPML_THICKNESS = 3
|
||||
SHAPE = (3, 37, 13, 13)
|
||||
SOURCE_SLICES = (slice(5, 6), slice(None), slice(None))
|
||||
MONITOR_SLICES = (slice(30, 31), slice(None), slice(None))
|
||||
WARMUP_PERIODS = 16
|
||||
|
||||
|
||||
def build_uniform_dxes(shape: tuple[int, int, int, int]) -> list[list[numpy.ndarray]]:
|
||||
return [[numpy.ones(shape[axis + 1]) for axis in range(3)] for _ in range(2)]
|
||||
|
||||
|
||||
def build_epsilon(shape: tuple[int, int, int, int]) -> numpy.ndarray:
|
||||
epsilon = numpy.ones(shape, dtype=float)
|
||||
y0 = (shape[2] - 3) // 2
|
||||
z0 = (shape[3] - 3) // 2
|
||||
epsilon[:, :, y0:y0 + 3, z0:z0 + 3] = 12.0
|
||||
return epsilon
|
||||
|
||||
|
||||
def build_stretched_dxes(base_dxes: list[list[numpy.ndarray]]) -> list[list[numpy.ndarray]]:
|
||||
stretched_dxes = [[dx.copy() for dx in group] for group in base_dxes]
|
||||
for axis in (0, 1, 2):
|
||||
for polarity in (-1, 1):
|
||||
stretched_dxes = scpml.stretch_with_scpml(
|
||||
stretched_dxes,
|
||||
axis=axis,
|
||||
polarity=polarity,
|
||||
omega=OMEGA,
|
||||
epsilon_effective=1.0,
|
||||
thickness=CPML_THICKNESS,
|
||||
)
|
||||
return stretched_dxes
|
||||
|
||||
|
||||
def build_cpml_params() -> list[list[dict[str, numpy.ndarray | float]]]:
|
||||
return [
|
||||
[fdtd.cpml_params(axis=axis, polarity=polarity, dt=DT, thickness=CPML_THICKNESS, epsilon_eff=1.0)
|
||||
for polarity in (-1, 1)]
|
||||
for axis in range(3)
|
||||
]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
epsilon = build_epsilon(SHAPE)
|
||||
base_dxes = build_uniform_dxes(SHAPE)
|
||||
stretched_dxes = build_stretched_dxes(base_dxes)
|
||||
|
||||
source_mode = waveguide_3d.solve_mode(
|
||||
0,
|
||||
omega=OMEGA,
|
||||
dxes=base_dxes,
|
||||
axis=0,
|
||||
polarity=1,
|
||||
slices=SOURCE_SLICES,
|
||||
epsilon=epsilon,
|
||||
)
|
||||
j_mode = waveguide_3d.compute_source(
|
||||
E=source_mode['E'],
|
||||
wavenumber=source_mode['wavenumber'],
|
||||
omega=OMEGA,
|
||||
dxes=base_dxes,
|
||||
axis=0,
|
||||
polarity=1,
|
||||
slices=SOURCE_SLICES,
|
||||
epsilon=epsilon,
|
||||
)
|
||||
|
||||
e_fdfd = unvec(
|
||||
fdfd.solvers.generic(
|
||||
J=vec(j_mode),
|
||||
omega=OMEGA,
|
||||
dxes=stretched_dxes,
|
||||
epsilon=vec(epsilon),
|
||||
matrix_solver_opts={'atol': 1e-10, 'rtol': 1e-7},
|
||||
),
|
||||
SHAPE[1:],
|
||||
)
|
||||
h_fdfd = functional.e2h(OMEGA, stretched_dxes)(e_fdfd)
|
||||
|
||||
update_e, update_h = fdtd.updates_with_cpml(
|
||||
cpml_params=build_cpml_params(),
|
||||
dt=DT,
|
||||
dxes=base_dxes,
|
||||
epsilon=epsilon,
|
||||
)
|
||||
|
||||
e_field = numpy.zeros_like(epsilon)
|
||||
h_field = numpy.zeros_like(epsilon)
|
||||
total_steps = (WARMUP_PERIODS + 1) * PERIOD_STEPS
|
||||
e_errors: list[float] = []
|
||||
h_errors: list[float] = []
|
||||
|
||||
for step in range(total_steps):
|
||||
update_e(e_field, h_field, epsilon)
|
||||
|
||||
# Real-valued FDTD uses the real part of the analytic mode source.
|
||||
t_half = (step + 0.5) * DT
|
||||
j_real = (j_mode.real * numpy.cos(OMEGA * t_half) - j_mode.imag * numpy.sin(OMEGA * t_half)).real
|
||||
e_field -= DT * j_real / epsilon
|
||||
|
||||
update_h(e_field, h_field)
|
||||
|
||||
if step >= total_steps - PERIOD_STEPS // 4:
|
||||
reconstructed_e = fdtd.reconstruct_real_e(
|
||||
e_fdfd[:, MONITOR_SLICES[0], :, :][numpy.newaxis, ...],
|
||||
OMEGA,
|
||||
DT,
|
||||
step + 1,
|
||||
)[0]
|
||||
reconstructed_h = fdtd.reconstruct_real_h(
|
||||
h_fdfd[:, MONITOR_SLICES[0], :, :][numpy.newaxis, ...],
|
||||
OMEGA,
|
||||
DT,
|
||||
step + 1,
|
||||
)[0]
|
||||
|
||||
e_monitor = e_field[:, MONITOR_SLICES[0], :, :]
|
||||
h_monitor = h_field[:, MONITOR_SLICES[0], :, :]
|
||||
e_errors.append(numpy.linalg.norm(e_monitor - reconstructed_e) / numpy.linalg.norm(reconstructed_e))
|
||||
h_errors.append(numpy.linalg.norm(h_monitor - reconstructed_h) / numpy.linalg.norm(reconstructed_h))
|
||||
|
||||
print(f'late-cycle monitor E errors: min={min(e_errors):.4f} max={max(e_errors):.4f}')
|
||||
print(f'late-cycle monitor H errors: min={min(h_errors):.4f} max={max(h_errors):.4f}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue