Hankel

Hankel implements the quasi-discrete Hankel transform (QDHT) as introduced in H. F. Johnson, Comput. Phys. Commun., 43, 2 (1987), laid out for 0-order in L. Yu, et al., Optics Letters 23 (1998), and extended to higher orders in M. Guizar-Sicairos and J. C. Gutiérrez-Vega, JOSA A 21, 53 (2004). We generalized the cylindrical QDHT in the above publications to its (hyper)spherical form. The forward $p^{\mathrm{th}}$-order (hyper)spherical Hankel transform of the radially symmetric function $f(r)$ is defined as

\[\tilde{f}(k) = c_n^{-1} \int_0^\infty f(r) j_p^n(kr) r^n\,\mathrm{d}r\,,\]

where $c_n$ is a constant (see sphbesselj_scale), and $j_p^n(x)$ is the $p^{\mathrm{th}}$-order (hyper)spherical Bessel function of the first kind with spherical dimension $n$ (see sphbesselj). Correspondingly, the inverse transform is

\[f(r) = c_n^{-1} \int_0^\infty \tilde{f}(k) j_p^n(kr) k^n\,\mathrm{d}k\,.\]

Note that here, $k$ includes the factor of $2\pi$, i.e. it is spatial angular frequency.

The remainder of the documentation considers without loss of generalization only the cylindrical case ($n=1$, with $j_p^n(x) = J_p(x)$).

The QDHT approximates these transforms under the assumption that $f(r) = 0$ for $r > R$ where $R$ is the aperture size. By expanding $f(r)$ as a Fourier-Bessel series and choosing to sample $f(r)$ at coordinates $r_n = j_nR/j_{N+1}$, where $j_n$ is the $n^{\mathrm{th}}$ zero of $J_p(x)$ and $N$ is the number of samples, the Hankel transform turns into a matrix-vector multiplication.

Hankel follows the AbstractFFTs approach of planning a transform in advance by creating a QDHT object, which can then be used to transform an array using multiplication (* or mul!) and to transform back by left-division (\ or ldiv!). In contrast to AbstractFFTs, however, pre-planning a transform is required since the transform only works properly if the sampling points are chosen for a particular combination of sample number $N$ and aperture size $R$. The workflow to transform a function $f(r)$ is therefore as follows:

j01 = besselj_zero(0, 1)
R = 1
N = 8
q = QDHT(0, R, N)
f(r) = besselj(0, r*j01/R)
fr = f.(q.r)
fk = q * fr # 0th-order QDHT => fk should have only the first entry non-zero
8-element Array{Float64,1}:
  0.13475706165848417
  4.76372956333634e-10
 -6.088187982006206e-10
  7.288009860206225e-10
 -8.36749654060558e-10
  9.16271604004363e-10
 -9.187249659023021e-10
  7.19563708969384e-10

Normalisation

The transform as implemented here is unitary, i.e.

q \ (q * fr) ≈ fr
true

The transform satisfies Parseval's Theorem. To calculate the $L^2$ norm (e.g. energy in a signal) with correct scaling, use integrateR in real ($r$) space and integrateK in reciprocal ($k$) space. For an explanation of how these functions work, see the Derivations page.

On-axis and symmetric samples

The QDHT does not contain a sample on axis, i.e. at $r=0$, but for the $0^{\mathrm{th}}$-order QDHT, it can be obtained using onaxis, which takes the transformed array as its input. This is because the on-axis sample is obtained from

\[f(r=0) = \int_0^\infty \tilde{f}(k) J_0(0) k\,\mathrm{d}k\,.\]

Note here why this does not work for other values of $p$: $J_p(0) = 0 \; \forall \; p>0$.

Given an array A, the convenience method symmetric produces the symmetric array, i.e. given A, sampled at $[r₁, r₂, r₃, ...]$, it generates the array sampled at $[...-r₃, -r₂, -r₁, 0, r₁, r₂, r₃...]$. This works also for higher-dimensional arrays. The corresponding spatial coordinates can be obtained with Rsymmetric.

Function Reference

Hankel.QDHTType
QDHT{p, n}(R, N; dim=1)
QDHT([p, ] R, N; dim=1)
QDHT(p, [n, ] R, N; dim=1)

p-th order quasi-discrete Hankel transform over aperture radius R with N samples which transforms along dimension dim. If not given, p defaults to 0, and n the spherical dimension defaults to 1 (cylindrical).

After:

[1] L. Yu, M. Huang, M. Chen, W. Chen, W. Huang, and Z. Zhu, Optics Letters 23 (1998)

[2] M. Guizar-Sicairos and J. C. Gutiérrez-Vega, JOSA A 21, 53 (2004)

[3] H. F. Johnson, Comput. Phys. Commun., 43, 2 (1987)

but with some alterations:

