Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
99c75ad
Add Clenshaw-Curtis quadrature rule implementation
matt-graham Apr 28, 2026
ae28cc1
Change to inverse RFFT based implementation
matt-graham Apr 30, 2026
f18db35
Correct CC weights expression for even L
matt-graham Apr 30, 2026
2e47ee4
Add Fejer-2 weights and refactor CC implementation to reuse
matt-graham May 19, 2026
efa2fb4
Add dimensions for CC and F2 sampling grids
matt-graham May 19, 2026
8f2c2d9
Add CC and F2 sampling methods to numpy quadrature wrapper
matt-graham May 19, 2026
2311a4f
Fix F2 theta positions
matt-graham May 19, 2026
8e7259a
Separate out CC and F2 phi grid sizes
matt-graham May 19, 2026
3462e95
Add tests for quadrature rules on known 1D integrals
matt-graham May 20, 2026
00baa27
Link quadrature weight and ntheta defs for CC and F2 rules
matt-graham May 21, 2026
21ed461
Simplify index to phi computation using nphi def
matt-graham May 21, 2026
9e1f84c
Increase number of theta samples for F2 rule by one
matt-graham May 21, 2026
f5e7278
Factor out m offset 1 and pole singularity sets
matt-graham May 21, 2026
63790b8
Factor out Gl quadrature theta only to match other rules
matt-graham May 21, 2026
a0e0d72
Match signature of DH theta only quadrature weight function to other …
matt-graham May 21, 2026
37bac50
Use nphi_equiang function uniformly in quadrature weight implementations
matt-graham May 21, 2026
a0c2891
Refactor index to theta to use ntheta
matt-graham May 21, 2026
530babe
Double CC and F2 scheme number of theta samples
matt-graham May 21, 2026
1ac5fc3
Include CC and F2 in m offset 1 schemes
matt-graham May 21, 2026
aa09f7c
Correct bug in determining scheme for dealing with pole singularity
matt-graham May 21, 2026
9366a6c
Correct bug in using MW rather than MWSS to compute nphi
matt-graham May 21, 2026
417b374
Generalize additional implicit special casing for handling pole singu…
matt-graham May 21, 2026
0daaeaa
Add CC and F2 to sample scheme lists in docstrings
matt-graham May 21, 2026
7f64fd7
Include CC and F2 schemes in spherical transform tests
matt-graham May 21, 2026
f801d6c
Move array-api-extra to package rather than build dependencies
matt-graham May 21, 2026
ed9dcc5
Remove CC + F2 from tests using SSHT
matt-graham May 21, 2026
176b82f
Further removing of CC + F2 from tests using SSHT
matt-graham May 21, 2026
832476b
Add equiangular sampling scheme set
matt-graham May 22, 2026
304bee5
Adjust tolerances for spherical precompute tests
matt-graham May 22, 2026
3756e22
Handle CC and F2 schemes in precompute kernel construction
matt-graham May 22, 2026
0be15f1
Generalize handling of poles in JAX precompute function
matt-graham May 22, 2026
bff50d3
Account for L=1 case for CC quadrature weights
matt-graham May 22, 2026
e0c447d
Add additional quadrature weights tests
matt-graham May 22, 2026
4fbdded
Remove unneeded special case for CC rule with n_theta = 2
matt-graham May 22, 2026
f6b4145
Give quadrature test using transform more descriptive name
matt-graham May 22, 2026
71de50f
Add further tests for quadrature rule exceptions
matt-graham May 22, 2026
e2aea88
Add tests for HEALPix quadrature weights
matt-graham May 22, 2026
b3efcf7
Add tests for Wigner kernel exceptions and n sample function
matt-graham May 22, 2026
9049377
Relax test condition on number of samples to non-negativity
matt-graham May 22, 2026
53573ed
Add CC and F2 sampling scheme details to docs
matt-graham May 22, 2026
3447e48
Minor fixes to sampling scheme doc page formatting
matt-graham May 22, 2026
87fb759
Merge branch 'main' into mmg/clenshaw-curtis-quadrature
matt-graham Jun 2, 2026
1058511
Merge branch 'main' into mmg/clenshaw-curtis-quadrature
matt-graham Aug 12, 2026
6adbe18
Add F2 sampling scheme to set in docstring
matt-graham Aug 12, 2026
c56a174
Update m offset logic and docstring to be more explicit
matt-graham Aug 12, 2026
52bcc30
Ensure polynomial quadrature tests check exactness at critical number…
matt-graham Aug 12, 2026
1c08016
Merge branch 'main' into mmg/clenshaw-curtis-quadrature
matt-graham Sep 17, 2026
b4d8bb7
Factor out constant for schemes with both poles
matt-graham Oct 20, 2026
3e684ec
Fix inconsistencies in sampling scheme lists in docstrings
matt-graham Oct 20, 2026
c97fdb4
Avoid duplication in sampling scheme list
matt-graham Oct 20, 2026
27cb440
Add explanatory comments to sampling scheme set constants
matt-graham Oct 20, 2026
a17772f
Raise early error for L=1 with sampling=cc
matt-graham Oct 20, 2026
5d6d078
Add test for CC sampling L=1 raising error
matt-graham Oct 20, 2026
61c80a2
Skip L=1 and sampling=cc test configurations
matt-graham Oct 20, 2026
74f37f3
Change guard conditions around L<=1 case for sampling='cc' and remove
matt-graham Oct 20, 2026
57a94fb
Test ntheta and thetas directly for cc / L=1 case rather than transforms
matt-graham Oct 20, 2026
86465a4
Use functions instead of sets to define sampling scheme properties
matt-graham Oct 20, 2026
2b10ab1
Add clarifying note about L > 1 requirement for CC sampling to docs
matt-graham Oct 20, 2026
33cbff1
Update stale comment about pole singularity handling
matt-graham Oct 20, 2026
23f3bb2
Fix incorrect testing of function set membership rather than function
matt-graham Oct 20, 2026
a904b58
Add assertion to check match between test parameters and equiangular
matt-graham Oct 20, 2026
337c6e6
Use assert rather than exception for check
matt-graham Oct 20, 2026
07844b7
Docstring typo fixes
matt-graham Oct 20, 2026
ba1a96f
Update ntheta raises docstring section and add check for L >= 1
matt-graham Oct 20, 2026
f54ea7b
Add test for L < 1 case
matt-graham Oct 20, 2026
5af2d9c
Revert unrelated trailing whitespace cleanup
matt-graham Oct 20, 2026
8aa6208
Correct regex match in exception test
matt-graham Oct 21, 2026
67cdeef
Merge branch 'main' into mmg/clenshaw-curtis-quadrature
matt-graham Oct 21, 2026
c419e66
Cover both invalid L check errors messages in exception test regex match
matt-graham Oct 21, 2026
bf51090
Merge branch 'main' into mmg/clenshaw-curtis-quadrature
matt-graham Oct 21, 2026
098e5cc
Update and tighten array-api-compat dependency version specifier
matt-graham Oct 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/api/utility/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ Utility Functions
- Compute MW quadrature weights for :math:`\theta` and :math:`\phi` integration.
* - :func:`~s2fft.utils.quadrature.quad_weights_mwss`
- Compute MWSS quadrature weights for :math:`\theta` and :math:`\phi` integration.
* - :func:`~s2fft.utils.quadrature.quad_weight_dh_theta_only`
- Compute DH quadrature weight for :math:`\theta` integration (only), for given :math:`\theta`.
* - :func:`~s2fft.utils.quadrature.quad_weights_dh_theta_only`
- Compute DH quadrature weight for :math:`\theta` integration (only).
* - :func:`~s2fft.utils.quadrature.quad_weights_mw_theta_only`
- Compute MW quadrature weights for :math:`\theta` integration (only).
* - :func:`~s2fft.utils.quadrature.quad_weights_mwss_theta_only`
Expand Down
91 changes: 77 additions & 14 deletions docs/sampling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,23 @@ Sampling schemes

