def iqdht(k: np.ndarray, f: np.ndarray, order: int = 0) -> Tuple[np.ndarray, np.ndarray]: """Perform a inverse quasi-discrete Hankel transform of the function ``f`` (sampled at points ``k``) and return the transformed function and its sample points in radial space. If you have the transform on a frequency axis (as opposed to a :math:`k`-axis), the :math:`k`-axis can be calculated using :math:`k = 2\\pi{}f`. .. warning:: This method is a convenience wrapper for :meth:`.HankelTransform.iqdht`, but incurs a significant overhead in calculating the :class:`.HankelTransform` object. If you are performing multiple transforms on the same grid, it will be much quicker to construct a single :class:`.HankelTransform` object and call :meth:`.HankelTransform.iqdht` multiple times. :param k: The :math:`k` coordinates at which the function is sampled :type k: :class:`numpy.ndarray` :param f: The value of the function to be transformed. :type f: :class:`numpy.ndarray` :param order: The order of the Hankel Transform to perform. Defaults to 0. :return: A tuple containing the radial coordinates of the transformed function and its values :rtype: (:class:`numpy.ndarray`, :class:`numpy.ndarray`) """ transformer = HankelTransform(order=order, k_grid=k) f_transform = transformer.to_transform_k(f) ht = transformer.iqdht(f_transform) return transformer.r, ht
def test_round_trip_3d(two_d_size: int, axis: int, radius: np.ndarray, transformer: HankelTransform): dims = np.ones(3, np.int) * two_d_size dims[axis] = radius.size func = np.random.random(dims) ht = transformer.qdht(func, axis=axis) reconstructed = transformer.iqdht(ht, axis=axis) assert np.allclose(func, reconstructed)
def test_inverse_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. transformer = HankelTransform(order=0, radial_grid=radius) ht = 2*np.pi*(1 / (2 * a**2)) * np.exp(-transformer.kr**2 / (4 * a**2)) actual_f = transformer.iqdht(ht) expected_f = np.exp(-a ** 2 * transformer.r ** 2) assert np.allclose(expected_f, actual_f)
def test_round_trip_with_interpolation(shape: Callable, radius: np.ndarray, transformer: HankelTransform): # the function must be smoothish for interpolation # to work. Random every point doesn't work func = shape(radius) func_hr = transformer.to_transform_r(func) ht = transformer.qdht(func_hr) reconstructed_hr = transformer.iqdht(ht) reconstructed = transformer.to_original_r(reconstructed_hr) assert np.allclose(func, reconstructed, rtol=2e-4)
def propagate_using_object(r: np.ndarray, field: np.ndarray) -> np.ndarray: transformer = HankelTransform(order=0, radial_grid=r) field_for_transform = transformer.to_transform_r(field) # Resampled field hankel_transform = transformer.qdht(field_for_transform) propagated_field = np.zeros((nr, Nz), dtype=complex) kz = np.sqrt(k0**2 - transformer.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 field_at_z_transform_grid = transformer.iqdht( hankel_transform_at_z) # iQDHT propagated_field[:, n] = transformer.to_original_r( field_at_z_transform_grid) # Interpolate output intensity = np.abs(propagated_field)**2 return intensity
def test_inverse_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. transformer = HankelTransform(order=0, radial_grid=radius) 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(transformer.r) a_reshaped = np.reshape(a, dims_a) r_reshaped = np.reshape(transformer.r, dims_r) kr_reshaped = np.reshape(transformer.kr, dims_r) ht = 2 * np.pi * (1 / (2 * a_reshaped**2)) * np.exp(-kr_reshaped**2 / (4 * a_reshaped**2)) actual_f = transformer.iqdht(ht, axis=axis) expected_f = np.exp(-a_reshaped**2 * r_reshaped**2) assert np.allclose(expected_f, actual_f)
def test_iqdht(engine): r_max = 5e-3 nr = 512 h = HankelTransform(0, r_max, nr) hm = engine.hankel_matrix(0., r_max, float(nr), nargout=1) dr = r_max / (nr - 1) # Radial spacing nr = np.arange(0, nr) # Radial pixels r = nr * dr # Radial positions er = r < 1e-3 # noinspection PyUnresolvedReferences er_m = matlab.double(er[np.newaxis, :].transpose().tolist()) matlab_mode = float(3) matlab_value = engine.iqdht(er_m, hm, matlab_mode, nargout=1) python_value = h.iqdht(er) assert matlab_python_allclose(python_value, matlab_value)
def example(): # Gaussian function def gauss1d(x, x0, fwhm): return np.exp(-2 * np.log(2) * ((x - x0) / fwhm) ** 2) nr = 1024 # Number of sample points r_max = .05 # Maximum radius (5cm) dr = r_max / (nr - 1) # Radial spacing ri = np.arange(0, nr) # Radial pixels r = ri * dr # Radial positions beam_radius = 5e-3 # 5mm propagation_direction = 5000 Nz = 200 # Number of z positions z_max = .25 # Maximum propagation distance dz = z_max / (Nz - 1) z = np.arange(0, Nz) * dz # Propagation axis ht = HankelTransform(0, radial_grid=r) k_max = 2 * np.pi * ht.v_max # Maximum K vector field = gauss1d(r, 0, beam_radius) * np.exp(1j * propagation_direction * r) # Initial field ht_field = ht.to_transform_r(field) # Resampled field transform = ht.qdht(ht_field) # Convert from physical field to physical wavevector propagated_intensity = np.zeros((nr, Nz + 1)) propagated_intensity[:, 0] = np.abs(field) ** 2 for n in range(1, Nz): z_loop = z[n] propagation_phase = (np.sqrt(k_max ** 2 - ht.kr ** 2) - k_max) * z_loop # Propagation phase propagated_transform = transform * np.exp(1j * propagation_phase) # Apply propagation propagated_ht_field = ht.iqdht(propagated_transform) # iQDHT propagated_field = _spline(ht.r, propagated_ht_field, r) # Interpolate output propagated_intensity[:, n] = np.abs(propagated_field) ** 2 return transform, propagated_intensity
def test_round_trip_2d(two_d_size: int, radius: np.ndarray, transformer: HankelTransform): func = np.random.random((radius.size, two_d_size)) ht = transformer.qdht(func) reconstructed = transformer.iqdht(ht) assert np.allclose(func, reconstructed)
def test_round_trip(radius: np.ndarray, transformer: HankelTransform): func = np.random.random(radius.shape) ht = transformer.qdht(func) reconstructed = transformer.iqdht(ht) assert np.allclose(func, reconstructed)
# Convert from physical field to physical wavevector EkrH = H.qdht(ErH) # %% # Propagate the beam - loop # ------------------------- # Do the propagation in a loop over :math:`z` # Pre-allocate an array for field as a function of r and z Erz = np.zeros((nr, Nz), dtype=complex) kz = np.sqrt(k0**2 - H.kr**2) for i, z_loop in enumerate(z): phi_z = kz * z_loop # Propagation phase EkrHz = EkrH * np.exp(1j * phi_z) # Apply propagation ErHz = H.iqdht(EkrHz) # iQDHT Erz[:, i] = H.to_original_r(ErHz) # Interpolate output Irz = np.abs(Erz)**2 # %% # Plotting # -------- # Plot the initial field and radial wavevector distribution (given by the # Hankel transform) plt.figure() plt.plot(r * 1e3, np.abs(Er)**2, r * 1e3, np.unwrap(np.angle(Er)), H.r * 1e3, np.abs(ErH)**2, H.r * 1e3, np.unwrap(np.angle(ErH)), '+') plt.title('Initial electric field distribution') plt.xlabel('Radial co-ordinate (r) /mm') plt.ylabel('Field intensity /arb.')
assert np.all(dynamical_error[not_near_gamma] < -35) # %% # Now we will reproduce figure 3 and confirm we can replicate # the errors in the top half of table 1. p = 4 a = 1 transformer = HankelTransform(order=p, max_radius=2, n_points=1024) top_hat = np.zeros_like(transformer.r) top_hat[transformer.r <= a] = 1 func = transformer.r**p * top_hat expected_ht = a**(p + 1) * scipybessel.jv( p + 1, 2 * np.pi * a * transformer.v) / transformer.v ht = transformer.qdht(func) retrieved_func = transformer.iqdht(ht) # %% # Plot the overlay as in figure 3 of |Guizar| `Guizar`_ plt.figure() plt.subplot(2, 1, 1) plt.plot(transformer.v, expected_ht, label='Analytical') plt.plot(transformer.v, ht, marker='x', linestyle='None', label='QDHT') plt.title(f'Hankel transform $f_2(v)$, order {p}') plt.xlabel('Frequency /$v$') plt.xlim([0, 10]) plt.legend() plt.subplot(2, 1, 2) plt.title('Round-trip QDHT vs analytical function')