The transform matrix T is not the same as C/T defined in [1, 2] but is more like the form used in equation 14 of [3]. Instead of dividing by $J_1(α_{pn}) J_1(α_{pm})$ we divide by $J_1(α_{pn})^2$. This cancels out the factor between $f$ and $F$ so we do not have to mutltiply (divide) by $J_1(α_{pn})$ ($J_1(α_{pm})$) before and after applying the transform matrix.

Follows AbstractFFT approach of applying fwd and inv transform with mul and ldiv.

To calculate radial integrals of functions sampled using QDHT, use integrateR and integrateK.

The type of the coefficients is inferred from the type of R (but is promoted to be at least Float), so for arbitrary precision use QDHT([p, ] BigFloat(R), ...).

source
Base.:*Method
*(Q::QDHT, A)

Calculate the forward quasi-discrete Hankel transform of array A using the QDHT Q.

Examples

julia> q = QDHT{0, 1}(1e-2, 8); A = exp.(-q.r.^2/(1e-3*q.R));
julia> q*A
8-element Array{Float64,1}:
  4.326937831591551e-6
  2.3341589529175126e-6
  7.689558743828849e-7
  1.546419420523699e-7
  1.8999259906096856e-8
  1.4159642663129888e-9
  7.013670190083954e-11
 -6.07681871673291e-13
source
Base.:\Method
\(Q::QDHT, A)

Calculate the inverse quasi-discrete Hankel transform of array A using the QDHT Q.

Examples

julia> q = QDHT{0, 1}(1e-2, 8); A = exp.(-q.r.^2/(1e-3*q.R));
julia> Ak = q*A;
julia> q \ Ak ≈ A
true
source
Base.invMethod
inv(Q::QDHT) -> QDHT

Compute the QDHT for the inverse of the Hankel transform Q, that is, the transform such that Q * A == inv(Q) \ A

source
Base.reshapeMethod
reshape(Q::QDHT, N::Int) -> QDHT

Compute a new QDHT with the same order, spherical dimension, and radius but with N points.

source
Hankel.RsymmetricMethod
Rsymmetric(Q::QDHT)

Create radial coordinate array to go along with symmetric(A, Q::QDHT).

Examples

julia> q = QDHT{0, 1}(10, 4);
julia> q.r
4-element Array{Float64,1}:
 1.6106347946239767
 3.697078919099734
 5.795844623798052
 7.8973942990196395
julia> Rsymmetric(q)
9-element Array{Float64,1}:
 -7.8973942990196395
 -5.795844623798052
 -3.697078919099734
 -1.6106347946239767
  0.0
  1.6106347946239767
  3.697078919099734
  5.795844623798052
  7.8973942990196395
source
Hankel.dimdotMethod
dimdot(v, A; dim=1)

Calculate the dot product between vector v and one dimension of array A, iterating over all other dimensions.

source
Hankel.dot!Method
dot!(out, M, V; dim=1)

Matrix-vector multiplication along specific dimension of array V, storing result in out.

This is equivalent to iterating over all dimensions of V other than dim and applying the matrix-vector multiplication M * v[..., :, ...], but works by reshaping the array if necessary to take advantage of faster matrix-matrix multiplication. If dim==1, dot! is fastest and allocation-free.

source
Hankel.integrateKMethod
integrateK(Ak, Q::QDHT; dim=1)

Radial integral of A, over the aperture of Q in reciprocal space.

Assuming A contains samples of a function f(k) at sample points Q.k, then integrateR(A, Q) approximates ∫f(k)k dk from k=0 to k=∞.

Note

integrateR and integrateK fulfill Parseval's theorem, i.e. for some array A, integrateR(abs2.(A), q) and integrateK(abs2.(q*A), q) are equal, but integrateR(A, q) and integrateK(q*A, q) are not equal.

Examples

julia> q = QDHT{0, 1}(10, 128); A = exp.(-q.r.^2/2);
julia> integrateR(abs2.(A), q) ≈ 0.5 # analytical solution of ∫exp(-r²)r dr from 0 to ∞
true
julia> Ak = q*A;
julia> integrateK(abs2.(Ak), q) ≈ 0.5 # Same result
true
source
Hankel.integrateRMethod
integrateR(A, Q::QDHT; dim=1)

Radial integral of A, over the aperture of Q in real space.

Assuming A contains samples of a function f(r) at sample points Q.r, then integrateR(A, Q) approximates ∫f(r)r dr from r=0 to r=∞.

Note

integrateR and integrateK fulfill Parseval's theorem, i.e. for some array A, integrateR(abs2.(A), q) and integrateK(abs2.(q*A), q) are equal, but integrateR(A, q) and integrateK(q*A, q) are not equal.

Warning

using integrateR to integrate a function (i.e. A rather than abs2(A)) is only supported for the 0th-order QDHT. For more details see Derivations.

Examples