The structure of the algorithms implemented in ``S2FFT`` can support a number of sampling schemes, which we give a brief overview of here.
An at-a-glance summary of the differences between the supported sampling schemes is also provided :ref:`in the table below <sampling-comparison-table>`, with further information available in the dedicated section for each scheme.
A more thorough overview of the schemes can be found in section 4.2 of `Price & McEwen (2025) <https://arxiv.org/abs/2311.14670>`_.
A more thorough overview of most of the schemes can be found in section 4.2 of `Price & McEwen (2025) <https://arxiv.org/abs/2311.14670>`_.

We adopt the usual ``S2FFT`` conventions for spherical coordinates; :math:`\theta\in[0, \pi]` (colatitude) and :math:`\varphi\in[0,2\pi)` (longitude), with :math:`\theta_t` and :math:`\varphi_p` being the discretised samples (indexed by $t$ and $p$) drawn by the sampling scheme.
We denote by $L$ the band-limit of the signals we are considering.
We adopt the usual ``S2FFT`` conventions for spherical coordinates; :math:`\theta\in[0, \pi]` (colatitude) and :math:`\varphi\in[0,2\pi)` (longitude), with :math:`\theta_t` and :math:`\varphi_p` being the discretised samples (indexed by :math:`t` and :math:`p`) drawn by the sampling scheme.
We denote by :math:`L` the band-limit of the signals we are considering.

