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.QDHT
— TypeQDHT{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), ...)
.
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
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
Base.inv
— Methodinv(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
Base.reshape
— Methodreshape(Q::QDHT, N::Int) -> QDHT
Compute a new QDHT
with the same order, spherical dimension, and radius but with N
points.
Hankel.Rsymmetric
— MethodRsymmetric(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
Hankel.besselj_zero
— Methodbesselj_zero(nu, n)
Get the $n$th zero of the Bessel function of order $\nu$.
Hankel.besselj_zero_init
— Methodbesselj_zero_init(nu, n)
Get an initial guess of the $n$th zero of the Bessel function of order $\nu$.
Hankel.dimdot
— Methoddimdot(v, A; dim=1)
Calculate the dot product between vector v
and one dimension of array A
, iterating over all other dimensions.
Hankel.dot!
— Methoddot!(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.
Hankel.integrateK
— MethodintegrateK(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=∞.
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
Hankel.integrateR
— MethodintegrateR(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=∞.
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.
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
Hankel.onaxis
— Methodonaxis(Ak, Q::QDHT; dim=Q.dim)
Calculate on-axis sample in space (i.e. at r=0) from transformed array Ak
.
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
Hankel.order
— Methodorder(q::QDHT)
Order of transform q
.
Hankel.oversample
— Methodoversample(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.
Unlike in zero-padding using FFTs, the old and oversampled spatial grids do not have any sampling points in common.
Hankel.padzeros
— Methodpadzeros(A, N; dim = 1)
Pad array A
with zeros to length N
along dimension dim
.
Hankel.sphbesselj
— Methodsphbesselj(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.
Hankel.sphbesselj_scale
— Methodsphbesselj_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.
Hankel.sphbesselj_zero
— Methodsphbesselj_zero(p, n, m)
Get the $m$th zero of the (hyper)spherical Bessel function of order $p$ and spherical dimension $n$.
See sphbesselj
.
Hankel.sphericaldim
— Methodsphericaldim(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
.
Hankel.symmetric
— Methodsymmetric(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
LinearAlgebra.ldiv!
— Methodldiv!(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
LinearAlgebra.mul!
— Methodmul!(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