[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
|
||||
sample at ``(nx // 2, ny // 2)``. Padding/cropping preserves that origin.
|
||||
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:
|
||||
E_near: List of 2 ndarrays containing the 2D phasor field slices for the transverse
|
||||
|
|
@ -71,9 +73,9 @@ def near_to_farfield(
|
|||
Returns:
|
||||
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.
|
||||
- `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.
|
||||
- `kx`, `ky`: Wavevector values corresponding to the x- and y- axes in E_far and H_far,
|
||||
normalized to wavelength (dimensionless).
|
||||
|
|
@ -95,8 +97,8 @@ def near_to_farfield(
|
|||
|
||||
padded_shape = _padded_shape(s, padded_size)
|
||||
|
||||
En_fft = [fftshift(fft2(ifftshift(_resize_centered(Eni, padded_shape)))) * dx * dy for Eni in E_near]
|
||||
Hn_fft = [fftshift(fft2(ifftshift(_resize_centered(Hni, padded_shape)))) * dx * dy for Hni in H_near]
|
||||
En_fft = [fftshift(ifft2(ifftshift(_resize_centered(Eni, padded_shape)), norm='forward')) * dx * dy for Eni in E_near]
|
||||
Hn_fft = [fftshift(ifft2(ifftshift(_resize_centered(Hni, padded_shape)), norm='forward')) * dx * dy for Hni in H_near]
|
||||
|
||||
# Propagation vectors kx, ky
|
||||
k = 2 * pi
|
||||
|
|
@ -165,12 +167,10 @@ def far_to_nearfield(
|
|||
inverse Fourier quadrature restores the cell-area normalization.
|
||||
|
||||
Args:
|
||||
E_far: List of 2 ndarrays containing the 2D phasor field slices for the transverse
|
||||
E fields (e.g. [Ex, Ey] for calculating the nearfield toward the z-direction).
|
||||
E_far: List of 2 ndarrays containing polar and azimuthal E components.
|
||||
Fields should be normalized so that
|
||||
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 fields (e.g. [Hx, hy] for calculating the nearfield toward the z-direction).
|
||||
H_far: List of 2 ndarrays containing polar and azimuthal H components.
|
||||
Fields should be normalized so that
|
||||
H_far = H_far_actual / (i k exp(-i k r) / (4 pi r))
|
||||
dkx: kx discretization, in units of wavelength.
|
||||
|
|
@ -262,8 +262,8 @@ def far_to_nearfield(
|
|||
dx = 2 * pi / (padded_shape[0] * dkx)
|
||||
dy = 2 * pi / (padded_shape[1] * dky)
|
||||
|
||||
E_near = [fftshift(ifft2(ifftshift(_resize_centered(Ei, padded_shape)))) / (dx * dy) for Ei in En_fft]
|
||||
H_near = [fftshift(ifft2(ifftshift(_resize_centered(Hi, padded_shape)))) / (dx * dy) for Hi in Hn_fft]
|
||||
E_near = [fftshift(fft2(ifftshift(_resize_centered(Ei, padded_shape)), norm='forward')) / (dx * dy) for Ei in En_fft]
|
||||
H_near = [fftshift(fft2(ifftshift(_resize_centered(Hi, padded_shape)), norm='forward')) / (dx * dy) for Hi in Hn_fft]
|
||||
|
||||
outputs = {
|
||||
'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['dy'] == pytest.approx(dy)
|
||||
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['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()
|
||||
for field, original in zip(fields, originals, strict=True):
|
||||
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