.. _sampling-comparison-table:

.. list-table:: At-a-glance comparison of sampling schemes
:header-rows: 1
:align: center
:width: 95
:widths: 20 10 20 20 15 15
:widths: 20 10 30 15 15 15

* - Scheme
- API string
- Number of sample points [#n-samples-vs-memory-storage]_
- Equi- angular
- Equi-angular
- Equal region area
- Sampling theorem
* - :ref:`mcewen-wiaux-mw`
Expand Down Expand Up @@ -54,10 +54,22 @@ We denote by $L$ the band-limit of the signals we are considering.
- Yes
* - :ref:`healpix`
- ``"healpix"``
- $12 N_{side}^2$
- :math:`12 N_{side}^2`
- No
- Yes
- No
* - :ref:`clenshaw-curtis-cc`
- ``"cc"``
- :math:`(2L - 1) \times 2L`
- Yes
- No
- Yes
* - :ref:`fejer-rule-2-f2`
- ``"f2"``
- :math:`(2L - 1) \times 2L`
- Yes
- No
- Yes

Specifying sampling schemes in ``S2FFT``
----------------------------------------
Expand Down Expand Up @@ -147,11 +159,11 @@ Further information; `McEwen & Wiaux (2012) <https://arxiv.org/abs/1110.6298>`_.

.. _mcewen-wiaux-mwss:

McEwen & Wiaux with Symmetric Sampling (MWSS)
---------------------------------------------
McEwen & Wiaux Symmetric Sampling (MWSS)
----------------------------------------

This sampling scheme uses slightly more samples than MW, requiring an array holding :math:`(L+1)\times 2L` elements (with $2(L^2 - L + 1)$ independent degrees of freedom).
Asymptotically, we still only require :math:`\sim 2L^2` elements in memory as $L$ increases.
This sampling scheme uses slightly more samples than MW, requiring an array holding :math:`(L+1)\times 2L` elements (with :math:`2(L^2 - L + 1)` independent degrees of freedom).
Asymptotically, we still only require :math:`\sim 2L^2` elements in memory as :math:`L` increases.
In exchange for slightly higher memory usage, the sample locations possess antipodal symmetry.

Sample positions are defined by
Expand Down Expand Up @@ -187,7 +199,7 @@ Gauss-Legendre (GL)
The GL sampling theorem also requires an array of :math:`L\times (2L-1) \sim 2L^2` elements to represent the signal.
Like :ref:`DH <driscoll-healy-dh>`, there is no redundancy in samples at the poles, so the same number of independent degrees of freedom are needed.

The :math:`\theta_t` are determined by the roots of the Legendre polynomials of order $L$, whilst the :math:`\varphi_p` are defined by
The :math:`\theta_t` are determined by the roots of the Legendre polynomials of order :math:`L`, whilst the :math:`\varphi_p` are defined by

.. math::

Expand All @@ -207,13 +219,64 @@ However, HEALPix sampling **does not** exhibit a sampling theorem and so round-t
An `iterative refinement <https://en.wikipedia.org/wiki/Iterative_refinement>`_ scheme can be applied to the forward transform to reduce this round-trip error at the cost of additional computation.
This can be applied in ``S2FFT``'s forward transforms by setting the `iter` argument to the number of iterations to perform, with more iterations giving a smaller round-trip error.

A HEALPix grid is defined by a resolution parameter $N_{side}$, requiring $12 N_{side}^2$ elements (and independent degrees of freedom) stored in memory.
Given a resolution parameter, the grid will contain $N_{hp} = 12 N_{side}^2$ regions of the same area :math:`\frac{\pi}{3N_{side}^2}`.
The regions will be laid out on $4N_{side}-1$ iso-latitude rings, and the distribution of regions will be symmetric about the equator.
A HEALPix grid is defined by a resolution parameter :math:`N_{side}`, requiring :math:`12 N_{side}^2` elements (and independent degrees of freedom) stored in memory.
Given a resolution parameter, the grid will contain :math:`N_{hp} = 12 N_{side}^2` regions of the same area :math:`\frac{\pi}{3N_{side}^2}`.
The regions will be laid out on :math:`4N_{side}-1` iso-latitude rings, and the distribution of regions will be symmetric about the equator.
For the equations defining the exact positioning of the regions, their centres, their boundaries, and how they are organised into an array, see section 5 of `Gorski et al. (2005) <https://arxiv.org/abs/astro-ph/0409513>`_.

Further information; `Gorski et al. (2005) <https://arxiv.org/abs/astro-ph/0409513>`_.

.. _clenshaw-curtis-cc:

Clenshaw-Curtis (CC)
--------------------

Clenshaw-Curtis quadrature `(Cleshaw and Curtis, 1960) <https://doi.org/10.1007/BF01386223>`_,
is based on expansion of the integrand in terms of Chebyshev polynomials.
It can be used in a spherical harmonic transform setting to numerically compute integrals over the co-latitude,
:math:`\theta`, dimension.
Clenshaw-Curtis quadrature requires roughly double the number of nodes to exactly integrate a polynomial of a given degree
compared to Gauss-Legendre quadrature,
however the co-latitude nodes are equispaced and grids of different resolutions can be nested.

Sample positions are defined by

.. math::

\theta_t &= \frac{\pi t}{2L-2}, &\quad t\in\lbrace 0,1,...,2L-2 \rbrace, \\
\varphi_p &= \frac{2\pi p}{2L}, &\quad p\in\lbrace 0,1,...,2L-1\rbrace.

Note that here we follow the original definition of the Clenshaw-Curtis quadrature rule
and include samples at both poles, and as there are :math:`2L-1` :math:`\theta` samples,
we require that :math:`L \geq 2`.
An analogous scheme which excludes the pole is available as :ref:`F2 <fejer-rule-2-f2>`.
Due to the redundancy in samples at the poles,
the total number of distinct sites on the sphere used by this sampling scheme is :math:`(2L-3)(2L)+2 = 4L^2 -6L + 2`.

For further information see `Hotte and Ujiie (2018) <https://doi.org/10.1002%2Fqj.3282>`_.

.. _fejer-rule-2-f2:

Fejér's second rule (F2)
------------------------

Fejér quadrature `(Fejer, 1933) <http://projecteuclid.org/euclid.bams/1183496842>`_,
is closely related to Clenshaw-Curtis quadrature.
Fejér's second rule can be used to define a sampling scheme analagous to :ref:`CC <clenshaw-curtis-cc>` but with the poles excluded.

Sample positions are defined by

.. math::

\theta_t &= \frac{\pi(t + 1)}{2L}, &\quad t\in\lbrace 0,1,...,2L-2 \rbrace, \\
\varphi_p &= \frac{2\pi p}{2L}, &\quad p\in\lbrace 0,1,...,2L-1\rbrace.

This requires :math:`(2L-1) \times 2L = 4L^2 - 2L` elements to be held in memory,
and since the poles are not sample points,
the same number of independent degrees of freedom to represent the signal.

For further information see `Hotte and Ujiie (2018) <https://doi.org/10.1002%2Fqj.3282>`_.

.. rubric:: Footnotes

.. [#n-samples-vs-memory-storage] The "number of sample points" listed here refers to the size of the array recording sample values in memory.
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies = [
"numpy>=1.20",
"jax>=0.5.0",
"array-api-compat~=1.15",
"array-api-extra~=0.11",
]
dynamic = [
"version",
Expand Down
32 changes: 16 additions & 16 deletions src/s2fft/base_transforms/spherical.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def inverse(
spin (int, optional): Harmonic spin. Defaults to 0.

sampling (str, optional): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "gl", "healpix"}. Defaults to "mw".
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}. Defaults to "mw".

nside (int, optional): HEALPix Nside resolution parameter. Only required
if sampling="healpix". Defaults to None.
Expand Down Expand Up @@ -80,7 +80,7 @@ def _inverse(
spin (int, optional): Harmonic spin. Defaults to 0.

sampling (str, optional): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "healpix"}. Defaults to "mw".
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}. Defaults to "mw".

method (str, optional): Harmonic transform algorithm. Supported algorithms include
{"direct", "sov", "sov_fft", "sov_fft_vectorized"}. Defaults to
Expand Down Expand Up @@ -154,7 +154,7 @@ def forward(
spin (int, optional): Harmonic spin. Defaults to 0.

sampling (str, optional): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "healpix"}. Defaults to "mw".
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}. Defaults to "mw".

nside (int, optional): HEALPix Nside resolution parameter. Only required
if sampling="healpix". Defaults to None.
Expand Down Expand Up @@ -217,7 +217,7 @@ def _forward(
spin (int, optional): Harmonic spin. Defaults to 0.

sampling (str, optional): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "healpix"}. Defaults to "mw".
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}. Defaults to "mw".

method (str, optional): Harmonic transform algorithm. Supported algorithms include
{"direct", "sov", "sov_fft", "sov_fft_vectorized"}. Defaults to
Expand Down Expand Up @@ -304,7 +304,7 @@ def _compute_inverse_direct(
spin (int): Harmonic spin.

sampling (str): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "healpix"}.
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}.

thetas (np.ndarray): Vector of sample positions in :math:`\theta` on the sphere.

Expand Down Expand Up @@ -391,7 +391,7 @@ def _compute_inverse_sov(
spin (int): Harmonic spin.

sampling (str): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "healpix"}.
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}.

thetas (np.ndarray): Vector of sample positions in :math:`\theta` on the sphere.

Expand Down Expand Up @@ -465,7 +465,7 @@ def _compute_inverse_sov_fft(
spin (int): Harmonic spin.

sampling (str): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "healpix"}.
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}.

thetas (np.ndarray): Vector of sample positions in :math:`\theta` on the sphere.

