def qdht(r: np.ndarray, f: np.ndarray, order: int = 0) -> Tuple[np.ndarray, np.ndarray]: """Perform a quasi-discrete Hankel transform of the function ``f`` (sampled at points ``r``) and return the transformed function and its sample points in :math:`k`-space. If you requires the transform on a frequency axis (as opposed to the :math:`k`-axis), the frequency axis :math:`v` can be calculated using :math:`v = \\frac{k}{2\\pi}`. .. warning:: This method is a convenience wrapper for :meth:`.HankelTransform.qdht`, 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.qdht` multiple times. :param r: The radial coordinates at which the function is sampled :type r: :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 k coordinates of the transformed function and its values :rtype: (:class:`numpy.ndarray`, :class:`numpy.ndarray`) """ transformer = HankelTransform(order=order, radial_grid=r) f_transform = transformer.to_transform_r(f) ht = transformer.qdht(f_transform) return transformer.kr, ht
def test_round_trip_r_interpolation(radius: np.ndarray, order: int, shape: Callable): transformer = HankelTransform(order=order, radial_grid=radius) # the function must be smoothish for interpolation # to work. Random every point doesn't work func = shape(radius) transform_func = transformer.to_transform_r(func) reconstructed_func = transformer.to_original_r(transform_func) assert np.allclose(func, reconstructed_func, rtol=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_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 test_round_trip_r_interpolation_2d(radius: np.ndarray, order: int, shape: Callable, axis: int): transformer = HankelTransform(order=order, radial_grid=radius) # the function must be smoothish for interpolation # to work. Random every point doesn't work dims_amplitude = np.ones(2, np.int) dims_amplitude[1 - axis] = 10 amplitude = np.random.random(dims_amplitude) dims_radius = np.ones(2, np.int) dims_radius[axis] = len(radius) func = np.reshape(shape(radius), dims_radius) * np.reshape( amplitude, dims_amplitude) transform_func = transformer.to_transform_r(func, axis=axis) reconstructed_func = transformer.to_original_r(transform_func, axis=axis) assert np.allclose(func, reconstructed_func, rtol=1e-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 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
# %% # Set up beam parameters Dr = 100e-6 # Beam radius (100um) lambda_ = 488e-9 # wavelength 488nm k0 = 2 * np.pi / lambda_ # Vacuum k vector # %% # Set up a :class:`.HankelTransform` object, telling it the order (``0``) and # the radial grid. H = HankelTransform(order=0, radial_grid=r) # %% # Set up the electric field profile at :math:`z = 0`, and resample onto the correct radial grid # (``transformer.r``) as required for the QDHT. Er = gauss1d(r, 0, Dr) # Initial field ErH = H.to_transform_r(Er) # Resampled field # %% # Perform Hankel Transform # ------------------------ # 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)