julia> q = QDHT{0, 1}(10, 128); A = exp.(-q.r.^2/2);
julia> integrateR(abs2.(A), q) ≈ 0.5 # analytical solution of ∫exp(-r²)r dr from 0 to ∞
true
source
Hankel.onaxisMethod
onaxis(Ak, Q::QDHT; dim=Q.dim)

Calculate on-axis sample in space (i.e. at r=0) from transformed array Ak.

Note

onaxis is currently only supported for 0-order transforms

Examples

julia> q = QDHT{0, 1}(10, 128); A = exp.(-q.r.^2/2);
julia> onaxis(q*A, q) ≈ 1 # should be exp(0) = 1
true
source
Hankel.oversampleMethod
oversample(A, Q::QDHT; factor::Int=4) -> Tuple{AbstractArray,QDHT}
oversample(A, Q::QDHT, QNew::QDHT) -> AbstractArray

Oversample (smooth) the array A, which is sampled with the QDHT Q, by a factor.

If calling the function many times, it is more efficient to precompute QNew with reshape and then provide it to the function.

This works like Fourier-domain zero-padding: a new QDHT is created with the same radius, but factor times more points. The existing array is transformed and placed onto this new spatial frequency grid, and the rest filled with zeros. Transforming back yields the same shape in space but with more samples.

Note

Unlike in zero-padding using FFTs, the old and oversampled spatial grids do not have any sampling points in common.

source
Hankel.padzerosMethod
padzeros(A, N; dim = 1)

Pad array A with zeros to length N along dimension dim.

source
Hankel.sphbesseljMethod
sphbesselj(p, n, x)

(Hyper)spherical Bessel function of order $p$ and spherical dimension $n$. The hyperspherical Bessel function generalizes the cylindrical and spherical Bessel functions to the $n$-sphere (embedded in $ℝ^{n+1}$). It is given as

\[j_p^{n}(x) = c_n x^{-(n-1)/2} J_{p + (n-1)/2}(x),\]

where $c_n$ is a normalization factor defined by sphbesselj_scale. Note that $n$ is not an exponent here.

It has as its special cases:

  • Cylindrical Bessel function ($n=1$): $j_p^{1}(x) = J_p(x)$
  • Spherical Bessel function ($n=2$): $j_p^{2}(x) = j_p(x)$

After:

[1] J. S. Avery, J. E. Avery. Hyperspherical Harmonics And Their Physical Applications. Singapore: World Scientific Publishing Company, 2017.

source
Hankel.sphbesselj_scaleMethod
sphbesselj_scale(n)

Return the normalization factor for the (hyper)spherical Bessel function (sphbesselj) with spherical dimension $n$, given as $c_n = \sqrt{\frac{π}{2}}$ for even $n$ and 1 otherwise.

After:

[1] J. S. Avery, J. E. Avery. Hyperspherical Harmonics And Their Physical Applications. Singapore: World Scientific Publishing Company, 2017.

source
Hankel.sphericaldimMethod
sphericaldim(q::QDHT)

Spherical dimension $n$ of transform q.

Dimension of the $n$-sphere over whose radial coordinate the bases of the transform q are orthogonal polynomials. For the circle, $n=1$, and for the sphere, $n=2$.

See sphbesselj.

source
Hankel.symmetricMethod
symmetric(A, Q::QDHT)

Create symmetric array from samples in A, including on-axis sample.

Given A, sampled at [r₁, r₂, r₃, ...], generates array sampled at [...-r₃, -r₂, -r₁, 0, r₁, r₂, r₃...]

Examples

julia> q = QDHT{0, 1}(10, 128); A = exp.(-q.r.^2);
julia> As = symmetric(A, q);
julia> size(As)
(257,)
julia> As[1:128] == A[128:-1:1]
true
julia> As[129] ≈ 1 # should be exp(0) = 1
true
julia> As[130:end] == A
true
source
LinearAlgebra.ldiv!Method
ldiv!(Y, Q::QDHT, A)

Calculate the inverse quasi-discrete Hankel transform of array A using the QDHT Q and store the result in Y.

Examples

julia> q = QDHT{0, 1}(1e-2, 8); A = exp.(-q.r.^2/(1e-3*q.R)); Y = similar(A);
julia> mul!(Y, q, A);
julia> YY = similar(Y); ldiv!(YY, q, Y);
julia> YY ≈ A
true
source
LinearAlgebra.mul!Method
mul!(Y, Q::QDHT, A)

Calculate the forward quasi-discrete Hankel transform of array A using the QDHT Q and store the result in Y.

Examples

julia> q = QDHT{0, 1}(1e-2, 8); A = exp.(-q.r.^2/(1e-3*q.R)); Y = similar(A);
julia> mul!(Y, q, A)
8-element Array{Float64,1}:
  4.326937831591551e-6
  2.3341589529175126e-6
  7.689558743828849e-7
  1.546419420523699e-7
  1.8999259906096856e-8
  1.4159642663129888e-9
  7.013670190083954e-11
 -6.07681871673291e-13
source