[docs] expand API and derivation docs
This commit is contained in:
parent
0568e1ba50
commit
5e95d66a7e
12 changed files with 608 additions and 127 deletions
|
|
@ -1,7 +1,12 @@
|
|||
"""
|
||||
Example code for running an FDTD simulation
|
||||
Example code for a broadband FDTD run with phasor extraction.
|
||||
|
||||
See main() for simulation setup.
|
||||
This script shows the intended low-level workflow for:
|
||||
|
||||
1. building a Yee-grid simulation with CPML on all faces,
|
||||
2. driving it with an electric-current pulse,
|
||||
3. extracting a single-frequency phasor on the fly, and
|
||||
4. checking that phasor against the matching stretched-grid FDFD operator.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
|
@ -150,7 +155,8 @@ def main():
|
|||
# print(f'Save time interval would be {sample_interval} * dt = {sample_interval * dt:3g}')
|
||||
|
||||
|
||||
# Source parameters and function
|
||||
# Source parameters and function. The pulse normalization is kept outside
|
||||
# accumulate_phasor(); the helper only performs the Fourier sum.
|
||||
source_phasor, delay = gaussian_packet(wl=wl, dwl=100, dt=dt, turn_on=1e-5)
|
||||
aa, cc, ss = source_phasor(numpy.arange(max_t))
|
||||
srca_real = aa * cc
|
||||
|
|
@ -170,7 +176,8 @@ def main():
|
|||
update_E(ee, hh, epsilon)
|
||||
|
||||
if tt < src_maxt:
|
||||
# This codebase uses E -= dt * J / epsilon for electric-current injection.
|
||||
# Electric-current injection uses E -= dt * J / epsilon, which is
|
||||
# the same sign convention used by the matching FDFD right-hand side.
|
||||
ee[1, *(grid.shape // 2)] -= srca_real[tt]
|
||||
update_H(ee, hh)
|
||||
|
||||
|
|
@ -193,9 +200,11 @@ def main():
|
|||
dt,
|
||||
ee,
|
||||
tt,
|
||||
# The pulse is delayed relative to t=0, so the readout needs the same phase shift.
|
||||
# The pulse is delayed relative to t=0, so the extracted phasor
|
||||
# needs the same phase offset in its sample times.
|
||||
offset_steps=0.5 - delay / dt,
|
||||
# accumulate_phasor() already includes dt, so undo the dt in phasor_norm here.
|
||||
# accumulate_phasor() already multiplies by dt, so pass the
|
||||
# discrete-sum normalization without its extra dt factor.
|
||||
weight=phasor_norm / dt,
|
||||
)
|
||||
|
||||
|
|
@ -205,6 +214,8 @@ def main():
|
|||
for pp in (-1, +1):
|
||||
for dd in range(3):
|
||||
stretch_with_scpml(dxes_fdfd, axis=dd, polarity=pp, omega=omega, epsilon_effective=n_air ** 2, thickness=pml_thickness)
|
||||
# Compare the extracted phasor to the FDFD operator on the stretched grid,
|
||||
# not the unstretched Yee spacings used by the raw time-domain update.
|
||||
A = e_full(omega=omega, dxes=dxes_fdfd, epsilon=epsilon)
|
||||
residual = norm(A @ vec(Eph) - vec(b)) / norm(vec(b))
|
||||
print(f'FDFD residual is {residual}')
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
"""
|
||||
Example code for running an OpenCL FDTD simulation
|
||||
Example code for guided-wave FDFD and FDTD comparison.
|
||||
|
||||
See main() for simulation setup.
|
||||
This example is the reference workflow for:
|
||||
|
||||
1. solving a waveguide port mode,
|
||||
2. turning that mode into a one-sided source and overlap window,
|
||||
3. comparing a direct FDFD solve against a time-domain phasor extracted from FDTD.
|
||||
"""
|
||||
from typing import Callable
|
||||
import logging
|
||||
|
|
@ -78,7 +82,7 @@ def get_waveguide_mode(
|
|||
omega: float,
|
||||
epsilon: fdfield_t,
|
||||
) -> tuple[vcfdfield_t, vcfdfield_t]:
|
||||
""" Create a mode source and overlap window """
|
||||
"""Create a mode source and overlap window for one forward-going port."""
|
||||
dims = numpy.array([[-10, -20, -15],
|
||||
[-10, 20, 15]]) * [[numpy.median(numpy.real(dx)) for dx in dxes[0]]]
|
||||
ind_dims = (grid.pos2ind(dims[0], which_shifts=None).astype(int),
|
||||
|
|
@ -94,6 +98,8 @@ def get_waveguide_mode(
|
|||
J = waveguide_3d.compute_source(E=wg_results['E'], wavenumber=wg_results['wavenumber'],
|
||||
omega=omega, epsilon=epsilon, **wg_args)
|
||||
|
||||
# compute_overlap_e() returns the normalized upstream overlap window used to
|
||||
# project another field onto this same guided mode.
|
||||
e_overlap = waveguide_3d.compute_overlap_e(E=wg_results['E'], wavenumber=wg_results['wavenumber'], **wg_args, omega=omega)
|
||||
return J, e_overlap
|
||||
|
||||
|
|
@ -111,7 +117,8 @@ def main(
|
|||
|
||||
grid, epsilon = draw_grid(dx=dx, pml_thickness=pml_thickness)
|
||||
|
||||
# Add PML
|
||||
# Add SCPML stretching to the FDFD grid; this matches the CPML-backed FDTD
|
||||
# run below so the two solvers see the same absorbing boundary profile.
|
||||
dxes = [grid.dxyz, grid.autoshifted_dxyz()]
|
||||
for a in (0, 1, 2):
|
||||
for p in (-1, 1):
|
||||
|
|
@ -275,7 +282,8 @@ def main2():
|
|||
# print(f'Save time interval would be {sample_interval} * dt = {sample_interval * dt:3g}')
|
||||
|
||||
|
||||
# Source parameters and function
|
||||
# Source parameters and function. The phasor helper only performs the
|
||||
# Fourier accumulation; the pulse normalization stays explicit here.
|
||||
source_phasor, delay = gaussian_packet(wl=wl, dwl=100, dt=dt, turn_on=1e-5)
|
||||
aa, cc, ss = source_phasor(numpy.arange(max_t))
|
||||
srca_real = aa * cc
|
||||
|
|
@ -295,7 +303,8 @@ def main2():
|
|||
update_E(ee, hh, epsilon)
|
||||
|
||||
if tt < src_maxt:
|
||||
# This codebase uses E -= dt * J / epsilon for electric-current injection.
|
||||
# Electric-current injection uses E -= dt * J / epsilon, which is
|
||||
# the sign convention matched by the FDFD source term -1j * omega * J.
|
||||
ee[1, *(grid.shape // 2)] -= srca_real[tt]
|
||||
update_H(ee, hh)
|
||||
|
||||
|
|
@ -317,9 +326,11 @@ def main2():
|
|||
dt,
|
||||
ee,
|
||||
tt,
|
||||
# The pulse is delayed relative to t=0, so the readout needs the same phase shift.
|
||||
# The pulse is delayed relative to t=0, so the extracted phasor must
|
||||
# apply the same delay to its sample times.
|
||||
offset_steps=0.5 - delay / dt,
|
||||
# accumulate_phasor() already includes dt, so undo the dt in phasor_norm here.
|
||||
# accumulate_phasor() already contributes dt, so remove the extra dt
|
||||
# from the externally computed normalization.
|
||||
weight=phasor_norm / dt,
|
||||
)
|
||||
|
||||
|
|
@ -329,6 +340,8 @@ def main2():
|
|||
for pp in (-1, +1):
|
||||
for dd in range(3):
|
||||
stretch_with_scpml(dxes_fdfd, axis=dd, polarity=pp, omega=omega, epsilon_effective=n_cladding ** 2, thickness=pml_thickness)
|
||||
# Residuals must be checked on the stretched FDFD grid, because the FDTD run
|
||||
# already includes those same absorbing layers through CPML.
|
||||
A = e_full(omega=omega, dxes=dxes_fdfd, epsilon=epsilon)
|
||||
residual = norm(A @ vec(Eph) - vec(b)) / norm(vec(b))
|
||||
print(f'FDFD residual is {residual}')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue