[farfield] align Fourier projection with outgoing wave convention
This commit is contained in:
parent
cde834582d
commit
112d88cac1
2 changed files with 32 additions and 11 deletions
|
|
@ -56,6 +56,8 @@ def near_to_farfield(
|
||||||
The input fields should be complex phasors. The spatial origin is the
|
The input fields should be complex phasors. The spatial origin is the
|
||||||
sample at ``(nx // 2, ny // 2)``. Padding/cropping preserves that origin.
|
sample at ``(nx // 2, ny // 2)``. Padding/cropping preserves that origin.
|
||||||
Fourier integrals include the cell area ``dx * dy``.
|
Fourier integrals include the cell area ``dx * dy``.
|
||||||
|
Phasors use ``exp(+i omega t)``; an outgoing plane wave has spatial
|
||||||
|
dependence ``exp(-i k dot r)`` and is projected with a positive Fourier phase.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
E_near: List of 2 ndarrays containing the 2D phasor field slices for the transverse
|
E_near: List of 2 ndarrays containing the 2D phasor field slices for the transverse
|
||||||
|
|
@ -71,9 +73,9 @@ def near_to_farfield(
|
||||||
Returns:
|
Returns:
|
||||||
Dict with keys
|
Dict with keys
|
||||||
|
|
||||||
- `E`: Normalized E-field farfield; multiply by
|
- `E`: Normalized polar and azimuthal E-field components; multiply by
|
||||||
(i k exp(-i k r) / (4 pi r)) to get the actual field value.
|
(i k exp(-i k r) / (4 pi r)) to get the actual field value.
|
||||||
- `H`: Normalized H-field farfield; multiply by
|
- `H`: Normalized polar and azimuthal H-field components; multiply by
|
||||||
(i k exp(-i k r) / (4 pi r)) to get the actual field value.
|
(i k exp(-i k r) / (4 pi r)) to get the actual field value.
|
||||||
- `kx`, `ky`: Wavevector values corresponding to the x- and y- axes in E_far and H_far,
|
- `kx`, `ky`: Wavevector values corresponding to the x- and y- axes in E_far and H_far,
|
||||||
normalized to wavelength (dimensionless).
|
normalized to wavelength (dimensionless).
|
||||||
|
|
@ -95,8 +97,8 @@ def near_to_farfield(
|
||||||
|
|
||||||
padded_shape = _padded_shape(s, padded_size)
|
padded_shape = _padded_shape(s, padded_size)
|
||||||
|
|
||||||
En_fft = [fftshift(fft2(ifftshift(_resize_centered(Eni, padded_shape)))) * dx * dy for Eni in E_near]
|
En_fft = [fftshift(ifft2(ifftshift(_resize_centered(Eni, padded_shape)), norm='forward')) * dx * dy for Eni in E_near]
|
||||||
Hn_fft = [fftshift(fft2(ifftshift(_resize_centered(Hni, padded_shape)))) * dx * dy for Hni in H_near]
|
Hn_fft = [fftshift(ifft2(ifftshift(_resize_centered(Hni, padded_shape)), norm='forward')) * dx * dy for Hni in H_near]
|
||||||
|
|
||||||
# Propagation vectors kx, ky
|
# Propagation vectors kx, ky
|
||||||
k = 2 * pi
|
k = 2 * pi
|
||||||
|
|
@ -165,12 +167,10 @@ def far_to_nearfield(
|
||||||
inverse Fourier quadrature restores the cell-area normalization.
|
inverse Fourier quadrature restores the cell-area normalization.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
E_far: List of 2 ndarrays containing the 2D phasor field slices for the transverse
|
E_far: List of 2 ndarrays containing polar and azimuthal E components.
|
||||||
E fields (e.g. [Ex, Ey] for calculating the nearfield toward the z-direction).
|
|
||||||
Fields should be normalized so that
|
Fields should be normalized so that
|
||||||
E_far = E_far_actual / (i k exp(-i k r) / (4 pi r))
|
E_far = E_far_actual / (i k exp(-i k r) / (4 pi r))
|
||||||
H_far: List of 2 ndarrays containing the 2D phasor field slices for the transverse
|
H_far: List of 2 ndarrays containing polar and azimuthal H components.
|
||||||
H fields (e.g. [Hx, hy] for calculating the nearfield toward the z-direction).
|
|
||||||
Fields should be normalized so that
|
Fields should be normalized so that
|
||||||
H_far = H_far_actual / (i k exp(-i k r) / (4 pi r))
|
H_far = H_far_actual / (i k exp(-i k r) / (4 pi r))
|
||||||
dkx: kx discretization, in units of wavelength.
|
dkx: kx discretization, in units of wavelength.
|
||||||
|
|
@ -262,8 +262,8 @@ def far_to_nearfield(
|
||||||
dx = 2 * pi / (padded_shape[0] * dkx)
|
dx = 2 * pi / (padded_shape[0] * dkx)
|
||||||
dy = 2 * pi / (padded_shape[1] * dky)
|
dy = 2 * pi / (padded_shape[1] * dky)
|
||||||
|
|
||||||
E_near = [fftshift(ifft2(ifftshift(_resize_centered(Ei, padded_shape)))) / (dx * dy) for Ei in En_fft]
|
E_near = [fftshift(fft2(ifftshift(_resize_centered(Ei, padded_shape)), norm='forward')) / (dx * dy) for Ei in En_fft]
|
||||||
H_near = [fftshift(ifft2(ifftshift(_resize_centered(Hi, padded_shape)))) / (dx * dy) for Hi in Hn_fft]
|
H_near = [fftshift(fft2(ifftshift(_resize_centered(Hi, padded_shape)), norm='forward')) / (dx * dy) for Hi in Hn_fft]
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
'E': E_near,
|
'E': E_near,
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ def test_inverse_resizing_matches_plane_wave_quadrature(shape) -> None:
|
||||||
assert result['dx'] == pytest.approx(dx)
|
assert result['dx'] == pytest.approx(dx)
|
||||||
assert result['dy'] == pytest.approx(dy)
|
assert result['dy'] == pytest.approx(dy)
|
||||||
x = (numpy.arange(shape[0]) - shape[0] // 2) * dx
|
x = (numpy.arange(shape[0]) - shape[0] // 2) * dx
|
||||||
expected = numpy.broadcast_to(amplitude * dkx * dky / (2 * numpy.pi) ** 2 * numpy.exp(1j * dkx * x[:, None]), shape)
|
expected = numpy.broadcast_to(amplitude * dkx * dky / (2 * numpy.pi) ** 2 * numpy.exp(-1j * dkx * x[:, None]), shape)
|
||||||
numpy.testing.assert_allclose(result['E'][0], expected, atol=1e-14)
|
numpy.testing.assert_allclose(result['E'][0], expected, atol=1e-14)
|
||||||
numpy.testing.assert_allclose(result['H'][1], expected / numpy.sqrt(1 - (dkx / (2 * numpy.pi)) ** 2), atol=1e-14)
|
numpy.testing.assert_allclose(result['H'][1], expected / numpy.sqrt(1 - (dkx / (2 * numpy.pi)) ** 2), atol=1e-14)
|
||||||
|
|
||||||
|
|
@ -150,3 +150,24 @@ def test_inverse_accepts_readonly_spectra_without_modifying_them() -> None:
|
||||||
assert numpy.isfinite(result['E']).all()
|
assert numpy.isfinite(result['E']).all()
|
||||||
for field, original in zip(fields, originals, strict=True):
|
for field, original in zip(fields, originals, strict=True):
|
||||||
numpy.testing.assert_array_equal(field, original)
|
numpy.testing.assert_array_equal(field, original)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('direction', [-1, 1])
|
||||||
|
def test_farfield_direction_and_roundtrip_match_outgoing_phasors(direction) -> None:
|
||||||
|
shape = (16, 8)
|
||||||
|
dx = dy = 0.25
|
||||||
|
k = 2 * numpy.pi
|
||||||
|
kx = direction * 2 * numpy.pi / (shape[0] * dx)
|
||||||
|
kz = numpy.sqrt(k * k - kx * kx)
|
||||||
|
x = (numpy.arange(shape[0]) - shape[0] // 2) * dx
|
||||||
|
phase = numpy.broadcast_to(numpy.exp(-1j * kx * x[:, None]), shape)
|
||||||
|
zeros = numpy.zeros_like(phase)
|
||||||
|
electric, magnetic = [kz / k * phase, zeros], [zeros, phase]
|
||||||
|
result = farfield.near_to_farfield(electric, magnetic, dx, dy, padded_size=shape)
|
||||||
|
intensity = numpy.abs(result['E'][0]) ** 2 + numpy.abs(result['E'][1]) ** 2
|
||||||
|
peak = numpy.unravel_index(numpy.argmax(intensity), shape)
|
||||||
|
assert result['kx'][peak] == pytest.approx(kx)
|
||||||
|
assert result['phi'][peak] == pytest.approx(numpy.arccos(kz / k))
|
||||||
|
restored = farfield.far_to_nearfield(result['E'], result['H'], result['dkx'], result['dky'], padded_size=shape)
|
||||||
|
numpy.testing.assert_allclose(restored['E'], electric, atol=1e-13)
|
||||||
|
numpy.testing.assert_allclose(restored['H'], magnetic, atol=1e-13)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue