You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I believe the derivatives defined for the s2fft.transforms.c_backend_spherical.healpy_forward function with custom_vjp are incorrect. As a minimal reproducing example:
Notice in all cases iter is fixed to zero so this is not due to using iterative refinement steps. While HEALPix sampling does not exhibit a sampling theorem and so round-tripping through the forward and backward ('inverse') transforms will introduce an error (which iterative refinement in the forward transform can reduce), this only affects the accuracy of the linear operator corresponding to the forward transform being the inverse of the linear operator corresponding to the backward transform.
Distinct from this property, is that the linear operator represented by the forward transform is a (scaled and conjugated) transposition of the linear operator represented by the backward transform (and vice-versa), and this property holds exactly (modulo floating point error) for the HEALPix forward and backward transforms. Importantly it is this transposition property that is required for implementation of the derivative rules and so the incorrectness of the derivatives in the current implementation cannot be explained by the error in the inverse relationship.
Let the matrix represented by HEALPix forward spherical transform (map2alm) be $F \in \mathbb{C}^{m \times n}$ and the matrix represented by HEALPix inverse (backward) spherical transform (alm2map) be $B \in \mathbb{C}^{n \times m}$, with a harmonic bandlimit $\ell$ in both cases and HEALPix resolution parameter $r$ and $n = 12r^2$, $m = \ell(\ell + 1) / 2$. That is
$$\texttt{map2alm}(x) = F x \quad\text{and}\quad \texttt{alm2map}(y) = B y.$$
We can construct the matrices numerically by mapping the standard basis vectors through the map2alm and alm2map functions:
F=np.stack(
list(
map(
lambdae: healpy.map2alm(e, lmax=L-1),
np.identity(12*nside**2, dtype=float),
)
),
1,
)
# As alm2map argument is complex need to use both real and imaginary basis vectorsB=np.stack(
list(
map(
lambdae: healpy.alm2map(e, nside=nside, lmax=L-1),
np.identity(flm_hp.shape[0], dtype=complex),
)
),
1,
) -1j*np.stack(
list(
map(
lambdae: healpy.alm2map(e, nside=nside, lmax=L-1),
1j*np.identity(L* (L+1) //2, dtype=complex),
)
),
1,
)
We then have the relationships
$$ B = F^H D \quad\text{and}\quad F = D^{-1} B^H$$
where $X^H= \textrm{conj}(X)^T$ (that is the conjugate / Hermitian transpose) and $D$ is a $n \times n$ diagonal matrix with the first $\ell$ entries along the diagonal equal to $3r^2 / \pi$ and the remaining $n - \ell = \ell(\ell - 1) / 2$ diagonal entries equal to $6r^2/\pi$.
As map2alm and alm2map are both linear, their Jacobian vector product (JVP) functions are just the original maps:
$$
\mathsf{jvp}(\texttt{map2alm})(x)(v) = \partial(x \mapsto F x) v = F v = \texttt{map2alm}(v)
$$$$
\mathsf{jvp}(\texttt{alm2map})(y)(v) = \partial(y \mapsto B y) v = B v = \texttt{alm2map}(v)
$$
For the vector Jacobian product (VJP) functions we have
It's a little difficult to relate this to the above derivation as this includes both the VJP for healpy.alm2map but also the VJP for s2fft.sampling.reindex.flm_2d_to_hp_fast with healpy_inverse defined as the composition of these:
Broadly though we can see the VJP rule corresponds to something which performs $\textrm{conj}(\texttt{alm2map}(\textrm{conj}(v)))$ plus an elementwise scaling operation corresponding to the application of $D$
The current (I think incorrect) VJP definition for healpy_forward is
Here we see there is no elementwise scaling before the application of alm2map corresponding to multiplication by $D^{-1}$ in $\textrm{conj}(\texttt{alm2map}(D^{-1} \textrm{conj}(v)))$
The text was updated successfully, but these errors were encountered:
I believe the derivatives defined for the
s2fft.transforms.c_backend_spherical.healpy_forward
function withcustom_vjp
are incorrect. As a minimal reproducing example:Running
check_grads
onc_sph.healpy_inverse
to check gradients against numerical finite differencing completes without any errorRunning the same on
c_sph.healpy_forward
however gives anAssertionError
outputting
Somewhat confusingly, running
check_grads
instead on specific scalar functions constructed usingc_sph.healpy_forward
does pass without error:Notice in all cases
iter
is fixed to zero so this is not due to using iterative refinement steps. While HEALPix sampling does not exhibit a sampling theorem and so round-tripping through the forward and backward ('inverse') transforms will introduce an error (which iterative refinement in the forward transform can reduce), this only affects the accuracy of the linear operator corresponding to the forward transform being the inverse of the linear operator corresponding to the backward transform.Distinct from this property, is that the linear operator represented by the forward transform is a (scaled and conjugated) transposition of the linear operator represented by the backward transform (and vice-versa), and this property holds exactly (modulo floating point error) for the HEALPix forward and backward transforms. Importantly it is this transposition property that is required for implementation of the derivative rules and so the incorrectness of the derivatives in the current implementation cannot be explained by the error in the inverse relationship.
Let the matrix represented by HEALPix forward spherical transform ($F \in \mathbb{C}^{m \times n}$ and the matrix represented by HEALPix inverse (backward) spherical transform ($B \in \mathbb{C}^{n \times m}$ , with a harmonic bandlimit $\ell$ in both cases and HEALPix resolution parameter $r$ and $n = 12r^2$ , $m = \ell(\ell + 1) / 2$ . That is
map2alm
) bealm2map
) beWe can construct the matrices numerically by mapping the standard basis vectors through the
map2alm
andalm2map
functions:We then have the relationships
where$X^H= \textrm{conj}(X)^T$ (that is the conjugate / Hermitian transpose) and $D$ is a $n \times n$ diagonal matrix with the first $\ell$ entries along the diagonal equal to $3r^2 / \pi$ and the remaining $n - \ell = \ell(\ell - 1) / 2$ diagonal entries equal to $6r^2/\pi$ .
Numerical verification:
As
map2alm
andalm2map
are both linear, their Jacobian vector product (JVP) functions are just the original maps:For the vector Jacobian product (VJP) functions we have
The current (correct) VJP definition for
healpy_inverse
iss2fft/s2fft/transforms/c_backend_spherical.py
Lines 282 to 297 in 909e6f1
It's a little difficult to relate this to the above derivation as this includes both the VJP for
healpy.alm2map
but also the VJP fors2fft.sampling.reindex.flm_2d_to_hp_fast
withhealpy_inverse
defined as the composition of these:s2fft/s2fft/transforms/c_backend_spherical.py
Lines 271 to 273 in 909e6f1
Broadly though we can see the VJP rule corresponds to something which performs$\textrm{conj}(\texttt{alm2map}(\textrm{conj}(v)))$ plus an elementwise scaling operation corresponding to the application of $D$
The current (I think incorrect) VJP definition for
healpy_forward
iss2fft/s2fft/transforms/c_backend_spherical.py
Lines 341 to 348 in 909e6f1
with
healpy_forward
here defined as a composition ofhealpy.map2alm
ands2fft.sampling.reindex.flm_hp_to_2d_fast
s2fft/s2fft/transforms/c_backend_spherical.py
Lines 331 to 332 in 909e6f1
Here we see there is no elementwise scaling before the application of$D^{-1}$ in $\textrm{conj}(\texttt{alm2map}(D^{-1} \textrm{conj}(v)))$
alm2map
corresponding to multiplication byThe text was updated successfully, but these errors were encountered: