[fdtd.phasor] add accumulate_phasor*
This commit is contained in:
parent
8cdcd08ba0
commit
d99ef96c96
5 changed files with 708 additions and 7 deletions
|
|
@ -151,7 +151,7 @@ def main():
|
|||
|
||||
|
||||
# Source parameters and function
|
||||
source_phasor, _delay = gaussian_packet(wl=wl, dwl=100, dt=dt, turn_on=1e-5)
|
||||
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
|
||||
src_maxt = numpy.argwhere(numpy.diff(aa < 1e-5))[-1]
|
||||
|
|
@ -160,7 +160,8 @@ def main():
|
|||
|
||||
Jph = numpy.zeros_like(epsilon, dtype=complex)
|
||||
Jph[1, *(grid.shape // 2)] = epsilon[1, *(grid.shape // 2)]
|
||||
Eph = numpy.zeros_like(Jph)
|
||||
omega = 2 * numpy.pi / wl
|
||||
eph = numpy.zeros((1, *epsilon.shape), dtype=complex)
|
||||
|
||||
# #### Run a bunch of iterations ####
|
||||
output_file = h5py.File('simulation_output.h5', 'w')
|
||||
|
|
@ -169,6 +170,7 @@ def main():
|
|||
update_E(ee, hh, epsilon)
|
||||
|
||||
if tt < src_maxt:
|
||||
# This codebase uses E -= dt * J / epsilon for electric-current injection.
|
||||
ee[1, *(grid.shape // 2)] -= srca_real[tt]
|
||||
update_H(ee, hh)
|
||||
|
||||
|
|
@ -185,17 +187,26 @@ def main():
|
|||
print(f'saving E-field at iteration {tt}')
|
||||
output_file[f'/E_t{tt}'] = ee[:, :, :, ee.shape[3] // 2]
|
||||
|
||||
Eph += (cc[tt] - 1j * ss[tt]) * phasor_norm * ee
|
||||
fdtd.accumulate_phasor(
|
||||
eph,
|
||||
omega,
|
||||
dt,
|
||||
ee,
|
||||
tt,
|
||||
# The pulse is delayed relative to t=0, so the readout needs the same phase shift.
|
||||
offset_steps=0.5 - delay / dt,
|
||||
# accumulate_phasor() already includes dt, so undo the dt in phasor_norm here.
|
||||
weight=phasor_norm / dt,
|
||||
)
|
||||
|
||||
omega = 2 * numpy.pi / wl
|
||||
Eph *= numpy.exp(-1j * dt / 2 * omega)
|
||||
Eph = eph[0]
|
||||
b = -1j * omega * Jph
|
||||
dxes_fdfd = copy.deepcopy(dxes)
|
||||
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)
|
||||
A = e_full(omega=omega, dxes=dxes, epsilon=epsilon)
|
||||
residual = norm(A @ vec(ee) - vec(b)) / norm(vec(b))
|
||||
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}')
|
||||
|
||||
|
||||
|
|
|
|||
338
examples/waveguide.py
Normal file
338
examples/waveguide.py
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
"""
|
||||
Example code for running an OpenCL FDTD simulation
|
||||
|
||||
See main() for simulation setup.
|
||||
"""
|
||||
from typing import Callable
|
||||
import logging
|
||||
import time
|
||||
import copy
|
||||
|
||||
import numpy
|
||||
import h5py
|
||||
from numpy.linalg import norm
|
||||
|
||||
import gridlock
|
||||
import meanas
|
||||
from meanas import fdtd, fdfd
|
||||
from meanas.fdtd import cpml_params, updates_with_cpml
|
||||
from meanas.fdtd.misc import gaussian_packet
|
||||
|
||||
from meanas.fdmath import vec, unvec, vcfdfield_t, cfdfield_t, fdfield_t, dx_lists_t
|
||||
from meanas.fdfd import waveguide_3d, functional, scpml, operators
|
||||
from meanas.fdfd.solvers import generic as generic_solver
|
||||
from meanas.fdfd.operators import e_full
|
||||
from meanas.fdfd.scpml import stretch_with_scpml
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
for pp in ('matplotlib', 'PIL'):
|
||||
logging.getLogger(pp).setLevel(logging.WARNING)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def pcolor(vv, fig=None, ax=None) -> None:
|
||||
if fig is None:
|
||||
assert ax is None
|
||||
fig, ax = pyplot.subplots()
|
||||
mb = ax.pcolor(vv, cmap='seismic', norm=colors.CenteredNorm())
|
||||
fig.colorbar(mb)
|
||||
ax.set_aspect('equal')
|
||||
|
||||
|
||||
def draw_grid(
|
||||
*,
|
||||
dx: float,
|
||||
pml_thickness: int,
|
||||
n_wg: float = 3.476, # Si index @ 1550
|
||||
n_cladding: float = 1.00, # Air index
|
||||
wg_w: float = 400,
|
||||
wg_th: float = 200,
|
||||
) -> tuple[gridlock.Grid, fdfield_t]:
|
||||
""" Create the grid and draw the device """
|
||||
# Half-dimensions of the simulation grid
|
||||
xyz_max = numpy.array([800, 900, 600]) + (pml_thickness + 2) * dx
|
||||
|
||||
# Coordinates of the edges of the cells.
|
||||
half_edge_coords = [numpy.arange(dx / 2, m + dx / 2, step=dx) for m in xyz_max]
|
||||
edge_coords = [numpy.hstack((-h[::-1], h)) for h in half_edge_coords]
|
||||
|
||||
grid = gridlock.Grid(edge_coords)
|
||||
epsilon = grid.allocate(n_cladding**2, dtype=numpy.float32)
|
||||
grid.draw_cuboid(
|
||||
epsilon,
|
||||
x = dict(center=0, span=8e3),
|
||||
y = dict(center=0, span=wg_w),
|
||||
z = dict(center=0, span=wg_th),
|
||||
foreground = n_wg ** 2,
|
||||
)
|
||||
|
||||
return grid, epsilon
|
||||
|
||||
|
||||
def get_waveguide_mode(
|
||||
*,
|
||||
grid: gridlock.Grid,
|
||||
dxes: dx_lists_t,
|
||||
omega: float,
|
||||
epsilon: fdfield_t,
|
||||
) -> tuple[vcfdfield_t, vcfdfield_t]:
|
||||
""" Create a mode source and overlap window """
|
||||
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),
|
||||
grid.pos2ind(dims[1], which_shifts=None).astype(int))
|
||||
wg_args = dict(
|
||||
slices = [slice(i, f+1) for i, f in zip(*ind_dims)],
|
||||
dxes = dxes,
|
||||
axis = 0,
|
||||
polarity = +1,
|
||||
)
|
||||
|
||||
wg_results = waveguide_3d.solve_mode(mode_number=0, omega=omega, epsilon=epsilon, **wg_args)
|
||||
J = waveguide_3d.compute_source(E=wg_results['E'], wavenumber=wg_results['wavenumber'],
|
||||
omega=omega, epsilon=epsilon, **wg_args)
|
||||
|
||||
e_overlap = waveguide_3d.compute_overlap_e(E=wg_results['E'], wavenumber=wg_results['wavenumber'], **wg_args, omega=omega)
|
||||
return J, e_overlap
|
||||
|
||||
|
||||
def main(
|
||||
*,
|
||||
solver: Callable = generic_solver,
|
||||
dx: float = 40, # discretization (nm / cell)
|
||||
pml_thickness: int = 10, # (number of cells)
|
||||
wl: float = 1550, # Excitation wavelength
|
||||
wg_w: float = 600, # Waveguide width
|
||||
wg_th: float = 220, # Waveguide thickness
|
||||
):
|
||||
omega = 2 * numpy.pi / wl
|
||||
|
||||
grid, epsilon = draw_grid(dx=dx, pml_thickness=pml_thickness)
|
||||
|
||||
# Add PML
|
||||
dxes = [grid.dxyz, grid.autoshifted_dxyz()]
|
||||
for a in (0, 1, 2):
|
||||
for p in (-1, 1):
|
||||
dxes = scpml.stretch_with_scpml(dxes, omega=omega, axis=a, polarity=p, thickness=pml_thickness)
|
||||
|
||||
|
||||
J, e_overlap = get_waveguide_mode(grid=grid, dxes=dxes, omega=omega, epsilon=epsilon)
|
||||
|
||||
|
||||
pecg = numpy.zeros_like(epsilon)
|
||||
# pecg.draw_cuboid(pecg, center=[700, 0, 0], dimensions=[80, 1e8, 1e8], eps=1)
|
||||
# pecg.visualize_isosurface(pecg)
|
||||
|
||||
pmcg = numpy.zeros_like(epsilon)
|
||||
# grid.draw_cuboid(pmcg, center=[700, 0, 0], dimensions=[80, 1e8, 1e8], eps=1)
|
||||
# grid.visualize_isosurface(pmcg)
|
||||
|
||||
|
||||
# ss = (1, slice(None), J.shape[2]//2+6, slice(None))
|
||||
# pcolor(J3[ss].T.imag)
|
||||
# pcolor((numpy.abs(J3).sum(axis=(0, 2)) > 0).astype(float).T)
|
||||
# pyplot.show(block=True)
|
||||
|
||||
E_fdfd = fdfd_solve(
|
||||
omega = omega,
|
||||
dxes = dxes,
|
||||
epsilon = epsilon,
|
||||
J = J,
|
||||
pec = pecg,
|
||||
pmc = pmcg,
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
# Plot results
|
||||
#
|
||||
center = grid.pos2ind([0, 0, 0], None).astype(int)
|
||||
fig, axes = pyplot.subplots(2, 2)
|
||||
pcolor(numpy.real(E[1][center[0], :, :]).T, fig=fig, ax=axes[0, 0])
|
||||
axes[0, 1].plot(numpy.log10(numpy.abs(E[1][:, center[1], center[2]]) + 1e-10))
|
||||
axes[0, 1].grid(alpha=0.6)
|
||||
axes[0, 1].set_ylabel('log10 of field')
|
||||
pcolor(numpy.real(E[1][:, :, center[2]]).T, fig=fig, ax=axes[1, 0])
|
||||
|
||||
def poyntings(E):
|
||||
H = functional.e2h(omega, dxes)(E)
|
||||
poynting = fdtd.poynting(e=E, h=H.conj(), dxes=dxes)
|
||||
cross1 = operators.poynting_e_cross(vec(E), dxes) @ vec(H).conj()
|
||||
cross2 = operators.poynting_h_cross(vec(H), dxes) @ vec(E).conj() * -1
|
||||
s1 = 0.5 * unvec(numpy.real(cross1), grid.shape)
|
||||
s2 = 0.5 * unvec(numpy.real(cross2), grid.shape)
|
||||
s0 = 0.5 * poynting.real
|
||||
# s2 = poynting.imag
|
||||
return s0, s1, s2
|
||||
|
||||
s0x, s1x, s2x = poyntings(E)
|
||||
axes[1, 1].plot(s0x[0].sum(axis=2).sum(axis=1), label='s0', marker='.')
|
||||
axes[1, 1].plot(s1x[0].sum(axis=2).sum(axis=1), label='s1', marker='.')
|
||||
axes[1, 1].plot(s2x[0].sum(axis=2).sum(axis=1), label='s2', marker='.')
|
||||
axes[1, 1].plot(E[1][:, center[1], center[2]].real.T, label='Ey', marker='x')
|
||||
axes[1, 1].grid(alpha=0.6)
|
||||
axes[1, 1].legend()
|
||||
|
||||
q = []
|
||||
for i in range(-5, 30):
|
||||
e_ovl_rolled = numpy.roll(e_overlap, i, axis=1)
|
||||
q += [numpy.abs(vec(E) @ vec(e_ovl_rolled).conj())]
|
||||
fig, ax = pyplot.subplots()
|
||||
ax.plot(q, marker='.')
|
||||
ax.grid(alpha=0.6)
|
||||
ax.set_title('Overlap with mode')
|
||||
|
||||
logger.info('Average overlap with mode:', sum(q[8:32]) / len(q[8:32]))
|
||||
|
||||
pyplot.show(block=True)
|
||||
|
||||
|
||||
def fdfd_solve(
|
||||
*,
|
||||
omega: float,
|
||||
dxes = dx_lists_t,
|
||||
epsilon: fdfield_t,
|
||||
J: cfdfield_t,
|
||||
pec: fdfield_t,
|
||||
pmc: fdfield_t,
|
||||
) -> cfdfield_t:
|
||||
""" Construct and run the solve """
|
||||
sim_args = dict(
|
||||
omega = omega,
|
||||
dxes = dxes,
|
||||
epsilon = vec(epsilon),
|
||||
pec = vec(pecg),
|
||||
pmc = vec(pmcg),
|
||||
)
|
||||
|
||||
x = solver(J=vec(J), **sim_args)
|
||||
|
||||
b = -1j * omega * vec(J)
|
||||
A = operators.e_full(**sim_args).tocsr()
|
||||
logger.info('Norm of the residual is ', norm(A @ x - b) / norm(b))
|
||||
|
||||
E = unvec(x, epsilon.shape[1:])
|
||||
return E
|
||||
|
||||
|
||||
def main2():
|
||||
dtype = numpy.float32
|
||||
max_t = 3600 # number of timesteps
|
||||
|
||||
dx = 40 # discretization (nm/cell)
|
||||
pml_thickness = 8 # (number of cells)
|
||||
|
||||
wl = 1550 # Excitation wavelength and fwhm
|
||||
dwl = 100
|
||||
|
||||
# Device design parameters
|
||||
xy_size = numpy.array([10, 10])
|
||||
a = 430
|
||||
r = 0.285
|
||||
th = 170
|
||||
|
||||
# refractive indices
|
||||
n_slab = 3.408 # InGaAsP(80, 50) @ 1550nm
|
||||
n_cladding = 1.0 # air
|
||||
|
||||
# Half-dimensions of the simulation grid
|
||||
xy_max = (xy_size + 1) * a * [1, numpy.sqrt(3)/2]
|
||||
z_max = 1.6 * a
|
||||
xyz_max = numpy.hstack((xy_max, z_max)) + pml_thickness * dx
|
||||
|
||||
# Coordinates of the edges of the cells. The fdtd package can only do square grids at the moment.
|
||||
half_edge_coords = [numpy.arange(dx/2, m + dx, step=dx) for m in xyz_max]
|
||||
edge_coords = [numpy.hstack((-h[::-1], h)) for h in half_edge_coords]
|
||||
|
||||
# #### Create the grid, mask, and draw the device ####
|
||||
grid = gridlock.Grid(edge_coords)
|
||||
epsilon = grid.allocate(n_cladding ** 2, dtype=dtype)
|
||||
grid.draw_slab(
|
||||
epsilon,
|
||||
slab = dict(axis='z', center=0, span=th),
|
||||
foreground = n_slab ** 2,
|
||||
)
|
||||
|
||||
|
||||
print(f'{grid.shape=}')
|
||||
|
||||
dt = dx * 0.99 / numpy.sqrt(3)
|
||||
ee = numpy.zeros_like(epsilon, dtype=dtype)
|
||||
hh = numpy.zeros_like(epsilon, dtype=dtype)
|
||||
|
||||
dxes = [grid.dxyz, grid.autoshifted_dxyz()]
|
||||
|
||||
# PMLs in every direction
|
||||
pml_params = [
|
||||
[cpml_params(axis=dd, polarity=pp, dt=dt, thickness=pml_thickness, epsilon_eff=n_cladding ** 2)
|
||||
for pp in (-1, +1)]
|
||||
for dd in range(3)]
|
||||
update_E, update_H = updates_with_cpml(cpml_params=pml_params, dt=dt, dxes=dxes, epsilon=epsilon)
|
||||
|
||||
# sample_interval = numpy.floor(1 / (2 * 1 / wl * dt)).astype(int)
|
||||
# print(f'Save time interval would be {sample_interval} * dt = {sample_interval * dt:3g}')
|
||||
|
||||
|
||||
# Source parameters and function
|
||||
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
|
||||
src_maxt = numpy.argwhere(numpy.diff(aa < 1e-5))[-1]
|
||||
assert aa[src_maxt - 1] >= 1e-5
|
||||
phasor_norm = dt / (aa * cc * cc).sum()
|
||||
|
||||
Jph = numpy.zeros_like(epsilon, dtype=complex)
|
||||
Jph[1, *(grid.shape // 2)] = epsilon[1, *(grid.shape // 2)]
|
||||
omega = 2 * numpy.pi / wl
|
||||
eph = numpy.zeros((1, *epsilon.shape), dtype=complex)
|
||||
|
||||
# #### Run a bunch of iterations ####
|
||||
output_file = h5py.File('simulation_output.h5', 'w')
|
||||
start = time.perf_counter()
|
||||
for tt in range(max_t):
|
||||
update_E(ee, hh, epsilon)
|
||||
|
||||
if tt < src_maxt:
|
||||
# This codebase uses E -= dt * J / epsilon for electric-current injection.
|
||||
ee[1, *(grid.shape // 2)] -= srca_real[tt]
|
||||
update_H(ee, hh)
|
||||
|
||||
avg_rate = (tt + 1) / (time.perf_counter() - start)
|
||||
|
||||
if tt % 200 == 0:
|
||||
print(f'iteration {tt}: average {avg_rate} iterations per sec')
|
||||
E_energy_sum = (ee * ee * epsilon).sum()
|
||||
print(f'{E_energy_sum=}')
|
||||
|
||||
# Save field slices
|
||||
if (tt % 20 == 0 and (max_t - tt <= 1000 or tt <= 2000)) or tt == max_t - 1:
|
||||
print(f'saving E-field at iteration {tt}')
|
||||
output_file[f'/E_t{tt}'] = ee[:, :, :, ee.shape[3] // 2]
|
||||
|
||||
fdtd.accumulate_phasor(
|
||||
eph,
|
||||
omega,
|
||||
dt,
|
||||
ee,
|
||||
tt,
|
||||
# The pulse is delayed relative to t=0, so the readout needs the same phase shift.
|
||||
offset_steps=0.5 - delay / dt,
|
||||
# accumulate_phasor() already includes dt, so undo the dt in phasor_norm here.
|
||||
weight=phasor_norm / dt,
|
||||
)
|
||||
|
||||
Eph = eph[0]
|
||||
b = -1j * omega * Jph
|
||||
dxes_fdfd = copy.deepcopy(dxes)
|
||||
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)
|
||||
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}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue