[phasor] add temporal_phasor and temporal_phasor_scale

This commit is contained in:
Forgejo Actions 2026-04-19 10:57:10 -07:00
commit c0b41752e1
6 changed files with 323 additions and 67 deletions

View file

@ -139,8 +139,8 @@ def main():
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)
ee = numpy.zeros_like(epsilon, dtype=complex)
hh = numpy.zeros_like(epsilon, dtype=complex)
dxes = [grid.dxyz, grid.autoshifted_dxyz()]
@ -149,25 +149,25 @@ def main():
[cpml_params(axis=dd, polarity=pp, dt=dt, thickness=pml_thickness, epsilon_eff=n_air ** 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)
update_E, update_H = updates_with_cpml(cpml_params=pml_params, dt=dt, dxes=dxes, epsilon=epsilon, dtype=complex)
# 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. 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
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)]
# Build the pulse directly at the current half-steps and normalize that
# scalar waveform so its extracted temporal phasor is exactly 1 at omega.
source_phasor, _delay = gaussian_packet(wl=wl, dwl=100, dt=dt, turn_on=1e-5)
aa, cc, ss = source_phasor(numpy.arange(max_t) + 0.5)
source_waveform = aa * (cc + 1j * ss)
omega = 2 * numpy.pi / wl
pulse_scale = fdtd.temporal_phasor_scale(source_waveform, omega, dt, offset_steps=0.5)[0]
j_source = numpy.zeros_like(epsilon, dtype=complex)
j_source[1, *(grid.shape // 2)] = epsilon[1, *(grid.shape // 2)]
jph = numpy.zeros((1, *epsilon.shape), dtype=complex)
eph = numpy.zeros((1, *epsilon.shape), dtype=complex)
hph = numpy.zeros((1, *epsilon.shape), dtype=complex)
# #### Run a bunch of iterations ####
output_file = h5py.File('simulation_output.h5', 'w')
@ -175,10 +175,10 @@ def main():
for tt in range(max_t):
update_E(ee, hh, epsilon)
if tt < src_maxt:
# 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]
# Electric-current injection uses E -= dt * J / epsilon, which is the
# same sign convention used by the matching FDFD right-hand side.
j_step = pulse_scale * source_waveform[tt] * j_source
ee -= dt * j_step / epsilon
update_H(ee, hh)
avg_rate = (tt + 1) / (time.perf_counter() - start)
@ -186,7 +186,7 @@ def main():
if tt % 200 == 0:
print(f'iteration {tt}: average {avg_rate} iterations per sec')
E_energy_sum = (ee * ee * epsilon).sum()
E_energy_sum = (ee.conj() * ee * epsilon).sum().real
print(f'{E_energy_sum=}')
# Save field slices
@ -194,21 +194,12 @@ def main():
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 extracted phasor
# needs the same phase offset in its sample times.
offset_steps=0.5 - delay / dt,
# accumulate_phasor() already multiplies by dt, so pass the
# discrete-sum normalization without its extra dt factor.
weight=phasor_norm / dt,
)
fdtd.accumulate_phasor_j(jph, omega, dt, j_step, tt)
fdtd.accumulate_phasor_e(eph, omega, dt, ee, tt + 1)
fdtd.accumulate_phasor_h(hph, omega, dt, hh, tt + 1)
Eph = eph[0]
Jph = jph[0]
b = -1j * omega * Jph
dxes_fdfd = copy.deepcopy(dxes)
for pp in (-1, +1):

View file

@ -266,8 +266,8 @@ def main2():
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)
ee = numpy.zeros_like(epsilon, dtype=complex)
hh = numpy.zeros_like(epsilon, dtype=complex)
dxes = [grid.dxyz, grid.autoshifted_dxyz()]
@ -276,25 +276,25 @@ def main2():
[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)
update_E, update_H = updates_with_cpml(cpml_params=pml_params, dt=dt, dxes=dxes, epsilon=epsilon, dtype=complex)
# 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. 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
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)]
# Sample the pulse at the current half-steps and normalize that scalar
# waveform so the extracted temporal phasor is exactly 1 at omega.
source_phasor, _delay = gaussian_packet(wl=wl, dwl=100, dt=dt, turn_on=1e-5)
aa, cc, ss = source_phasor(numpy.arange(max_t) + 0.5)
source_waveform = aa * (cc + 1j * ss)
omega = 2 * numpy.pi / wl
pulse_scale = fdtd.temporal_phasor_scale(source_waveform, omega, dt, offset_steps=0.5)[0]
j_source = numpy.zeros_like(epsilon, dtype=complex)
j_source[1, *(grid.shape // 2)] = epsilon[1, *(grid.shape // 2)]
jph = numpy.zeros((1, *epsilon.shape), dtype=complex)
eph = numpy.zeros((1, *epsilon.shape), dtype=complex)
hph = numpy.zeros((1, *epsilon.shape), dtype=complex)
# #### Run a bunch of iterations ####
output_file = h5py.File('simulation_output.h5', 'w')
@ -302,17 +302,17 @@ def main2():
for tt in range(max_t):
update_E(ee, hh, epsilon)
if tt < src_maxt:
# 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]
# Electric-current injection uses E -= dt * J / epsilon, which is the
# sign convention matched by the FDFD source term -1j * omega * J.
j_step = pulse_scale * source_waveform[tt] * j_source
ee -= dt * j_step / epsilon
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()
E_energy_sum = (ee.conj() * ee * epsilon).sum().real
print(f'{E_energy_sum=}')
# Save field slices
@ -320,21 +320,12 @@ def main2():
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 extracted phasor must
# apply the same delay to its sample times.
offset_steps=0.5 - delay / dt,
# accumulate_phasor() already contributes dt, so remove the extra dt
# from the externally computed normalization.
weight=phasor_norm / dt,
)
fdtd.accumulate_phasor_j(jph, omega, dt, j_step, tt)
fdtd.accumulate_phasor_e(eph, omega, dt, ee, tt + 1)
fdtd.accumulate_phasor_h(hph, omega, dt, hh, tt + 1)
Eph = eph[0]
Jph = jph[0]
b = -1j * omega * Jph
dxes_fdfd = copy.deepcopy(dxes)
for pp in (-1, +1):