def test_top_hat(radius: np.ndarray, a: float, order: int): f = generalised_top_hat(radius, a, order) kr, actual_ht = qdht(radius, f, order) v = kr / (2 * np.pi) expected_ht = generalised_jinc(v, a, order) error = np.mean(np.abs(expected_ht-actual_ht)) assert error < 1e-3
def test_top_hat_equivalence(a: float, order: int, radius: np.ndarray): transformer = HankelTransform(order=order, radial_grid=radius) f = generalised_top_hat(radius, a, order) kr, one_shot_ht = qdht(radius, f, order=order) f_t = generalised_top_hat(transformer.r, a, transformer.order) standard_ht = transformer.qdht(f_t) assert np.allclose(one_shot_ht, standard_ht)
def test_gaussian(a: float, radius: np.ndarray): # Note the definition in Guizar-Sicairos varies by 2*pi in # both scaling of the argument (so use kr rather than v) and # scaling of the magnitude. f = np.exp(-a ** 2 * radius ** 2) kr, actual_ht = qdht(radius, f) expected_ht = 2*np.pi*(1 / (2 * a**2)) * np.exp(-kr**2 / (4 * a**2)) assert np.allclose(expected_ht, actual_ht)
def test_gaussian_equivalence(a: float, radius: np.ndarray): # Note the definition in Guizar-Sicairos varies by 2*pi in # both scaling of the argument (so use kr rather than v) and # scaling of the magnitude. transformer = HankelTransform(order=0, radial_grid=radius) f = np.exp(-a ** 2 * radius ** 2) kr, one_shot_ht = qdht(radius, f) f_t = np.exp(-a ** 2 * transformer.r ** 2) standard_ht = transformer.qdht(f_t) assert np.allclose(one_shot_ht, standard_ht, rtol=1e-3, atol=1e-4)
def test_1_over_r2_plus_z2_equivalence(a: float): r = np.linspace(0, 50, 1024) f = 1 / (r ** 2 + a ** 2) transformer = HankelTransform(order=0, radial_grid=r) f_transformer = 1 / (transformer.r**2 + a**2) assert np.allclose(transformer.to_transform_r(f), f_transformer, rtol=1e-2, atol=1e-6) kr, one_shot_ht = qdht(r, f) assert np.allclose(kr, transformer.kr) standard_ht = transformer.qdht(f_transformer) assert np.allclose(one_shot_ht, standard_ht, rtol=1e-3, atol=1e-2)
def test_1_over_r2_plus_z2(a: float): # Note the definition in Guizar-Sicairos varies by 2*pi in # both scaling of the argument (so use kr rather than v) and # scaling of the magnitude. r = np.linspace(0, 50, 1024) f = 1 / (r**2 + a**2) # kn cannot handle complex arguments, so a must be real kr, actual_ht = qdht(r, f) expected_ht = 2 * np.pi * scipy_bessel.kn(0, a * kr) # as this diverges at zero, the first few entries have higher errors, so ignore them expected_ht = expected_ht[10:] actual_ht = actual_ht[10:] error = np.mean(np.abs(expected_ht - actual_ht)) assert error < 1e-3
def test_gaussian_2d(axis: int, radius: np.ndarray): # Note the definition in Guizar-Sicairos varies by 2*pi in # both scaling of the argument (so use kr rather than v) and # scaling of the magnitude. a = np.linspace(2, 10) dims_a = np.ones(2, np.int) dims_a[1 - axis] = len(a) dims_r = np.ones(2, np.int) dims_r[axis] = len(radius) a_reshaped = np.reshape(a, dims_a) r_reshaped = np.reshape(radius, dims_r) f = np.exp(-a_reshaped**2 * r_reshaped**2) kr, actual_ht = qdht(radius, f, axis=axis) kr_reshaped = np.reshape(kr, dims_r) expected_ht = 2 * np.pi * (1 / (2 * a_reshaped**2)) * np.exp( -kr_reshaped**2 / (4 * a_reshaped**2)) assert np.allclose(expected_ht, actual_ht)
def propagate_using_single_shot(r: np.ndarray, field: np.ndarray) -> np.ndarray: kr, hankel_transform = qdht(r, field) propagated_field = np.zeros((nr, Nz), dtype=complex) kz = np.sqrt(k0**2 - kr**2) for n, z_loop in enumerate(z): phi_z = kz * z_loop # Propagation phase hankel_transform_at_z = hankel_transform * np.exp( 1j * phi_z) # Apply propagation r_transform, field_at_z_transform_grid = iqdht( kr, hankel_transform_at_z) # iQDHT f = interpolate.interp1d(r_transform, field_at_z_transform_grid, axis=0, fill_value='extrapolate', kind='cubic') propagated_field[:, n] = f(r) intensity = np.abs(propagated_field)**2 return intensity
# %% # First import the ``qdht`` function and other packages. from pyhank import qdht import numpy as np import scipy.special import matplotlib.pyplot as plt # %% # Create a grid for :math:`r` points and calculate the jinc function. # The calculation fails at :math:`r = 0`, so we have to set that manually to the limit of :math:`1/2`. r = np.linspace(0, 100, 1024) f = np.zeros_like(r) f[1:] = scipy.special.jv(1, r[1:]) / r[1:] f[r == 0] = 0.5 plt.figure() plt.plot(r, f) plt.xlabel('Radius /m') # %% # Now take the Hankel transform using ``qdht``: kr, ht = qdht(r, f) plt.figure() plt.plot(kr, ht) plt.xlim([0, 5]) plt.xlabel('Radial wavevector /m$^{-1}$') # %% # As expected, this is a top-hat function bandlimited to :math:`k<1`, except for numerical error.
import matplotlib.pyplot as plt from pyhank import qdht, iqdht, HankelTransform # %% # First we try a Gaussian function, the Hankel transform of which should also be Gaussian. # # Note the definition in Guizar-Sicairos [#Guizar]_ varies from that used by # Pissens [#Pissens]_ by a factor of :math:`2\pi` in # both scaling of the argument (so we use ``HankelTransform.kr`` rather than # ``HankelTransform.v``) and also scaling of the magnitude. a = 3 radius = np.linspace(0, 3, 1024) f = np.exp(-a ** 2 * radius ** 2) kr, actual_ht = qdht(radius, f) expected_ht = 2*np.pi*(1 / (2 * a**2)) * np.exp(-kr**2 / (4 * a**2)) assert np.allclose(expected_ht, actual_ht) plt.figure() plt.subplot(2, 1, 1) plt.title('Gaussian function') plt.plot(radius, f) plt.xlabel('Radius /$r$') plt.subplot(2, 1, 2) plt.plot(kr, expected_ht, label='Analytical') plt.plot(kr, actual_ht, marker='x', linestyle='None', label='QDHT') plt.title('Hankel transform - also Gaussian') plt.xlabel('Frequency /$v$') plt.xlim([0, 50]) plt.legend()