Expand All @@ -486,7 +486,7 @@ def _compute_inverse_sov_fft(
assert L >= 2 * nside

ftm = np.zeros(samples.ftm_shape(L, sampling, nside), dtype=np.complex128)
m_offset = 1 if sampling in ["mwss", "healpix"] else 0
m_offset = samples.m_offset(L, sampling)

for t, theta in enumerate(thetas):
phi_ring_offset = (
Expand Down Expand Up @@ -558,7 +558,7 @@ def _compute_inverse_sov_fft_vectorized(
spin (int): Harmonic spin.

sampling (str): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "healpix"}.
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}.

thetas (np.ndarray): Vector of sample positions in :math:`\theta` on the sphere.

Expand All @@ -576,7 +576,7 @@ def _compute_inverse_sov_fft_vectorized(

"""
ftm = np.zeros(samples.ftm_shape(L, sampling, nside), dtype=np.complex128)
m_offset = 1 if sampling in ["mwss", "healpix"] else 0
m_offset = samples.m_offset(L, sampling)

for t, theta in enumerate(thetas):
phase_shift = (
Expand Down Expand Up @@ -634,7 +634,7 @@ def _compute_forward_direct(
spin (int): Harmonic spin.

sampling (str): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "healpix"}.
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}.

thetas (np.ndarray): Vector of sample positions in :math:`\theta` on the sphere.

Expand Down Expand Up @@ -726,7 +726,7 @@ def _compute_forward_sov(
spin (int): Harmonic spin.

sampling (str): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "healpix"}.
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}.

thetas (np.ndarray): Vector of sample positions in :math:`\theta` on the sphere.

Expand Down Expand Up @@ -822,7 +822,7 @@ def _compute_forward_sov_fft(
spin (int): Harmonic spin.

sampling (str): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "healpix"}.
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}.

thetas (np.ndarray): Vector of sample positions in :math:`\theta` on the sphere.

Expand All @@ -844,7 +844,7 @@ def _compute_forward_sov_fft(
flm = np.zeros(samples.flm_shape(L), dtype=np.complex128)
ftm = np.zeros_like(f).astype(np.complex128)

m_offset = 1 if sampling in ["mwss", "healpix"] else 0
m_offset = samples.m_offset(L, sampling)

if sampling.lower() == "healpix":
ftm = hp.healpix_fft(f, L, nside, "numpy", reality)
Expand Down Expand Up @@ -938,7 +938,7 @@ def _compute_forward_sov_fft_vectorized(
spin (int): Harmonic spin.

sampling (str): Sampling scheme. Supported sampling schemes include
{"mw", "mwss", "dh", "healpix"}.
{"mw", "mwss", "dh", "gl", "healpix", "cc", "f2"}.

thetas (np.ndarray): Vector of sample positions in :math:`\theta` on the sphere.

Expand All @@ -960,7 +960,7 @@ def _compute_forward_sov_fft_vectorized(
flm = np.zeros(samples.flm_shape(L), dtype=np.complex128)
ftm = np.zeros_like(f).astype(np.complex128)

m_offset = 1 if sampling in ["mwss", "healpix"] else 0
m_offset = samples.m_offset(L, sampling)
if reality:
m_conj = (-1) ** (np.arange(1, L) % 2)

Expand Down
Loading
Loading